Bristlenose
Open-source user-research transcript analysis.
Point Bristlenose at a folder of interview recordings – videos, audio or transcripts from Zoom, Teams or Google Meet.
It transcribes, identifies moderators and participants, extracts good verbatim quotes, groups them by screen and theme. Filler words are stripped, editorial context is (sparingly) added [thus]. Emotion and strong language are preserved.
The (HTML) report aggregates all your interview quotes.
Click a timecode to watch the video of that quote.
You can check a summary of unused quotes - to ensure nothing crucial was trimmed by the AI - typically ~20% or less of the original.
You can tag and star quotes to organise them. It auto-tags with sentiment analysis to help you identify moments of frustration or delight.
Filter your quotes, and export via CSV to your boards in Miro, Figjam, Mural or Lucidspark, or spreadsheet - for further analysis.
Bristlenose transcribes on your machine and sends the transcripts to a frontier model for the thematic analysis — Claude, ChatGPT, Gemini, or Azure OpenAI. That is the everyday path, and you'll want an API key for it. Running the analysis on a local model instead is supported through Ollama (a separate install), but it is slower and wants a capable machine. For commercial work, check your org's policies on public LLM use.
Expect about $0.40 per hour of interview audio with Claude — provider costs vary.
Beta software, without warranty. All feedback welcome.
Bristlenose is built by me, Martin Storey, a practising user researcher. It's free and open source under AGPL-3.0.
Help translate Bristlenose into your language on Weblate — see TRANSLATING.md for details.
Sidequest: what is a Bristlenose?
What it does
The report includes:
- Sections -- quotes grouped by screen or task
- Themes -- cross-participant patterns, surfaced automatically
- Tags -- your own free-text tags with auto-suggest
- Sentiment -- AI-generated badges per quote
- Charts -- histogram of emotions across all quotes
- User journeys -- per-participant stage progression
- Per-participant transcripts -- full transcript pages with clickable timecodes, linked from the participant table
- Clickable timecodes -- jump to the exact moment in a popout video player
- Starred quotes -- star, reorder
- Inline editing -- fix transcription errors directly in the report
- Editable participant names -- click the pencil icon to name participants in-browser; export edits as YAML
- Filter and export as CSV into Miro and more
- Keyboard shortcuts -- j/k navigation, s to star, t to tag, / to search, ? for help; multi-select with Cmd+click or Shift+j/k for bulk actions
- Codebook frameworks -- import Nielsen's 10 Heuristics, Yablonski's Laws, Don Norman, Jesse James Garrett, or the Morville honeycomb, or build your own
- AutoCode -- LLM proposes tags from your codebook; review with a confidence threshold
- Inspector panel -- DevTools-style heatmaps and signal cards with Win/Problem/Niggle/Surprise flags
- Word-level transcript highlighting -- karaoke-style sync with video playback
- Video clip extraction -- export starred quotes as video clips (FFmpeg stream-copy)
- Self-contained HTML export -- one-click bundle for stakeholders, optional anonymisation
- Send to Miro -- push analysed quotes straight onto a Miro board (a native sheet on macOS; a panel in the browser report)
- Incremental analysis -- add more interviews to a finished project and re-run; your starred, tagged, and edited quotes carry across, and new material is flagged
- Ask your study from your own agent -- a read-only MCP endpoint hands Claude, Claude Code, ChatGPT or Codex four tools over your curated quotes, themes, signals and codebook; on macOS it installs into Claude Desktop as a one-click extension and into the ChatGPT app as a one-click plugin, with no address, token or permission to set, and each project is exposed only when you turn agent access on
Install
For LLM analysis, you can use Claude, ChatGPT, Azure OpenAI, Gemini, or Local AI (free, via Ollama), or, on the command line, reach Mistral, OpenRouter and others through any-llm — see Getting an API key below.
macOS (desktop app) — easiest
Download Bristlenose.dmg, drag to Applications, open it. The app prompts for your Claude API key on first launch and bundles everything else (Python, FFmpeg, Whisper). No terminal needed.
First launch: because it's downloaded from the internet, macOS shows a "are you sure you want to open it?" confirmation — click Open. One time only. (The app is signed and notarised, so there's no "unidentified developer" block to work around.)
Requires macOS 15 Sequoia, Apple Silicon (M1+).
macOS (Homebrew), Linux, Windows
Requires Python 3.10 or newer (3.11 or newer for any-llm).
# macOS (Homebrew) -- handles ffmpeg + Python for you
brew install cassiocassio/bristlenose/bristlenose
brew trust --formula cassiocassio/bristlenose/bristlenose
# Ubuntu / Debian and most other distros (Snap) -- bundles Python + FFmpeg
sudo snap install bristlenose --edge
# Fedora (Copr) -- bundles Python, pulls FFmpeg from Fedora's own repos
sudo dnf copr enable cassiocassio/bristlenose
sudo dnf install bristlenose
# Linux / macOS / Windows (pipx or uv)
pipx install bristlenose
uv tool install bristlenose # alternative
The brew trust line is a one-off. Homebrew 6.0 and later skip third-party taps during brew upgrade unless trusted, so without it Bristlenose installs fine but silently stops receiving updates.
The Snap and the Fedora package are both amd64/x86_64 only — on ARM Linux, use pipx.
The [voice] extra is optional: pipx install "bristlenose[voice]" (add --force if Bristlenose is already installed) adds a voice pass to speaker identification, which tells two speakers apart by how they sound when the transcript doesn't already say who is speaking. It downloads a 40 MB model on first use (or with bristlenose doctor --fetch), runs on your machine, and is skipped without the extra. Set BRISTLENOSE_VOICE_PASS=false to turn it off. It isn't available on Windows with Python 3.14 yet, because one of its libraries has no Windows build for 3.14; Python 3.13 or earlier has it. (Quote the spec: zsh reads bare brackets as a glob, and only double quotes work in every shell, Windows cmd included.)
If using pipx or uv, you'll also need FFmpeg (brew install ffmpeg on macOS, sudo apt install ffmpeg on Ubuntu, sudo dnf install ffmpeg-free on Fedora, winget install --id Gyan.FFmpeg -e --source winget on Windows).
If you have Anaconda installed, its bundled Python may shadow newer system Pythons. Either conda deactivate before running pip/pipx, or install via Homebrew (which uses its own bundled Python 3.12).
After installing, run bristlenose doctor to verify your setup.
See the installation guide for detailed step-by-step instructions for your platform, including Python/FFmpeg setup and AI provider configuration.
Getting an API key
If you've used Claude or ChatGPT before, you might only know the chat interface. Bristlenose talks to the same AI models, but through their API (a direct connection for software). This needs a separate API key -- a password that lets bristlenose call the AI on your behalf.
You need one key:
Option A: Claude (by Anthropic)
- Go to console.anthropic.com and sign up or log in
- Click Create Key, give it a name (e.g. "bristlenose"), and copy the key
- Store it securely:
bristlenose configure claude # validates the key and stores it in your system credential store
Option B: ChatGPT (by OpenAI)
- Go to platform.openai.com and sign up or log in
- Click Create new secret key, give it a name, and copy the key
- Store it securely:
bristlenose configure chatgpt # validates the key and stores it in your system credential store
Configuring makes ChatGPT your current provider. To pick it for a single run without switching, add --llm chatgpt:
bristlenose run interviews -o results/ --llm chatgpt
Option C: Azure OpenAI (enterprise)
If your organisation has a Microsoft Azure contract that includes Azure OpenAI Service:
export BRISTLENOSE_AZURE_ENDPOINT=https://your-resource.openai.azure.com/
export BRISTLENOSE_AZURE_DEPLOYMENT=your-deployment-name
bristlenose configure azure # validates the key and stores it in your system credential store
bristlenose run interviews --llm azure
You'll need your endpoint URL, API key, and deployment name from the Azure portal.
Option D: Gemini (by Google)
- Go to aistudio.google.com/apikey and sign up or log in
- Click Create API key, give it a name, and copy the key
- Store it securely:
bristlenose configure gemini # validates the key and stores it in your system credential store
Configuring makes Gemini your current provider. To pick it for a single run without switching, add --llm gemini:
bristlenose run interviews -o results/ --llm gemini
Option E: Local AI (via Ollama) — free, no signup
Run analysis entirely on your machine using open-source models. No account, no API key, no cost.
- Install Ollama (one download, no signup)
- Run bristlenose — it will offer to set up local AI automatically:
bristlenose interviews
# Or explicitly:
bristlenose run interviews --llm local
If Ollama isn't installed, bristlenose will offer to install it for you (via Homebrew on macOS, snap on Linux).
Trade-offs: Local models are slower (~10 min vs ~2 min per study) and less accurate (~85% vs ~99% JSON reliability). Good for trying the tool; use cloud APIs for production quality.
Option F: Other providers, through any-llm
On the command line, Bristlenose reaches providers it doesn't build in through any-llm, Mozilla AI's library for talking to many model providers in one way. It needs Python 3.11 or newer. Homebrew includes it; with pipx or uv, add it to the install you have (--force keeps your settings and keys, and if you added [voice], list it too: "bristlenose[voice,any-llm]"):
pipx install --force "bristlenose[any-llm]" # or: uv tool install --force "bristlenose[any-llm]"
bristlenose configure any-llm
If it isn't installed, configure any-llm prints the exact command for your install. The Snap and Fedora packages don't include it.
configure any-llm asks for the model, written as provider:model, then for that provider's API key, unless it's already set in your shell (MISTRAL_API_KEY, OPENROUTER_API_KEY, DEEPSEEK_API_KEY…; a .env file isn't read for these). It checks the key, stores it like the others, and makes it your provider. A key set in your shell takes precedence over the stored one. Switch between models you've set up with bristlenose use deepseek:deepseek-v4-pro, and back with bristlenose use claude.
| I want to use | How |
|---|
| Claude, ChatGPT, Gemini, Azure OpenAI | Options A–D: bristlenose configure claude (or chatgpt, gemini, azure) |
| A local model with Ollama | Option E: bristlenose configure local |
| Mistral | Model mistral:mistral-large-latest; key from console.mistral.ai. Mistral's free plan allows no API requests: add credit first (a €10 prepay was enough) |
| DeepSeek | Model deepseek:deepseek-v4-pro or the cheaper deepseek:deepseek-flash; key from platform.deepseek.com, after a top-up ($2 minimum) |
| Hundreds of models with one key (Grok, Qwen, DeepSeek, Mistral, Claude…) | Model openrouter: plus the id from openrouter.ai/models, vendor included: openrouter:x-ai/grok-4.3. Key and credit from openrouter.ai. Pick a model whose page lists structured outputs |
| Another provider any-llm supports (list) | Model <provider>:<model>. The first configure names the add-on that provider needs and prints the command for your install |
| Claude or Gemini through your company's AWS, Google Cloud or Azure AI account | Not supported |
Choose a provider's larger models. On the same three interviews, Mistral Large covered nearly the same ground as Claude Sonnet, while Mistral Small found about a quarter of it and dropped fields Bristlenose needs; DeepSeek V4 Pro came closer than Flash, which also over-extracts.
The transcript text goes to the provider you named, under your account and its terms; through OpenRouter, to OpenRouter, which passes it to the model's host. Before you send participant data, check the data policy of the provider and model, whether a free tier may use your inputs for training, and where the provider is based (DeepSeek is in China). Bristlenose doesn't estimate costs for these providers; their own consoles show what you've spent. Measured quality, speed and quirks per model: docs/design-any-llm-models.md.
any-llm isn't in the Mac app yet.
Use whichever provider you already have an API key for. The provider you configure becomes your current one and stays current until you switch (bristlenose use <provider>) — with a single configured provider, a bare bristlenose run just uses it. A typical 8-participant study costs roughly $1--3 with a cloud provider -- costs vary, check your provider's pricing page.
Note: A ChatGPT Plus/Pro or Claude Pro/Max subscription does not include API access. The API is billed separately — you need an API key from console.anthropic.com or platform.openai.com.
Making your key permanent
The recommended way is bristlenose configure (shown in Options A--D above). It validates your key and stores it:
- macOS — saved to your login keychain. You can view or delete it in the Keychain Access app (search for "Bristlenose")
- Linux — saved via Secret Service (GNOME Keyring / KDE Wallet). Requires
secret-tool to be installed (included by default on most desktop Linux distributions); without it, saved to ~/.config/bristlenose/.env, readable only by you
- Windows — saved to a config file in your user folder,
C:\Users\<you>\.config\bristlenose\.env. Windows Credential Manager isn't supported yet
The key is loaded automatically on every run — no environment variables needed. Run bristlenose doctor to verify your key is detected (it will show "(Credential Store)" next to the API key check).
Windows, as an environment variable instead: setx (built into Windows) sets the key permanently:
setx BRISTLENOSE_ANTHROPIC_API_KEY "sk-ant-..."
For ChatGPT, use BRISTLENOSE_OPENAI_API_KEY; for Gemini, use BRISTLENOSE_GOOGLE_API_KEY; through any-llm, the provider's own name, such as MISTRAL_API_KEY. Close and reopen your terminal after running setx — the variable only takes effect in new windows.
Alternative: shell profile (macOS/Linux). If you prefer environment variables over the credential store, add the export to your shell startup file:
# macOS (zsh is the default shell):
echo 'export BRISTLENOSE_ANTHROPIC_API_KEY=sk-ant-...' >> ~/.zshrc
# Linux (bash is the default shell on most distributions):
echo 'export BRISTLENOSE_ANTHROPIC_API_KEY=sk-ant-...' >> ~/.bashrc
Then open a new terminal window (or run source ~/.zshrc / source ~/.bashrc).
Alternative: .env file. Create a .env file in your project folder -- see .env.example for a template.
Troubleshooting: Run bristlenose doctor at any time to check your setup — it verifies your API key, FFmpeg, transcription backend, and other dependencies, and exits non-zero if any check fails, so a script can rely on it.
Quick start
bristlenose interviews
That's it. Point it at a folder containing your recordings and it will produce the report inside that folder. Expect roughly 2--5 minutes per participant on Apple Silicon, longer on CPU.
Open interviews/bristlenose-output/bristlenose-interviews-report.html in your browser.
What goes in
Any mix of audio, video, subtitles, or transcripts:
.wav .mp3 .m4a .flac .ogg .wma .aac .aiff .aif .caf .mp4 .m4v .mov .avi .mkv .webm .wmv .asf .mts .m2ts .3gp .flv .mpg .mpeg .srt .vtt .docx
Files sharing a name stem (e.g. p1.mp4 and p1.srt) are treated as one session. Existing subtitles skip transcription.
What comes out
Output goes inside the input folder by default:
interviews/ # your input folder
├── Session 1.mp4
├── Session 2.mp4
└── bristlenose-output/ # output folder (created inside input)
├── bristlenose-interviews-report.html # the report -- open this
├── bristlenose-interviews-report.md # Markdown version
├── people.yaml # participant registry
├── assets/ # static files (CSS, logos, player)
├── sessions/ # per-session transcript pages
│ ├── transcript_s1.html
│ └── transcript_s2.html
├── transcripts-raw/ # one .txt + .md per session, as first transcribed; rewritten every run
├── transcripts-cooked/ # PII-redacted (only with --redact-pii)
└── .bristlenose/ # internal files
└── intermediate/ # JSON snapshots (re-readable by `bristlenose serve`)
Override the output location with --output: bristlenose run interviews/ -o /elsewhere/
More commands
bristlenose run interviews -p "Q1 Usability Study" # name the project
bristlenose transcribe interviews # transcribe, no LLM
bristlenose run interviews --whisper-language cy # pin the spoken language (detected per file by default)
bristlenose analyze interviews/bristlenose-output/transcripts-raw/ # skip transcription, run LLM analysis
bristlenose serve interviews # open a previous report (no analysis)
bristlenose status interviews # check project status (read-only)
bristlenose doctor # check dependencies
bristlenose configure claude # store an API key in your credential store
bristlenose codebooks # list AutoCode framework templates
bristlenose pipeline # show which AI models each stage uses
bristlenose help # detailed help on commands and configuration
Full reference for every command, option, and exit code: man bristlenose, or the CLI reference on the website. The man page is installed automatically — by the package manager for Homebrew and Snap, and on first run for pip/pipx installs.
Configuration
Via .env file, environment variables (prefix BRISTLENOSE_), or bristlenose.toml. See .env.example for all options.
Hardware
Transcription hardware is auto-detected. Apple Silicon uses MLX on Metal GPU. NVIDIA uses faster-whisper with CUDA. Everything else falls back to CPU.
Roadmap
Analysis
- Your own themes -- create, rename and reorder themes manually, not just the AI-generated ones
Sharing
- Edit writeback -- save your in-browser corrections back to the transcript files on disk
Platform
- Windows installer -- native setup wizard so you don't need Python or the command line
- Moderator identity across sessions -- name each session's moderator from the meeting platform's own label or your pick, so sessions run by different people stop sharing one moderator name
Priorities may shift. If something is missing that matters to you, open an issue.
Get involved
Researchers -- use it on real recordings, open issues when the output is wrong or incomplete.
Developers -- Python 3.10+, fully typed, Pydantic models. See CONTRIBUTING.md for the CLA, project layout, and design system docs.
Help us test -- we'd love feedback from people using bristlenose with:
- Gemini -- the least-exercised of the cloud providers
- Azure OpenAI -- enterprise deployments
- Windows -- the pipeline works but hasn't been widely tested
- Linux -- pipx works today; the Snap ships on the edge channel (
snap install bristlenose --edge), and Fedora has a Copr (dnf copr enable cassiocassio/bristlenose)
Development setup
Clone the repo, create a virtual environment, and install in editable mode:
# Prerequisites (macOS)
brew install [email protected] ffmpeg pkg-config
# Clone and set up
git clone https://github.com/cassiocassio/bristlenose.git
cd bristlenose
python3.12 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev,serve,apple]" # serve extra adds FastAPI/SQLAlchemy/uvicorn; apple adds MLX for Apple Silicon GPU acceleration (omit on other platforms)
cp .env.example .env # add your API key
On Linux, install python3.12 and ffmpeg via your package manager. On Windows, use python -m venv .venv and .venv\Scripts\activate.
Verify everything works
.venv/bin/python -m pytest tests/ # ~4300 Python tests; frontend has ~1700 Vitest tests (`npm test` in frontend/)
.venv/bin/ruff check . # lint
.venv/bin/mypy bristlenose/ # type check (some third-party SDK errors are expected)
If you rename or move the project directory, the editable install breaks silently
(absolute paths baked into .pth files and CLI shebangs). Fix with:
find . -name __pycache__ -exec rm -rf {} + then
.venv/bin/python -m pip install -e ".[dev]"
Architecture
For how the whole thing fits together — the shared Python core (CLI ≡ the macOS desktop's
bundled sidecar), the React SPA, the SwiftUI host, and how a release is cut — see
docs/ARCHITECTURE.md. See CONTRIBUTING.md
for the full project layout. The short version:
frontend/ -- Vite + React + TypeScript SPA (the product UI in serve mode)
bristlenose/server/ -- FastAPI + SQLite + SQLAlchemy data layer behind serve mode
bristlenose/stages/ -- 12-stage pipeline (ingest through render), one module per stage
bristlenose/llm/ -- multi-provider client + prompt templates (Markdown)
bristlenose/locales/ -- 22 UI locales (en, es, ca, fr, de, ko, ja, cs, it, pt-BR, pt-PT, zh-Hant, zh-Hant-HK, nl, fi, pl, ru, uk, da, sv, nb, tr)
desktop/ -- SwiftUI macOS shell that bundles a signed PyInstaller sidecar
bristlenose/pipeline.py -- orchestrator that wires the stages together
bristlenose/cli.py -- Typer CLI entry point
bristlenose/stages/s12_render/ and bristlenose/theme/js/ -- legacy static-render scaffolding from the React migration; deprecated, on the deletion path
Releasing
Edit bristlenose/__init__.py (the single source of truth for version), commit, tag, push. GitHub Actions handles CI, build, and PyPI publishing automatically. See CONTRIBUTING.md for details.
Changelog
0.43.0 — 24 Oct 2026
The reliability burn-down: in five days well over a hundred ways for Bristlenose to fail, lose work or stay quiet were found and closed. Analyses stop, resume and fail honestly; changes that don't save say so and take themselves back; a crash in one part of the report leaves the rest working; and a failure that survives its retry offers to send a problem report.
New
- Problem reports. When something fails and still fails after Retry, Bristlenose offers to send a problem report. This covers the Mac app failing to start (Show details, Send Feedback) and the report crashing as a whole. The feedback form opens as a problem report with the details filled in, in the Mac app and the browser.
- Right-click menus in the Mac app say exactly what each item does. A blue timecode plays. A badge opens the transcript. A quote card offers Star or Unstar, Hide, Add Tag…, Copy Quote and its transcript. Each item has a "…in New Window" twin: Quotes ▸ Show in Transcript, Open Transcript in New Window. The menus are in all 21 languages, and they keep their items after the report reloads. WebKit's own Download, Share and new-window items are gone.
- A new window can open at a place in the study, such as a transcript at a moment or a Quotes section. It waits for the report before going there. ⌘-click on a Dashboard featured quote opens its transcript in a new window again.
- View ▸ Reload Report (⌘R) in the Mac app, also on the report's right-click menu.
- A first analysis on a new Mac starts at once. On macOS 26 and later, in Apple's nine languages, it transcribes with Apple's on-device speech model while Whisper downloads. Whisper now downloads in the background from launch, once the AI disclosure is accepted and only with 20 GB free, with progress in the toolbar. A session in another language waits for Whisper and runs on its own when it arrives. To turn Apple's model off, run
defaults write app.bristlenose BristlenoseAppleSpeech -bool NO.
- A sixth provider in the CLI, any-llm: Mistral, OpenRouter and others.
bristlenose configure any-llm stores the model and key and checks them with the run's own request. bristlenose use mistral:mistral-large-latest switches between stored models. It's an optional extra, bristlenose[any-llm].
- A report that can't be shown says so. "This report couldn't be shown." comes with Retry instead of a bare "Error". In the Mac app the project's row shows a warning, and its popover offers Copy details. The row clears when the report renders again.
run --clean asks before it discards your stars, tags and edits. It lists them and asks, as the Mac's Re-analyse does. --yes skips the question, and a script with no terminal is refused rather than left to guess.
Improved
- Every detail pane in the Mac app says what happened, in plain words, and offers the one thing that helps.
- No selection: "Drop a folder of recordings or transcripts".
- An empty project: "Drag interviews here".
- Several projects selected: "3 projects selected".
- Folder moved: "Can't find this project's folder".
- A drive and a network share each have their own pane, named by their own name: "“Samsung T7” isn't connected", "“server.local” can't be reached", then "Reconnect to continue."
- A run that ended with the app: "The analysis stopped before it finished", with Resume Analysis.
- A Stop: "Analysis stopped", with Resume Analysis. A Stop over an earlier report shows that report.
- No provider: "Choose an AI provider to analyse this project", with Set Up Provider….
- A failure: "The analysis didn't finish", the reason in a sentence, the button that remedy allows, and Show details.
- The analysing pane shows the row's ring, and "Stopping…" while a Stop plays out.
- The engine-failed pane is the standard macOS one.
- The browser's status pages say the same things.
- Dialogs look and behave like macOS sheets. All nine report dialogs draw through one shared component: Export, Feedback, Miro, FigJam, Settings, AutoCode Review, Uninstall codebook and the two merge confirms.
- They drop into place and rise out.
- The page behind is greyed, not blacked out.
- Escape closes only the topmost dialog.
- Focus goes in on open and back where it was on close.
- Tab stays inside the dialog.
- Every one has a close button.
- A click outside never closes a dialog.
- Re-analyse is a clean fresh start that keeps what it should. Your exported clips and screens move into the new output under the old analysis's date. A Re-analyse that fails, stops or crashes after finishing a session keeps that session for Analyse to finish. Only a confirmed Re-analyse replaces a report.
- A partial run says how much it analysed. The sidebar reads "6 of 8 sessions analysed" instead of "Partial completion". A CLI run that lost recordings ends "Done with warnings — 8 of 10 sessions analysed", not "✓ Done".
- After an analysis, lenses update quietly. Nothing dims while a lens reads the new results, a failed read is tried once more and leaves the lens as it was, and new data fades in as it arrives.
- A speaker you recode into or out of participant is picked up by the next Analyse. That session's quotes are found again from the right speaker, together with any new files. The separate Re-analyse button on the Sessions row is gone. Analyse is also offered after a run that finished with some sessions failed.
Fixed
Stopping, quitting and resuming
- Quitting during an analysis left it to fail. On relaunch the row read "Resuming…" and then "CLI exited with code 1". Quitting or force-quitting now stops the run cleanly, and Resume Analysis picks up where it left off.
- Quitting dropped the studies waiting to be analysed. Reopening Bristlenose within four days now starts them again in order, the one in progress first, unless a study's folder has gone or holds nothing to analyse. A restored queue waits for the AI disclosure.
- Stop could take minutes during transcription. It now ends the run within 30 seconds whatever it is doing, and almost always at once.
- Stopping a run that took a while to wind down read "Analysis stopped unexpectedly". It now reads Stopped, and the run's history records a stop rather than a failure.
- Stopping an analysis left the row blank. It now says Stopped, as a reconnected run already did.
- Stopping a re-run made the next run redo every recording. Work a stopped run had already finished is now kept, so the next run does only what is left.
- Stopping or quitting could leave a run's helper processes going. Stop and Quit now reach everything the analysis started.
- Quitting the Mac app during a stalled first download ends the run within 20 seconds.
- Quitting the Mac app while it was putting a previous report back could leave the report missing.
- Stopping a re-analysis while it cleaned up could bring back the old report over the new one.
- Stopping a run that was slow to stop could leave the report hidden until the next run. It is now put back before the run exits.
- A run that ended after the Mac app had gone was recorded as failed. It is recorded as stopped.
- Closing the terminal mid-run was recorded as a crash. It is recorded as stopped.
- A stopped or failed analysis could only be restarted from scratch. If it ended after transcription, the only choice was Re-analyse, which discards your work. Analyse is now offered too and carries on where the run left off.
- A Re-analyse that had to wait for another study ended on the old report. It now starts afresh when its turn comes.
- A study could stay on "Analysing" for good if it finished just as another was started. It now settles, and the other starts after it.
- A Mac run could read "stopped unexpectedly" in its first moments.
- A run started from Terminal on a study in the sidebar read "Analysis stopped unexpectedly" while it ran. It now reads Analysing and settles to its real end a few seconds after it finishes.
Your curation: stars, tags, hides, edits, undo
- Tags, stars, hides and edits could vanish from quotes you hadn't touched. Each change now saves only the quotes it was made on. Before, it could erase the tags AutoCode added after a run, the curation of quotes a speaker recode had set aside, and, from a transcript's margin, every other quote's tags. A deleted tag no longer comes back under Uncategorised.
- "Couldn't save" left the change on screen anyway. A star, hide, tag or edit that doesn't save is now taken back.
- A refresh in the middle of curating could undo changes still being saved. Changes in flight, and AutoCode accepts and denies, are kept.
- Undo offered steps that never saved. If a star, hide, tag, edit or speaker name fails to save, Edit ▸ Undo names the step before it.
- Undoing the removal of an AutoCode tag brought it back as your own. It now comes back as AutoCode.
- Deleting a sentiment badge couldn't be undone. Edit ▸ Undo Remove Tag (⌘Z) now brings it back.
- An open transcript didn't show an undo made elsewhere. Undoing a star, hide, tag or badge change in Quotes while that session's transcript was open left the transcript showing the old state. It now reads again after the undo, without bringing back a badge or tag deleted meanwhile.
- A refused undo or redo of a paragraph split, join or speaker move said nothing. It now says so, and Edit offers the same Undo again, not a Redo.
- A failed undo of a speaker name said nothing. The name goes back to what it was, and you can try the undo again.
- A rename on the Sessions grid that didn't save kept showing the new name. The grid puts the old name back.
- Clicking into a quote or heading and out again recorded an edit. Nothing is written now unless you changed something, spacing included.
- A quote edit in progress was lost when you switched lens with ⌘1–5 or the sidebar. It is now kept.
- Re-analyse could skip its "you'll lose N edits" warning. Stars and tags made in the current session were invisible to the check; they now count.
- Re-analyse could discard your work without asking. When Bristlenose couldn't read a study's stars, tags and edits, it treated them as none and rebuilt straight away. It now asks first, and says it couldn't count them.
- Re-analyse could discard a star made moments before without asking. If the report was busy reading at the instant you starred a quote, the count could miss it. The app now asks whenever it can't be sure the count is complete.
- Two quotes that started at the same moment could collapse into one. The second now keeps its own row and its own id. A star, hide, tag or edit on it stays on it across re-analyses, and existing projects keep every id they had.
Hidden quotes
- A hidden quote still counted. Signals, the session sentiment bars and the Dashboard's featured quotes now leave out quotes you've hidden, matching the chat lens. A starred quote you then hid is no longer cut as a video clip. The Quotes lens still lists hidden quotes so you can unhide them.
- The Dashboard's quote and tag counts included hidden quotes. They now match the Quotes lens.
- An export of selected quotes included ones you'd hidden. Exporting a selection to CSV, Excel, Miro or FigJam now leaves out hidden quotes, as the whole-project export always has.
- Codebook counts included other projects' quotes. A tag's count in the Codebook lens could include quotes from other projects using the same codebook. It now counts this project's quotes only, and leaves out hidden ones.
AutoCode and codebooks
- AutoCode left some sessions uncoded for good. If some of a codebook's batches failed, the sessions in them were never coded by that codebook again. Bristlenose now remembers which sessions each codebook has finished, so the next analysis, or switching the codebook back on, codes the rest. A stopped re-code keeps what it had already tagged, and a codebook's suggested tags no longer disappear from Quotes while it re-codes.
- Applying a codebook again after stopping it re-coded everything. It paid again for quotes already coded and brought back tags you had rejected. It now sends only the quotes the stopped run never reached. Apply while a stopped run's batch is still out is refused with "Already tagging with this codebook."
- A finished AutoCode run didn't show its proposals until a reload. Quotes, its tag sidebar, Discussion and Screens now update when it finishes.
- Switching a codebook back on re-coded its new sessions without a word. It now shows a progress chip, a reload brings it back, and a catch-up that fails says so instead of reporting done.
- Turning a codebook off and on during re-coding started a second re-coding. The first one is left to finish.
- Re-coding chips said "some batches failed" when nothing had. Each re-code reports its own result.
- A failed re-coding said only "Tagging failed." It now says why, for example that the account is out of credit.
- Stopping a re-code didn't stop it. A Stop ends it, and the sessions it hadn't reached are coded next time.
- A run finishing during a re-code could undo it. The two no longer run together.
- A reinstalled codebook's run showed the previous run's "Done". It now gets its own chip.
- The AutoCode catch-up chip was in English in every language. It now reads "Coding the remaining sessions…" in all 21.
- Accepting or rejecting an AutoCode suggestion could look done when it hadn't saved. It now says so, and the suggestion comes back, or follows whatever was decided elsewhere.
- Codebook edits and on/off switches could fail without a word. A rename, move, merge, delete or switch that doesn't save now says so, and the codebook shows what was saved.
Files, folders and recordings
- The same interview under two names was transcribed twice and counted as two participants. A Finder copy or a renamed download beside its original is now one session. The copy is listed in the Files bucket as a second recording, Whisper runs once, and Signals count the interview once. A re-exported or trimmed recording is still a different recording. On a study analysed before this fix, the next run folds the duplicate away, and any stars, tags or edits on the duplicate's quotes go with it. That's rare: it needs both a duplicate and curation on it.
- The same transcript downloaded twice counted as two participants.
transcript.vtt beside transcript (1).vtt, or a Teams .vtt and .docx pair taken twice, is now one session. The copy is listed under Files and never read.
- A meeting recorded with Zoom's desktop app came out as several interviews. Today's Zoom names the folder without a meeting ID, so the video, the audio and each person's separate track were analysed apart. Each meeting folder is now one session, and the combined audio is the one transcribed.
- A transcript or recording added beside an interview you'd already analysed changed it. The next Analyse paid for that interview again, renumbered its speakers and gave starred quotes uncurated twins. The new file is now set aside and named in the run's summary, and Re-analyse reads it.
- A study with its interviews in participant folders looked empty in the Mac app. It now counts files the way analysis finds them, up to three folders deep, shows how many there are to analyse and offers Analyse. The never-run status page finds them too.
- An empty folder, or one with nothing Bristlenose reads, ended "Done" on an empty report. The run stops with "No analysable interviews found.", and a Re-analyse of an emptied folder keeps the previous report.
analyze on a folder with no transcripts finished as if it had worked. It now stops with an error.
analyze skipped plain-text transcripts with no timecodes without saying so. It names each one under "files not analysed" as a format it doesn't read.
- A folder Bristlenose couldn't read didn't list the files it had passed over.
run and transcribe record each one with its reason, so the Mac app can show them too.
bristlenose run or analyze on your home folder or a system folder crashed with a traceback. It now refuses the folder by its full path and writes nothing, including when the report would have landed in your home folder. In the Mac app such a study says why and refuses dropped or added files.
Cloud folders, drives and network shares
- Dropping a recording still in iCloud froze the copy until you force-quit. The drop is now refused straight away.
- Choosing a discussion guide still in iCloud froze the app. Bristlenose says the guide isn't downloaded yet and leaves the project as it was.
- A recording not downloaded in time failed the whole run. It is now that session's own failure, "Wasn't downloaded to this Mac in time.", which the next run retries. The other sessions go on.
- A stalled cloud provider could hold a run for hours. After a provider's first timeout, its other files wait two minutes, not thirty, so twenty stalled files cost about 40–70 minutes, not 2½–10 hours. Audio-only placeholders, which had no limit at all, now have one.
- Analyse ran a study whose drive had been ejected. The row now says "Folder not found", and the next study in the queue goes ahead.
- A study whose iCloud, Dropbox or OneDrive folder had been deleted or moved was still analysed, and failed. It now shows as a missing folder with Locate…, like an ejected drive. A folder whose files are only stored online still analyses, downloading each file as it goes.
- A vanished cloud folder was labelled "In iCloud", with advice to connect a drive and no Locate…. It is now the Can't Find pane with Locate….
- A study stayed on "Folder not found" after its drive or folder came back. It updates on its own, without relaunching.
- A study added through a linked folder opened once, then read "Can't be read". The app keeps access to the folder the link points to, so the study opens after a relaunch.
- Cloud messages promised more than Bristlenose can know. They say what is known and stop: no "download it in Finder", no "still downloading", no "yet".
Providers, keys and the LLM
- Setup could say your API key was fine, then the run failed on it. The key check now sends the same model and settings the analysis uses, so a refused setting fails at setup, in the provider's own words.
bristlenose doctor does the same for an Azure OpenAI deployment.
- A new key, endpoint, model or output limit wasn't checked the same day. Changing any of them within 24 hours skipped the check before a run, so a bad one failed at the first stage. It's checked again now.
- A model the provider has retired showed as "the provider rejected the request". Bristlenose now says the model is unavailable and where to choose another.
- A Gemini run could hang for good after the Mac slept or changed network. Gemini calls now time out after ten minutes and retry, as Claude's and ChatGPT's do.
- A lost connection on ChatGPT or Gemini showed a generic failure. It now says the provider couldn't be reached, as Claude's always did.
- With no AI provider set up, adding interviews started an analysis that could only fail, then said "Something went wrong" and "No interviews to analyse yet". The study now says no AI provider is set up, with Set Up Provider…, and Analyse runs once a key is saved.
- A long, dense interview could run for over an hour and then fail. Quote extraction splits such a session rather than waiting on one oversized request.
- A failed speaker-identification call wasn't retried. The next run retries it.
- A grouping that fell back after a failed call kept its fallback. If one of the two grouping calls failed, every later analysis reused the fallback grouping. The next Analyse tries both calls again.
- The "Out of credit" notice stayed lit after a top-up and a successful run, or could go out after a run that never contacted the provider. It now clears only after a run that actually used that provider.
- The run summary could name the wrong model. An Azure run printed Claude's price. The reported model is now the one that ran.
- Provider errors and failed replies could be logged in full. The log now keeps a status, a message and field names, never the reply or the provider's body.
API keys and Settings (Mac)
- An API key that failed to save looked saved. Settings ▸ LLM Provider now says so under the field and keeps your pasted key, so you can press Return to try again. If you move to another provider, that provider's row reads "Key not saved".
- A pasted API key could be lost. Closing Settings, switching back to your report, picking another provider or quitting before pressing Return now saves it.
- Showing a half-typed API key saved it. The show/hide button no longer saves the key or reloads the report.
- A key the Keychain refused could still be used in Terminal. When Settings couldn't save a new key over the old one,
bristlenose run still switched to the new key while the app kept the old one. Now neither changes until the save works.
- A key first set up with
bristlenose configure couldn't be removed in the Mac app: it came straight back. Remove now keeps it out of the app until the CLI writes it afresh, and the CLI keeps its copy. Clear in Settings also reads back, so a key the Keychain kept is shown rather than an empty field.
- Changing LLM settings reloaded the report over and over. Tabbing through the key field, dragging the concurrency slider or typing an Azure address reloads it once, when you've finished, and only if something changed.
- A setting changed while a study was opening never reached it. A new key, a provider switch or Anonymise now applies to the study that is still opening. A setting changed then no longer briefly says it stopped.
- The first launch on a new Mac could fail with "Bristlenose didn't finish starting". A slow first start is now given time, and a start that crashes still says so at once.
- Install for Claude Desktop or ChatGPT could fail without a word. It now says the installation couldn't start, under the button. When it falls back to the older extension, the tab shows how to let Claude read Bristlenose.
- A stored sign-in this build couldn't read couldn't be removed. It is now listed as needing sign-in.
The report: crashes, lenses and chips
- A crash in one lens, sidebar, the header, tab bar, footer or a dialog blanked the whole report. The broken part now shows a quiet broken mark, or a short message with Retry in a lens, and the rest keeps working. A crashed dialog can still be closed.
- A lens that couldn't load showed a raw error such as "GET /transcripts/s3 404". It now says the lens couldn't be shown, with Retry, and clears once a later load works. The Sessions tab does the same.
- A failed refresh could replace what was on screen with an error. Quotes, Dashboard, Codebook, Discussion, Sessions and the Screens board keep what they show.
- The Screens lens could keep retrying a failed read for hours. It tries once more, then stops.
- An activity chip spun for ever when the server restarted or stopped answering. After a few seconds, or about half a minute if the server hangs, the chip says the task failed and can be dismissed. A finished export stays finished.
- A reload during AutoCode, a clip export or a screens export lost its progress chip. The chip comes back with its progress, and an export that finishes during the reload still ends with Show in Finder or reaches the clipboard. Cancel names its export.
- View Report on a chip that came back after a reload opened nothing on a first visit to the Codebook lens. It now opens the review.
- A screenshot that failed to load stayed "unavailable". It's tried again the next time you show it.
- A transcript's Timeline button could disappear after one failed read.
- The transcript of a session a re-analysis removed offered a Retry that could never work. It now says the session is no longer in the report, and links back to All sessions.
- Clicking a session row on the Project tab did nothing; only the #N link did. The whole row now opens its transcript.
- Quotes ▸ Reveal in Transcript did nothing. It opens the quote's session at the moment the quote starts.
- A quote edit could be lost if you dragged a crop bracket and put it back.
- ⌘R on a focused quote applied the last tag instead of reloading, and ⌘T opened the tag box. Both are left to the browser and the app.
- An unmeasured session's length read "0m". It reads "—" everywhere.
- Two quotes starting in the same second could produce a clip that stopped before the second one ended. Both are clipped.
- Report feedback disappeared when it could neither be sent nor copied. The form stays open with your text.
Mac app: sidebar, menus, export and windows
- Commands that couldn't run stayed lit and did nothing. After the report process stopped, the Quotes, Video and Undo menus now dim. Export Report dims until the whole report is there. Send to Miro and Send to FigJam dim when the report server isn't running, and follow the window you're in. Re-analyse is no longer dimmed after a first analysis until you reselected the study.
- File ▸ Add Files… opened on a study that couldn't take files, then took them and did nothing. It now dims while a study is analysing, queued, failed, or its folder can't be reached. Markdown files are greyed out.
- Dropping files on a study that was running, queued, failed or couldn't be reached was accepted and ignored. The files spring back on both sidebars.
- Dragging files Bristlenose can't use onto the sidebar looked accepted. The pointer refuses them.
- Dropping a folder on the empty window could add a second copy of a study. It now does what the same drop on the sidebar does.
- Dropping a folder already in the sidebar showed nothing. Its row flashes.
- A new or re-dropped study wasn't scrolled into view.
- A new study lost its icon while you typed its name, and other studies' progress froze until you pressed Return.
- Deleting a sidebar folder scattered its studies. They stay together where the folder was.
- Pressing Export Report twice opened two save panels. The second press is ignored.
- Export Report showed nothing until the save panel opened. The row reads "Exporting…" meanwhile.
- A failed export was saved as though it were the report. You see "The export could not be saved." instead.
- Ticking Anonymise in the save panel asked you to save twice.
- Show in folder on a finished clips or screens export did nothing in the Mac app. It opens Finder at the clips folder, or with the first screenshot selected.
- A failed Show in folder said nothing, and one whose file manager never returned could hang the report server. It now says the folder couldn't be shown, and gives up after 10 seconds.
- A toast appeared in every open window, not just the one you were working in.
- Send Feedback opened a sheet in every open window, or none. It opens one, in the window that asked.
- The feedback sheet couldn't be closed while it was loading. Cancel and Esc work from the moment it appears.
- Cancelling Send Feedback while it was sending could still copy to the clipboard or say "Thanks". Cancel and Esc stop the send, and nothing appears afterwards.
Miro, FigJam and cloud import
- A failed Miro status check showed Connect as if you were signed out. It now says the check failed.
- Connecting to Miro while the server was down said "check the token". It now says the server isn't running.
- Starting a Miro board while not connected was said in English. It's in the app's language.
- A Miro board that completed without a link was reported as ready. It's reported as failed.
- Closing the Miro sheet mid-load, or cancelling a board build mid-request, was reported as an error. Both are a cancel.
- A Miro token that connected but wasn't saved said nothing in the browser. It now says where it couldn't be saved.
- A screens board that Miro refused said nothing useful. It now says so, as the quotes board does.
- Cloud import: a declined grant cleared nothing on the sidebar's batch ring, a stopped batch settling late could end the next one, closing Google's Picker marked the rows failed instead of leaving them ticked, a declined Teams sign-in was said in English, and a Zoom name found after sign-in wasn't saved. All five now behave.
CLI
transcribe and analyze gave up with a traceback. They stop with one line, as run does.
bristlenose transcribe transcribed every recording again on a project that had already been run. It reuses the earlier transcription and transcribes only new or changed recordings. --whisper-model or --whisper-language still transcribe everything.
transcribe replaced the speakers a full run had identified. On a project you had already analysed, it rewrote the transcripts with one guessed speaker and gave participants new numbers. Unchanged recordings now keep the speakers the run found.
transcribe counted retried sessions as new. Its "Transcribed … new sessions" line counts only new recordings, as run's does.
bristlenose analyze on a project you had already run made the next run pay again for every session's analysis. The next run keeps what analyze produced, and analyses again only a transcript you edited by hand.
- A project from an older
--skip-transcription run said "No speech found" for every recording, on every run. The next run transcribes them, and --skip-transcription now prints one line saying how many recordings it didn't transcribe.
- A recording could be reported as having no speech after an interrupted or damaged cache. It is transcribed again on the next run.
- A transcript file that couldn't be read was reported as a recording with no speech from the second run on, and the recording beside it was transcribed again on every run. It's reported as unreadable on every run, and transcribed once.
- A damaged speaker or transcription cache could stop a run with an error. The run redoes that session's speakers or transcription instead.
- A run with more than ten problems could miss the re-run when Whisper arrived. It now picks those recordings up, and a run with more than ten waiting says "Waiting for Whisper".
serve said it rendered a report it couldn't, printing "Rendered report 0 quotes" after the error.
bristlenose help listed the removed render command. The help pages also give the right default output and transcripts folders.
bristlenose configure pointed at itself, and / didn't say when a file overrode them. They now do.
Removed
- File ▸ Print and Page Setup are gone for now. What printed wasn't a usable deliverable: cards below the fold came out blank, the app's controls printed, and real names printed whatever the Anonymise setting said. Printing comes back once it has been designed properly.
- Archive, which was always dimmed, is gone from the Project and Folder menus. It returns when it does something.
- The "In iCloud" state and its cloud glyphs. A folder that has gone is a folder that has gone.
- Loose-file studies. Every study has been a folder since July. A study record older than that loads as a study of its folder.
- The per-session Re-analyse button on the Sessions row. A speaker recode is picked up by the next Analyse.
0.42.0 — 20 Oct 2026
Quotes in the Discussion lens work like quotes everywhere else, and Re-analyse no longer loses curation made in the same sitting.
New
- Quotes in the Discussion lens are full quote cards. The timecode plays, the badge opens the transcript, and you can star, hide, tag and edit a quote and take AutoCode's proposals, as on Quotes and Screens. A hidden quote leaves the lens and is counted beside its question. A question with nothing to show is a quiet grey row.
Improved
- The tag sidebar no longer fills with zeros after AutoCode. Each tag shows one number: what you have accepted, or, before you accept anything, its proposals in grey.
- Miro boards start in flat colour, as FigJam boards do; sentiment colour is still offered. Theme columns follow the order of the themes in the Quotes lens, and a long quote is never split where words were cut.
Fixed
- Re-analyse could discard stars, tags and edits made in the same sitting in the Mac app, without its "you'll lose edits" warning.
- After a stopped run, the next run redid every session, unchanged ones included, and paid for the analysis again. It now reuses what the stopped run finished.
- Teams and Google Meet sign-in didn't start in the Mac app downloaded from bristlenose.app.
- A meeting downloaded from Zoom's web portal came in as separate sessions, the video transcribed from scratch and Zoom's transcript on its own. They are now one session, with Zoom's transcript and speaker names.
- Quote exports from sessions over an hour long were out of order. CSV and Excel rows, and board theme columns, put 1:00:05 before 59:00.
- The Project page flickered between one and two rows of tabs at some window widths.
- On Windows, a run could stop with an access-denied error while the report was open.
- Code scanning findings closed: the video player reads only the parameters it uses, and translations and codebook templates load only from Bristlenose's own files.
Removed
- "Link quotes to clips" is hidden on Miro and FigJam boards. Every link it made pointed at a clip file that didn't exist.
0.41.0 — 19 Oct 2026
Miro and FigJam boards show their progress and can be stopped, and a long quote split over several stickies breaks where it reads, in every language Bristlenose speaks.
New
- Building a Miro or FigJam board shows its progress. What is being placed and how many of how many, a bar, and the time left once there is enough to go on. Stop ends the build and keeps what was made. In Send to Miro, closing the window and opening it again picks the build back up.
Improved
- Long quotes break where they read. A quote split over two or three stickies breaks at a sentence end, at a cut the quote already marks, or where the participant paused, and never just after a word like "the" or "of". Each language follows its own subtitling and typography rules. Japanese and Chinese break between phrases instead of mid-word.
- Japanese, Chinese and Korean stickies on Miro are sized for their characters, so their type matches the rest of the board.
- In the Discussion lens, the selected session's column is lighter. A pale band behind its cells replaces the blue outlines, and it slides across when you pick another session.
Fixed
- On Miro, the stickies of a split quote sat apart, so they read as separate quotes. They now sit much closer.
- A speaker trailing off showed as "that..…" where a quote was split.
- Two labels in the Mac app's Send to Miro window were cut short.
0.40.0 — 18 Oct 2026
The Discussion lens opens on all sessions at once, its guide shows how much each question drew out of each session, and a failed re-analyse no longer leaves the report empty.
New
- The Discussion lens opens on All. In a study with two or more sessions, each question appears once, with every session's answers beneath it, each led by that session's badge, which opens the session at that question. Sessions that skipped the question are listed under "Not asked in".
- The guide shows how much each question drew out of each session. It replaces the session chips and the #N pull-down, and grows with the guide's width: a bar and a count, then a strip with one cell per session, then a grid. A cell's shade is how many quotes it drew compared with the rest of the study.
Improved
- In the Mac app, the transcript's Timeline menu is a native Mac menu.
- The Screens lens names its screens, screenshots and quotes in the window subtitle, as in "23 Screens · 51 Screenshots · 41 Quotes". English now says "screenshot" where it said "screengrab", as every translation already did.
Fixed
- After a failed Re-analyse, the report could reopen empty, and stars, tags and edits made afterwards went into a set-aside copy rather than the project.
- A long moderator question's timecode sits beside its first line in the Discussion lens, not its last.
0.39.0 — 17 Oct 2026
Every row of the Sessions lens carries its timeline and a transcript can show it too, the transcript margin links each quote, screen and question to its lens, the Screens lens exports to a folder, the clipboard and Miro, and four new colour palettes arrive in light and dark.
New
- Every session in the Sessions lens shows a thin strip of its interview under its row. The participant's turns, in the colours of what was on screen. Clicking the strip opens the full timeline at the moment clicked, growing out of the strip; clicking anywhere else on the row opens the transcript. The timeline now flags every visit to a section, not only the first.
- A transcript can show its session's timeline in place of the journey chain. The Timeline button at the end of the transcript's header row switches between the two, and its menu sets how much the timeline shows: Compact (sections and speakers), Standard (adds sentiment) or Full (adds themes). While you read, the timeline marks the paragraphs on screen; clicking a flag, a speaker clip or a sentiment bar scrolls the transcript there, and dragging the marked window scrolls the page. In the Mac app, View ▸ Show Session Timeline does the same.
- The transcript margin links what it marks. Each quoted passage is marked in a new gutter and links to its section or theme in the Quotes lens; where a session moved to a screen, the margin shows the screen's title and a thumbnail and links to it in the Screens lens; a moderator's question links to it in the Discussion lens. Hovering outlines the passage.
- The Screens lens exports its screens. Export Screens writes every screen, one image per participant, full size, to
bristlenose-output/screens; Copy Screens puts them on the clipboard, to paste into Miro or FigJam as one image each. Send to Miro on the Screens lens builds a board with a frame per screen and everyone's quotes about it beneath. Mac only, as the Screens lens is.
- Four new palettes: Catppuccin, Gruvbox, Rosé Pine and Solarized, each in light and dark, in the report, the Mac app's sidebar, the welcome screen and the command line.
- Miro boards offer square or rectangle stickies, and can break long quotes into a short set of stickies. The board name arrives filled with the project's name.
Improved
- A link lands with a yellow flash on what it points at, in every lens, as the transcript already did.
- A renamed section or theme shows its new name everywhere: the transcript, the Dashboard, Signals, the exports and agent access, not only the Quotes lens. Links to it keep working after the rename.
- Miro boards carry each quote in full. Quotes over 300 characters used to be cut short.
- A heading in the Quotes contents lands below the search bar on the first click, not behind it.
Fixed
- The quotes CSV and Excel downloads failed for a project named outside the Latin alphabet, and the HTML export dropped such a name from its filename. Both now keep the name as typed.
- The Dashboard's featured quote opens the transcript at the quote, not at the top.
- A click on the transcript's journey chain lands its paragraph below the header, not behind it.
- The keyboard focus ring was missing in every palette.
- Removing an interview now also removes its exported screen images.
0.38.0 — 16 Oct 2026
The Sessions and Quotes lenses open while a first analysis is still running, the Mac app reads the screens participants shared into a new Screens lens, the session timeline shows what was on screen, transcripts in eleven more languages come out punctuated, and a moved, renamed or copied project stays one project.
New
- On a first analysis, you can read each session as soon as it is transcribed, and its quotes as soon as they are found. The Sessions lens opens while the run is still going: sessions already transcribed can be opened, the one being transcribed shows its filename shimmering, and the rest wait in grey with what is already known about them. The Quotes lens opens at the first session with quotes, grouped by session; once sections and themes exist, a control above the quotes switches between the two groupings, and stars, hides and tags made during the run are kept. Each session's quotes are found while the next is being transcribed, so they arrive during transcription and the whole analysis finishes sooner; a transcript you have open picks them up as they arrive, keeping your place. With PII redaction on, sessions open together when redaction finishes. In the Mac app the analysis animation gives way to the report at the first readable session, and the sidebar and window subtitle say how many sessions can be read so far. Not yet for a resumed run or a re-analyse, which show the finished report as before. Export waits until the run ends.
- The Mac app reads the screens participants shared, and a Screens lens shows each screen with what was said about it. With Settings ▸ Transcription ▸ Read shared screens on (the default), each recording with video is read in the background after the report opens. Once a project has screens, the Screens lens joins the rail: each screen once, with the quotes that share its words placed beside it as the report's own quote cards. Drag a card to place it, and Tidy puts them back; the arrangement is kept with the project. What is read stays in the project's output folder, and turning the setting off deletes it. Mac only.
- The session timeline shows what was on screen. Inside each speaker clip, a strip of colour follows the recording moment by moment, so the call becoming a screen share, or a browser becoming a terminal, shows as a change in the bar. The strip is computed in the background after the report opens and appears on its own; a project analysed earlier gets it the next time it is opened. Serve only, and only for sessions with video.
- Resting the pointer on a session's timeline shows the participant's screen at that moment. A small still appears below the speaker clips, one for each participant turn and one more wherever the screen changes within a turn; it steps as you move along, and clicking while it shows a change opens the transcript just before that change. Taken in the background like the strip, and kept out of exported reports, which carry no participant screens.
- With diagnostics on, each session's timeline shows how well the recording transcribed. Under the timeline, a closed Diagnostics line gives the share of windows Whisper had to retry and how long the recording has sound but no words; opened, it adds lanes for sound level, pace, doubtful words and gaps, and the numbers behind them, with links to the places worth a listen. In the Mac app it appears with Settings ▸ Appearance ▸ Show Diagnostics menu turned on, in the browser with . Numbers only, never in an exported report, and for recordings transcribed from now on; on a Mac with Apple silicon it measures the most.
Improved
- The first Whisper download shows its progress. The command line used to print a banner and then nothing for minutes while about 1.6 GB downloaded; Hugging Face's progress bar now shows in a terminal, with where the model goes and that Ctrl+C is safe.
- The session timeline opens at a scale that suits every session, and keeps it. It used to fit the longest session loaded, so a project of short sessions opened with white space on the right, and an open timeline shrank whenever a longer session arrived. The shortest typical session now fills the width, the longest opens at no more than four widths, and a session far shorter or longer than the rest no longer sets the scale for them.
- During a first analysis, the timeline is drawn as each session arrives, with its scene colours. Option-click opens every session readable so far; an open quote stays open when another session arrives.
- Interviews in Spanish, French, Italian, German, Portuguese, Catalan, Swedish, Danish, Norwegian, Turkish and Korean come out with punctuation and capitals. On a Mac with Apple silicon, Whisper now gets a punctuated example in each of those languages, as English and Japanese already did, so a transcript no longer drifts into long lower-case runs without full stops. Dutch, Polish, Czech, Russian, Ukrainian and Finnish were measured too and already come out punctuated, so they get none. Applies to recordings transcribed from now on.
- Speech the transcriber couldn't make out is shown as inaudible, not as invented words. On a Mac with Apple silicon, a line where Whisper only repeated its own example phrases ("Ainda, hum, na verdade.") is now marked as a gap of that length in the transcript, and its words are not kept, so they cannot be quoted. Applies to recordings transcribed from now on.
- Whisper's repetition loops are taken out of new transcripts. On a Mac with Apple silicon, a word or line repeated as many separate lines ("nada nada nada…", or "Gracias." every half minute over music) is removed, and where Whisper's first pass looped, the stretch is transcribed again in case speech was under it.
serve --dev restarts only for changes to Bristlenose's own Python. It used to watch the whole working directory, so a sidecar rebuild under desktop/ restarted the dev server dozens of times.
- A re-run leaves the videos of sessions already transcribed alone. Every run used to decode every video again, which for a project in iCloud meant downloading it again. A session's thumbnail is now re-cut when its video changes, where before it was kept for good.
- Timecodes, tags and codes no longer fall back to Courier. They draw in the system's monospace face in the Mac app and Safari, Menlo in Chrome, and Cascadia Mono or Consolas on Windows. Every tag is now one weight, and numbers in the Signals lens use the report's body type with aligned digits.
Fixed
- Ctrl+C during the first Whisper download stops it. It used to be ignored until the whole file had arrived; it now stops within about two seconds, and the next run starts the download again.
- The Mac app's failure popover says "CLI exited with code N" in the app's language.
- A report could come up empty after the first analysis in the Mac app, with every signal green. The app starts the report server on the next pass after it starts a run, so the server usually came up before the run had made
bristlenose-output/, rooted its database at the interview folder, and the run's results then landed in a second project row the report never reads. The database and log are now created under bristlenose-output/ before the first run, a never-analysed project's row names that folder, serve <folder> and serve <folder>/bristlenose-output are one project (recordings play from either), and the server says so in its log if a second row ever appears. One visible consequence: opening a never-analysed project in the app creates an empty bristlenose-output/ folder beside the interviews straight away, where it used to leave a hidden .bristlenose/ folder.
- The report server follows a replaced output folder. A failed Re-analyse puts the previous report back, and the next run reclaims one left by a crash; the server used to keep answering from the copy that had been set aside, and curation went into it, until it was restarted. It reattaches to what is on disk the moment the folder changes. If the project folder itself is renamed or moved under a running server, the log says so once; restart the server at the new location.
- Nothing writes the session registry under a live run. Swapping or recoding a speaker and reassigning a paragraph to a new moderator are refused while an analysis owns the project (the run would overwrite the write at its end), and the importer no longer carries speaker picks until the run has let go.
run --clean no longer moves a live run's output out from under it.
- A session that was transcript-only gains its recording. Source files are re-verified on every import, so a recording that turns up later plays, and a file that has gone is no longer marked as seen.
bristlenose status finds the report, and names the stash. It used to look for .bristlenose/ at the project root first, and said nothing about a stashed previous report while the output folder was absent, which is when that stash is the only copy. It no longer offers rm -rf for it, or calls it "kept while a run is in flight" when no run is.
bristlenose serve on an output folder outside the interview folder () no longer tries to analyse the folder above it, which crashed when that was the home folder.
0.37.0 — 12 Oct 2026
Transcripts come out punctuated, Word tables are read, PII redaction works on the Snap and Fedora packages, and the Sessions Start column shows when a session began.
New
- Transcripts come out punctuated. Whisper now gets a short punctuated prompt on every 30-second window, in English and Japanese, so a recording no longer turns into long lower-case stretches with no full stops. This applies to recordings transcribed from now on; existing transcripts are not redone.
- Text inside Word tables is read. A discussion guide laid out as a table was reported unreadable, and a transcript in a table came out nearly empty. Tables are now read; a timed table that can't be aligned is refused with a clear message. Sessions from
.docx files are re-parsed on the next run, so their analysis runs again.
- PII redaction on the Snap and Fedora packages. They had shipped the wrong language model, so
--redact-pii failed. The first redacting run now downloads the right model into your own cache, checked against a pinned hash.
- A session timeline on the Mac app's Welcome screen, showing how a session's row opens into its timeline.
Improved
bristlenose doctor exits 1 when a check fails, so a script or CI step can rely on it. Warnings still exit 0.
- A missing PII model is a warning on pip and Homebrew installs, since the run downloads it. It had been a failure that stopped
run --redact-pii before it could.
- The Welcome screen's background links say where they go ("Analysis method", "Sentiment taxonomy") instead of "Learn more", in every language.
- Faster first model download on Windows, which now uses Hugging Face's Xet transfer.
Fixed
- The Sessions Start column showed when a recording was saved or downloaded, not when the session began. Start now comes from the recording's own clock, from Bristlenose's import stamp, or from the meeting app's filename (Teams, Zoom, Meet, Screen Recording, Android, OBS). It also no longer shows an hour early during British Summer Time. Re-run a project to correct its existing sessions.
- Reopening a project whose output folder had lost files could delete your quotes. With the cluster or theme files missing, quotes went with their hidden flags and tags, and with transcripts gone too, starred and edited quotes and whole sessions. The cleanup now runs only when the output is complete.
- One duplicate in an AutoCode answer threw away the whole job. Every proposal was rolled back and a retry failed the same way. The best-scoring proposal per quote is now kept.
- An anonymised clip export sat beside the previous export's clips, whose filenames carry participant names. Each export now removes the previous one's clips first.
- An API key containing a line break could be printed in full by
doctor and run. It is now refused with a fixed message.
- On Windows, a
winget.exe or nvidia-smi.exe in the current folder could run in place of the real one. The same fix 0.36.0 made for ffmpeg and Ollama.
- Google Meet
.docx transcripts kept only the first turn after each timestamp. Every turn is now read; turn times between Meet's timestamps are estimated from text length.
- A Gemini answer cut off at its output limit failed the session instead of being split and retried, as with the other providers.
configure local named a model it never saved, so the next run asked for a model that wasn't installed.
doctor reported a local model installed when only a similarly named one was, and suggested pulling the wrong tag.
- A failed download of Ollama's install script was reported as "Ollama installed".
- An Azure endpoint without
https:// crashed run with a traceback. It now fails the network check with advice.
- The first transcription printed Hugging Face warnings, each twice, asking for an access token, Developer Mode or a
pip install. They are silent now; real download failures still show.
serve never checked its own dependencies before starting. It does now.
0.36.0 — 8 Oct 2026
Transcript text can be selected across paragraphs and pasted into a document with its timecodes, and the transcript no longer shows a platform's speaker name past anonymisation.
New
- Copy transcript paragraphs into a document. Drag across as many paragraphs as you like and paste: a plain-text editor gets Markdown, one paragraph per block with its speaker code and timecode; Word and other rich-text editors get the timecode styled as the app draws it. Badges, annotations and buttons are left behind.
- A paragraph starts with a capital where a sentence begins — the first paragraph, a new speaker's turn, or after a full stop. This is display only: the transcribed words are unchanged, and a word spelled lower-case on purpose, such as iPhone, keeps its spelling.
Fixed
- A speaker's name from the source transcript could appear past anonymisation. Imported text kept the label written after each speaker code — "(Speaker B)", or a meeting platform's real name — and it reached exports, search and the agent endpoint. It is now removed on import; "(laughs)" and similar stay.
- On Windows, an
ffmpeg.exe or ollama.exe in the folder you ran Bristlenose from could run in place of the real one. A file planted in a client's interview folder would have been run. Bristlenose now looks only in the install folder and in the folders on PATH.
- Removing a session that had a split, join or move made every later import fail. Its edits now go with it.
- A paragraph given to a new moderator could pass that moderator's code, and name, to another voice on a re-run.
- A split in a paragraph without word timings appeared at the end of the session instead of where it was made.
- Paragraph edits from 0.35.0 could stop applying after a change to speaker labels or to PII redaction.
0.35.0 — 7 Oct 2026
Each session opens into a timeline of the interview, a speaker's role can be changed after the fact, and transcript paragraphs can be split, joined and moved to another speaker.
New
- Each session can open into a timeline. In the Sessions lens, the chevron beside a session number opens a strip under that row that runs the length of the interview. It shows where each section starts, who is talking, every quote as a sentiment mark (positive above the line, negative below), and the span of each theme. The talking turns are tinted with the colour of what was on screen at the time. Click the strip to open the transcript at that moment, or click a sentiment mark to read the quote. One zoom control sets the scale for every open timeline, and they stay open when you come back from a transcript. On-screen colours are worked out when a project is run, so a project analysed before 0.35.0, or with
analyze alone, shows plain grey turns until its next run.
- Change a speaker's role. In the person picker, a moderator can now be made an observer and back, and any speaker can be moved into or out of participant. The picker also opens from the transcript and the dashboard, not only the Sessions page, and it opens on the current name, ready to type over. Edit ▸ Undo takes a change back.
- Re-analyse one session. If a participant turns out to have been the moderator, or the other way round, that session's quotes came from the wrong person. They leave the report until the session is re-analysed, and come back with their stars and tags if you undo. The Sessions page offers Re-analyse… on that session, saying what will change and what it costs; on the Mac it runs from there, and in the browser it shows the command to run.
- Split and join transcript paragraphs. Place the caret in a paragraph and press Return to split it in two; Backspace at the start of a paragraph joins it to the one above when both are the same speaker. The words themselves can't be edited — they are the recording's. A split survives a re-run, and Undo takes it back.
- Move one paragraph to another speaker. The transcript's picker has a Session | Paragraph switch: Session changes who the speaker is throughout, Paragraph gives just that paragraph to someone else — including a new moderator, for a call where two voices were heard as one. A quote whose words moved away from its participant leaves that participant.
Improved
- The ✕ in the person picker means "not this person". The speaker goes back to unknown, and a re-run won't propose that name again.
- The picker offers only people someone has confirmed. A name Bristlenose guessed for one speaker no longer appears in every other speaker's list, so one wrong guess doesn't spread.
- Renaming a person renames them everywhere, and a name another moderator or observer already has is refused rather than merging the two.
- Two unknown moderators in one session are told apart as mA? and mB?.
Fixed
- A new voice in a re-run could take an earlier moderator's code, and their name with it. A moderator's code is now never reissued within a session.
- The Star & hide picture on the Welcome screen was cut off in a narrow window. It now shrinks to fit.
- The Windows installer said "Bristlenose was not installed" when it had been. A failure after the install now says Bristlenose is installed, and what went wrong.
0.34.0 — 5 Oct 2026
The command-line tool now installs and runs on Windows, through pipx or uv with x64 Python — newly tested there, so expect rough edges. Installing with pipx or uv no longer needs [serve].
New
- One line installs Bristlenose on Windows. In Terminal,
irm https://bristlenose.app/install.ps1 | iex installs uv, FFmpeg and Bristlenose for your user, without administrator rights, then runs bristlenose doctor; run it again to upgrade. It works on x64 and on Windows on Arm, where Bristlenose runs through x64 Python and transcribes more slowly. If a company proxy or policy blocks a step, INSTALL.md has the same install one command at a time.
- Local AI installs on Windows.
bristlenose configure local now installs Ollama with winget, starts it and downloads a model, as it already did on macOS and Linux. Without winget, it points you to the download page.
Improved
pipx install bristlenose and uv tool install bristlenose are the whole install. The packages behind the report used to come with the [serve] extra, and without it Bristlenose could only transcribe. They now come with every install. 'bristlenose[serve]' still works.
bristlenose doctor gives Windows users commands they can type: winget for FFmpeg, setx rather than export, and where the key file really is.
Fixed
- On Windows, every run stopped before it started. The first record a run writes failed on Windows, so no run got past it, and past that, a run stopped again at its second step.
- On Windows, a run in progress read as stopped. A second run on the same folder was not refused, and the report could call a running analysis stranded.
- On a fresh Windows machine,
bristlenose doctor said the network was down and configure could not check a key. Windows had not yet fetched the certificate the Claude and ChatGPT servers use; Bristlenose now carries its own copy alongside the system's.
- Saving
bristlenose doctor to a file, or piping it, crashed on Windows. Redirected output now prints + for a pass and x for a fail.
run --clean on Windows wrote over the previous report, and a failed run could not put it back.
- A recording named in Japanese or Chinese stopped the run on Windows.
- On Windows, every run crashed at its very end once a project had made 1,000 AI calls.
- Burned-in subtitles were never made on Windows.
bristlenose configure on Windows named the key file as ~/.config\bristlenose\.env. It now shows the full path.
- A run could crash as it finished. The library behind the voice pass, added in 0.33.0, sends usage statistics to Microsoft by default, and its upload could crash Bristlenose on exit. Bristlenose now switches it off.
0.33.0 — 4 Oct 2026
A Discussion lens beside your interview guide, speakers told apart by voice, a person picker, search that suggests, and Undo.
New
- The Discussion lens. Put your interview guide in a folder named "Discussion guide" beside the recordings (on the Mac, Add your guide…) and the lens lays the quotes against the questions you planned and the ones you actually asked. The guide is a panel you can show and hide.
- Speakers are told apart by voice as well as by what they say. When a recording has no transcript naming its speakers, Bristlenose also compares the two voices and corrects the lines the text alone got wrong — on a test interview, 12 lines wrong instead of 22. It runs on your machine and makes no extra AI call; the first time it is needed it downloads a 40 MB model. Built into the Mac app, Homebrew, the Snap and the Fedora package; with pip, install
bristlenose[serve,voice]. Turn it off with BRISTLENOSE_VOICE_PASS=false.
- Who is this speaker? Click a speaker on the Sessions page to confirm the name Bristlenose found, pick another, or type someone new. On the Mac it opens as a native popover; in the browser, as a menu. A name you haven't confirmed yet has a dotted ring and grey text, and screen readers announce it as a proposed name. Press Return to confirm it.
- Undo for your edits to the report. Edit ▸ Undo and Redo (⌘Z, ⇧⌘Z; Ctrl+Z and Ctrl+Y off the Mac) take back naming, picking or confirming a speaker, starring, hiding and unhiding quotes, adding and removing tags (a whole selection is one step), and editing a quote, a section or theme title, or a description. On the Mac the menu names what it will undo ("Undo Rename Moderator"). Undo lasts as long as the report is open: reloading, switching project or running a new analysis starts afresh. On the Mac, undoing a quote edit with ⌘Z isn't reliable yet; the revert arrow on the quote card always restores the original text.
- Search suggests people and tags as you type. In Quotes, a chosen suggestion becomes a token whose meaning you can change from its menu (said by, mentions, not; tagged, contains, not tagged). Words typed together are found together, in order, and accents and apostrophes don't need typing.
Improved
- Speaker labels now hold for the whole recording. For a recording without a platform transcript, speakers used to be worked out from the first few minutes and every later line took the last label, so a moderator could be credited with most of an interview. The whole transcript is read now. Projects analysed before this release keep their old labels until analysed afresh. With redaction on, this step sends the whole transcript, unredacted, to your AI provider, where it used to send the first few minutes (
SECURITY.md).
- A meeting transcript that names nobody is now split into speakers, instead of being kept as one voice.
- A resumed run's time estimate covers only the steps still to run.
- On the Mac, the sidebar's + button offers New Project… and New Folder…, and a new project opens with its name ready to edit.
Fixed
- The dashboard's Words card never appeared. It now counts the words in your transcripts.
- Choosing a smaller transcription model with
-w crashed before transcribing. On Linux and other non-Apple-Silicon machines, -w tiny, base, small or medium failed with "Repository Not Found". Every model name now fetches the model that is used, and the download notice gives that model's real size.
- A mistyped model name gets a plain answer.
-w smal stops before anything downloads and asks whether you meant small, listing the names this machine accepts.
- A session in which nobody was detected as a participant could take a real participant's number, and its placeholder could overwrite their figures. Participant numbers are now never issued twice.
- A speaker the pipeline re-detected as an observer could hand their participant number, and the name typed for them, to the next new participant.
bristlenose transcribe put every speaker in a session under one participant. It now numbers moderator and participants as a full run does.
0.32.0 — 30 Sep 2026
Exported clips carry subtitles, ChatGPT can read your studies, and the video player learns its keys.
New
- Exported clips carry subtitles. Each clip gets a
.vtt file beside it and a subtitle track inside it, tagged with the language that was spoken so QuickTime shows it on a plain double-click. Everyone audible is subtitled, and speakers are told apart by colour, following the BBC's subtitle guidelines. Your corrections to a quote reach its subtitles.
- Subtitles can be burned into the picture, for slides and screen shares that ignore a subtitle track. Tick Burn subtitles into the video under Extract clips (on the Mac, Quotes ▸ Burn Subtitles into Clips) and each clip also gets a
(subtitled) copy; the clean clip is kept. If your FFmpeg can't draw subtitles, the export says so and still makes the plain clips.
- The video player shows subtitles. Switch them on in Settings ▸ Video, or on the Mac with Video ▸ Subtitles or the C key.
- Japanese and Chinese subtitles break between characters, keeping closing punctuation off the start of a line and opening brackets off the end.
- Install Plugin… for ChatGPT. Settings ▸ MCP Agents ▸ ChatGPT & Codex adds Bristlenose to the ChatGPT desktop app in two clicks, with no Node and no config file to edit. Ask in Work mode and start the question with @Bristlenose — ChatGPT doesn't use a plugin otherwise. Bristlenose needs to be in your Applications folder.
- The video player has keyboard controls on the Mac. With the player in front: Space plays and pauses, ← → jump 5 seconds, ⌥← ⌥→ jump 30, < and > change speed, C switches subtitles, and fn-F goes full screen. The Video menu shows them while the player is in front.
Improved
- The Claude Desktop extension is now a small helper signed by Bristlenose, rather than a script run on Claude's own Node. If you installed it before, Settings ▸ MCP Agents offers Update Extension….
- ChatGPT no longer asks you to approve each tool call. Bristlenose's tools now declare themselves read-only.
- The projects column scrolls under the toolbar, fading out beneath it as in Photos and Notes, instead of stopping at a hard edge with a grey band.
Fixed
- On macOS 27, Claude and ChatGPT couldn't read a shared study, and nothing said why. macOS 27 blocks another app from reading Bristlenose's files without asking. The new helper reads them with no extra permission, so there is nothing to switch on.
- Closing the video player and then clicking a timecode could crash the Mac app.
- The Video menu was greyed out exactly while the player was in front, and Picture in Picture did nothing from the menu.
- Japanese and Chinese transcripts had no word timings, so playback highlighting could not follow the words as it does in other languages.
- Transcription failed on a fresh install on Linux, Windows and Intel Macs. A new release of PyAV, the audio library faster-whisper reads recordings with, broke its decoder on the day it came out. Bristlenose now stays on the previous version until faster-whisper catches up.
0.31.5 — 29 Sep 2026
Other programs on your computer can no longer read a project's private files through the report server, and a re-analysis reaches the report without a restart.
Fixed
- Other programs on the same computer could read files the report server should have kept to itself. While a report was open, another local program could fetch the PII re-identification key, the unredacted transcripts and the run log without the report's token, and a web page could reach the server by pretending to be your own machine. The server now hands out only recordings and the report's own assets without the token, answers only to local addresses, and its sign-in cookie no longer works for pages served from other local ports.
- A static report could run a script hidden in a transcript. A speaker name in a supplied .docx or .vtt could break out of the page's embedded data and run when the HTML file was opened. It is now escaped.
- After Re-analyse, the report could stay on the old analysis until the server was restarted. A run that is in progress, or that stopped without finishing, also no longer hides the report you already have, and a run that crashed stops reading as in progress.
- Queuing a re-analysis showed the previous run's failure. The Mac app now shows "Queued" or "Analysing…" in its place until the new run finishes.
- The projects column could hide itself when the window was resized and not come back.
0.31.4 — 28 Sep 2026
A recording with no sound no longer comes back as an interview of "Thank you.", and the transcript page shows the whole interview again.
Fixed
- A recording with no sound was transcribed as "Thank you." every thirty seconds, and analysed as an interview. On the Mac, and at the command line on Apple Silicon, Whisper answers silence with an invented "Thank you." for each thirty-second stretch rather than with nothing, so a 29-minute video whose audio track was silent produced some sixty lines that were then read as a participant's session. Whisper's own no-speech check never fires on the model Bristlenose uses. The audio is now measured before it is transcribed: a recording with nothing in it is reported as "No speech found" and left out, and lines invented over long silences inside a real interview are dropped. Quiet speech is kept — the cut-off sits far below the quietest speaker measured. Speech invented over room tone or music is not caught yet. Projects already transcribed keep their old transcripts until those sessions are transcribed again.
- Most of a transcript could vanish behind a few words from its first minutes. Word-level timings were matched to transcript paragraphs by position instead of by time, and the transcript page shows those words in place of the paragraph. So an interview whose lines were merged by speaker displayed its opening minutes spread across the whole timeline. Existing projects correct themselves the next time they open; nothing needs re-running.
- Playback highlighting went out early on the last paragraph of a transcript. It now lasts until the recording's final words.
- A project whose folder permission was lost had no way back. The Mac app showed "Can't be read", and Show Log failed with a permission error. The project now offers Locate…, which opens on the folder, so one click grants access again.
- At rest, the book shelf on the Welcome screen showed every cover through the one in front.
0.31.3 — 26 Sep 2026
Dropping the same folder in twice no longer imports everything twice.
Improved
- The Signals lens scrolls like every other lens, and the heatmap stays put. It was the one lens with its own scroll model — the cards scrolled inside a fixed frame while the page did not — and that is where the phantom scrollbar, the heading cut off under the toolbar and the clipped card shadows all came from. The page scrolls now, and the heatmap pane is pinned to the bottom of the window as you go. Jumping to a card or tabbing to one lands it above the pane rather than behind it.
- The heatmap bar reads as part of the panel it sits on. It runs edge to edge, wears the left panel's tint, keeps its height whether open or closed, and its title and icon line up with their neighbours.
Fixed
- Dropping a folder you had already dropped made a second copy of every recording in it. The importer asked only whether something already sat at that name, so re-dropping — the natural way to ask for another analysis — put
clip 2.mov beside clip.mov. That cost more than disk. The transcription cache keys on the file's path, so every renamed copy was a guaranteed miss and paid to transcribe the same interview again: one re-dropped folder came to $0.62 against $0.09 for the same work done once. Worse, the two copies became two participants saying identical things, which then clustered together — so the report read as corroboration rather than as a mistake. A file you already hold is now recognised by its contents and skipped, and identity is proven rather than guessed, because getting it wrong the other way would silently drop a real interview.
- Folders in the sidebar would not stay collapsed. Closing one held only until the next selection change or progress tick, which re-expanded it. Collapsed folders now stay shut, and survive a relaunch.
- The projects column stayed away when the window grew, and came back after you had hidden it. Both were the same root: the column's width was being measured at moments when it had none — as the window mounted, and mid-animation — so a column you had taken never fitted again, and one you had deliberately hidden was handed back on the next resize.
- A browser set to Norwegian got an English report. The codes a system reports for Norwegian —
no, and Nynorsk's nn — were not recognised as the Bokmål we ship, so the interface fell through to English. Both resolve to Bokmål now, as the Mac app already did. bristlenose --lang no works too.
- Show Diagnostics did not hide the build-info capsule in development builds. One switch now hides the machinery, which is what you want before a screenshot or a demo.
0.31.2 — 23 Sep 2026
A second control in the Quotes lens announced itself in English to screen readers.
- The tag badge that assigns a tag to your selected quotes spoke English to screen readers. It announced "Assign Frustration to selected quotes" in every language, because the label was assembled in English in the code rather than translated. Sighted users saw the tag's own name, which was already correct; anyone using VoiceOver or a screen reader in one of the other twenty languages heard the surrounding sentence in English. Same defect as the tag group's show and hide button in 0.31.1, one control along.
0.31.1 — 23 Sep 2026
A control in the Quotes lens announced itself in English to screen readers, whatever language the report was in.
- The show/hide button on a tag group spoke English to screen readers. It announced "Show Frustration" or "Hide Onboarding" in every language, because the label was assembled in English in the code rather than translated. The identical control in the panel beside it had been translated since the day it shipped, so the two disagreed. Sighted users saw nothing wrong; anyone using VoiceOver or a screen reader in one of the other twenty languages heard English. It now uses the same words as the rest of the lens.
0.31.0 — 22 Sep 2026
Bristlenose hears the language a recording was spoken in, writes its headings back in yours, and the Mac app speaks it everywhere — and the framework codebooks now carry only their authors' words.
New
- Recordings are transcribed in the language they were spoken. Every run told Whisper the audio was English — the command line by default, and the Mac app whatever language its interface happened to use — so a non-English interview was decoded under an English assumption and came out wrong. The spoken language is now detected per file: a study whose sessions are in different languages transcribes each in its own, with nothing to configure, and the transcript files record what was detected. Pin it when you already know the corpus:
--whisper-language es on run and transcribe, or BRISTLENOSE_WHISPER_LANGUAGE.
- Section and theme names come back in your language. A Spanish researcher got English headings over Spanish quotes. The stages that write section names, theme titles and descriptions now write them in the interface language —
--lang es, or the language the Mac app is running in — and the quotes stay verbatim, product names and on-screen labels as the participants said them. English sends the model nothing extra, so an English run's prompts are unchanged. Signal findings, AutoCode reasons and chat answers are not steered and usually arrive in English.
- The framework codebooks say what their authors said. Every codebook shipped in February had exactly four tags per group, because the extraction padded each group where the book ran out. Norman, Garrett, Nielsen, Yablonski and Morville have been re-read against the source: tags the author never wrote are gone (Morville's twenty-eight are fifteen), tags in the author's own words are back, groups take the author's titles, and the useful tags that were ours moved to the Bristlenose UXR Codebook. A codebook you already have installed migrates in place: a renamed tag keeps the quotes it was on, a retired tag stays if any quote still carries it, and new tags arrive empty. Each codebook has a decision register, and a change to one now needs a witness quote.
- The Mac menu bar follows your language. File, Edit, View, Window and Help — and every standard item inside them — were English in every language, because the app declared no localisations; Project, Codes, Quotes and Video were English literals. All follow the language now. Changing it offers to relaunch, in Apple's own words; the menus pick up the change at the next launch, and an analysis that is running is never interrupted. A fresh install starts in your Mac's language instead of English, and a language chosen in System Settings ▸ Apps ▸ Bristlenose is respected.
- The Welcome screen is translated. Its prose, tips and study tools, and the example quotes, tags, theme words and filenames inside the illustrations, authored per language rather than translated — the disfluency demo strikes out "o sea" in Spanish and 「えっと、あの」 in Japanese. The shelf shows the local edition of a book where one exists: Norman in ten languages, Braun & Clarke in Polish.
Improved
- The Mac app speaks your language in the places that were still English. Failure rows and sign-in failures in the Teams, Meet and Zoom import window; the "Sign in with Microsoft / Google / Zoom" buttons, in the vendors' own words; Today, Yesterday and attendee counts in the recordings list; the run-failure line under a project and the diagnostic popover's category; the explanations under the status dot in Settings ▸ AI; the screen that says why Bristlenose couldn't start; the player window's title; drag-and-drop refusals; the Miro sheet's own failure sentences. Settings tab titles follow a language change and no longer jump you back to General.
- The Language picker tells the truth. It showed English while the app ran in German on a fresh install, and choosing the row it showed would have switched you to English; the report opened in English inside a French app.
- The edit pencil on a section or theme heading is visible. It was drawn in the disabled-icon colour and hovering the heading did nothing. It now appears when you hover the heading, brightens under the pointer, reveals for keyboard focus, stays visible on touch — and announces itself to screen readers in your language.
- Codebook author cards fit their column, and Morville's says he devised the UX Honeycomb the codebook is built on.
- Signals navigation rows indent under their location, so a heading and its entries no longer start at the same edge.
- Better words in more places. Traditional Chinese uses Apple's ⋯; Norwegian says Finn… and markering; the French export column says favori; dialog titles use each language's own quotation marks; the catch-all theme uses each platform's own "Uncategorised".
Fixed
- The AI-tags stat card put you back on the dashboard you clicked from — a 0.30.0 regression from the Signals rename. It opens the Signals lens again.
- Twenty languages still described "the analysis page" one sentence below the Signals labels 0.30.0 renamed, the help text had lost its definition of a signal, and the intensity tooltip said 0–3 when the scale is 1–3.
- Hiding a selection sent a write for every pair of quotes — twenty-five for five, all racing — and starring a selection or Restore all sent one per quote. Each is one write now, and hiding a selection that spans several groups collapses every group.
- Star and Unstar disagreed. With a mixed selection the Quotes menu said Star while
s unstarred everything. One rule everywhere: unstar only when every selected quote is already starred.
- The Codebooks uninstall sheet had an English title and buttons inside a translated warning, since 0.29.0.
- Miro export failures were English prose in the report's panel; they are sentences in your language now, and a half-built board still gives you its link.
- "1 hidden quote" sat in English above a translated "Show:", in every language.
- The Welcome illustrations didn't change language until dark mode was toggled, and one depicted a 0–3 scale and "2 quote(s)" — two things the product doesn't do.
Removed
- The pattern chip from the illustrated signal card. The Signals lens dropped it in 0.30.0; the illustration was teaching a control that no longer exists.
0.30.0 — 21 Sep 2026
The Analysis lens is called Signals, its cards were redrawn, and PII redaction finally works end to end on the command line.
New
- The Analysis lens is now Signals. The name was doing two jobs and neither well: analysis is what you do — reading, tagging, revising — and what the pipeline does when you press Analyse. Signals is the thing Bristlenose computes: the concentration scores, the cards, the lens that shows them. Calling one lens "Analysis" implied the other four weren't, which is untrue — tagging in Quotes is analysis. Everything that names the lens now says Signals, in all 21 languages. Old links keep working:
/report/analysis/ redirects, and bookmarks and already-exported reports still open.
- Signal cards, redrawn. A card now names its location once, in the heading, and opens with four quotes rather than one. The chip says what the feeling is — "Frustration", "Mixed sentiments" — instead of naming the framework it came from, which told you nothing. Findings arrive a chunk at a time over cards already on screen, so a long elaboration no longer holds the whole list back.
- PII redaction works end to end — on the command line.
--redact-pii bundles the detection engine, fetches its model on first use with the terminal to itself, and the report records when the transcripts were redacted. A phone number read aloud is now caught; it wasn't before. Turning redaction off clears the redacted copy rather than leaving a stale one for three different readers to disagree over. This is the CLI only — the Mac app can't reach it yet.
- A focus cursor in Codebooks, and a Codes menu to drive it. Move between codes from the keyboard, with one focus ring at a time and an edit field that looks like one.
- ⌘F searches. It was bound to nothing on lenses that have a search box, and dims on the ones that don't.
- One keychain. A provider key saved in the app is now seen by the CLI, and the other way round. They had been writing same-named items to different keychains and neither read the other's.
Improved
- The Signals lens has one navigation, one card list, and one order. The left panel and the main content used to disagree about what came first.
- The report keeps its reading measure when the window narrows. Panels give way in a deliberate order — the Mac sidebar first — instead of squeezing the text.
- Provider defaults are current again. ChatGPT moves off a May-2024 model, Gemini's two picker models are retired for new users (the default was already broken), and Claude's default returns to the model that passes our tool schema on real transcripts. ChatGPT requests now use structured outputs, so the response shape is guaranteed rather than asked for.
- Doctor stops blaming your Python version for a corrupt model pack.
- Better words in more places. The codebook is
kod kitabı in Turkish — the term MAXQDA's own Turkish UI uses — and stops reading as a person rather than a book in Russian and Ukrainian. The Codes menu no longer says "Install Codebook" in twenty languages that never wanted it.
- Settings ▸ AI sizes to the provider you picked instead of holding a fixed height for the tallest one.
Fixed
- Quote deep links could point at the wrong moment. Some models write timecodes with minutes where hours belong, so a quote at 3:36 was recorded as 3 hours 36 minutes and its link landed nowhere. Every quote's timecode is now range-checked against its own session. If you have deep links that went somewhere odd, re-analysing will correct them.
- A lens could go dead after switching projects. Clicking Quotes or Signals did nothing at all until the window was reopened.
- A session that failed was never retried. It was recorded as complete, so the next run skipped it — your interview stayed missing from the report with nothing saying why. It is now retried.
- An AutoCode run that coded nothing claimed it had. A job whose every batch failed is now a failed job, and one that produced no codes no longer stamps the watermark that stops it running again.
- A redacted project leaked participant names into the export.
- The Agree. figure claimed more voices than there were people.
- The window menu collapsed a moment after opening, because every update rewrote the whole menu bar.
- The app waited on a webfont it then threw away, delaying the first window.
- A drag-in copy that failed now says why, whichever gesture started it.
- The Signals lens had a second scrollbar — the page was scrolling its own padding.
Removed
- The temperature slider in Settings ▸ AI. Several models reject the parameter outright; the rest are tuned for this work already.
- Six Codes menu commands that never had a target, and Find Next / Find Previous — a filter has no "next".
0.29.1 — 31 Aug 2026
Switching one codebook off switched the others back on — and three more surfaces reported more than they had actually done.
- Only one codebook could be off at a time. Switching a second one off silently switched the first back on, and its tags reappeared across the report without anything saying so. Every switch now records the state of every codebook rather than just the one you touched. If a codebook came back on without you asking, switch it off again — it will stay off this time.
- Switching a codebook off could start AutoCode on a different one. A codebook coming back on is treated as a deliberate re-enable, so it catches up on any sessions imported while it was off. Because the switch was turning others back on by accident, that catch-up could run on a codebook you had deliberately switched off — spending on codes you never asked for. Worth a look at your review queue if you were toggling codebooks on 0.29.0.
- An AutoCode run that coded only some of your quotes said it had finished. When a batch fails the run carries on with the rest, which is right — but the activity chip had no way to say so, so a run that tagged 58 of 72 arrived as an unqualified success with the shortfall dropped. It now says the run was partial, and keeps the link through to review what it did produce. The correct behaviour had been written and tested some time ago; it had landed in a component nothing renders.
- Reopening a review drew the confidence thresholds in the wrong place. The thresholds you set when applying a codebook are stored with the run, but the review panel reset to its own defaults every time it opened — so a report reopened after an apply showed lines at 0.30 and 0.70 whatever you had actually chosen, a picture of a run that never happened. It now opens where you left it.
- On macOS 26 the toolbar pills sat inside a second capsule. The system now wraps every toolbar item in its own glass capsule, and the status pills already drew one, so they arrived nested inside a brighter chrome that was never designed. All three now keep their own capsule and opt out of the system's.
- "Switch provider" opened whichever settings pane you last used. It asked for the settings window without naming a destination. It now opens the AI provider pane — the one that answers the question.
0.29.0 — 31 Aug 2026
The codebook is a lens you navigate rather than a page you scroll — and three things the app was telling you were not true.
- Codebooks got a navigator. Every codebook in the project is a row in the left panel, grouped into your own tags, what ships by default, and the frameworks you installed — each with its author under the title. Click a row to open it; switch one off and its tags stay on your quotes but stop appearing anywhere until you switch it back. The old single page, with every codebook stacked on it, is gone.
- The library is somewhere you can go back from. Browse Codebooks opens a catalogue of what's available, and it is now a real place — the back button in the app toolbar and in your browser both return you to the codebook you came from, and the page can be linked to and reloaded. Each card says how big the codebook is, and for one you have installed, how far it has actually reached: 28 tags · applied to 2 quotes in Ikea.
- Uninstalling told you the opposite of what it does. The confirmation said "AutoCode results are preserved — reinstall any time". They are not: uninstalling deletes the AutoCode run and every proposal under it, including ones you had already reviewed. It said so in 21 languages, on the one screen you read before losing the work. The dialog now says what is true, and the new lens measures the cost before you confirm.
pipx install bristlenose produced a tool that could not run. run, analyze and serve all need the serve extra and the instructions did not ask for it, so following the README gave you a Bristlenose that could only transcribe. Homebrew, Snap and Fedora were unaffected; pipx and uv are the routes we recommend first, and the only ones on ARM Linux. Now pipx install 'bristlenose[serve]'.
- The privacy page said there is no Bristlenose server. There is one: the feedback form posts a rating and your message to it. Nothing else — no project, no transcript, nothing identifying you or your machine — and only when you submit the form. But the page listing where data goes had left it out, and denied it above the list. Corrected, along with two other claims that were wrong: exports do not anonymise by default, and speaker identification sends a sample of the transcript to your AI provider before PII redaction runs.
- A long description now reads at two levels. The first sentence carries the claim in full-strength text; the rest continues in a lighter tone, so you can take the point at a glance without anything being hidden or truncated. Applied to codebook descriptions and to the finding on every signal card.
0.28.0 — 27 Aug 2026
- Export HTML worked in the Mac app and nowhere else. The single-file export build is produced by the frontend build and gitignored, and it was never declared for packaging — so it shipped in no wheel and no sdist ever published. The Export HTML button returned a 500 on every pip, pipx, Homebrew and Snap install. Only the Mac app worked, because it lists that directory separately. Verified against the published 0.27.0 wheel: 226 files of the report app, none of the export one. A test now fails the build if any generated asset directory goes undeclared, so this cannot recur silently.
- Exported reports are less than half the size. Every export carried all 22 languages — 1,802 KB of a 3.38 MB file you hand to a client, including Czech strings for a
--whisper-model flag. A report now embeds the language you chose, plus its fallback chain, so a missing key can never print a key name at a stakeholder. 3.38 MB → 1.55 MB.
- A project Bristlenose can't reach now says why — in your language, and where you'll see it. A volume that has gone, a project it can't read, a folder taking too long: five states that rendered as bare English sentences on the row, with no glyph and nothing to click, in every language. They now carry a glyph, a popover with the full explanation, and translations in 21 languages.
man bristlenose no longer outlives the package. Snap and Homebrew installs ship their own man page, but Bristlenose was also dropping a copy into your home directory. man reads that first, so it shadowed the packaged one — and because no package owned it, uninstalling left man bristlenose still working, showing whatever version last wrote it.
- Snap users with a broken PII component are no longer told to run
pipx. The suggested fix was pipx inject into a strictly-confined install, which cannot work. presidio ships inside the snap, so the answer is snap refresh.
- Fedora: two commands, and
dnf upgrade keeps you current. sudo dnf copr enable cassiocassio/bristlenose, then sudo dnf install bristlenose. The package bundles Python and every dependency; FFmpeg comes from Fedora's own repositories, whose free build handles everything Teams, Zoom and Meet produce. x86_64, Fedora 43. The install was proven on a clean box before these instructions went live.
- Agent access keeps explaining itself. A fresh install this week would have picked up a newer MCP library that swallows Bristlenose's refusal messages — an agent asking about a closed study would see
Error executing tool instead of being told the project is out of scope and to reopen it. Pinned until refusals survive the newer library.
0.27.0 — 22 Aug 2026
- Open as many studies as you want, each in its own window — and as many windows on one study as you need. The Mac app showed one study at a time; now every window holds its own. File ▸ New Window (⌥⌘N) opens another view of the study you're on, each window on its own lens — quotes here, the transcript they came from there, the analysis beside it. Open in New Window brings up a different study alongside; on a folder, Open in New Windows opens every study in it at once. Windows keep their own lens, sidebar and scroll position, and their titles say which study each is showing. Bristlenose now opens on the Welcome screen rather than reopening whatever you had last.
- Bring in recordings from Teams and Google Meet without downloading them by hand. Connect a work account and File ▸ Import lists the meetings you recorded: filter by title or attendee, choose how far back to look, tick what you want. Bristlenose fetches the recordings, puts them in a project and starts the analysis — recognising recordings you already have on disk, and saying plainly when one has expired or your account can't reach it. Connect and disconnect in Settings ▸ Accounts. Zoom isn't offered yet.
- If you have run a Teams or Meet Word transcript through Bristlenose, re-run that study. Both were read wrong and neither said so. Teams packs a whole speaking turn into one paragraph, so turns arrived anonymous and stamped 0:00. Meet was worse: its download flattens Gemini's summary into the same file as the transcript, so an AI paraphrase about your participant could be shown in your report as their verbatim quote. Both now yield genuine speech only, with real timecodes and speaker names.
- One unreadable file no longer costs you the whole batch. A single empty or truncated recording used to stop the run and take every healthy session with it. It's now refused by name, with the reason, and everything else is analysed. Declined files are listed rather than silently skipped, and Bristlenose reads 27 file types, up from 16.
- You can see which studies an agent can read. Settings ▸ MCP Agents lists every project: whether an agent can reach it now or only once you open it, how many sessions that exposes, and when it was last asked. Access survives quitting — it used to look permanent and quietly wasn't. The sidebar antenna lights while an agent is actually reading.
- Re-analyse works, and tells you what it would cost. It had been permanently dimmed. It now runs, and asks first — naming the edited quotes, tags, starred quotes, renamed speakers and named themes a fresh analysis would discard. Recordings you drop into a project folder in Finder are noticed and offered.
- An exported report is meant to be a read-only leave-behind, and five things in it weren't — including the accept and deny buttons on suggested tags, clickable in exports since a redesign renamed the control out from under the rule that hid it. All five are gated again, and a test now fails the build if a rule stops matching what it hides.
0.25.3 — 10 Aug 2026
- Cancelling a run no longer records it as a failure — pressing Ctrl-C while a run was still starting up wrote nothing about the cancellation, so the run looked abandoned and the next one reported it as having stopped unexpectedly. A cancellation is now always recorded as one, including when the signal lands while the run's opening log entry is still being written — the case 0.25.2 left open.
0.25.2 — 9 Aug 2026
Reached TestFlight and the direct-download .dmg only; its fix was incomplete. (Superseded by 0.25.3 — use that one.)
0.25.1 — 7 Aug 2026
- Speaker names in a shared offline report are links again — in an exported report, clicking a speaker badge navigated the browser off the file to a path that doesn't exist, silently. It lands on the transcript at the right timecode again. If you are on 0.25.0, upgrade.
0.25.0 — 6 Aug 2026
The Sessions lens learns to shrink, the arrow keys learn the layout, and two build gates that couldn't fail learn to. (Superseded within the hour by 0.25.1 — use that one.)
- The Sessions lens no longer runs off the side of the window. A plain table with no responsive strategy became a grid that degrades on purpose: column order never changes, and detail is shed in a deliberate order — surnames, then the user journey, then names, leaving the speaker badges. The badge is the anchor and never moves; names are never broken mid-word. Pure CSS, so it behaves identically in the app, in a browser, and in an exported report opened offline.
- Every lens now has a proper title — Sessions had no heading, and Codebook's had lost its rule in all three render states. Headings are a component now, so a zone title can't be produced without one.
- The arrow keys move by what you can see;
j/k move by reading order. On a multi-column grid, down used to move the cursor to the card on the right, and left/right did nothing. The arrows are now geometric and j/k are a list cursor. Quote shortcuts also stop firing once you leave the lens, where they'd been eating page scroll and starring quotes you'd navigated away from.
- The Analysis heatmap was drawing light-mode colours on a dark background — permanently, in the Mac app. It now follows the system appearance and repaints live.
- Focus Mode, tuned against real studies — the starred edge dissolves with everything else (a mark that reads well on one card is noise on twenty), the keyboard cursor gains a thin ring in dark, and descriptions fade with their titles. The starred edge is also tamed in dark outside Focus Mode, where at triple the light-mode contrast it read as a divider between two quotes rather than the bracket of one.
- Switching Light/Dark on the Mac no longer reloads your report, losing your route, scroll position and open panels — all to serve byte-identical HTML.
- Also: the moderator pill and quote-context expansion are switched off behind flags rather than shipped half-discoverable; the disk-image build gained a shippability gate that mounts the image and checks the app inside it, after an unnotarised build sat looking finished for 14 hours; and the Mac app's freshness check stopped ignoring the theme and every LLM prompt.
0.24.0 — 3 Aug 2026
A reading mode for the report, and a pass over the Mac app's chrome.
- Focus Mode — read the quotes, not the furniture.
z on the Quotes lens (⌘⌥F, or View ▸ Focus Mode, on the Mac) recedes tag chips, badges, timecodes, context lines and the card box to a faint outline while quote text, speaker codes and stars stay lit. Nothing moves — colour and opacity only, never layout — and anything you hover, tab to or edit comes straight back. A mode, not a theme: it works in light, dark and Edo alike, and honours reduced motion.
- Hiding the sidebars remembers what you had open.
\ (⌘⌥\ on the Mac) stashes the arrangement instead of toggling both, so "contents open, tags closed, hide, show" no longer hands back a sidebar you never had.
- The View menu reads each panel's real state — the four toggle rows now swap between "Show …" and "Hide …" instead of always offering to show.
- The Mac app's appearance preference reaches every surface — save and Locate panels, alerts, context menus, the icon popover, the video pop-out and the five auxiliary windows were all following System Settings instead.
- Homebrew 6.0 had quietly stopped upgrading Bristlenose. Untrusted taps are dropped from a bare
brew upgrade with a warning rather than an error, so installs went stale unnoticed. bristlenose doctor now catches it and prints the one-off brew trust fix.
- Connecting an agent is disclosed as a second recipient — a different vendor, at the moment you ask — and the disclosure no longer contradicts itself about participant names.
- Also: the Pipeline view knows all four local models and the memory each needs; Settings ▸ MCP Agents shows the configuration's shape before a project is running, and pre-announces the one-time macOS cross-app prompt; hand-typed tags now appear in the tag sidebar immediately; and wrapped journey chains sit flush.
0.23.0 — 1 Aug 2026
Ask your study questions from your own AI agent — and pick which provider runs the analysis.
- Ask your study from Claude, Claude Code, ChatGPT or Codex. A read-only MCP endpoint gives a connected agent four tools — project overview, quote search, signals, and your codebook — over the curated corpus: hidden quotes excluded, your edits applied, every quote citing an id you can look up. No writes, no filesystem paths, and no LLM calls from Bristlenose's side. Optional extra on the CLI (
pip install 'bristlenose[mcp]'); built into the macOS app.
- On the Mac, connecting Claude Desktop is one button. Install the extension from Settings ▸ MCP Agents and confirm — no address, no token, no JSON, and nothing to re-copy when Bristlenose restarts on a new port. Claude Code, ChatGPT & Codex and a Generic MCP tab keep the copyable form for every other client.
- Exposure is per project and visible. No agent can read a project until you choose Turn On Agent Access; while it's on, an antenna shows on the project's sidebar row — solid when that project is open and reachable now, pale when it isn't. Turning it off revokes on the spot. One Anonymise switch, off by default, decides whether agents see participant names or codes only.
- Choose which AI runs the analysis. New
bristlenose use <provider>; configure now makes the provider it stores current too. No vendor default — --llm, then your recorded choice, then your sole configured key, and if several keys exist with no choice made, Bristlenose asks once instead of guessing.
- Also: a native Health window on macOS; inline project rename from four places; transcripts name the speaker in the sticky header; the welcome screen gains a Connect-an-agent cell; clip export fetches cloud-evicted sources before cutting and survives cancellation; failed files can name themselves; and diagnostics no longer vanish when a re-run's stages are all cache hits.
0.22.0 — 26 Jul 2026
A large release — codebooks you can switch on and off, a shareable offline report, a more reliable AutoCode, and a pass over the report's visual system.
- Codebooks you can install, switch off, and trust to stay put. Every Codebook Library tile has one button — Install or Uninstall (retiring Add/Remove) — so an installed codebook is no longer look-only. Each carries an on/off switch and a matching sidebar status dot (blue on, grey off); switch one off and its section folds to a summary, its badges hide report-wide, and it leaves the tag sidebar and suggestions — but its tags and results are kept, so flipping it back on restores everything. Uninstalling forgets the on/off state (reinstall returns it on, not greyed out), and codebooks you have switched on now code newly-added sessions too.
- Share an offline report — a self-contained file. Export a report as a single HTML file that runs the full app from
file:// with the data inlined: no server, no internet, nothing to host. It opens read-only (controls that can't persist offline are removed, not dead), bakes in the researcher's language, and a build-time coverage gate makes it impossible to silently ship half a report. Security: the embedded data is now escaped against </>/&, closing a stored-XSS hole from a literal </script> in transcript text, a name, or a tag.
- AutoCode stopped losing proposals — and a stalled run is recoverable. A run could report "AutoCoded in 50s ✓ — 0 of 0 proposals" while the model returned dozens: a cosmetic progress-counter write (read-then-write) hit SQLite's "database is locked" under any concurrent write and, un-isolated, took the batch's proposals with it. The progress write is now write-only and non-fatal. Re-apply proposals de-dupe per quote, a cancelled/failed job can be re-run, start errors surface, and orphaned jobs reconcile on startup.
- The report reads calmer. One heading per content zone, section dividers unified to a single full-width rule, balanced gutters on minimap-less panes, and a consistent radius tier. The Analysis lens scrolls to the bottom again, transcript margins use a cleaner grid layout, and the quotes-page minimap keeps its proportion when quotes flow into columns.
- CLI: configure a provider without a keyring, with the env vars the SDKs already use.
configure persists keys to a config file when no OS keyring is available, and Bristlenose reads the SDK-native, unprefixed env vars for Gemini and Azure. No-provider guidance now points straight at configure.
- The Snap is now strictly confined — converted from classic (home/network/network-bind/removable-media), with the shared libs
ffprobe needs staged and the frontend built under Node 22. Snap users only.
- Fixes: media probing fails loud when / errors instead of reporting a blank success; the Sessions sidebar detects moderators again; the thin-theme bucket is plain .
0.21.0 — 16 Jul 2026
- "Out of credit" now says out of credit — not "rate limit reached." When a provider refused a call because the account had run dry, Bristlenose told you to wait: credit exhaustion and rate-limiting shared one category that rendered as "rate limit reached" — advice that never comes true for a bankrupt key. Billing exhaustion is now its own non-retryable category everywhere a failed LLM call is reported (run cause, CLI banner, desktop row and pill, Settings check). The wire is stranger than the docs admit — Anthropic serves out-of-credit as an HTTP 400 with "credit balance is too low", not the documented 402; OpenAI shares 429 between billing and real rate-limiting — so detection reads the provider's error code first and the HTTP status last. Where a provider genuinely can't distinguish the two (Azure, Gemini), Bristlenose keeps calling it rate-limiting rather than inventing a top-up story.
- The dashboard fills the window, and features up to five quotes. The Project tab shed the 52rem reading-width cap every other tab had already lost, so it no longer sits in a column with the right-hand side empty. Featured quotes go from three to five — three across on a laptop, five from 1400px up.
0.20.0 — 11 Jul 2026
- Feedback survives a failed run. When a run is cancelled or fails, the report gives way to a status page whose "Send feedback" link used to dead-end at "Method not allowed." Now the macOS app opens a native feedback sheet and the browser opens an inline form on the page — both sending the same anonymous
{rating, message}, and both reporting "sent" only when the server confirms receipt, so a captive-portal 200 OK can't silently swallow it; unconfirmed sends fall back to the clipboard. The reliability fix reaches the in-report form too. Browser form + reliability on PyPI; native sheet with the next bundled build.
- Help opens the live docs. The in-app Help overlay is retired in favour of the online documentation, which supersedes it. Help → Bristlenose Help and Keyboard Shortcuts open
bristlenose.app/docs in the browser; Acknowledgements opens the open-source credits. Help you read beside the report, not a modal over it (the ? key reuses a single tab). Ships on PyPI and in the desktop app.
0.19.0 — 4 Jul 2026
- Nine new locales — twenty-one languages in total. Dutch (
nl), Finnish (fi), Polish (pl), Russian (ru), Ukrainian (uk), Danish (da), Swedish (sv), Norwegian Bokmål (nb), and Turkish (tr) join the roster across the desktop app, web report, and CLI. Each is a machine-seeded community preview — complete against all nine namespaces, offered for native speakers to refine on Weblate, with fill-empty-only seeding so contributed translations always win. Slavic 1/few/many/other plurals, East-Slavic _one retaining {{count}} because the form recurs at 21/31, Turkish formal register with Vazgeç for Cancel. New tooling — placeholder-union check, _one gate, pytest classification guard — catches regressions. Norwegian ships as Bokmål only (the no → nb fallback is an open follow-up).
- Shoal — a live word-flock while your interviews are being analysed. During a run, a SpriteKit flock of the words the pipeline is currently extracting drifts across the detail pane in the desktop app. Colour-graded by sentiment, respects Reduce Motion and a dedicated Appearance toggle. If the sidecar feed stalls it falls back to a canned word pool so the pane's never empty. Desktop-only; the Python-side sampler + i18n strings ship on PyPI.
- Palette picker + Edo, end to end. Settings → Display picks between Default (re-grounded on Apple's system values — system-blue accent, the neutral-grey AppKit selection capsule, 6 px corners, native inactive-window dimming) and Edo (Prussian-blue accent from the pigment's brilliant undertone, paper-white paper, passport-navy ink, warmer paper in dark mode). Native macOS pulldown, no restart, live switch via WKWebView bridge. Paints before first render (no flash). New
--palette CLI flag boots straight into your preference. Ships on PyPI and in the desktop app.
- Terminology fixes across four locales. A glossary-consistency pass corrected drift in Japanese (Quotes 引用 → 発言), Korean (a typo, Accept 승인, Signals 시그널), German (Sessions → Interview, undo → Widerrufen, three du → formal Sie), and Spanish (dropping the over-literal Tubería). Values only, verified against the locale-key tests. Ships on PyPI.
- Supply-chain hardening. Every release now attaches runtime-scoped CycloneDX SBOMs (Python + frontend) as GitHub Release assets, and binds the Python SBOM to the wheel + sdist with a Sigstore attestation. A gitleaks history scan runs on every push and PR; a tracked-vs-gitignore guard catches the class of -shaped accidents. No user-facing surface.
0.17.0 — 30 Jun 2026
- Five new locales — Portuguese, Italian, and Traditional Chinese. Brazilian & European Portuguese (
pt-BR / pt-PT), Italian (it), and Traditional Chinese for Taiwan and Hong Kong (zh-Hant / zh-Hant-HK) across the desktop app, web report, and CLI (Settings → Language) — twelve locales in all. Portuguese and Chinese each ship as a deliberate pair (a single code reads as foreign to one side — arquivo / ficheiro, salvando vs. a guardar; Chinese is Traditional only, not Simplified). Each is a machine-seeded community preview for native speakers to refine on Weblate; the seed is fill-empty-only, so contributed translations always win. Apple-HIG verbs + the research glossary seeded per language; new guard tests pin key parity, placeholders, and plural completeness.
0.16.1 — 28 Jun 2026
- Send to Miro becomes a native macOS sheet, and shows exactly where the board will land. On the desktop app the export moves into a native sheet with its own Keychain-held Miro connection, and names the connected account, workspace, and organisation plus the destination team before you push. The web report's Miro panel gains the same connected-account / workspace identity —
serve users see it too. Desktop sheet next bundled-sidecar build; Miro-panel changes ship on PyPI.
- Desktop Quotes toolbar: a native search field and a starred-only toggle. The Quotes search and "starred only" filter are now native macOS toolbar controls (the tag dropdown is gone). Desktop-only — next bundled-sidecar build.
- Fix: the native Miro export no longer 401s after switching projects. The serve process resets its bearer token on a cold start, so the desktop app's token stays in sync with the freshly-booted server. The
serve change ships on PyPI; the symptom was desktop-only.
- Desktop embedded report hides the web sidebar rails and close-×, since the native sidebar owns navigation. Ships on PyPI; only takes effect embedded in the app.
- Contributor tooling: a dev-only Run Inspector (
serve --dev) over instrumentation the pipeline already captures, a native Debug menu (⌃⌘R), and a build-time sidecar-staleness gate. Developer-facing; no change to the report or exports.
0.16.0 — 27 Jun 2026
- Send to Miro — push your analysed quotes onto a Miro board. A new export builds a first-draft research wall: a fresh board per export, one column per section then per theme, pink section headers and yellow quote stickies coloured by sentiment. Connect by pasting a Miro access token (guided setup at
bristlenose.app/docs/send-to-miro.html), preview credential-free, then push. Participant display names never leave your machine — codes only; hidden quotes excluded. Experimental, phase 1 (paste-token). SPA export menu now; macOS native menu next build.
- Desktop: native SF Pro typography, with a Typography setting (SF Pro / Inter). The macOS app renders on a calibrated SF Pro type scale aligned to Apple's text styles, with a setting to switch fonts. Includes WKWebView type-parity fixes so the in-app report matches.
0.15.19 — 24 Jun 2026
- Desktop: the macOS project sidebar is rebuilt on native AppKit. Off SwiftUI's
List (which hit selection/tap dead-ends on macOS 26) onto a native NSOutlineView, bringing two-line project rows (icon · name · session count + a live status subtitle), activity/copy rings with hover-to-cancel on the row, can't-find and iCloud status glyphs, the failure → diagnostic popover, project/folder context menus, and Finder folder drops into the sidebar.
- Desktop: the five report views become a lens rail, with a native window title + session subtitle. Project · Sessions · Quotes · Codebook · Analysis move from the toolbar picker into a lens rail; switching lens updates a native window title and session subtitle (the toolbar title pill is gone). Desktop-only — ships to the alpha cohort with the next bundled-sidecar build; the package gains the supporting interface strings in all seven languages.
0.15.18 — 21 Jun 2026
- Desktop: copy progress moved onto the project row, and switching back to a recent project is near-instant. Copy progress (a determinate ring + "Copying · N%" + hover-to-cancel) now rides the project's sidebar row instead of a separate toolbar pill; and switching back to the immediately-previous project re-points to its still-running ("parked") server instead of restarting it (the "warm-sidecar pool").
0.15.17 — 18 Jun 2026
- Default Claude runs no longer break when Anthropic retires a model. The built-in Claude default pointed at
claude-sonnet-4-20250514, retired by Anthropic on 15 June — so a plain bristlenose run on Claude (no --model) failed at topic segmentation with a 404 model_not_found. Defaults now track current aliases (claude-sonnet-4-6, plus claude-opus-4-8 where Opus is offered), so the next retirement is a one-line change. Pin a model with --model and nothing changes. Ships on PyPI.
- Quote cards pack tighter on the report, where the browser supports it. The quote grid uses CSS Grid Lanes masonry (Safari 26.4+ / the desktop WebKit) so variable-height cards settle into the shortest column — a progressive enhancement that falls back cleanly elsewhere, with reading order intact for keyboard and screen-reader users.
- The desktop sidebar shows real progress on cached and first-time runs, not a frozen "Analysing…". The per-stage verb ladder was driven only by the timing estimator, so cache-verified / transcript-only runs and every new user's first few runs (the estimator needs ~4 runs of history) never advanced it. An estimator-independent signal now fires at each stage entry, carrying the last-known ETA so warm runs are unchanged and cached / cold-start runs advance the verb with the ETA simply absent. Python-only — ships on PyPI and in the desktop sidecar.
0.15.16 — 10 Jun 2026
- Quote extraction no longer fails on dense interviews when the model has a small output limit. Some models cap a single response (ChatGPT's gpt-4o at 16,384 tokens; small local models far less), and on a long, quote-rich transcript the quote-extraction stage could hit that ceiling mid-response — the reply came back truncated and the whole run failed, on roughly one dense ChatGPT run in three. Bristlenose now detects the truncation, splits the transcript at a natural topic boundary (or even halves when there's no confident one), extracts each piece separately, and merges — recursing up to three levels deep (≤8 chunks). Duplicate quotes straddling a split are de-duplicated by verbatim text, and the split is all-or-nothing per interview so a partial failure never leaves a half-analysed session. New typed
TruncatedResponseError / OUTPUT_TRUNCATED cause (mirrored on desktop), plus up-to-six retries on rate-limit bursts. Every quote still lands in exactly one report section. Ships on PyPI — benefits every provider with a tight output cap.
0.15.15 — 9 Jun 2026
- Čeština — Bristlenose now speaks Czech. Czech (
cs) joins Spanish, Japanese, French, German, and Korean across the desktop app, web report, and settings (Settings → Language → Čeština). A volunteer signed up to translate Bristlenose into Czech on Weblate before we'd shipped the language — the first organic demand signal for a locale — so we machine-seeded a complete baseline (all 8 namespaces + preflight, ~950 strings, with proper Czech four-form plurals) for them to react to and refine. Fill-empty-only: the seed never overwrites a contributed string. Apple-HIG Czech glossary terms added.
- A coverage sweep so more of the app translates — settings, codebook, transcripts, accessibility labels, the desktop pipeline — plus the first Czech-correct plurals. Adding Czech surfaced strings still hard-coded in English (reaching none of the seven languages); this pass wired them through i18n, including the Settings config reference, ~30 screen-reader labels and tooltips, and the desktop pipeline status. The headline fix is grammatical: a hardcoded
count == 1 ? one : other selector in the desktop overflow text could never reach Czech's few form (counts 2–4), so a new I18n.pluralCategory() now picks the right CLDR form (proven by a Swift test). Guard tests pin key parity, placeholder consistency, the CLI↔GUI mirror, and four-form completeness. Quality refinement stays the native-speaker community's; this is the mechanical groundwork.
0.15.14 — 7 Jun 2026
- Desktop runs the provider you chose, and stops blaming transcription for LLM errors. Four provider-resolution defects closed: the consent resolver no longer reverts a deliberately-chosen ChatGPT / Gemini / Azure account to Claude when its cached verdict is merely absent; spawn-time overlay injects provider and model as a matched pair (fixes
gpt-4o 404'ing under the Anthropic endpoint); the failure classifier checks LLM markers before Whisper and names the provider, so an out-of-credit / bad-key failure no longer reads as "Transcription failed"; and the Settings status board paints eagerly from cache + silently reconfirms instead of lazily, with out-of-credit (402) as its own sticky state. Reviewed by six agents + William. Desktop-only.
- Desktop status polish. Provider explanations are now selectable markdown with one-click copyable shell-command rows (silent copy, Finder/Safari idiom);
⌘, opens native Settings from report focus too (WKWebView was claiming the key equivalent); offline caption softened "Your key is fine" → "Your key was fine"; status vocabulary settled on colour + label, no per-state glyphs. Design docs trued to the data-protection-keychain + eager-status-board reality. Desktop-only; reaches the alpha cohort with the next bundled-sidecar build.
0.15.13 — 4 Jun 2026
bristlenose pipeline gains per-(provider, model) grain. The pipeline view (0.15.11) now renders a row per (provider, model) pair — each LLM stage shows Claude / ChatGPT / Gemini / Azure with their individual models, per-model quality ratings, and a recommended axis split from default (Opus 4, gpt-4o). CLI + React Settings → Pipeline tab share a sectioned matrix with quality glyphs, a compact symbol key, fixed column alignment across stage tables, and the current row marked by the selection wash + aria-current rather than a loud badge. Payload schema_version 3 → 4; feature rungs and schema are decoupled both ways. Ships on PyPI.
- Desktop: model-first Ollama setup (flow B) + consent activates the chosen provider. A toolbar pill drives the on-device setup popovers (choose model → get Ollama → wait → download), with an honesty rule — static hourglass while waiting on the human, motion only while Bristlenose is working, red only for genuine failure. The AI-consent sheet's Continue/Done now actually activates the chosen provider (was leaving a validated cloud key inert and routing to a stale backend); Stay-local applies the RAM-aware default and pulls the model ambiently so the fetch survives sheet dismissal. 43-key locale layer across 6 locales. Desktop-only.
- CI hardening. Job timeouts on e2e + perf-gate with a bounded-retry around the Playwright install, SHA-pinned third-party actions that touch secrets, read-only default
permissions: blocks, and a six-class fragility map in docs/design-ci.md. Workflow + docs only.
0.15.12 — 23 May 2026
- Anthropic credit-exhausted now reports "out of credit", not "model unavailable". Preflight's BadRequestError classifier was substring-matching on
credit_balance_too_low (an error.type token that doesn't appear in str(exc)) — the live SDK message is Your credit balance is too low to access the Claude API. ..., so the branch silently fell through to the model-unavailable copy. Substring widened to credit balance; regression-pinned with the real SDK message. Runtime path was already correct; preflight-only miss.
- Desktop: ⌘⌫ no longer beeps on placeholder projects or multi-selection. Project-menu disable gates were over-broad —
hasProject required either a non-empty path OR an unavailable project, so brand-new placeholders (empty path, available) and multi-selections (path cleared) both failed and hit AppKit's disabled-shortcut beep. Gates dropped; receivers were already total over empty/mismatched selection. Rename and Move-to-folder kept a narrower gate so they dim correctly on multi-select rather than silently no-op. Desktop-only; reaches the alpha cohort with the next bundled sidecar build.
0.15.11 — 21 May 2026
bristlenose pipeline + read-only Pipeline tab in Settings. New CLI verb (bristlenose pipeline, with --json and --stage <id> filter) and a matching last-position tab in the React Settings modal render the mixture of models Bristlenose actually uses across its pipeline stages: MLX/faster-whisper for transcription, the chosen LLM provider × model for speaker identification / topic segmentation / quote extraction / quote clustering / thematic grouping, Presidio (or "Off") for anonymisation, and an Apple Foundation Models row that returns Unknown from CLI until the Swift-side probe ships. Read-only on purpose — to change a backend you still edit settings or .env. Host context strip shows OS · arch · RAM · keys present · ollama status. Cross-language schema lock at tests/fixtures/pipeline-view-contract.json keeps the Python emit and React consumer in agreement. 28 new tests.
0.15.10 — 17 May 2026
- Release pipeline actually fixed (test-only — wheel unchanged). PyPI had been stuck at 0.15.3 since ~10 May — six tag pushes (0.15.4-0.15.10) reached GitHub but the perf-gate CI job failed on each one, blocking the release workflow. Root cause: the server-rendered status page interceptor was returning "Nothing to see here, yet." against the Playwright smoke fixture because the fixture had never carried a
pipeline-events.jsonl. #bn-app-root mounted empty, perf-gate's DOM-nodes test timed out, and the cascade silently masked the cause for five releases. Fix: ship a RunCompletedEvent line in the smoke fixture, plus a mount-precondition test at the pytest layer and a standalone Playwright mount gate so the next silent regression fails loudly. CLAUDE.md release flow grows a post-push PyPI verification step — pushing a tag is not the same as shipping a release. Test-only fix; v0.15.10's wheel is byte-identical with or without it.
--codebook=<slug> flag + bristlenose codebooks subcommand. Pick an AutoCode codebook framework from the command line. bristlenose codebooks lists the nine available templates with title and author; --codebook=<slug> on run and analyze validates the slug against the YAML inventory and exits cleanly with the available list on a typo. Validation only for now — the pipeline doesn't auto-run AutoCode yet; the flag stores the preference for a follow-up branch to wire the consumer.
0.15.9 — 16 May 2026
- Multi-project Phase 2 #11 + #14 — drag-onto-folder copy + folder watcher. Drag-onto-existing-project now uses a copy path (
CopyMachinery) with progress pill, NewFilesSheet stub, and collision-rename + folder-name-preservation regression tests. Folder watcher: NSFilePresenter + SQLite ingested-set, count pill on the row, delta sheet for triage. Two review passes for visual + i18n polish (cloud-evicted glyph swap to icloud outline, pointing-hand cursor on delta button, .footnote typography for session count, .secondary text uniformly, F59 / F62 i18n).
- Sidebar honesty wave 2.
.ready now requires disk evidence — re-opened completed projects read "Ready" instantly instead of briefly flashing "analysing…". cantFind glyph specialises per reason (.moved / .cloudEvicted / .permissionDenied / .removed). Volume remount no longer regresses to .moved. AppleDouble (._*) + .DS_Store skipped across every directory scanner — closes a 16 May ExFAT SD-card crash where ._s1.txt looked like a transcript and broke utf-8 decode.
- Pipeline subtitle i18n across 6 locales. Sidebar in-flight stage subtitles ("Transcribing…", "Extracting quotes…", "Clustering themes…", etc.) now translated in es / fr / de / ko / ja.
- Keychain hardening. API keys in the macOS Keychain now require Touch ID (or password fallback) to read — biometric ACL on the keychain item. Debug builds switched from Apple Distribution to Apple Development signing so ACLs persist across rebuilds (was un-dogfoodable; every debug Run re-prompted for access).
- HIG corpus mirror. Local Apple HIG mirror so
what-would-gruber-say cites by path + paragraph anchor instead of paraphrasing. Generic scraper for future public release.
- Misc.
Window > Bristlenose menu item to reopen the closed main window. Session start time no longer floored to midnight on the dashboard. In-flight switch modal: "Continue analysing" reframed as the cancel role (default-action stays non-destructive per HIG).
0.15.8 — 14 May 2026
- Honesty everywhere — the pipeline, the install path, and the sidebar all stop faking success. Walks-fix-walks groundwork across three currents:
- CLI honesty (A-stream).
bristlenose doctor hard-errors with a brew/pipx/pip-aware fix message when [serve] extras are missing (zsh-glob-safe quoted); bristlenose run / serve / analyze gate serve_deps in preflight; mid-run abandons surface a researcher-facing banner (QUOTA, AUTH, NETWORK, API_SERVER, DISK, MISSING_INPUT, MISSING_BINARY) instead of a generic "Failed". The static-render naming is sealed: bristlenose render removed, --static deleted, --no-serve restored as a hidden flag for the desktop sidecar's auto-serve suppression.
- Stage-cache honesty (A4). Failed analysis stages no longer cache empty output and silently re-serve it on the next run;
mark_stage_complete refuses empty content_hash; s10/s11 hard-fail at the LLM call site before fallback runs. Privacy contract on cause.message (class name + provider + stage, never str(exc)).
- Long-audio quality (B1).
collapse_adjacent_repeats() post-processor catches Whisper looping ("thanks thanks thanks") without harming natural interjection doubling. Three Whisper params tuned for mlx-whisper. Speaker-propagation regression-pin tests + INFO log surfacing the LLM-splitter's sample-window limit.
- Multi-project foundation (Phase 0 through Phase 2 partial).
ProjectAvailability enum (ready / not-found / cloud-evicted / permission-denied) with last_seen_at; undoable Remove via 5-second toast; "Locate…" flow for not-found projects (Spotlight 2-second one-shot → NSOpenPanel fallback → security-scoped bookmark capture). Phase 2 #1/#2/#3 landed: sidebar-clicking a different project tears down + respawns the sidecar with HIG-correct in-flight-run confirm sheet; drag-onto-existing-project adds files via addFiles; project-onto-project drops surface a non-modal toast pointing at the two right gestures. Sidebar drop hit-test refactored to native per-row .dropDestination (since macOS 13) — replaces ~209 lines of bespoke .onDrop + GeometryReader + DropDelegate coordinate-space machinery. Cache-Control: no-store on /api/projects/* belt-and-braces against WKWebView HTTP cache surviving the rebuild.
0.15.7 — 12 May 2026
- Release-pipeline fix — three CI failures had stacked up since 10 May, leaving PyPI + Homebrew on 0.15.3 while source was on 0.15.6. No new source features in 0.15.7; on upgrade you pick up everything from v0.15.4 / v0.15.5 / v0.15.6 (preflight block, server-rendered status page, SPA auto-refetch, sandbox-safe Export, structured failure causes). The fixes: ffmpeg preflight bypass in the pipeline-abandon test; stub SPA bundle in the cookie auth test; CI Node 20 → 24 (the bundle-size "overage" was an older-zlib measurement artefact, not a real regression)
0.15.6 — 11 May 2026
- Server-rendered status page for runs the SPA can't render —
bristlenose serve now serves a server-rendered page (no React mount) when the project has no completed run, or the latest run failed or was cancelled. The SPA's invariant becomes: it only mounts when there's data to render. Three states surfaced — no-run-yet (CLI vs desktop copy), failed (with cause + log tail in a <details> block, sourced from pipeline-events.jsonl), cancelled. Reuses the five-kind MessageKind taxonomy — no sixth kind invented. Styling lives at bristlenose/theme/templates/status-page.css and uses the design system's tokens exclusively. Event-watcher startup seed also widened to seed failed and cancelled termini, not just completed, so non-happy-path runs survive a server restart. English-only in v1; locale fills batched into the next translation pass
0.15.5 — 11 May 2026
- First-run preflight block — Whisper / ffmpeg / API-key / spaCy preflights all fire between ingest and audio extraction, surfacing every "would this run actually work?" question up front. Whisper banner + native HF Hub progress with hardcoded
~1.5 GB; per-distro ffmpeg install table with macOS-brew auto-install offer (default N); paid single-token API-key validation against billing (Anthropic + OpenAI; 24h TTL cached in ~/Library/Application Support/Bristlenose/state.json mode 0600). Closing line "No more questions. ~X min to your report. Ctrl+C anytime." after which no prompt fires
--no-fetch flag on run / transcribe / analyze — aborts cleanly instead of fetching missing models. Pair with bristlenose doctor --fetch to pre-warm the Whisper cache before going offline
BRISTLENOSE_SKIP_PREFLIGHT=1 — defence-in-depth env-var escape hatch for spoofed-TTY CI runners
bristlenose/preflight/{whisper,ffmpeg,api_key}.py new package; bristlenose/llm/billing_hints.py code-owned provider URLs / minimums / recovery copy; new preflight.* i18n namespace across all six locales (English source; es/fr/de/ko/ja mirror en pending native review)
- Documentation truing — design doc carries a post-implementation Status table; SECURITY.md adds preflight bullet + "What Bristlenose writes to your machine" appendix + HF Hub LFS-hash integrity caveat; man page gains a doctor-options section plus
--no-fetch / --fetch / --yes / --static / --dev (the last three pre-existing gaps), state.json under FILES, and exit code 2
0.15.4 — 10 May 2026
- SPA: auto-refetch on pipeline completion — new
GET /api/projects/{id}/last-run endpoint and LastRunStore (3 s poll, visibility-paused, single timer). Browser SPA now refreshes content within ~3 s of pipeline completion instead of going silent. All four content tabs and five island consumers wired
- SPA: trust-UX layer — manual refresh button in the NavBar (serve-mode only, hidden in the desktop app), refetch overlay on Quotes / Sessions / Dashboard while a refetch is in flight, post-zero-quotes empty state on the Quotes tab
- Desktop: Export downloads route via
WKDownload + NSSavePanel — sandbox-safe Export. The previous synthetic <a download> click was silently dropped by the App Sandbox; the shell now intercepts the request, hands the response to WKDownload, and prompts via NSSavePanel. Six locales updated for the save dialog
- Desktop: structured failure category surfaced in the failure pill —
EventLogReader now exposes the categorised Cause from the events log (10 categories: missing input, missing binary, all-LLM-failed, etc.). The failure pill renders the category-appropriate glyph + label instead of the generic "Failed" badge
- Pipeline: structured per-stage failure summaries + abandon path — every terminus event (
run_completed / run_failed / run_cancelled) now carries an optional PipelineSummary rollup. When every transcription session fails or every quote-extraction LLM call fails, the pipeline now raises PipelineAbandonedError instead of silently writing an empty report. Two new Cause.category values (MISSING_INPUT, MISSING_BINARY)
- Sidecar: sandbox compliance for the bundled FFmpeg
$PATH, the libproc start-time check, and the React-bundle static serving — three fixes that unblock the sandboxed desktop sidecar (mlx_whisper's bare "ffmpeg" shellout now resolves; /bin/ps exec replaced with proc_pidinfo; Starlette StaticFiles mmap replaced with in-memory read_bytes for the React bundle)
- CLI: glyphs and colours sourced from a single
MessageKind table — CLI, popover, and toast now consult bristlenose.ui_kinds.cli_prefix(kind) instead of ~20 ad-hoc console.print("[colour]...[/colour]") calls
- Desktop: i18n sweep + locale delegated to System Settings — extracted hardcoded English literals from , , , default project name (all six locale files updated). The in-app language picker is removed on macOS; selection flows through System Settings → Apps → Bristlenose. Web/CLI serve picker unchanged
0.15.3 — 4 May 2026
- Desktop: provider-switching hardened end-to-end — Change-provider flow now activates the new provider across Keychain, sidecar env, and UI; Ollama choice persists before consent log; sidecar key injection scoped to the active provider only; LLM Settings lazy-loads Keychain per row
- Prompt-injection sentinel-tag boundary (Phase A) — defence-in-depth boundary that lets the LLM structurally distinguish transcript content from prompt instructions
- Sidecar bundle trim (S1+S2+S3) —
mlx_whisper.torch_whisper excluded, dedicated build venv, torch + onnxruntime evicted, mlx-whisper assets shipped via collect_all. Significantly smaller sidecar
- Doctor: recognises
mlx-whisper as a complete backend on Apple Silicon
- Desktop: trusts log tail on sidecar exit-1-after-success; sandbox-on Debug builds load locales + resize correctly on macOS Tahoe
0.15.2 — 2 May 2026
- Bundled FFmpeg/ffprobe discovery for sandboxed sidecar — new
bristlenose/utils/bundled_binary.py resolves FFmpeg/ffprobe from a bundle-relative path before falling back to $PATH, so the sandboxed desktop sidecar can find ship-with-app binaries. CLI users see no behaviour change
bristlenose/server/lifecycle.py — serve-mode lifecycle factored out (parent-death detection, sandbox-friendly bind(0), graceful shutdown)
- PipelineRunner
SidecarMode.resolve migration (PR #96) and TLS→certifi redirect under sandbox (PR #97). Desktop-only impact
- Frontend size budget re-baselined to live-SPA gzipped (lazy locales excluded). Budget remains 320 kB
- Docs: manual + README reconciled with shipped bundled-binary-helper; pricing reframed per hour of audio
0.15.1 — 1 May 2026
- Desktop alpha-readiness (Sprint 2 Track C C2–C5) — sandbox-safe API-key injection via Swift Keychain → env vars (no more
/usr/bin/security exec from Python), libproc-based zombie cleanup, os.Logger with privacy redaction, key-shape stdout redactor, SidecarMode.resolve + three Xcode schemes, privacy manifests for host + sidecar, per-binary signing, supply-chain provenance. Desktop-only
- Desktop first-run experience (Sprint 2 Track B Branch 1) —
BootView unified boot/loading/failed surface, WelcomeView two-variant empty state, Beat 3 round-trip API key validation, Beat 3b Ollama setup sheet for local AI install, boot.* + welcome.* translation blocks across all 6 desktop locales. Desktop-only
bristlenose serve fall-back to bundled theme CSS — fixes brand-new projects rendering as raw HTML inside WKWebView when no per-project CSS has been generated yet
bristlenose doctor --self-test — verify sidecar bundle integrity (spec → bundle file presence)
bristlenose serve fail-loud on missing React bundle — returns a "Build incomplete" 500 page instead of silently falling back to the static render
- Japanese (
ja) locale completion — ~614 strings across 8 namespaces, alpha gate cleared
- i18n: tag sidebar — 11 keys across all 6 locales
0.15.0 — 26 Apr 2026
- Pipeline resilience: run-level event log + honest run state — pipelines now write
pipeline-events.jsonl recording how each run ended, with a structured Cause (10 categories). Replaces the desktop's old inference path that mis-classified interrupted runs as ready
- Cost as honest estimate —
cost_usd_estimate + price_table_version + input_tokens + output_tokens stamped on every terminus event. Surfaced as "~$0.46 (est.)" — never bare dollars
- Stranded-run reconciliation — prior runs that died mid-flight are now noticed on the next
bristlenose run and synthesised as failed with cause unknown ("Analysis stopped unexpectedly.")
- Desktop:
EventLogReader — Swift consumer of the events log; new PipelineState.partial / .stopped cases. UI verb wiring (Resume / Retry / Re-analyse…) lands in a follow-up iteration
- Desktop: Swift test target wired up —
xcodebuild test runs 90 tests including new EventLogReaderTests
0.14.6 — 18 Apr 2026
- CI: e2e gate re-enabled as a blocking check (three P3 items cleared)
- Fix: Analysis "Show all N quotes" toggle now a proper
<button> (was an <a> without href)
- Fix:
playwright.config.ts shell-quotes paths so worktrees with spaces in the name work
SECURITY.md: corrected auth-token description (env-override path is real and used by CI fixtures / uvicorn reload; future hardening tracked)
bristlenose doctor: new Auth token check warns if _BRISTLENOSE_AUTH_TOKEN is set in the shell
- CI: allowlist register at
e2e/ALLOWLIST.md — every test suppression categorised and tracked; prevents silent accumulation
0.14.5 — 17 Apr 2026
- CI unblock release: no user-facing changes
- Fix
eslint-plugin-react-hooks 7.0.1 → 7.1.0 (eslint 10 peer-range)
- Pin
jsdom to 27.x (29 dropped localStorage shims, 140+ test failures)
- Fix stale Vitest mocks after
api.ts and i18n changes
- Regenerate
e2e/package-lock.json (lighthouse was added without npm install)
- Exclude
perf-stress.spec.ts from default Playwright discovery
- e2e CI gate temporarily informational; three P3 findings parked to sprint 2
0.14.4 — 16 Apr 2026
- Pipeline resilience: input change detection (added/removed/modified files trigger re-run)
- Alembic migration infrastructure replaces manual schema migrations
- CI: multi-python matrix (3.10–3.13), split lint from test jobs
0.14.3 — 27 Mar 2026
- Export: video clip extraction from starred and featured quotes (FFmpeg stream-copy)
- Fix: video player "Cannot play" error caused by bearer token on
/media/ routes
- Fix:
safe_filename() path traversal reassembly vulnerability
0.14.2 — 24 Mar 2026
- Localhost bearer token for serve mode API access control
- Fix: TOC sidebar empty after auth middleware added
0.14.1 — 24 Mar 2026
- French, German, Korean locales — machine-translated across all 8 namespace files, cross-checked against Apple and ATLAS.ti glossaries
- Contextual left-panel labels — desktop toolbar shows Contents/Codes/Signals per tab
- Tags sidebar shortcut (
⌘⌥T), keyboard shortcuts design doc
0.14.0 — 20 Mar 2026
- Inspector panel — DevTools-style collapsible bottom panel in Analysis tab with heatmap matrices, signal card selection sync, drag-resize, and
m keyboard shortcut
- Codebook sidebar — left-hand navigation panel on every tab with tag groups, counts, and micro-bars
- Finding flags — Win/Problem/Niggle/Success/Surprising/Pattern direction labels on signal cards, computed from sentiment valence, breadth, and intensity
- Settings modal (
⌘,) and Help modal (?) — sidebar-nav modals with shared ModalNav organism
- Activity chips persist across tab navigation; new framework codebooks (Nielsen 10, Yablonski Laws)
- Fixes: codebook tab sidebar with trailing slash, codebook grid width, autocode toast behaviour
0.13.7 — 16 Mar 2026
- Spanish locale — machine-translated 102 UI strings across 8 locale files, ready for native-speaker review
0.13.6 — 16 Mar 2026
- Single tag focus mode — click a tag count to solo that tag (mixing-desk style), click again or Escape to exit. Sidebar dims non-focused tags; focused tag row stays at full brightness. "You are here" blue wash highlight on the active count matches TOC sidebar style
- Fix circular dependency crash in production build (SidebarStore → QuotesContext import cycle caused "Cannot access uninitialized variable" error)
- Used/unused tag filter toggle — quickly show only tags that appear on quotes
- Sidebar tag click-to-assign — select quotes then click a tag badge to apply it
- Tier 1 logging instrumentation and PII hardening
- Multilingual UI infrastructure (i18next + Python i18n module, browser language auto-detection)
- Responsive signal cards — grid layout with narrow-screen stacking
- Cache
system_profiler results with 24h TTL to avoid slow macOS startup
0.13.4 — 14 Mar 2026
- Fix import FK constraint — delete AutoCode proposals before removing stale quotes during re-import
- Sessions sidebar with live-resize content-adaptive breakpoints
0.13.3 — 14 Mar 2026
- Help modal polish — platform-aware shortcuts (⌘/Ctrl), keycap typography, entrance animation, custom tooltips with shortcut badges
- Bulk actions respect multi-selection on click (star, hide, tag). Shift+click range selection fix
- Sidebar: push animations, 4× faster transitions, rail drag-to-open, drag handle hover-intent, scroll spy Safari fix
- Pipeline CLI: red ✗ for failed stages, yellow ⚠ for partial success
- Speaker badge wrap fix, scroll-margin fix for sticky navbar, TOC overlay clip-path animation
0.13.2 — 11 Mar 2026
- Render refactor —
render_html.py (2,903 lines) broken into bristlenose/stages/s12_render/ package with 8 submodules. Static render path formally deprecated (DeprecationWarning). No behaviour change
0.13.1 — 10 Mar 2026
- Responsive layout playground, sidebar TOC overlay mode, minimap component, responsive quote grid CSS
0.13.0 — 10 Mar 2026
- Codebook-aware tag autocomplete — suggestions grouped by codebook section with colour pills and headers, IDE-style
- Sentiment tags unified into the codebook framework system (proper
sentiment.yaml codebook)
- Tag provenance tracking —
"human" vs "autocode" source on every tag, preserved across edits
- Hidden-group tag UX — eye-closed icon in autocomplete, auto-unhide on accept
- Quick-repeat tag shortcut (
r) — re-apply the last-used tag on a focused quote
- Fix: context expansion crash (infinite re-render loop on up chevron)
- Doctor offers interactive MLX install on Apple Silicon for GPU-accelerated transcription
- Sidebar accessibility improvements (focus management, ARIA, reduced-motion support)
- Dashboard stat cards now open in new tab on Cmd+click
0.12.2 — 2 Mar 2026
- Footer feedback restored in React serve mode: "Feedback" now opens a dedicated feedback modal (not the Help modal), and "Report a bug" remains a direct GitHub issue link
- Feedback submit flow parity with legacy behavior: POST to configured endpoint when available, with clipboard fallback + toast if endpoint submit is unavailable
- Health API now includes footer config (
links.github_issues_url, feedback.enabled, feedback.url) while preserving status and version
- Exported reports now embed the same health/footer config shape as serve mode for consistent footer behavior
0.12.1 — 1 Mar 2026
- Word-level transcript highlighting — individual words highlight in sync with video playback (karaoke-style). Whisper sessions get per-word timing; VTT/SRT imports fall back to segment-level glow
0.12.0 — 1 Mar 2026
- Dual sidebar for Quotes tab — left: TOC with scroll-spy, right: tag filter with codebook tree and eye toggles for badge hiding. Drag-to-resize, keyboard shortcuts (
[ ] \ ⌘.)
- Frontend CI — ESLint, TypeScript typecheck, and Vitest in GitHub Actions
0.11.2 — 1 Mar 2026
- Self-contained HTML export — download button bundles all data as embedded JSON with hash router for
file://, optional anonymisation
- About panel redesign — sidebar layout with 5 sections (About, Signals, Codebook, Developer, Design)
- Configuration reference panel — read-only grid of all 63 configurable values with defaults, env var names, and valid options
- Morville honeycomb codebook — 7 groups, 28 tags with discrimination prompts and cross-codebook references
0.11.1 — 28 Feb 2026
- Fix video player broken by double URL encoding and missing subdirectory in source file paths
0.11.0 — 28 Feb 2026
- Full React SPA in serve mode — React Router replaces vanilla JS tab navigation, all 26 vanilla JS modules retired from serve path
- Player integration —
PlayerContext manages popout video player lifecycle, glow sync, and progress bar from React
- Keyboard shortcuts — j/k navigation, multi-select, bulk star/hide/tag,
? help modal, Escape cascade
- React app shell —
Header, Footer, HelpModal components; serve mode serves Vite-built SPA directly
- Video player links on sessions page and dashboard open the popout player
- Importer finds source files in subdirectories (mirrors ingest scan pattern)
- Speaker display names in sessions grid use normal font size
0.10.3 — 21 Feb 2026
- Split speaker badges — two-tone code+name pills across all surfaces, with "Code and name / Code only" settings toggle
- Always-on sticky transcript header with session selector; serve-mode session links navigate to React transcript pages
bristlenose status command — read-only project status from the manifest with session counts, intermediate file validation, and -v per-session detail
- Pre-run resume summary — one-line status message when resuming an interrupted pipeline run
- AutoCode frontend — ✦ button triggers LLM tag application, progress toast, confidence-aware threshold review dialog, proposed badges on quotes with accept/deny
- Transcript page — non-overlapping span bars, topic-change label dedup, speaker badge styling, back link stays in serve mode
- Journey chain — sticky header shows full journey with revisits, active step highlighted with hover-tint pill, index-based click-to-jump
- Resilient transcript discovery — serve-mode importer searches four locations instead of one
- Generic analysis matrix and signals API for serve mode
- Man page and docs updated for
status and serve commands
- Fix: transcript back link no longer escapes from serve mode to raw static HTML
0.10.2 — 21 Feb 2026
- Pipeline crash recovery — interrupted runs resume automatically, only re-processing unfinished sessions
- Per-session tracking for topic segmentation and quote extraction with transparent cache + fresh merge
- CLI resume guard — re-run into existing output without
--clean, manifest detected automatically
0.10.1 — 19 Feb 2026
- Desktop app API key onboarding — first-run setup screen, Keychain storage, Settings panel (⌘,)
.dmg packaging — ad-hoc signed, drag-to-Applications, one-command build via build-all.sh
- Serve mode after pipeline — auto-launches
bristlenose serve, report at http://127.0.0.1:8150/report/
- Deployment target updated to macOS 15 Sequoia
0.10.0 — 18 Feb 2026
- Desktop app v0.1 — SwiftUI macOS launcher with folder picker, drag-and-drop, pipeline streaming, View Report in browser
- Xcode 26 project with 4-state UI (ready → selected → running → done), ANSI stripping, report path detection
0.9.4 — 17 Feb 2026
bristlenose serve — local dev server with SQLite persistence, React islands, live JS reload
- 5 React islands (SessionsTable, Dashboard, QuoteSections, QuoteThemes, CodebookPanel) + 16 reusable primitives (182 Vitest tests)
- Codebook CRUD — drag-and-drop, inline editing, tag merge, pentadic colours, MicroBar frequency bars
- Data API — 6 endpoints sync researcher state from localStorage to SQLite (94 tests)
- Desktop app scaffold — SwiftUI macOS shell with sidecar architecture
0.9.3 — 13 Feb 2026
- Interactive dashboard — clickable stat cards, featured quotes with video playback, session row drill-down, cross-tab navigation
- Fix: logo dark/light swap on appearance toggle
0.9.2 — 12 Feb 2026
- Sessions table redesign — speaker badges, user journey paths, video thumbnails, per-session sentiment sparklines
- Appearance toggle — system/light/dark mode in settings tab
- User journeys — derived from screen clusters, shown in sessions table and sortable journeys table
- Time estimates — upfront pipeline duration estimate after ingest, recalculated as stages complete
- Clickable logo — navigates to project tab
- Fix:
llm_max_tokens truncation causing silent 0-quote extraction
- Fix: sentiment sparkline bars now align with video thumbnail baseline
0.9.1 — 11 Feb 2026
- Moderator and observer names shown in Project tab stats row (Oxford comma lists, observer box only when observers exist)
- Fix: clicking [+] to add a tag on a quote now tags that quote, not the previously-focused quote
0.9.0 — 11 Feb 2026
- Tab navigation remembers position across reloads via URL hash; browser back/forward navigates between tabs
- Analysis tab — inline signal cards and heatmaps in the main report (no longer just a placeholder)
- Codebook tab — fixed empty grid bug caused by JS function name collision in the concatenated bundle
0.8.2 — 9 Feb 2026
- Transcript annotations — transcript pages highlight selected quotes with margin labels, sentiment colours, span bars, and playback-synced glow
- Gemini provider —
--llm gemini for budget-conscious teams (~$0.20/study, 5–7× cheaper than Claude or ChatGPT); bristlenose configure gemini stores your key in the system credential store
- Jinja2 templates — report renderer migrated from f-strings to 13 Jinja2 templates (internal refactor, no output changes)
0.8.1 — 7 Feb 2026
- Hidden quotes — press
h to hide volume quotes from your working view; per-subsection badge with dropdown previews; bulk hide via multi-select
- Codebook — standalone page for organising tags into groups with drag-and-drop, inline editing, and colour-coded badges
- Toolbar redesign — unified round-rect button styling
- Python 3.14 compatibility fix for PII check
0.8.0 — 7 Feb 2026
- Azure OpenAI provider —
--llm azure for enterprise users with Microsoft Azure contracts
- Install smoke tests — new CI workflow verifies install instructions on clean VMs
0.7.1 — 6 Feb 2026
- Bar chart alignment — sentiment and user-tag charts use CSS grid so bar left edges align within each chart; labels hug text with variable gap to bars
- Histogram delete — hover × on user tag labels in the histogram to remove that tag from all quotes (with confirmation modal)
- Surprise placement — surprise sentiment bar now renders between positive and negative sentiments
- Quote exclusivity in themes — each quote assigned to exactly one theme (pick strongest fit)
0.7.0 — 5 Feb 2026
- Multi-select — Finder-like click selection (click, Shift-click, Cmd/Ctrl-click) with bulk starring (
s key) and bulk tagging; selection count shown in view-switcher label; CSV export respects selection
- Tag filter — toolbar dropdown between search and view-switcher filters quotes by user tags; checkboxes per tag with "(No tags)" for untagged quotes; per-tag quote counts, search-within-filter for large tag lists, dropdown chevron, ellipsis truncation for long names
0.6.15 — 4 Feb 2026
- Unified tag close buttons — AI badges and user tags now use the same floating circle "×" style
- Tab-to-continue tagging — pressing Tab commits the current tag and immediately opens a new input for adding another tag (type, Tab, type, Tab, Enter for fast keyboard-only tagging)
- Render command path fix —
bristlenose render <input-dir> now auto-detects bristlenose-output/ inside the input directory
0.6.14 — 4 Feb 2026
- Doctor fixes — improved Whisper model detection and PII capability checking
0.6.13 — 3 Feb 2026
- Keychain credential storage —
bristlenose configure claude (or chatgpt) validates and stores API keys securely in macOS Keychain or Linux Secret Service; keys are loaded automatically with priority keychain → env var → .env; bristlenose doctor now shows "(Keychain)" suffix when key comes from system credential store; --key option available for non-interactive use
0.6.12 — 3 Feb 2026
- File-level transcription progress — spinner now shows "(2/4 sessions)" during transcription
- Improved Ollama start command detection — uses
brew services start ollama for Homebrew installs, open -a Ollama for macOS app, platform-appropriate commands for snap/systemd
- Doctor displays "(MLX)" accelerator — when mlx-whisper is installed on Apple Silicon, doctor now shows "(MLX)" instead of "(CPU)"
- Provider header fix — pipeline header now shows "Local (Ollama)" instead of "ChatGPT" when using local provider
Older versions →
Licence
Copyright (C) 2025-2026 Martin Storey ([email protected])
AGPL-3.0. See LICENSE and CONTRIBUTING.md.