Selecting a different embryo on the SPIM head changes the caption and nothing else. The view keeps showing the previous embryo while claiming another one is selected.
From the recording, three attempts in a row (18:45, 19:00, 19:45): Selected: embryo 3 → Selected: embryo 1 → Selected: embryo 2, with SAMPLE → OBJECTIVE 28.2 µm, the banner and the image pixels identical every time.
Ryan, 18:49: "so right now it seems like we're just looking at embryo one… but the image itself isn't changing." Magdalena, 19:56: "if you click a different embryo, I would like maybe an empty image to show up, even if you don't move the objective. Because right now I think it might be confusing, because you might think that this is the embryo — like the selected embryo is the image that's shown."
Cause
There is no predicate anywhere in Operate relating the live stage position to the selected embryo's position, even though both facts are in hand: _xy (fed from SharedState stageXY, operate.js:1304-1306) and resolveXY(emb) (:126-133).
selectEmbryo (operate.js:817-826) calls four renderers, none of which touch the image.
- The caption is the only thing that moves —
renderSpimTarget (:809-814) writes Selected: embryo N into index.html:637.
onSpimFrame (:1159-1166) paints every arriving frame unconditionally.
Caption and pixels are two independent channels that can disagree indefinitely. Commit 0120487 (fix(lightbox): never leave a stale frame) does not cover this surface — it touched the lightbox, not the Operate SPIM pane.
Done when
The light-sheet view does not present a frame as belonging to the selected embryo unless the stage is at that embryo — blank or explicitly mark it otherwise.
Smallest fix: add one pure predicate and use it at the only two points that can make caption and pixels disagree. In operate-math.js (which exists for exactly this and is covered by tests/js/operate-math.test.mjs), add atPosition(xy, target, tol) with AT_TOL_UM = 50 as the tunable knob. In operate.js, atSelected() over _embryos/_selected/_xy, then gate onSpimFrame (:1159) and renderSpimTarget (:809).
Leave the tolerance as a named constant — 50 µm is a guess about stage repeatability and embryo spacing that wants tuning against the real scope, not a derived value.
Distinct from #111: Go to is the capability, this is the correctness bug. Fixing #111 does not fix this — you would still show a stale frame between issuing a move and arriving.
Evidence: 2026-08-07 walkthrough, 18:45–20:11. Not previously captured.
Selecting a different embryo on the SPIM head changes the caption and nothing else. The view keeps showing the previous embryo while claiming another one is selected.
From the recording, three attempts in a row (18:45, 19:00, 19:45):
Selected: embryo 3→Selected: embryo 1→Selected: embryo 2, withSAMPLE → OBJECTIVE 28.2 µm, the banner and the image pixels identical every time.Ryan, 18:49: "so right now it seems like we're just looking at embryo one… but the image itself isn't changing." Magdalena, 19:56: "if you click a different embryo, I would like maybe an empty image to show up, even if you don't move the objective. Because right now I think it might be confusing, because you might think that this is the embryo — like the selected embryo is the image that's shown."
Cause
There is no predicate anywhere in Operate relating the live stage position to the selected embryo's position, even though both facts are in hand:
_xy(fed from SharedStatestageXY,operate.js:1304-1306) andresolveXY(emb)(:126-133).selectEmbryo(operate.js:817-826) calls four renderers, none of which touch the image.renderSpimTarget(:809-814) writesSelected: embryo Nintoindex.html:637.onSpimFrame(:1159-1166) paints every arriving frame unconditionally.Caption and pixels are two independent channels that can disagree indefinitely. Commit 0120487 (
fix(lightbox): never leave a stale frame) does not cover this surface — it touched the lightbox, not the Operate SPIM pane.Done when
The light-sheet view does not present a frame as belonging to the selected embryo unless the stage is at that embryo — blank or explicitly mark it otherwise.
Smallest fix: add one pure predicate and use it at the only two points that can make caption and pixels disagree. In
operate-math.js(which exists for exactly this and is covered bytests/js/operate-math.test.mjs), addatPosition(xy, target, tol)withAT_TOL_UM = 50as the tunable knob. Inoperate.js,atSelected()over_embryos/_selected/_xy, then gateonSpimFrame(:1159) andrenderSpimTarget(:809).Leave the tolerance as a named constant — 50 µm is a guess about stage repeatability and embryo spacing that wants tuning against the real scope, not a derived value.
Distinct from #111: Go to is the capability, this is the correctness bug. Fixing #111 does not fix this — you would still show a stale frame between issuing a move and arriving.
Evidence: 2026-08-07 walkthrough, 18:45–20:11. Not previously captured.