AutoCode progress

Where a tagging run reports itself, in the two channels that render it. The SPA toast is shipped and unchanged. The macOS project sidebar row is the proposal: progress is a state that outlives the click, and the Mac already owns a surface for exactly that. Failure strings are not re-drawn here — they are inventoried in codebook-v2-messages.html, which this page extends rather than repeats.
Artefact — toast, row and modal are real pixels on shipped classes. Commentary — hatched blocks. Not the product. Budgets — toast 60 chars, sidebar status line ~22.

1 — The run, in both channels

Same job, same second, two renderings. The toast column is what AutoCodeToast.tsx paints today. The sidebar column uses the vocabulary already shipped for a pipeline run: verb-led text,  ·  separator, a bare n of m count, and the 16pt determinate ring in the subtitle-right slot with its hover-× cancel.
when
SPA toast — ships today, unchanged
macOS sidebar row — proposed
T+0sJob accepted, no counts yet
✦ AutoCoding …/… quotes…
×
no progress track until total_quotes > 0
project-ikea
Tagging…
8 chars / 22  ·  indeterminate, like an uncalibrated run
T+1m 12sBatches landing, 2s poll
✦ AutoCoding 48/72 quotes…1:12
×
28 chars / 60
project-ikea
Tagging · 48 of 72
18 chars / 22  ·  no elapsed, no ETA — the count is the sentence
T+1m 48sComplete, 36 proposals waiting
✦ AutoCoded 72 quotes in 1:48.Report
30 chars / 60  ·  sticky — no close button once completed
project-ikea
36 tags to review
17 chars / 22  ·  ring gone — motion is for in-flight only
T+1m 48sComplete, but batches failed
Tagged 58 of 72 quotes — some batches failed.Report
45 chars / 60
project-ikea
58 of 72 quotes tagged
AutoCodePartial run
22 chars / 22  ·  at budget in English, over it in German
anyRun died
Your Claude account is out of credit.×
auto-dismisses after 30s
project-ikea
Out of credit
AutoCodeOut of credit
the seven failure kinds are inventoried in the sibling page
Commentary

The sidebar row needs no new vocabulary, and that is the argument for it. Every element above already ships for a pipeline run: chrome.pipeline.sessionsCount is literally "{{complete}} of {{total}}", the  ·  separator is SidebarSubtitleText.activityText's, and the ring is SidebarActivityRing at its shipped 16pt with the hover-× cancel. A tagging run is a long job against a project; the row already knows how to say that.

The Mac drops the elapsed timer and the percentage on purpose. The toast can afford 1:12 because it is a transient thing you are looking at. The row is glanced at from across a window that is otherwise about something else, and cloud-import-sidebar-progress.html already settled the question for the import phase: a count is the question a closed window leaves — not how fast, not which batch, but how many are still to wait for. Same argument, same answer.

22 characters is a real ceiling, not a guess. SidebarSubtitleText.swift:34-44 records the run-failed string being cut back to "Run failed" because a 36-character sentence truncated mid-phrase in English before any locale swell. 58 of 72 quotes tagged is exactly 22 and therefore already at the line in the shortest language we ship. It wants shortening before it is translated, not after.

2 — The door to the review

The toast carries two jobs fused into one control: it reports progress, and it is the way into the review. Moving progress to the sidebar splits them, and the split has to be deliberate — a status line is attention, not affordance.
A — row states the count (consistent with the sibling page)
project-ikea
36 tags on 72 quotes
20 chars / 22  ·  reports what happened
B — row states the pending decision
project-ikea
36 tags to review
17 chars / 22  ·  reports what is owed
Commentary — the open call

A is what the sibling page already drew; B is what this flow argues for. The run is not finished when the job completes — nothing has been applied to a single quote yet. Thirty-six proposals sit in ProposedTag with status="pending" until someone opens the review and moves the thresholds. A row that says 36 tags on 72 quotes describes a completed act; the researcher's actual state is a decision is waiting. B says that, and is shorter.

Neither row is a button, and neither should become one. The sidebar is attention, not affordance — the standing rule for this surface. So on the Mac the door to the review lives where the codebook lives: the framework card in the codebook lens, in the WKWebView, which is the same React surface the browser shows. The toast's Report link is the browser's door because the browser has no sidebar; it is not a second door on the Mac, it is the only door on the other channel.

3 — The review is one surface on both channels

Whatever opens it, ThresholdReviewModal is React inside the same web view, so there is nothing to fork. Drawn once, on the shipped threshold-* classes.
Commentary

The three zones are where the researcher's judgement actually goes, and only one of them is a decision. Above the upper threshold is accepted, below the lower is denied, and the band between is left pendinghandleApply calls acceptAllProposals(upper) then denyAllProposals(lower) and touches nothing in the middle. Tentative is not a third verdict, it is the absence of one, which is why its counter is the dashed border rather than a filled state.

Apply is the only irreversible act in the whole flow, and it is two sequential network calls with no transaction around them. If the second fails, the accepts have already landed and the denials have not; autocode.review.applyFailed says "your per-row decisions are saved", which is true and does not describe that particular half-applied state.

The tokens here are correct, unlike the toast's. threshold-review.css names --bn-colour-positive and --bn-colour-warning, both of which the palette defines in default and Edo. It is autocode-toast.css that reaches for --bn-colour-success and --bn-colour-danger, which the palette does not define at all. So the review renders correctly today and the toast's tick does not — and if progress moves to the sidebar, that bug stays behind in the browser channel rather than being fixed by the move.

4 — What this needs that does not exist

Commentary — findings, not designs

1. There is no SubtitleVariant case for tagging. The enum (ProjectSubtitle.swift) carries running, queued, copying, importingBatch, addingInterviews and nine more — every long job the app knows about. AutoCode is not among them, so the row above cannot be produced by the shipping code. Adding a case is the smallest change that makes any of this drawable, and it is genuinely new — flagged here rather than slipped in.

2. The ring is driven from PipelineState, and AutoCode is not one. ProjectRowActivityIndicator.Kind.from switches exhaustively over PipelineState with no default: — deliberately, so a new state forces a decision at compile time. AutoCode runs in serve mode and has its own AutoCodeJob table; it never becomes a PipelineState, so it cannot reach that switch at all. Either the indicator grows a second input or autocode jobs get projected into the state the sidebar already reads.

3. Nothing under bristlenose/server/ writes to the events log. That log is the channel the Mac glyph, subtitle and popover are fed from, and the sibling page recorded the same gap for the failure strings. AutoCode is entirely a serve-mode feature — server/autocode.py, server/routes/autocode.py, polled over HTTP by the SPA every 2s. Every sidebar row on this page is therefore a drawing of a surface that exists, fed by a wire that does not. This is the one blocking dependency; items 1 and 2 are small in front of it.

4. The completed toast never dismisses itself, and that is load-bearing. AutoCodeToast.tsx renders the close button only when the job is not completed, and auto-dismisses on failure alone. So today the toast is the persistence layer for "a decision is waiting" — badly, since it dies with the tab. If the Mac takes that state into the row, the browser still needs somewhere durable for it, and the framework card in the lens is the obvious candidate on both channels.