Skip to content

feat: tell a queued item it's queued, and where it stands in line - #15

Merged
Seanathon merged 1 commit into
mainfrom
feat/queue-position-in-loading-state
Aug 19, 2026
Merged

feat: tell a queued item it's queued, and where it stands in line#15
Seanathon merged 1 commit into
mainfrom
feat/queue-position-in-loading-state

Conversation

@Seanathon

Copy link
Copy Markdown
Owner

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: status already distinguishes pending from processing, and already flows through both SSE and hydrateItemForUi. The ordinal position is what pulls in queue.ts, sse.ts, and hydrate.ts.

Design decisions worth reviewing

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. 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 pending transition rather than a new queued status. An unknown status value would fall through itemRenderState'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 processing transition carries no position, and a stale one would leave the card claiming to be queued for the entire capture.

isInFlight counts queued — otherwise applyFilters drops the card the user just added, since it has no facets to match on yet.

Known imprecision

runSnapshotJob calls enqueueJob 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, so this is noted in a comment rather than plumbed.

Verification

Live in a browser against a copy of the real DB:

  • Four rapid adds render Next up, Queued, 2nd in lineQueued, 4th in line
  • Positions survive a hard reload (the hydrateItemForUi path)
  • The line advances 4th → 3rd → 2nd → Next up → Capturing the page → Reading it, with no stale label left behind

598 tests + typecheck green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01C83mrW9X8zBLY1sSZRgdCa

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
@Seanathon
Seanathon merged commit 5453051 into main Aug 19, 2026
2 checks passed
@Seanathon
Seanathon deleted the feat/queue-position-in-loading-state branch August 19, 2026 22:13
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