This folder hasn’t been analysed yet
$ bristlenose run interviews/
Every state the detail pane can show, sorted into five groups by what the researcher can do about it. The Mac draws each one with the stock ContentUnavailableView that eight panes already use: an icon, a headline, one line of body, and at most one action. Where bristlenose serve has a twin, the browser frame shows the shipped status page. Nothing here is a new component. What changes is the words and the icon in each slot. Every changed string is outlined, and the string table in §5 lists them all in one place.
ContentUnavailableView, the run's progress on the sidebar row's status line, app-global progress in the toolbar pill, and the shipped status page for the browser. The run ring is the sidebar's own SidebarActivityRing.ContentUnavailableView, rendered through ImageRenderer on this Mac. The icons are the real SF Symbols, rendered from AppKit by experiments/icon-system/render_sf.swift. The status page CSS is templates/status-page.css, inlined verbatim. detail-pane-states.gen.py regenerates those three blocks.Drop a folder of recordings or transcripts
Bristlenose adds the folder as a project.
Drag interviews here
Recordings or transcripts.
6 files to analyse
3 projects selected
Choose one to see its report.
$ bristlenose run interviews/
welcome.dropFolderTitle and dropFolderHint are read only by the no-selection pane (ContentView.swift:3903). Since Model 2 (3 Oct 2026) the Welcome window has no drop card — “the Drop-a-folder card lives in the main window” (WelcomeHomeView.swift:11) — so rewording them changes nothing else. The Welcome window’s nearest line is the Ingest cell: “Drop a folder of recordings or transcripts — Bristlenose transcribes, analyses and reports back.” (welcome.home.tools.ingest.text). Checked 24 Oct 2026.square.and.arrow.down today and N00 uses tray.and.arrow.down. The proposal puts the tray on both, since they are one concept.Queued · position 2
Starts when the analysis before it finishes.
Transcribing · 3 of 8 · ~12 min left
The report opens as soon as the first interview is readable.
SidebarActivityRing drawn at icon size and fed by the same run_progress fraction the row uses. The headline is the row’s own line, composed by RunProgressSubtitle from keys that already exist, so the pane and the row cannot disagree and there is no new headline key. Only the body is new.Can’t find this project’s folder
It may have been moved, renamed or deleted. If you know where it is, point Bristlenose at it.
Choose an AI provider to analyse this project
Bristlenose sends the transcripts to an AI provider such as Claude or ChatGPT. Pick one in Settings.
Analysis stopped
docs/design-project-condition.md §6.1); D4 for failed runs stays open.“Samsung T7” isn’t connected
Reconnect to continue.
“server.local” can’t be reached
Reconnect to continue.
The analysis stopped before it finished
The app quit, or the Mac lost power.
docs/design-feedback-native.md § “Feedback and problem reports” (mockup n08-button-set.html))Couldn’t start Bristlenose
Bristlenose stopped before it finished starting (code 1).
This report couldn’t be shown.
UnsupportedSubsetView and its unsupportedSubset.* keys were deleted (d94edf6d1), and nothing has created a loose-file study since 10 Jul 2026 (0ac2136d).bbf75243). A cloud folder that has gone is N02 (Project Moved or Deleted, Locate…); an evicted one still exists and is simply ready. No cloud pane, glyph or provider advice — docs/design-cloud-files.md.dt()/ct() fork, docs/platform-text-map.md). The button is Resume Analysis (owner, 24 Oct 2026): Analyse already resumes from the partial output, so the button names what it does.BootView’s existing disclosure, not a second action.The analysis didn’t finish
Claude wouldn’t accept the API key. Check it in Settings ▸ LLM Provider.
Claude wouldn’t accept the API key.
category: auth
The Bristlenose React bundle is missing from this build. Serve mode refuses to start without it.
If you are running the desktop app, this build is broken — reinstall or report a bug.
If you are a developer: run npm run build in frontend/ and restart bristlenose serve.
The Bristlenose React bundle is missing from this build. Serve mode refuses to start without it.
If you are running the desktop app, this build is broken — reinstall or report a bug. If you are a developer: run npm run build in frontend/ and restart bristlenose serve. If running from the PyInstaller sidecar, check that bristlenose/server/static/ is in the spec’s datas. BUG-3 class — see the C3 smoke test post-mortem for context.
error-patterns-and-chrome-crash.html: a remedy-1 cause gets the button that goes to the fix (Set Up Provider…, Locate…), a remedy-2 cause gets Retry, and the others get no button.analysis-failure-states.html § D. The popover ships pipeline.failure.auth, which says “Your {{provider}} key was rejected.” Either wording gives one vocabulary. The owner words it once, and both surfaces then read the same key (see §2.3)._format_cause writes (category, code, stage, provider, message). The record stays English, as forensic records do.bbf75243), so there is no before or after. A gone cloud folder is N02; policy docs/design-cloud-files.md.Choose an AI provider to analyse this project
Bristlenose sends the transcripts to an AI provider such as Claude or ChatGPT. Pick one in Settings.
Error code: 401 - {'type': 'error', 'error': {'type': 'authentication_error', 'message': 'invalid x-api-key'}}
category: auth
Your Claude key was rejected.
Provider key issue
The analysis didn’t finish
Claude wouldn’t accept the API key. Check it in Settings ▸ LLM Provider.
Claude wouldn’t accept the API key. Check it in Settings ▸ LLM Provider.
Provider key issue
Bristlenose
Sensemaking for User Research
Couldn’t start Bristlenose
Bristlenose stopped before it finished starting (code 1).
Couldn’t start Bristlenose
Bristlenose stopped before it finished starting (code 1).
boot.failedTitle, boot.failure.*, boot.retry, boot.showDetails) are all kept, and only boot.tagline would lose its one consumer here. Retry stays the default button, as .keyboardShortcut(.defaultAction) makes it today.Queued · position 2
Starts when the analysis before it finishes.
Transcribing · 3 of 8 · ~12 min left
The report opens as soon as the first interview is readable.
RunCover cases (DetailPaneKind.runCover), with a body line added and the ring standing in for waveform. Frame 3 is the shipped progressive hand-over (shoalTakesPane gives way when sessionsReadable > 0, design-progressive-lenses.md). It is drawn here only as a schematic, so the sequence reads end to end.| Concept | SF Symbol (Mac) | Lucide (browser) | Used by | Today |
|---|---|---|---|---|
| Empty — add interviews | tray.and.arrow.down | inbox | N00, N04a, N04b | square.and.arrow.down on N04 |
| Several selected | square.stack | layers | N10 | same |
| Queued | clock | clock | Queued cover | same |
| Working | no icon — the run ring takes the slot | Analysing cover | waveform | |
| Folder moved | questionmark.folder | folder-search | N02 | same |
| Needs a provider | key | key-round | Refused | — (status page ℹ) |
| Stopped | stop.circle | circle-stop | Stopped | — (status page ⚠) |
| Drive not connected | externaldrive.badge.xmark | hard-drive | N01 | externaldrive.trianglebadge.exclamationmark |
| Share unreachable | network.slash | wifi-off | N01n | the drive’s icon |
| Stopped unexpectedly / failed | exclamationmark.triangle | triangle-alert | Stranded, Failed, N08 (option B) | — (status page ⚠ / ✗; N08 the app icon) |
CLI_GLYPH), not an icon, and this proposal leaves it that way. The Lucide twin is the name the SPA reaches for when the same concept appears in the report. Listing both keeps one concept to one picture on each platform.waveform to the ring. Every other icon is the one the pane already uses. The SF Symbols were rendered on this Mac from AppKit. The Lucide icons come from the installed lucide-react.E existing key, reworded N new key (the suggested key name is in amber) K keep as shipped. Each E or N row is a change across 21 locales.
| State | Element | Key | Current | Proposed | |
|---|---|---|---|---|---|
| Group 1 — Nothing here yet | |||||
| N00 no selection | headline | desktop.welcome.dropFolderTitle | Drop a folder | Drop a folder of recordings or transcripts | E |
| body | desktop.welcome.dropFolderHint | Drag a folder of recordings or transcripts here to add it as a project. | Bristlenose adds the folder as a project. | E | |
| N04a empty | headline | desktop.chrome.dragInterviews | Drag Interviews Here | Drag interviews here | E |
| body | desktop.chrome.dragInterviewsDescription | Add interview recordings or transcripts to get started. | Recordings or transcripts. | E | |
| N04b files waiting | headline | desktop.chrome.filesToAnalyse_one / _other | 1 file to analyse / {{count}} files to analyse | — | K |
| action | desktop.menu.project.analyse | Analyse | — | K | |
| N10 multi-select | headline | desktop.chrome.multipleSelected | %d items selected | {{count}} projects selected (becomes a plural set, _other etc.) | E |
| body | desktop.chrome.multipleSelectedHint | Select a single project to view its report. | Choose one to see its report. | E | |
| W02 CLI, never run | headline | server.statusPage.noRunCliShort | Nothing to see here, yet. | This folder hasn’t been analysed yet | E |
| body | literal, not localised | $ bristlenose run interviews/ | — | K | |
| Group 2 — Working | |||||
| Boot | — | desktop.boot.startingSidecar / loadingReport | not drawn (bar only) | — | K |
| Queued | headline | desktop.chrome.pipeline.queuedPosition | Queued · position {{position}} | — | K |
| body | desktop.chrome.pipeline.queuedHint | (none) | Starts when the analysis before it finishes. | N | |
| Analysing | headline | desktop.chrome.pipeline.analysing → RunProgressSubtitle (stage.* · sessionsCount · etaMinutes) | Analysing… | the row’s own line, e.g. Transcribing · 3 of 8 · ~12 min left — no new key | K |
| body | desktop.chrome.pipeline.analysingHint | (none) | The report opens as soon as the first interview is readable. | N | |
| W-IP in progress | headline | server.statusPage.inProgressShort | Analysing… | Analysing interviews | E |
| Shoal | — | — | no text | — | K |
| Group 3 — Needs you | |||||
| N02 moved | headline | desktop.chrome.projectMoved | Project Moved or Deleted | Can’t find this project’s folder | E |
| body | desktop.chrome.projectMovedDescription | The project folder couldn’t be found. It may have been moved or deleted. | It may have been moved, renamed or deleted. If you know where it is, point Bristlenose at it. | E | |
| action | desktop.chrome.locate | Locate… | — | K | |
| Refused, no provider | headline | desktop.chrome.providerNeeded (pane today: server.statusPage.noRunDesktopHasInterviews) | Not analysed yet. | Choose an AI provider to analyse this project | N |
| body | desktop.chrome.providerNeededHint | (none — the reason is only on the row: “Provider not set up”) | Bristlenose sends the transcripts to an AI provider such as Claude or ChatGPT. Pick one in Settings. (two examples, not the list — decided Q6) | N | |
| action | desktop.pipeline.diagnostic.action.setUpProvider | Set Up Provider… (popover only) | — (reused in the pane) | K | |
| Stopped | headline | server.statusPage.cancelledShort (+ desktop.chrome.stoppedTitle for the native pane) | Last run was cancelled. | Analysis stopped | E |
| body | server.statusPage.cancelledLong | Re-run when ready. | — no body; the key is retired | E | |
| action | desktop.chrome.resumeAnalysis | — | Resume Analysis (the same key as the stranded button; Analyse after a stop resumes from the partial output, adaa97e5) | N | |
| Group 4 — Can’t reach it | |||||
| N01 drive | headline | desktop.chrome.projectUnavailable + the volume name as a second line | Project Unavailable / Samsung T7 | “{{name}}” isn’t connected | E |
| body | desktop.chrome.projectUnavailableHint | Connect the drive to access this project. | Reconnect to continue. | E | |
| N01n network share | headline | desktop.chrome.shareUnreachable | (the drive’s: Project Unavailable / server.local) | “{{host}}” can’t be reached | N |
| body | desktop.chrome.projectUnavailableHint | Connect the drive to access this project. | Reconnect to continue. (the drive’s key: one body for both) | K | |
| Stranded | headline | server.statusPage.strandedShort · desktop.pipeline.failure.stranded | Analysis stopped unexpectedly. | The analysis stopped before it finished | E |
| body | server.statusPage.strandedLong (+ desktop.chrome.strandedHint, the Mac form) | Re-run when ready. | Mac: The app quit, or the Mac lost power. Browser: — no body; strandedLong is retired | E | |
| action | desktop.chrome.resumeAnalysis | — | Resume Analysis (Analyse after a stranded run resumes from the partial output, adaa97e5; the menu item for the same command stays Analyse) | N | |
| N08 engine failed | headline · reason · buttons | desktop.boot.failedTitle · boot.failure.* · boot.retry · boot.showDetails | Couldn’t start Bristlenose · e.g. Bristlenose stopped before it finished starting (code 1). · Retry · Show details | — | K |
| tagline | desktop.boot.tagline | Sensemaking for User Research | no longer drawn here (option B, decided Q2) | K | |
| Report crash | body · button | common.labels.reportError · buttons.retry | This report couldn’t be shown. · Retry | — (settled 20–21 Oct) | K |
| row · popover | desktop.pipeline.diagnostic.header.reportError · desktop.pipeline.diagnostic.reportError | Report couldn’t be shown · Your analysis is safe — only the display failed. … | — (settled) | K | |
| Lens / part crash | body | common.labels.lensError · common.labels.partError | This lens couldn’t be shown. Try again, or choose another lens. · This couldn’t be shown. | — (settled) | K |
| Group 5 — It broke | |||||
| Failed | headline | server.statusPage.failedShort (+ desktop.chrome.failedTitle) | Last run failed. | The analysis didn’t finish | E |
| body | today: raw cause.message; proposed: the popover’s reason key, e.g. desktop.pipeline.failure.auth | Error code: 401 - {'type': 'error', …} (provider’s raw text). The popover today says: Your {{provider}} key was rejected. | Claude wouldn’t accept the API key. Check it in Settings ▸ LLM Provider. (from analysis-failure-states.html § D; one sentence per cause, read by both the popover and the pane; the browser drops the Settings clause) | E | |
| action | desktop.pipeline.diagnostic.action.setUpProvider · desktop.boot.retry | — | Set Up Provider… or Retry, by remedy | K | |
| disclosure | server.statusPage.showDetails · desktop.boot.showDetails | Show details | — | K | |
| W05 build incomplete | page | app.py literal (dev only) | Build incomplete + three paragraphs | — (restyle only) | K |
ContentUnavailableView titles do on macOS.ContentUnavailableView; the browser keeps the status page with the same words.docs/design-feedback-native.md § “Feedback and problem reports” (mockup n08-button-set.html).docs/design-project-condition.md §6.1); D4 for failed runs is not decided here.docs/design-feedback-native.md § “Feedback and problem reports” (mockup n08-button-set.html). Stranded-run copy was being worded in another session; the owner set it here on 24 Oct 2026 (body “The app quit, or the Mac lost power.”, button Resume Analysis; sleep never strands a run, so “the Mac may have slept” was dropped), so that session should take these words.