@@ -170,20 +170,22 @@ def _stamp_proteoform_index(
170170 {"title" : "Accession" , "field" : "accession" , "sorter" : "string" },
171171 {"title" : "Description" , "field" : "description" , "sorter" : "string" },
172172 {"title" : "Length" , "field" : "length" , "sorter" : "number" },
173- # Legacy TabulatorProteinTable.vue renders the `-1` sentinel as "-" (the raw
174- # value otherwise). The OI `dashNegativeOne` formatter reproduces the sentinel
175- # rule; precision 4 matches the app-wide `toFixedFormatter()` default (4 dp,
176- # used for every other numeric mass column, e.g. MonoMass in the mass table).
173+ # Legacy TabulatorProteinTable.vue renders the `-1` sentinel as "-" and the
174+ # RAW unrounded value otherwise (TabulatorProteinTable.vue:78-81). The OI
175+ # `dashNegativeOne` formatter reproduces the sentinel rule and renders the raw
176+ # value when `precision` is omitted -- so we drop `formatterParams` to avoid
177+ # rounding (critical for tiny Q-values: 0.00012 must NOT become 0.0001).
177178 {"title" : "Mass" , "field" : "ProteoformMass" , "sorter" : "number" ,
178- "formatter" : "dashNegativeOne" , "formatterParams" : { "precision" : 4 } },
179+ "formatter" : "dashNegativeOne" },
179180 {"title" : "No. of Matched Fragments" , "field" : "MatchingFragments" , "sorter" : "number" },
180181 {"title" : "No. of Modifications" , "field" : "ModCount" , "sorter" : "number" },
181182 {"title" : "No. of Tags" , "field" : "TagCount" , "sorter" : "number" },
182183 {"title" : "Score" , "field" : "Score" , "sorter" : "number" },
183- # Q-Value also uses the `-1 -> "-"` sentinel rule (legacy formatter); 4 dp
184- # matches the app-wide default decimal precision.
184+ # Q-Value also uses the `-1 -> "-"` sentinel rule with the RAW unrounded value
185+ # otherwise (TabulatorProteinTable.vue:105-108). No `formatterParams` so the
186+ # raw value is shown -- rounding would corrupt tiny Q-values (e.g. 0.00012).
185187 {"title" : "Q-Value (Proteoform Level)" , "field" : "ProteoformLevelQvalue" , "sorter" : "number" ,
186- "formatter" : "dashNegativeOne" , "formatterParams" : { "precision" : 4 } },
188+ "formatter" : "dashNegativeOne" },
187189]
188190
189191# TabulatorTagTable.vue columns -> tag_dfs fields.
@@ -194,12 +196,13 @@ def _stamp_proteoform_index(
194196 {"title" : "Sequence" , "field" : "TagSequence" , "sorter" : "string" },
195197 {"title" : "Length" , "field" : "Length" , "sorter" : "number" },
196198 {"title" : "Tag Score" , "field" : "Score" , "sorter" : "number" },
197- # N/C mass use the legacy `-1 -> "-"` sentinel rule (TabulatorTagTable.vue
198- # ~72-83); precision 4 matches the app-wide mass decimal default.
199+ # N/C mass use the legacy `-1 -> "-"` sentinel rule and render the RAW
200+ # unrounded value otherwise (TabulatorTagTable.vue:72-83). No `formatterParams`
201+ # so the raw value is shown (rounding would lose precision on the mass offset).
199202 {"title" : "N mass" , "field" : "Nmass" , "sorter" : "number" ,
200- "formatter" : "dashNegativeOne" , "formatterParams" : { "precision" : 4 } },
203+ "formatter" : "dashNegativeOne" },
201204 {"title" : "C mass" , "field" : "Cmass" , "sorter" : "number" ,
202- "formatter" : "dashNegativeOne" , "formatterParams" : { "precision" : 4 } },
205+ "formatter" : "dashNegativeOne" },
203206 {"title" : "Δ mass" , "field" : "DeltaMass" , "sorter" : "number" },
204207]
205208
@@ -310,15 +313,18 @@ def _resolve_tag_masses(file_manager, experiment_id: str, state_manager) -> None
310313
311314 Only the selected tag's row is collected (filtered by ``TagIndex``). The tag
312315 ``mzs`` are a comma-joined string (trailing comma); parse and drop non-numeric
313- entries, keeping the STORED order (ascending for C-term tags, descending for
314- N-term tags). ``TagSequence`` gives the residue letters; the legacy walks
315- consecutive stored masses labelling gap ``i`` with ``sequence[len-1-i]`` —
316- i.e. the REVERSED sequence aligns to the stored-order gaps regardless of
317- anchoring (verified against both an ascending C-term and a descending N-term
318- tag). Do NOT sort the masses: sorting breaks the alignment for descending
319- (N-term) tags. The published value is a dict
320- ``{"masses": [...], "residues": [...]}`` consumed by the OI LinePlot tag walk;
321- when no residues are available it carries only masses (highlight-only)."""
316+ AND zero entries (legacy ``number !== 0``), keeping the STORED order (ascending
317+ for C-term tags, descending for N-term tags). ``TagSequence`` gives the residue
318+ letters; the legacy walks consecutive stored masses labelling gap ``i`` with
319+ ``sequence[len-1-i]`` — i.e. the REVERSED sequence aligns to the stored-order
320+ gaps regardless of anchoring (verified against both an ascending C-term and a
321+ descending N-term tag). Do NOT sort the masses: sorting breaks the alignment
322+ for descending (N-term) tags. The published value is a dict
323+ ``{"masses": [...], "residues": [...], "nTerminal": bool}`` consumed by the OI
324+ LinePlot tag walk; when no residues are available it carries only masses
325+ (highlight-only). When a residue within the selected tag's span is also
326+ selected (``selectedAApos``), a tag-relative ``selectedAA`` index is added so
327+ the walk gold-highlights that residue (legacy ``selectedAApos - StartPos``)."""
322328 def _clear_all () -> None :
323329 state_manager .clear_selection (TAG_MASSES_KEY )
324330 state_manager .clear_selection (TAG_SPAN_KEY )
@@ -355,8 +361,12 @@ def _clear_all() -> None:
355361
356362 raw = selected ["tag_masses" ][0 ]
357363 # Keep STORED order (do not sort) so the reversed-sequence walk aligns for
358- # both ascending (C-term) and descending (N-term) tags.
359- masses = [m for m in raw if m is not None ] if raw is not None else []
364+ # both ascending (C-term) and descending (N-term) tags. Drop null AND zero
365+ # masses (legacy `number !== 0`, TabulatorTagTable.vue:140): a literal 0 mass
366+ # would misalign the reversed-residue walk.
367+ masses = (
368+ [m for m in raw if m is not None and m != 0 ] if raw is not None else []
369+ )
360370 if not masses :
361371 _clear_all ()
362372 return
@@ -373,15 +383,33 @@ def _clear_all() -> None:
373383 n_mass = selected ["n_mass" ][0 ]
374384 n_terminal = (n_mass is not None ) and (float (n_mass ) == - 1.0 )
375385
376- state_manager .set_selection (
377- TAG_MASSES_KEY ,
378- {"masses" : list (masses ), "residues" : residues , "nTerminal" : n_terminal },
379- )
386+ tag_masses = {
387+ "masses" : list (masses ),
388+ "residues" : residues ,
389+ "nTerminal" : n_terminal ,
390+ }
380391
381- # Tag-span highlight on the SequenceView. StartPos/EndPos are protein-absolute
382- # (matching the full-protein residue grid), so they bracket the tag directly.
392+ # Selected-residue gold (#F3A712) highlight (legacy
393+ # `selectedTag.selectedAA = selectedAApos - StartPos`, TabulatorTagTable.vue:
394+ # 151,169). When a residue is selected (AA_KEY holds its protein-absolute
395+ # position) AND it falls within the selected tag's [StartPos, EndPos] span,
396+ # publish the tag-relative residue index so the LinePlot tag walk highlights
397+ # that residue; omit otherwise (no highlight).
383398 start_pos = selected ["start_pos" ][0 ]
384399 end_pos = selected ["end_pos" ][0 ]
400+ selected_aa_pos = state_manager .get_selection (AA_KEY )
401+ if (
402+ selected_aa_pos is not None
403+ and start_pos is not None
404+ and end_pos is not None
405+ and int (start_pos ) <= int (selected_aa_pos ) <= int (end_pos )
406+ ):
407+ tag_masses ["selectedAA" ] = int (int (selected_aa_pos ) - int (start_pos ))
408+
409+ state_manager .set_selection (TAG_MASSES_KEY , tag_masses )
410+
411+ # Tag-span highlight on the SequenceView. StartPos/EndPos are protein-absolute
412+ # (matching the full-protein residue grid), so they bracket the tag directly.
385413 if start_pos is not None and end_pos is not None :
386414 state_manager .set_selection (
387415 TAG_SPAN_KEY ,
@@ -799,7 +827,9 @@ def render_experiment_panel(
799827 best_per_spectrum = best_per_spectrum ,
800828 )
801829 if component is None :
802- st .warning (f"No data for '{ comp_name } '." )
830+ # Silently skip an absent component (data frame missing),
831+ # matching the Deconv viewer's documented intent and avoiding
832+ # noisy warnings on stale / partial caches.
803833 continue
804834 key = f"tnt_oi_{ panel_index } _{ row_index } _{ col_index } _{ comp_name } "
805835 component (key = key , state_manager = state_manager )
0 commit comments