Skip to content

Viewer: rebuild tables on experiment switch and keep selections (follow-up to #99) - #101

Open
t0mdavid-m wants to merge 1 commit into
fix/viewer-rerun-cascadefrom
fix/viewer-selection-sync
Open

t0mdavid-m wants to merge 1 commit into
fix/viewer-rerun-cascadefrom
fix/viewer-selection-sync

Conversation

@t0mdavid-m

Copy link
Copy Markdown
Member

Follow-up to #99, stacked on fix/viewer-rerun-cascade. Addresses the "table contents only load when I select the first element" report and the selection-protocol part of #100 (§2). Companion Vue PR: t0mdavid-m/openms-streamlit-vue-component#31 (this PR ships its bundle).

Problem

Switching the Viewer's experiment dropdown to a run whose data is byte-identical to the previous one (re-run of the same mzML with the same parameters) left the Mass Table, both spectra and the 3D plot empty, kept the previous experiment's spectra on screen, and needed two clicks on the still-highlighted first Scan Table row before anything loaded. Reproduced deterministically on develop and on fix/viewer-rerun-cascade.

Root cause chain:

  1. render_grid installs a fresh StateTracker (new id, empty selection) when the dataset changes, but render_component hashed only the cell data, so identical runs produced identical hashes.
  2. The Vue store skips re-parsing a render whose hash is unchanged, so the Scan Table was never rebuilt, never selected its default row, and never sent scanIndex.
  3. With no scanIndex, filter_data sliced the dependent cells to zero rows; the spectrum component kept its stale plot; Tabulator's click toggling deselected the highlighted row on the first click.

Separately, StateTracker.updateState adopted any unknown key unconditionally, so with a bundle that sends null for unset fields (every CI-built bundle since t0mdavid-m/openms-streamlit-vue-component#29) the first cell to report claimed every key and a later first-time value with the same counter was dropped as a stale conflict.

Changes

  • src/render/util.py, src/render/render.py: payload_hash(data, tracker_id); the hash now changes with the StateTracker, so a new experiment rebuilds every cell with any bundle while the per-rerun skip for unchanged data (heatmap Arrow payload) is preserved.
  • src/render/StateTracker.py: a None never claims an unknown key (and id is not treated as state); the conflict check is guarded so skipped keys cannot raise; rejected stale updates are logged at debug level so lost clicks can be diagnosed from the server log.
  • tests/test_selection_clear.py: three new tests (hash changes with the tracker; an all-None first message does not block the default row selection; None for a never-set key is a no-op).
  • docs/viewer-selection-sync.md: how the cells share a selection and the invariants above.
  • js-component/dist: rebuilt from the companion Vue PR (tracker-aware hash skip, click never deselects, old Tabulator instances destroyed, spectra clear on empty data and no longer push massIndex = null to Python, 3D plot follows the data).

Verification

  • pytest tests/ -v: 55 passed.
  • Playwright probe against streamlit run app.py local (fresh session per trial), before → after:
    • switch to an identical experiment: Mass Table rows 0 → 20 without a click, 2/2 trials in both directions; the spectra and the 3D plot follow.
    • click on the already highlighted Scan Table row: deselected it (2 clicks needed) → stays selected, 1 click.
    • Viewer load: 4 rerun requests / 5 script runs → 2 / 3.
    • single clicks on rows 3, 6, 1 and double clicks 0 ms / 150 ms apart: all applied (2/2 trials); no server crash during the runs.

Not covered here (tracked in #100): the counter-based rejection of a click sent while another cell's update is in flight, and the residual polars access violation.

Note for maintainers: the "Selection linking" bullet in CLAUDE.md should mention that the payload hash includes the tracker id and that None never claims a key; CLAUDE.md is not touched by this PR because the local checkout carries an unrelated uncommitted rewrite of it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Wda4mDs1DSWJuU1beJJKvL

…ction keys

Switching to an experiment whose data is byte-identical to the previous one
(a re-run of the same file) left the Mass Table, spectra and 3D plot empty until
the user clicked the first Scan Table row twice: the payload hash covered only
the cell data, so the frontend skipped the render, never rebuilt the Scan Table
and never sent the default scan selection.

- render/util: payload_hash(data, tracker_id); render_component uses it, so a
  new experiment always rebuilds every cell while unchanged data is still
  skipped within an experiment
- StateTracker: a None never claims an unknown key (with a bundle that sends
  null for unset fields the first cell to report owned every key and the
  default row selection was dropped as a stale conflict), id is not state,
  rejected stale updates are logged at debug level
- tests: three new cases in test_selection_clear.py
- docs/viewer-selection-sync.md: how the cells share a selection, invariants
- js-component/dist: rebuilt from openms-streamlit-vue-component
  fix/viewer-selection-sync (no hash skip on tracker change, a click never
  deselects, old Tabulator instances destroyed, spectra clear on empty data
  and no longer push massIndex null to Python, 3D plot follows the data)

Refs #100.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wda4mDs1DSWJuU1beJJKvL
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant