feat: tell a queued item it's queued, and where it stands in line - #15
Merged
Conversation
Captures run one at a time, so a second add waits on the first. The card said "Capturing the page" the whole time — a claim about work that had not started. It now says "Next up" or "Queued, 3rd in line", counting down as the line moves. Two parts, and they're different sizes. Stopping the lie needed no server change: `status` already distinguishes pending from processing and already flows through both SSE and hydrateItemForUi. The ordinal position is what pulls in the queue, the SSE event, and hydration. The new render state keys on `queuePosition`, NOT on `status`. `pending` with no job behind it is a real, persistent state — manual-upload boards, a missing source, an unregistered ingest_mode, and the 150 legacy imports that sat at pending with complete data — and the in-memory line is empty after a restart. Gating on the position means the queued state appears exactly when real queue information exists and degrades to the old behavior otherwise. The line is tracked in the queue rather than derived from `status='pending'` in SQL, for the same reason: those never-enqueued items would inflate everyone else's position permanently. Position rides on a normal `pending` transition rather than a new `queued` status. An unknown status would fall through itemRenderState's pending/ processing gate and render a waiting card as FINISHED. Both SSE application paths clear the position on absence rather than only assigning when present — the `processing` transition carries none, and a stale one would leave the card claiming to be queued for the whole capture. And `isInFlight` counts queued, or applyFilters would drop the card the user just added, since it has no facets to match on yet. Known imprecision, noted in the code: runSnapshotJob enqueues directly, so an archival snapshot holds the lane without appearing in the line, and "next up" can wait one out. Snapshots are opt-in and status-neutral; not worth plumbing. Verified live: four rapid adds show 1..4, positions survive a hard reload (the hydrate path), and the line advances 4th->3rd->2nd->Next up->Capturing the page->Reading it with no stale label left behind. 598 tests + typecheck green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C83mrW9X8zBLY1sSZRgdCa
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.
Follow-up to #14. Captures run one at a time, so a second add waits on the first — but the card said "Capturing the page" the whole time, a claim about work that hadn't started. It now says "Next up" or "Queued, 3rd in line", counting down as the line moves.
Two parts, different sizes
Stopping the lie needed no server change:
statusalready distinguishespendingfromprocessing, and already flows through both SSE andhydrateItemForUi. The ordinal position is what pulls inqueue.ts,sse.ts, andhydrate.ts.Design decisions worth reviewing
The new render state keys on
queuePosition, not onstatus.pendingwith no job behind it is a real, persistent state — manual-upload boards, a missing source, an unregisteredingest_mode, and the 150 legacy imports that sat atpendingwith complete data. The in-memory line is also empty after a restart. Gating on the position means the queued state appears exactly when real queue information exists, and falls back to the old behavior otherwise.The line is tracked in the queue, not derived from
status='pending'in SQL, for the same reason: never-enqueued items would inflate everyone else's position permanently.Position rides on a normal
pendingtransition rather than a newqueuedstatus. An unknown status value would fall throughitemRenderState's pending/processing gate and render a waiting card as finished.Both SSE paths clear the position on absence, rather than only assigning when present. The
processingtransition carries no position, and a stale one would leave the card claiming to be queued for the entire capture.isInFlightcounts queued — otherwiseapplyFiltersdrops the card the user just added, since it has no facets to match on yet.Known imprecision
runSnapshotJobcallsenqueueJobdirectly, so an archival snapshot holds the lane without appearing in the line, and "next up" can wait one out. Snapshots are opt-in and status-neutral, so this is noted in a comment rather than plumbed.Verification
Live in a browser against a copy of the real DB:
Next up,Queued, 2nd in line…Queued, 4th in linehydrateItemForUipath)4th → 3rd → 2nd → Next up → Capturing the page → Reading it, with no stale label left behind598 tests + typecheck green.
🤖 Generated with Claude Code
https://claude.ai/code/session_01C83mrW9X8zBLY1sSZRgdCa