Skip to content

fix(brain): give search an index to search - #196

Merged
ABB65 merged 1 commit into
mainfrom
fix/brain-search-index
Aug 14, 2026
Merged

fix(brain): give search an index to search#196
ABB65 merged 1 commit into
mainfrom
fix/brain-search-index

Conversation

@ABB65

@ABB65 ABB65 commented Aug 14, 2026

Copy link
Copy Markdown
Member

Search always said "no results", with no error — the worker received the message and answered with an empty array.

Two causes. Both had to be fixed; either one alone still leaves search empty.

1. The content store was never created

idb-keyval's createStore opens the database without a version and creates only its own object store in the upgrade. Called twice for one database — brain-meta and brain-content — only the first store ever exists: by the time the second opens, the database is already at version 1, no upgrade fires, and every transaction against it throws NotFoundError.

brain-meta is touched first, on init. So brain-content was never created, and the IndexedDB content cache has never worked.

The app did not visibly suffer because the sync response is also kept in an in-memory map, which is what queryContent reads. The search index is the one consumer that reads from IndexedDB — so it was built over zero keys, every time.

Both stores are created in one upgrade now, at an explicit version. Version 2, not 1: a browser that has already visited Studio holds cr-brain at version 1 with only brain-meta, and opening at 1 would find it current and fire no upgrade at all.

An explicit version brings upgrade blocking, so that is handled too:

  • onversionchange closes the connection rather than blocking another tab's upgrade
  • onblocked rejects rather than leaving every read pending forever — an error is visible and recoverable, a promise that never settles is neither
  • a failed open is not cached, so one blocked upgrade cannot poison the worker for its lifetime

2. The index was built in exactly one place

rebuildSearchIndex ran only after the "nothing changed" early return in sync. A worker is created fresh on every page load and its FlexSearch index lives only in memory, while the content it is built from lives in IndexedDB and survives.

So on the common path — reload a project nobody has edited — the delta sync returns nothing, the early return fires, and the index is never built.

Ensured now in the delta branch, and again before any search. Every path that can leave a worker without an index reaches one of the two.

Neither was introduced by #193

Nothing in app/ called searchContent before it. The store bug predates the search feature entirely; #193 is just the first thing that read from IndexedDB.

Verified

  • The test drives the worker's own message handler over fake-indexeddb, loading it twice: once to populate the database as a real session would, once as the reloaded worker that has never synced. It reproduces the report exactly and fails against either bug alone — I checked both.
  • It also covers model scoping (another model's hits must not eat the caller's slots) and locale scoping.
  • pnpm lint 0 errors · pnpm typecheck clean · pnpm test 148 files / 1282 tests

Worth knowing

The IndexedDB cache starts working now, for the first time. That is the intent — but it means the offline/instant-load path is genuinely exercised in a way it never has been, so it deserves a look in a browser rather than a shrug.

Not verified — needs a browser

  • search returning results on a real project, which is the report
  • the version-2 upgrade against a browser that already holds the version-1 database (the test starts from an empty one)
  • two tabs open across the upgrade

Reported: the content search always says no results. No error in the
console — the worker receives the message and answers, with an empty
array.

Two causes, both of which had to be fixed.

## The content store was never created

`idb-keyval`'s `createStore` opens the database **without a version** and
creates only its own object store in the upgrade. Called twice for one
database — `brain-meta` and `brain-content` — only the first store ever
exists: by the time the second opens, the database is already at version
1, no upgrade fires, and every transaction against it throws
`NotFoundError`.

`brain-meta` is touched first, on `init`. So `brain-content` was never
created, and the IndexedDB content cache has never worked. The app did
not visibly suffer because the sync response is also kept in an
in-memory map, which is what `queryContent` reads. The search index is
the one consumer that reads from IndexedDB — so it was built over zero
keys, every time.

Both stores are now created in one upgrade, at an explicit version.
Version 2 rather than 1 on purpose: a browser that has already visited
Studio holds `cr-brain` at version 1 with only `brain-meta`, and opening
at 1 would find it current and fire no upgrade at all.

With a version comes upgrade blocking, so: `onversionchange` closes the
connection instead of blocking another tab's upgrade, `onblocked`
rejects instead of leaving reads pending forever, and a failed open is
not cached.

## The index was built in exactly one place

`rebuildSearchIndex` ran only after the "nothing changed" early return in
`sync`. A worker is created fresh on every page load and its FlexSearch
index lives only in memory, while the content it is built from lives in
IndexedDB and survives. So on the common path — reload a project nobody
has edited — the index was never built.

It is now ensured in the delta branch and, belt and braces, before any
search: every path that can leave a worker without an index ends up at
one of the two rather than silently answering nothing.

Neither of these was introduced by #193. Nothing called `searchContent`
before it, so nothing noticed.

The test drives the worker's own message handler over fake-indexeddb,
loading it twice — once to populate the database as a real session
would, once as the reloaded worker that has never synced. It fails
against either bug alone.
@ABB65
ABB65 merged commit 4554bfa into main Aug 14, 2026
2 checks passed
@ABB65
ABB65 deleted the fix/brain-search-index branch August 14, 2026 10:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant