Codebook messages

Every message the install-and-autotag path can produce, in the two renderings that carry it: the SPA toast and the macOS project-sidebar row. No new glyphs, no new colours, no new surfaces — the five kinds from bristlenose/ui_kinds.py, the shipped .autocode-toast, and the existing sidebar status line and popover.
Artefact — the toast and sidebar renderings are real pixels. Commentary — hatched blocks. Not the product. Budgets — toast 60 chars, cause label 40, pill 28.

Pre-flight — the install is refused, nothing is spent

These fire on the click, before a job exists. Under D4 the researcher performed one act — get and do — so a refusal must explain which half could not happen. Three of the seven have no job to attach to on the Mac, which is a finding rather than a design: see the commentary below.
P1
503 — no API key for the current provider
✗ error
SPA toast
Add an API key in Settings to autotag.
×
38 chars / 60
macOS sidebar row
project-ikea
Needs an API key
AutoCodeMissing API key
Pre-flight. No job is created, nothing is spent.
P2
503 — provider is a local model (Ollama)
✗ error
SPA toast
Autotagging needs a cloud provider.
×
35 chars / 60
macOS sidebar row
project-ikea
Needs a cloud provider
AutoCodeLocal model
States the requirement, not the refusal.
P3
409 — several providers, none selected
✗ error
SPA toast
Choose an AI provider in Settings.
×
34 chars / 60
macOS sidebar row
project-ikea
Choose a provider
AutoCodeNo provider selected
The CLI detail names a shell command; the app must not.
P4
400 — project has no quotes
⚠ warning
SPA toast
No quotes yet — analyse your sessions first.
×
44 chars / 60
macOS sidebar row
project-ikea
No quotes to tag
AutoCodeNothing to tag
Warning, not error: correct behaviour, wrong order.
P5
409 — a job is already running
ℹ info
SPA toast
Already tagging with this codebook.
×
35 chars / 60
macOS sidebar row
project-ikea
no status line — a clean row shows none
no popover row
Info. The running toast is already on screen.
P6
409 — already applied
ℹ info
SPA toast
Already applied.
×
16 chars / 60
macOS sidebar row
project-ikea
no status line — a clean row shows none
no popover row
Info. The page already shows the result.
P7
400 — template not found
✗ error
SPA toast
That codebook is no longer available.
×
37 chars / 60
macOS sidebar row
project-ikea
Codebook unavailable
AutoCodeTemplate missing
Shipping bug, not user error. Still needs a sentence.
Commentary

Nothing here reaches the Mac today, and three of them never can as written. A pre-flight refusal creates no AutoCodeJob, so there is no job for a status line to describe. P5 and P6 are fine — they are answers to a click and the toast is the whole message. But P1, P2, P3 and P7 are configuration problems that outlive the click, and a toast that vanishes in four seconds is the wrong home for a state that will still be true tomorrow. Those belong in the same place a missing API key already belongs, not invented here.

The HTTP details are developer copy and must not be shown. Today CodebookPanel renders err.detail straight into the toast. Those strings name framework ids in quotes and, in one case, tell the researcher to run bristlenose use <provider> — a shell command, inside a Mac app. Every sentence above replaces one of them.

Lifecycle — the job exists

Running is a status, not a kind: ui_kinds.py says so in its own docstring, and the toast already renders it with a spinner and a progress track rather than a glyph.
L1
Job running
status
SPA toast
Tagging with Nielsen…
×
21 chars / 60
macOS sidebar row
project-ikea
Tagging with Nielsen…
no popover row
Status, not a kind — ui_kinds.py says so explicitly.
L2
Job completed
✓ success
SPA toast
Nielsen: 36 tags on 72 quotes.
View Report×
30 chars / 60
macOS sidebar row
project-ikea
36 tags on 72 quotes
no popover row
Carries View Report, which opens the same modal as the Review door.
L3
Completed, no matches
ℹ info
SPA toast
Nielsen found nothing to tag.
×
29 chars / 60
macOS sidebar row
project-ikea
Nothing matched
no popover row
Not a failure. The codebook ran and disagreed with the corpus.
L4
Completed, some batches failed
⚠ warning
SPA toast
Nielsen tagged 58 of 72 quotes.
×
31 chars / 60
macOS sidebar row
project-ikea
58 of 72 quotes tagged
AutoCodePartial run
The state autocode.py:425 already produces and nothing currently reports.
L5
Cancelled by the researcher
— skipped
SPA toast
Tagging cancelled.
×
18 chars / 60
macOS sidebar row
project-ikea
Cancelled
no popover row
SKIPPED, not ERROR — they chose it.
Commentary

L4 is a state the code already produces and nothing reports. autocode.py:425 logs "Batch failed: %s" and carries on, so a job can complete having tagged a subset. Today that finishes as an unqualified success and the researcher is told a number that is quietly short. It is the clearest case in this table for WARNING: complete, usable, and not what was asked for.

L3 is not a failure and must not look like one. A codebook that finds nothing has run correctly and disagreed with the corpus — which is a finding about the corpus. INFO.

Runtime failure — the job died

Mapped onto the existing classifier: LLMFailureKind in bristlenose/llm/failure_classifier.py already distinguishes all seven, and CauseCategoryEnum already has a case for each. No new category is proposed here.
F1
out_of_credit
✗ error
SPA toast
Your Claude account is out of credit.
×
37 chars / 60
macOS sidebar row
project-ikea
Out of credit
AutoCodeOut of credit
MUST NOT read "rate limited" — the doc names that exact bug.
F2
rate_limited → quota
⚠ warning
SPA toast
Rate limited — tagging stopped. Try again soon.
×
47 chars / 60
macOS sidebar row
project-ikea
Rate limited
AutoCodeRate limited
Transient. Warning because retrying is the fix.
F3
invalid_key → auth
✗ error
SPA toast
Your Claude API key was rejected.
×
33 chars / 60
macOS sidebar row
project-ikea
Key rejected
AutoCodeKey rejected
Terminal until the key changes.
F4
server_error → api_server
⚠ warning
SPA toast
Claude is unavailable. Try again shortly.
×
41 chars / 60
macOS sidebar row
project-ikea
Provider unavailable
AutoCodeProvider unavailable
Their fault, transient.
F5
bad_request → api_request
✗ error
SPA toast
Claude rejected the request.
×
28 chars / 60
macOS sidebar row
project-ikea
Request rejected
AutoCodeRequest rejected
Often a retired model id. Detail carries the raw message.
F6
network → network
⚠ warning
SPA toast
Couldn’t reach Claude. Check your connection.
×
45 chars / 60
macOS sidebar row
project-ikea
No connection
AutoCodeNo connection
F7
unknown
✗ error
SPA toast
Tagging failed.
×
15 chars / 60
macOS sidebar row
project-ikea
Tagging failed
AutoCodeUnknown error
Deliberately short. The detail is in the popover, not the sentence.
Commentary

F1 against F2 is the one that has bitten before. The popover doc records a bankrupt account being told to wait, because out-of-credit was rendered as rate-limited. Anthropic returns 400 for exhausted billing rather than 402, which is exactly why the classifier exists. These two must never share a string.

The kinds split on whether waiting fixes it. F2, F4 and F6 are WARNING — transient, retry is the remedy. F1, F3, F5 and F7 are ERROR — terminal until the researcher changes something. That is the whole rule, and it is why F2 is not an error despite stopping the run.

Today none of this survives the trip. autocode.py:458 catches bare Exception and stores str(exc), so the job's error_message is raw exception text — unclassified, unbounded, and occasionally a stack-adjacent string. The classifier is imported nowhere in the autocode path. Wiring it is the prerequisite for every row in this table.

What this needs that does not exist

Commentary — findings, not designs

0. The palette can colour one of the five kinds. Measured across bristlenose/theme and frontend/src: --bn-colour-success, --bn-colour-danger and --bn-colour-warning are used sixteen times and defined nowhere. Eleven uses carry a hard-coded fallback and work; five do not, and two of those five are the surfaces on this page — .toast-check and .toast-error (autocode-toast.css:32,37), plus both activity-chip states and the autocode report. An undefined custom property makes color invalid at computed-value time, so it inherits: the tick and the cross on the shipped autocode toast have no colour at all.

--bn-colour-danger is simply a misspelling of --bn-colour-negative, which does exist — light-dark(#dc2626, #ef4444). Worse, the eleven fallbacks pin its light-mode hex, so every one of them is wrong in dark mode. --bn-colour-success and --bn-colour-warning have no counterpart token at all, and the loose greens disagree with each other (#22c55e against #16a34a). A five-kind taxonomy needs three semantic colours and the palette defines one.

1. The SPA toast has two kinds, not five. autocode-toast.css ships .toast-check and .toast-error. Warning, info and skipped have no treatment, and toast() itself takes (message, duration) with no kind at all. Completing that is finishing an existing pattern with the existing vocabulary — not new UX.

2. The autocode path never classifies. Bare except Exceptionstr(exc). classify_exception() already exists and is not called.

3. Nothing under bristlenose/server/ writes to the events log (Q17), which is what feeds the Mac glyph and popover. Every macOS rendering above is therefore a drawing of a surface that exists, fed by a channel that does not.

4. Pre-flight configuration refusals want a durable home, not a four-second toast. Named, not solved.