Repository navigation
feat(shotload): shared shot-load state machine with a visible load chip - #63
Merged
obvious-autobuild[bot] merged 2 commits intoOct 11, 2026
Conversation
Generalize the compare chip fetch machine into web/shotload.js as THE one
shot-load lifecycle (spec L4): queued -> fetching -> ready | error per shot,
AbortController supersede by generation, honest per-shot errors. A superseded
fetch lands back in 'queued' — an abort is not a failure.
Single-shot loading now runs through the machine and renders its states as a
load chip in Replay's status cluster ("loading shot #30192..."; on error the
reason plus Retry/Drop), closing the silent cold-load gap the audit found.
getReplay stores each cached promise with the signal it was created under so a
new-signal fetch never adopts an in-flight promise an older signal is about to
abort, and a failed fetch only evicts its own cache entry.
Co-authored-by: Tapan Ganatma Nakkina <[email protected]>
…achine The compare fan-out's hand-rolled state Maps (cmp.st/cmp.err/cmp.ctrls) are gone: per-shot fetch state now lives in web/shotload.js, the same machine a single pick uses. cmpFetch becomes a thin payload router — supersede and honest errors are the machine's to record — and Cancel returns in-flight chips to 'queued' instead of marking them 'failed: fetch cancelled', matching the spec's state table (an abort is a supersede, not a failure). Selection changes drop the previous batch's machine state, so re-entering compare refetches exactly as before; payload ownership (cmp.data, replayCache) is unchanged. Co-authored-by: Tapan Ganatma Nakkina <[email protected]>
obvious-autobuild
Bot
merged commit Oct 11, 2026
7fd008d
into
release/task-mode-ia-redesign-20261011-170103
3 checks passed
This was referenced Oct 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
A cold shot load is silent: pick an uncached shot and the panels just sit there while the server fetches the bundle from FAIR-MAST. The audit found the compare flow solved this years before single-shot did — per-shot chips with queued/fetching/ready/error states, AbortController cancellation, Retry/Drop — but that machinery was private to the compare fan-out. This PR generalizes it into one shared shot-load machine and wires every shot fetch through it, closing the single-shot gap without changing the compare flow's behavior.
What
web/shotload.js(new) — THE one shot-load state machine (spec §2, L4): per-shotqueued → fetching → ready | error, AbortController supersede by generation, honest per-shot error reasons, listener notification. A superseded fetch lands back inqueued— an abort is a supersede, not a failure.web/replay.jsloadShot) and render a load chip in Replay's status cluster:loading shot #30192…while fetching; on error, the reason plus working Retry and Drop. The silent cold-load is gone.cmp.st/cmp.err/cmp.ctrlsmaps are deleted;cmpFetchbecomes a thin payload router over the machine. Cancel returns in-flight chips toqueuedinstead of marking them "failed: fetch cancelled", matching the spec's state table.getReplaycaches each promise with the signal it was created under, so a new-signal fetch never adopts an in-flight promise an older signal is about to abort; a failed fetch evicts only its own cache entry.Scope:
web/only (newshotload.js, edits toreplay.js,index.htmlscript order,style.csschip styles). No backend changes, no build step, ES5-compatible IIFEs, tokens extended (--chipcolors reuse the existing ok/warn/bad semantic tokens).How to Review
web/shotload.jsfirst (~90 lines) — the whole state contract is there.replay.jsis a mechanical map→machine swap; payload ownership (cmp.data,replayCache) is unchanged, so re-entering compare refetches exactly as before.data-guideanchors,window.FusionLab, andfusionlab:*events are untouched (compat contract L3).renderShotwiring is byte-identical to the base (git diff cdb157d..HEAD -- web/replay.js). Flagging for the compare-workspace lane rather than patching out of scope here.Acceptance criteria
web/shotload.js)uv run pytest -qgreen (352 passed, 1 skipped),uv run ruff checkclean,uv run mypy fusionlabclean (V1)web/+ nolessons.jsoncopy changes needed; stored-energy units and naming untouchedEvidence (QA records against head
946687b8bbe6321b80a7e997868ded604c056369)Recorded via
obvious autobuild upload(15 s/replaythrottle proxy; uncached-shot failure is shot #12017's real FAIR-MAST 502):fl_xVRldRRr,fl_sS18tvvd): status cluster chip "loading shot #30192…" while panels keep last-valid data; then the chip is gone with #30192 rendered.fl_aCWLHiPh,fl_2wMHy7C2,fl_rE9smba0,fl_k4MKxGpp): error chip "couldn't load #12017 — shot 12017 could not be fetched from FAIR-MAST" with Retry · Drop; Retry restarts "loading shot #12017…" and honestly re-errors; Drop clears the chip and restores the picker.fl_IOhwFA7E,fl_rkTA1DqG): deep-linked compare boots both chips "fetching from the archive…", then both ready with per-shot traces and the ΔW strip.CI is the final gate; pytest re-runs via the pre-push hook.
Sibling PRs (Direction C release lane)
cdb157d)1b752ec)mainHuman author: Tapan Ganatma Nakkina ([email protected])
🔗 Obvious Project · 🧵 Obvious Thread · 🔀 Obvious PR Artifact