v0.9.4 — Search that actually finds your content, plus a usable light mode
The big idea this milestone: stop trusting the LLM to count characters.
For months the chunker for PDFs, Word docs, and Markdown asked Gemini Flash to identify each section’s start and end position in the document. Flash is a language model — it’s reliable at writing text, but its character arithmetic was off by hundreds of characters. Every downstream system (search index, BM25 keyword matching, semantic embeddings, the body text shown to you in the UI) was built on top of those wrong numbers. So when you searched for “meatball” and the recipe was on page 12 of an Italian cookbook, the system couldn’t find it — even though the chunk technically existed, it was sliced from the wrong offset and contained the previous recipe’s tail instead.
This release rebuilds that pipeline so character offsets are computed by deterministic Swift code from a verbatim quote returned by Flash, never by Flash itself. After this, sections start where you’d expect, search returns the right chunks, and Ask AI cites the right recipes.
Beta 17 — 2026-06-11
Section titled “Beta 17 — 2026-06-11”The biggest beta of this cycle: Search now works by meaning, ingestion gets a full history-and-control panel with real cost figures, and the beta clock has been extended with friendlier expiry behaviour.
Search now understands meaning
Section titled “Search now understands meaning”The Search tab no longer matches letters — it matches meaning, using the same retrieval engine as Ask AI. Type “pasta techniques” and you’ll find the cooking video scenes and the cookbook sections, even if neither contains those exact words, across documents, images, audio and video alike.
Under the hood we also fixed a long-standing bug: query embeddings were sent with a parameter the embedding model had quietly deprecated and ignored, which made meaning-based matching far weaker than designed. With the fix, semantic results are dramatically better — this is the single biggest search-quality change since the chunker rebuild at the start of this milestone.
Two practical notes: each search now makes one tiny Gemini call (fractions of a hundredth of a cent — your key, but effectively free), and the old keyword search remains only as the fallback before you’ve set up a Gemini key.
One click to upgrade your existing index
Section titled “One click to upgrade your existing index”Content you indexed before this beta was embedded under the old convention, so it doesn’t benefit fully from the search fix above. The Library tab now shows a banner — Re-index for improved search results — when it detects older content. One click re-embeds just the affected files and the banner goes away. It costs normal embedding tokens for those files, nothing else changes, and you can keep using the app while it runs.
Runs: a history and control room for ingestion
Section titled “Runs: a history and control room for ingestion”The Library sidebar has a new Runs group: every ingestion run, grouped by Today / Yesterday / Previous 7 Days / Older. Click a run and you get a per-file table with live status while it’s running, plus what each file cost and the run’s total once done.
From there you can do the things you previously couldn’t: cancel the whole run or individual files (one button, it adapts), retry just the failed files, rename a run (double-click or the pencil), delete a run from history, reveal a file in Finder, or export a run summary. The progress banner also grew up — it now distinguishes “cancelled” from “failed” and stays put when you switch tabs.
See the cost before a big import
Section titled “See the cost before a big import”Before a large ingestion starts, a preflight sheet shows the estimated AI cost broken down per file, and you can trim the batch before approving. Actual costs are recorded at every AI call as the run executes, so the estimate and the final figure sit side by side in the run’s detail pane.
Beta expiry: extended, and no longer a dead end
Section titled “Beta expiry: extended, and no longer a dead end”This build’s beta period runs to 11 August 2026. And when a beta build does expire, the lock screen is no longer a wall: it offers Check for Updates (the usual fix — get the next build) and an upgrade path. Paid and complimentary tiers are never locked out at all.
Smaller fixes
Section titled “Smaller fixes”- Cancelling a run no longer leaves the progress banner stuck showing a running state.
- Section and file titles no longer fall back to whitespace-only strings.
Beta 16 — 2026-05-10
Section titled “Beta 16 — 2026-05-10”Mostly invisible plumbing this beta. Backend hardening — Stripe webhooks now signature-verified and idempotent, the public API has proper rate limits and security headers, the admin dashboard is locked to our internal network — and one user-visible change: Settings now has an Account section to link your email, with an optional banner that nudges free users post-value. Tier changes (upgrades, downgrades, complimentary grants for press / partners) now show up on the next launch instead of needing a 60-second background-and-foreground.
Settings → Account: link your email (optional)
Section titled “Settings → Account: link your email (optional)”Settings now has a new Account section. If you’ve never given us an email — and most users haven’t, because Golden Retriever doesn’t require an account to ingest your library or run AI queries — you’ll see a “Link email” entry with a primary button.
Click it, type your email, and we’ll send a magic-link sign-in to that address. Click the link from the same Mac and Settings flips to “Linked as your@email.com”.
Linking is optional. The reasons you might want to:
- You want to keep your library if you switch Macs (linking is the one thing that lets us re-associate a fresh install with you).
- You’re a press reviewer or partner who’s been told to link before being granted a free Pro / Business / Enterprise tier.
- You’d like the rare email about major features. (We never spam, never enroll you in a newsletter.)
Linking does not upload your library, send anything to a marketing list, or change anything about how the app works locally. The only thing that changes is a single record on our backend mapping your-email ↔ this-install.
A banner nudges free users to link, post-value
Section titled “A banner nudges free users to link, post-value”Once you’ve ingested at least one document or run at least one Ask AI query — i.e. you’ve gotten something out of the app — and you’re on the free tier without a linked email, a soft banner appears across the top of the main window suggesting you link.
It’s polite. Two buttons (Link email / Not now) and a close X. “Not now” hides it for 14 days. The X hides it permanently. Linking does the same as the Settings → Account flow above. The banner never reappears once you’ve linked, dismissed permanently, or gone past free tier.
Tier changes show up on relaunch
Section titled “Tier changes show up on relaunch”Previously: if you’d just upgraded via Stripe Checkout, or if our admin had granted you a complimentary tier, the app’s launch-time tier check would early-exit for free-tier users — so the new tier didn’t surface until you backgrounded the app for at least 60 seconds and brought it back. Slightly maddening for upgrades.
This release fixes the launch path to always re-fetch entitlement, regardless of cached tier. Open the app after an upgrade or comp grant; tier is correct from frame one.
Backend hardening: things you mostly don’t see
Section titled “Backend hardening: things you mostly don’t see”Three weeks of work on the API side:
- Stripe webhooks now verify the signature on every delivery and reject duplicates by
event.id— Stripe’s normal retry behaviour can no longer double-credit, double-flip, or write two rows for the same event. - The public API has rate limits per IP (60 requests/minute on most routes) and per-API-key for upload / appcast traffic, plus security headers (
X-Content-Type-Options,X-Frame-Options), origin-restricted CORS, and 404s on common scan paths instead of inviting bot probes. - The admin dashboard at
/admin/*is now reachable only from our internal Tailscale tailnet. From the public internet it’s a 404 — no login page, no signal that the path exists. - A pre-merge sandbox environment is now wired up: backend changes deploy to
sandbox-api.goldenretriever.aifirst for end-to-end validation before they touch production. Most of the work above shipped through this loop, which paid off when one issue (a Caddy directive-ordering quirk) was caught at sandbox before reaching real users.
You’ll notice nothing if you’re using the app normally. If you somehow trip a rate limit, requests start returning HTTP 429 — one bug-report retry that fires 60 attempts in a tight loop, for example. Back off, try again in a minute.
Smaller fixes worth mentioning
Section titled “Smaller fixes worth mentioning”- Citation crash: clicking certain Ask AI citations no longer crashes the app on a 4pt frame measurement that AppKit didn’t like.
- Worktree builds: developers building from a fresh
git worktreeno longer need to re-symlinkSecrets.xcconfigby hand —bin/setup-worktreedoes it.
Beta 15 — 2026-05-08
Section titled “Beta 15 — 2026-05-08”A focused morning of UI polish across every tab. Resizable side panes are back on Search and Library, narrow windows no longer shove the navigation off-screen, every tab now uses the same dark / cream tone, page headings stop getting squeezed to ellipsis by the pills next to them, and Ask AI’s three columns finally have a real visual hierarchy.
Drag-to-resize side panes on Search and Library
Section titled “Drag-to-resize side panes on Search and Library”You can now drag the divider between Search’s results pane and the preview pane, and between Library’s folder list, file list, and detail panes. Hover the divider, the cursor changes to ↔, drag to any width, release. Positions persist across app launches independently per shell, so Search and Library each remember the layout you set.
This brings Search and Library in line with Ask AI, which already had drag-to-resize panes since beta.12.
Narrow windows no longer push the navigation off-screen
Section titled “Narrow windows no longer push the navigation off-screen”If you sized the window narrow enough — especially on Search, Library with a folder selected, or Billing — the outer navigation (Library / Ask AI / Search / Billing / Privacy / Settings) would slide off the left edge of the window, leaving only fragments of icons visible. The content panes were demanding more horizontal space than the window could give them, and instead of compressing they pushed the sidebar out.
The minimum widths on every tab have been retuned so the content always fits, and Search and Library use a new pane layout under the hood that compresses gracefully when the window shrinks. Drag the window down to its 960pt minimum on any tab and the sidebar stays fully visible.
Ask AI: layered three-column shell
Section titled “Ask AI: layered three-column shell”The conversation list (left), Q&A pane (centre), and source preview (right) now read as three distinct surfaces. The centre Q&A pane is the darkest — a deeper warm gradient in light mode, near-black in dark — and the side columns sit one shade lighter. Same depth hierarchy as Claude.ai or ChatGPT, just in our own colour palette.
All three columns also paint flush to the very top of the window now (extending behind the macOS title-bar area), so the column tones meet the window edge cleanly. Earlier the side columns stopped just below the title bar and the gradient peeked through the gap.
Two horizontal rules — the one under the conversation title and the one above the input dock — have been removed. The panes now flow into each other.
Page headings no longer get squeezed by their own pills
Section titled “Page headings no longer get squeezed by their own pills”On Search, Library Overview, Billing, Privacy, and Ask AI, the page heading sat on the same row as its trailing pills (e.g. “Cited X sources”, “BM25 + dense”, tier name, “GDPR · DPA available”). When the window was narrow, the pills took horizontal space and the heading was compressed into ellipsis truncation — "how do i mak..." instead of the full conversation title.
Every tab now uses the same heading treatment: pills on the top row, full-width title on its own row below. The title can wrap to a second line if the column is very narrow, but it never abbreviates.
The Library file-list header (testdata + the full file path) gets the same treatment — the folder name and path use the full width of the file list pane and stop getting truncated by the filter menus.
Same surface tone on every tab
Section titled “Same surface tone on every tab”Each tab used to use a slightly different shade for its main background — the content tabs (Search, Library, Billing, Privacy, Settings) were one step lighter than Ask AI’s side columns, and Team used a system-default grey that was different again. Three different tones depending on where you were in the app.
All tabs now use one unified surface tone, matching the side columns of Ask AI. Single colour across the app means a more cohesive feel and one knob to retune the whole palette in future updates.
”+ New chat” button on Ask AI’s conversation list
Section titled “”+ New chat” button on Ask AI’s conversation list”The conversation history pane’s header used to read “CONVERSATIONS” with a small circular + icon to its right. The eyebrow text was redundant in a column titled by its own contents, and the circular plus was an awkward target. It’s now a single left-aligned + New chat button.
The whole row is the click target, and ⌘N is wired up to the same action — works any time the Ask AI tab is showing.
”Cited X sources” actually means cited
Section titled “”Cited X sources” actually means cited”The green pill above an Ask AI answer used to read “Cited 189 sources” or similar. Cited usually means actually referenced in the answer, but the count was retrieved sources — typically much higher than what the answer actually cited. Misleading.
The pill now uses the truly-cited count: how many of the source [N] markers actually appear in the answer text. The number is much smaller (typically 3–10) and matches the “X Cited Sources of Y retrieved” label in the inline sources panel below the answer.
Ask AI input field matches Search
Section titled “Ask AI input field matches Search”The “Ask anything about your knowledge base…” field on Ask AI now uses the same 18pt text size as Search’s “Search your knowledge base…” field, and gets a leading bubble icon (matching the Ask AI sidebar icon). Both fields share one styling under the hood — gold focus ring in dark mode, neutral hairline when not focused — so future visual changes apply to both at once.
Beta 14 — 2026-05-03
Section titled “Beta 14 — 2026-05-03”Two Ask AI fixes from a focused audit of the retrieval and answer-rendering paths.
Question pill, progress, and streaming all visible on the very first question
Section titled “Question pill, progress, and streaming all visible on the very first question”In beta.12, sending the very first question into a fresh conversation produced a confusing sequence: the centre pane went blank for several seconds, then the answer arrived mostly built and finalised in place, and only afterwards did your question pill appear and the pane scroll up to it. The intended flow — your question pill, then a “thinking” progress card, then the answer streaming in token by token — never showed at all on that first question. Subsequent questions in the same conversation worked fine; only the very first one of any new chat misbehaved.
The fault was in how the conversation-switch handler interacted with the new-conversation creation path. Creating a fresh conversation transitions the active conversation ID from “none” to “new-id”, which fired the same state-clearing logic that the app uses when you manually click into a different existing conversation. So the question, progress, and streaming buffer all got wiped milliseconds after you pressed send, leaving the centre pane blank until the message finally persisted at completion.
The state-clearing logic now only fires for genuine “user switched between two existing conversations” events, not the “fresh conversation just created from this very question” case. First-question UX now matches subsequent-question UX end to end.
Same recipe no longer cited multiple times
Section titled “Same recipe no longer cited multiple times”When Ask AI retrieved several pieces of the same recipe / chapter / topic — which happens whenever a long section was sliced into multiple chunks during ingestion and the query matched more than one of them — the LLM would cite them as if they were distinct sources. Answers like “Italian Meatballs (Polpette) [2] [7]” with both refs pointing at the same recipe weren’t unusual; clicking each citation showed near-identical content from the same section. Crowded the source pane and made comparing citations harder than it should be.
The retrieval layer now collapses multiple hits from the same section into a single best-scoring representative, the same way it already did for video / audio scenes. The LLM sees one source per section, the citations stop duplicating, and the source pane reflects what’s actually distinct in the answer.
This doesn’t fix the related problem where adjacent sections’ headings can leak into each other’s chunk boundaries — that’s a chunker-side issue tracked for v0.9.5 in #218 — but it cuts the most visible Ask AI symptom in the meantime.
Beta 12 — 2026-05-01
Section titled “Beta 12 — 2026-05-01”A focused polish pass on Ask AI: the three-pane layout finally behaves like a proper macOS three-pane window, conversations are renameable from two places with native click-outside-to-commit behaviour, the streaming experience holds together visually from the moment you press send, and the filter chips no longer mangle their own labels.
Resizable, persistent side panes
Section titled “Resizable, persistent side panes”The conversation history (left) and source preview (right) panes can now be dragged to any width that suits your monitor. The middle Q&A pane absorbs the difference. Your divider positions persist across app launches and across navigation — switching to Library or Settings and coming back to Ask AI keeps the layout exactly as you left it.
The Search section has its own independent layout, so dragging Search’s panes wide no longer bleeds into Ask AI on first show after switching between them.
Citation clicks no longer crash the app
Section titled “Citation clicks no longer crash the app”Clicking a citation [N] in an answer with multiple historic messages — especially when the citation pointed to a PDF or audio/video source — could trigger an NSGenericException and abort the app on first click. That’s fixed; clicks land cleanly across PDF, audio, video, and image sources in both light and dark mode.
Conversation rename in two places, with proper defocus-commits behaviour
Section titled “Conversation rename in two places, with proper defocus-commits behaviour”In the left column: Right-click any conversation → Rename. The text field is now auto-focused so you can start typing immediately. Click outside the field to commit your edit (the way Finder renames work). Pressing Enter still commits. Empty title cancels.
In the centre column: Hover the conversation title above the Q&A pane and a small pencil glyph appears next to it — click to rename in place. Same defocus / Enter / empty-cancel behaviour as the left column. The “Ask your knowledge base” placeholder (when no conversation is selected) stays static.
Streaming UX holds together end-to-end
Section titled “Streaming UX holds together end-to-end”- Your question pill stays visible while the AI is thinking. Previously, after pressing send, the pill briefly disappeared until the answer started streaming — now it persists from the moment you press send through to when the streamed answer arrives.
- “You · just now” label above your question pill makes who-said-what unambiguous in long conversations.
✨ ASSISTANT · {provider} · {duration}attribution row above each answer card tells you which provider answered and how long it took. Visible during streaming too.- The input dock is more obvious in both light and dark modes — larger font, lighter standard surface, gold send button.
Filter chips fit their labels again
Section titled “Filter chips fit their labels again”Inside the Ask AI advanced options popover, the file-type chips (“Video”, “Audio”, “Documents”, …) used to wrap mid-word in some windows (“Vide / o”, “Docu / ment / s”). The popover now sizes to fit the chips at their natural width and the chips themselves never wrap, so labels render cleanly.
Beta 11 — 2026-04-29
Section titled “Beta 11 — 2026-04-29”A focused fix-up: the Upgrade flow on beta.10 couldn’t reach the entitlement server for some users. Beta.11 fixes that and hardens the billing-backend URL so it can no longer be silently overwritten at runtime.
- “Couldn’t start checkout. Could not reach the entitlement server.” Beta.10’s billing backend URL was a runtime-mutable variable that an older remote-config code path could silently overwrite, leaving Release builds reaching for the wrong host. The URL is now a true compile-time constant in Release — there is no code path that can change it after launch. Clicking Upgrade on the Billing tab now reliably opens Stripe Checkout.
Under the hood
Section titled “Under the hood”- Every request to the billing backend now carries an
X-KE-Buildheader reporting whether the requesting client is a Debug or Release build. Invisible in this beta; sets up a server-side check that will eventually block Debug builds from reaching live billing endpoints.
Beta 10 — 2026-04-28
Section titled “Beta 10 — 2026-04-28”A new Chunking Method pill on every file tells you exactly how each one was indexed — Semantic, Standard, or Retrying. Files whose semantic indexing hits a transient problem (network blip, Gemini rate-limit, parse glitch) are now picked up automatically by a background retry queue instead of silently falling back to fixed-size chunks.
Chunking method pill on every file
Section titled “Chunking method pill on every file”Open any file in the Library and the detail panel now shows a coloured pill in its metadata grid:
- Semantic (green) — the file was broken into meaningful units. For PDFs, Word, and text files, that’s chapter / recipe / topic sections; for video and audio, that’s content-aware scenes. Search and Ask AI can pinpoint the exact unit that answered your question.
- Standard (grey) — the file was broken into evenly-sized pieces (one page per chunk for PDFs; ~75-second segments for media). Search still works, just less precisely about where in the file the answer came from. This happens when a file has no clear logical structure or hits a permanent problem during semantic indexing.
- Retrying 1/3, 2/3, 3/3 (orange) — semantic indexing hit a temporary problem and the file is queued to be re-indexed automatically. Files in this state are still searchable using basic chunks in the meantime.
The (?) next to the pill opens a plain-language explainer modal.
Automatic retry queue for transient indexing failures
Section titled “Automatic retry queue for transient indexing failures”When a Gemini Flash call fails because of a network blip, a 429 rate-limit, or a flaky 5xx response, the file no longer drops to Standard chunks and stay there. It’s instead enqueued for automatic retry on a 5 s / 30 s / 5 min schedule (three attempts), surviving across app restarts.
While files are queued, a flush orange banner runs across the top of the Library with a “Show files” button that filters the list to just the queued files — so you can see at a glance what’s affected and click into specific files. Once retries succeed, the banner self-dismisses.
If all three attempts fail, the file falls back to Standard chunks gracefully — search and Ask AI keep working, you just lose the per-section precision until you click Reprocess later.
The categorisation is bucket-based: only genuinely transient errors retry. Permanent problems (parse failures, malformed responses, hitting the input character cap) accept the Standard fallback immediately and stop, so the queue doesn’t loop forever on a file that will never succeed.
Standard PDF fallback now produces one chunk per page
Section titled “Standard PDF fallback now produces one chunk per page”When a PDF couldn’t be indexed semantically, the Standard fallback used to emit one chunk per six pages — handy for embedding economy, less helpful when you actually want to find the right page. Standard PDFs now produce one chunk per page so search and citations land where you’d expect.
Already-indexed PDFs keep their existing chunks; click Reprocess on any file to pick up the new layout.
- Chunking pill always showed “Standard.” A column-name typo in the pill’s database query (
chunker_versionvschunkerVersion) made the lookup silently fail, so every file rendered as Standard regardless of what actually produced its chunks. Files now read Semantic where they should. - Retry banner sat as a wide floating card with too much chrome around it, out of step with the pipeline timeline banner above it. Now flush full-width like the pipeline banner — flat tinted background, 1px bottom divider, no stranded margins.
- Retry-queue filter list gained a Back button. Drilling into “Show files” used to leave only a “Clear filter” button on the trailing edge; now there’s a
← Backaffordance on the leading edge, matching how macOS users expect drill-in views to navigate. - Cost estimator modal had no close button. The Open Cost Calculator sheet only listened to the system Esc key, which doesn’t always fire on macOS sheets — leaving users stuck. Added the same
✕close affordance the chunking-method explainer uses. - Billing tab “Files used” count was a stale counter that monotonically incremented on every ingest but never decremented when files or folders were removed, so removing a folder left the hero stat reading e.g. 246 / 100 while the live library held only 35 files. The hero now reads the live
ke_filescount, agreeing with the sidebar and Library Overview. The cached counter remains in the database for now but is no longer surfaced anywhere.
Beta 9 — 2026-04-28
Section titled “Beta 9 — 2026-04-28”A small follow-up to beta.8: the Billing tab now always shows the plan options, matching what the upgrade modal already showed.
Plan tiers visible on the Billing tab
Section titled “Plan tiers visible on the Billing tab”When you crossed a file or folder cap, the Library Exceeds New Plan modal correctly showed Free / Pro / Business / Enterprise cards with upgrade buttons. But if you dismissed it and went to the Billing tab to upgrade later, the same plan grid was hidden — so it looked like the upgrade options had been removed. They hadn’t; they were gated behind a feature flag left over from the closed-beta period when subscriptions weren’t yet live.
Beta.9 removes the gate. The Billing tab now shows the same four-tier comparison grid (with Monthly / Annual toggle) as the modal, in every build.
Beta 8 — 2026-04-28
Section titled “Beta 8 — 2026-04-28”A focused fix-up release: the file-count and folder-count tier limits are now visible and actionable instead of failing silently, the Stripe portal cancel/downgrade flow actually round-trips back into the app, and the sidebar order matches how people actually use the app.
File and folder limits no longer silently drop content
Section titled “File and folder limits no longer silently drop content”When a Free user added a folder containing more than 100 files, the first 100 used to get indexed and the rest were silently dropped — only a one-line string buried in the ingest banner mentioned it. After the banner closed, the overflow was invisible. Same with the folder cap: clicking Add Folder while at 3 folders showed a vanilla “Folder Limit Reached” alert with just an OK button, no upgrade path.
This release replaces both with a single Limit Reached modal that always offers an upgrade. It covers five scenarios:
- File-count cap during a user-initiated add — shows how many were dropped, recommends the smallest tier that clears the overflow.
- Folder cap when clicking Add Folder while at the cap.
- Multi-folder partial accept when you pick or drag-drop several folders and only some fit — surfaces “X added, Y rejected” once at the end instead of N alerts.
- Ambient watcher overflow — when your folder watcher discovers new files past the cap without you actively doing anything, the modal shows up the next time you focus the app.
- Downgrade overage — see below.
Already-indexed files always stay; only overflow is held back. Dismiss the modal and the indexed-up-to-the-cap state is preserved.
7-day grace after a downgrade
Section titled “7-day grace after a downgrade”Cancel your subscription or downgrade in the Stripe portal and your library exceeds the new tier’s cap, you now have a 7-day grace period to either upgrade back, manually delete the overage, or do nothing and let the app evict the oldest files automatically when grace expires. A persistent banner in the Library shows the day countdown; clicking it re-opens the modal with Undo — back to {previous tier} and Remove oldest files actions. No files are deleted until grace ends or you explicitly choose Remove.
The eviction at grace expiry is oldest-first by ingest date — so your most recent ingests, which are statistically the most relevant, are preserved.
Policy is documented in docs/superpowers/specs/2026-04-28-tier-limits-policy.md.
Stripe portal cancel/downgrade now reflects in the app
Section titled “Stripe portal cancel/downgrade now reflects in the app”Two real bugs in the customer-portal flow:
- Sign-in to manage billing. If your auth token was lost (keychain reset, manual sign-out, app reinstall while the DB tier persists — and always in dev) clicking “Manage in portal” or selecting a lower tier silently failed with “Sign in first”. Now the same magic-link sign-in flow used for upgrades collects an email and opens the portal once you click the link.
- Return from the portal. Stripe’s portal was using a return URL that the app couldn’t route, so clicking Done left you stranded on Stripe and the app stayed at the old tier. Fixed on the backend (matching
goldenretriever://billing/portal-returnroute on the app side); a 5-minute polling fallback also catches changes for users who close the tab without clicking Done.
Sidebar order
Section titled “Sidebar order”Library, Ask AI, Search, then admin sections — matching the actual frequency of use rather than the historical order. Settings stays at the bottom.
Pill components no longer wrap
Section titled “Pill components no longer wrap”KEStatusPill, KEFileTypeBadge, and KeyboardShortcutBadge all enforce .fixedSize() and .lineLimit(1) at the component level, so tier cards in narrow grids (and any future host) can’t squeeze them into “Cur-rent” or “Recom-mended” two-liners. Tier-grid hosts also need a ≥ 1000 pt width to fit the longest tier-name + pill pairing in one row.
Beta 7 — 2026-04-27
Section titled “Beta 7 — 2026-04-27”The processing pipeline now runs through a single serial queue. Every entry point — Reprocess, Reprocess all selected, Retry Failed, Rescan All, Process all unprocessed, Run Pipeline — enqueues jobs into the same queue and the timeline banner subscribes to one event stream. No more flashing between idle states between jobs, no more waiting for a “Reprocess” click to do something visible.
Reprocess now means reprocess
Section titled “Reprocess now means reprocess”Clicking Reprocess on a single file used to re-run the pipeline scoped to that file but leave the existing chunks in place. So if you’d just changed the chunker strategy, Reprocess wouldn’t pick it up — you’d need the hidden “Reprocess from scratch” path under the file’s ··· menu. Felt broken.
This release renames “Reprocess from scratch” semantics into the primary Reprocess button: clicking Reprocess clears the file’s scenes, sections, and chunks first, then re-runs the chunker and pipeline. The redundant “Reprocess from scratch” menu item is gone.
Multi-file reprocess no longer flashes
Section titled “Multi-file reprocess no longer flashes”When you’d multi-select 5 files and click “Reprocess all selected”, the timeline banner used to flash between completed and running states between each file’s pipeline run, with a nest of “wait for orchestrator” sleep loops in the view. Now the queue handles all of it: the banner stays mounted across the whole batch, shows the current file’s stage, and appends ”+ N queued” so you know how much is still ahead.
Cancel and Cancel All
Section titled “Cancel and Cancel All”The timeline banner gains two buttons whenever the queue is driving a run:
- Cancel — stops the in-flight job mid-stage (within ~5 s — Gemini API calls honour cancellation cooperatively). The queue moves on to the next pending job.
- Cancel all — drops everything: cancels the in-flight job and clears the pending list.
Process all unprocessed
Section titled “Process all unprocessed”The “Process N file(s)” button on the file list and the “Process all unprocessed” path through Run Pipeline both now route through the queue, with the same per-file event stream as Reprocess. Same banner, same Cancel buttons.
Add Folder / Add File
Section titled “Add Folder / Add File”Scanning a freshly-added folder still surfaces as a single “Ingesting…” event for visual continuity (one event, not N). The heavier post-scan stages — text extract, scene description, embedding — split into individual queue jobs once the scan completes, so a long-running embed for one file doesn’t block you from cancelling and seeing what’s next.
Beta 6 — 2026-04-26
Section titled “Beta 6 — 2026-04-26”A milestone-defining release. The chunker, the retrieval pipeline, and the entire light-mode color system are all rewritten.
Search and Ask AI now find what’s in your documents
Section titled “Search and Ask AI now find what’s in your documents”The original bug: a recipe heading lived 700 characters into a chunk’s body, and the chunk’s preview was the first 200 characters of a different recipe. Search for “meatball” returned zero results because the BM25 keyword index only ever saw the first 200 characters of any chunk. Ask AI returned “no information” for the same reason — it never saw the recipe text in its prompt context.
Three independent fixes, all shipped together:
- The keyword index now contains the full body of each chunk, not a 200-character preview. A migration runs automatically on first launch; you don’t need to re-ingest.
- The semantic search index (Qdrant) gets the full body too. A second backfill runs on first launch and re-indexes existing chunks against the local Qdrant instance — no calls to Gemini, so no cost.
- Both the keyword and semantic indexes also include the section’s title, summary, and topic keywords that Flash already wrote during ingestion. So even if a chunk’s body slice is slightly off (which can still happen when Flash returns weird offsets — see “deterministic alignment” below), the section’s own metadata anchors the search match. Diversifying the signal so retrieval doesn’t depend on any single field being perfect.
If you’d been wondering why search worked for “cheese” but not “meatball” in the same cookbook — that’s why. Both terms now work.
Deterministic section alignment
Section titled “Deterministic section alignment”The architectural rewrite. New documents ingested in this release work like this:
- Flash reads the document and returns each section’s title, summary, topic keywords, and a verbatim 50-100 character quote from inside the section’s body (a
bodyAnchor). - Pure Swift code searches for that quote in the document’s actual text and computes the section’s character range from where the quote was found.
- No LLM-supplied position numbers ever enter the pipeline.
Flash is good at quoting text it just read. It’s bad at counting where that text was. We now use it for what it’s good at and let code do the counting.
For libraries indexed before this release, the existing offsets are kept (they still mostly work). To get the new alignment, click Reprocess on a file in the Library — that re-runs the new chunker. Future betas may add a one-shot “realign all” if it turns out to matter; it didn’t in our testing.
Light mode is now usable
Section titled “Light mode is now usable”The previous light theme was a cool-gray Apple-default palette that fought the gold brand color. This release replaces it with a warm-neutral system designed around the brand:
- Surfaces are warm off-white with restrained tonal hierarchy.
- Cards lift via white-on-warm-bg plus a 1px border (the previous theme tried to lift them via tonal contrast alone, which doesn’t read on macOS at standard window sizes).
- All status colors (success / warning / error / info) and file-type chip colors got new light-mode hex values that hold weight on the warm bg without screaming.
- Shadows are now warm-tinted brown-black on light, pure black on dark — purer-black hairlines and shadows on warm surfaces read cold.
- Seven new color tokens (
borderDefault,borderStrong,brandTintBg,successTintBg,warningTintBg,textSecondary,textTertiary) let future UI work use the system consistently instead of hardcoding.
Dark mode is unchanged.
Search result cards show section title and summary
Section titled “Search result cards show section title and summary”Search hits now lead with the section’s actual title (e.g. “Italian Meatballs (Polpette)”) and a one-sentence summary above the body snippet. Previously the cards showed a truncated raw body slice — useful when the slice was correct, misleading when it wasn’t. The same change applies to the Ask AI citation popover: each cited source now shows its section title and summary at the top, with the body text below.
Multi-citation support in Ask AI answers
Section titled “Multi-citation support in Ask AI answers”When Gemini returns an answer like “the cookbook lists multiple desserts [3, 5, 8]” with three citations in one bracket, the Ask AI renderer now splits that into three separate clickable links. Previously the comma-separated form rendered as plain unclickable text. Belt-and-braces: the system prompt also instructs Gemini to prefer one ref per bracket.
Ingestion timeline shows scene-analysis progress
Section titled “Ingestion timeline shows scene-analysis progress”Long video / audio files send their content to Gemini for scene analysis, which can take 30+ seconds for a long video. The ingestion timeline banner now says “Analysing video.mp4 with Gemini Flash (video scenes)…” while it’s running and “video.mp4: 7 scenes detected.” when it finishes. Previously it sat on the prior stage’s text the whole time.
Beta 5 and earlier
Section titled “Beta 5 and earlier”Previous betas in this milestone shipped without dedicated release notes; their changes are summarised here for completeness.
- Beta 5 — Library manual-processing UX, wizard header, pending-not-failed status differentiation.
- Beta 4 — Hide Team features on Business plan; restrict to Enterprise.
- Beta 3 — Sidebar background fix (column-width fill), Ask AI three-pane shell expensive treatment, billing portal downgrade flow, Settings tab redesign with grouped layout, Stripe customer portal integration.
- Beta 2 — Initial billing flow polish, pricing math finalisation.
- Beta 1 — Started the milestone with the ingestion + Ask AI three-pane shell groundwork.
Detail-level commits are visible in git log v0.9.3..v0.9.4-beta.6 in the repository.
Under the hood
Section titled “Under the hood”DocumentSemanticAnalyzerrewritten: prompt schema dropscharStart/charEnd, addsbodyAnchor. NewalignSections(_:documentText:)helper does deterministic in-order alignment against the document’s joined-pages text with whitespace-and-case normalisation for PDF spacing weirdness (“F O R H E A L T H” matches “forhealth”). 8 unit tests cover the algorithm.KEChunk.enrichedCorpus(section:body:)is the single helper that builds thetitle + summary + topics + bodytext consumed by both the dense embedding input and the BM25 corpus. Replaces N hardcoded inline concatenations across the embedding service.- New launch-time
PipelineOrchestrator.reindexFullTextChunksandreindexQdrantBM25routines run once per machine viaUserDefaultsflags. Both heal existing libraries to the new contract; neither calls Gemini. - New
QdrantClient.updateSparseBM25(points:)wraps Qdrant’supdate_vectorsendpoint so we can refresh the BM25 sparse vector without touching the dense vector. TextExtractionService.extractTextfor PDFs no longer requireschunk.sectionIDto take the char-slice path — char range alone is sufficient.RAGService.chunkForRAGnow passes throughhit.charStart/hit.charEndfor non-text/docx file types, fixing the “every PDF chunk extracts page 1” bug that was producing the cover-page hallucination in Ask AI.SearchResultmodel gainedsectionTitle,sectionSummaryEnglish,sectionTopicsEnglishfields populated from the FTS5 backend, the SQLite LIKE fallback, and the Qdrant payload uniformly.KEColorslight-mode palette replaced with warm-neutral hex values; seven new adaptive tokens added;keShadowmade appearance-aware.- 28 atomic commits in the v0.9.4-beta.5..v0.9.4-beta.6 range.
git log v0.9.4-beta.5..v0.9.4-beta.6 --onelinefor the full list.
- “Italian Meatballs” recipe was invisible to search and Ask AI on the bundled Italian cookbook PDF. Same issue would have affected any text content positioned more than ~200 characters into a section.
- Search detail “Text Content” panel for section-tier PDF chunks rendered the first page of the source PDF instead of the chunk’s actual body. The right pane and the Ask AI source popover now extract the same body text the LLM saw, with the section’s title and summary at the top.
- Ask AI’s three-pane shell had a hardcoded near-black gradient backdrop that stayed dark in light mode while the rest of the app went light.
- Multi-citation answers like
[3, 5, 8]now render as three clickable refs instead of plain text. - Magic-link email logo failed to render in Outlook for Mac (image format compatibility — fixed in earlier semantic-chunking work that ships in this release).
- Ask AI sources pane briefly showed truncated, unreadable chunk previews on the right pane (fixed in earlier semantic-chunking work).