Surface the search filters as controls, extract snippets in the engine, and fix the test suite on modern Node - #226
Open
franciscoxc wants to merge 5 commits into
Open
Conversation
… pinned toolchain Node 22+ defines its own global `localStorage` accessor that returns undefined unless `--localstorage-file` is passed. Vitest's populateGlobal skips copying a jsdom key when one already exists on globalThis unless it is in an internal allowlist, and `localStorage`/`sessionStorage` are not on it (`navigator` is). So jsdom's storage never reaches window, and every test touching it fails — 51 of them — while CI on Node 20 stays green. CI moves to Node 24 because Node 20 rejects the flag outright, and reached end of life in April 2026. AGENTS.md also named nightly-2025-05-09 while rust-toolchain.toml pins nightly-2025-12-11. Following the doc and forcing the old toolchain onto PATH builds fine from a warm target dir, then fails the moment a proc-macro has to be rebuilt: rustc 1.88 emits dylibs that recent macOS dyld rejects with "mis-aligned LINKEDIT string pool".
…ype:email The app was reimplementing the content scan to show a snippet: a second chunked reader with a naive substring search, next to `node_content_matches` which already does it with a Rabin-Karp finder. `content_snippet` now lives beside that matcher and shares its chunking, so a snippet is found wherever the filter found a match. Case folding goes to a scratch buffer rather than in place, because the snippet is cut from those same bytes. The highlighted terms come from `content_terms_of_query`, which walks the parsed query rather than re-parsing the text in the UI. Two behaviours fall out of using the real parser: a negated `!content:` no longer highlights anything, and `content:"Bearer "` keeps its trailing space. `type:email` covers .eml, .emlx, .emlxpart, .msg and .mbox, with mail and message as synonyms. Apple Mail's formats are the ones nobody finds by name.
Cardinal's syntax is powerful and undiscoverable: nothing in the window suggests that `type:image` or `content:"…"` exist. These controls do not add a second filtering path — each one edits the query text, so the search bar stays the single source of truth and the syntax it writes stays visible and learnable. - A file-type dropdown: Image, Video, Audio, Document, Email, Archive, Code, App, Folder. "Document" writes an OR of doc/pdf/presentation/spreadsheet rather than widening `type:doc`, which would change that filter's meaning. - A "Contains" field writing `content:"…"`. - A context column with the matching text from inside each file, so a content search shows why every result is there. - Columns reorderable by dragging their titles, snippet next to the name. - The folder icon in the scope field opens a folder picker; folding moved to a chevron beside it. The icon looked like "choose a folder" and folded the field instead. - The scope placeholder names its default: "Search in… (whole disk)". Each control reads back only the token it authored; a query it cannot represent faithfully (negated, duplicated, or inside a boolean group) reads as "custom" and is left alone rather than misreported. The wording change from "folder scope" to "search in" is opinionated and easy to drop if you would rather keep the original term.
…lves Adding tauri-plugin-dialog pulled @tauri-apps/api up to 2.11.1, while the crate stayed on 2.10.3. `tauri build` refuses that combination outright: "Found version mismatched Tauri packages". `cargo check` does not, so the mismatch only shows up when you package.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Author
|
CI was red on two checks. One was mine, one was not. Tauri fmt — my Rust test — Running the test binary 400 times on each tree:
Same rate within noise, and this PR touches no globstar code — its |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cardinal's engine is excellent and almost none of it is visible: nothing in the window suggests that
type:image,content:"…"orin:exist. This PR adds controls for the common filters, plus a fix for the test suite, which is currently red on any recent Node.I maintain a fork, but this branch carries none of it: no bundle identifier, signing config, icon, version bump, README or CHANGELOG change.
tauri.conf.jsonand every icon file are untouched. It is only the parts that seemed useful to Cardinal itself. Happy to split it into smaller PRs if that is easier to review.1. The test suite is red on Node 22+ (
7c8eedb)51 tests fail with
Cannot read properties of undefined (reading 'clear'). Node 22+ defines its own globallocalStorageaccessor that returnsundefinedunless--localstorage-fileis passed, and Vitest'spopulateGlobalskips copying a jsdom key when one already exists onglobalThisunless it is in an internal allowlist —navigatoris on it,localStorageandsessionStorageare not. So jsdom's storage never reacheswindow. CI stays green only because it pins Node 20, which has no such global.The test script now passes
--no-experimental-webstorage, and CI moves to Node 24 since Node 20 rejects that flag outright (and went EOL in April 2026).Same commit fixes
AGENTS.md, which namesnightly-2025-05-09whilerust-toolchain.tomlpinsnightly-2025-12-11. Following the doc and putting that older toolchain onPATHbuilds fine from a warmtarget/, then fails as soon as a proc-macro has to be rebuilt: rustc 1.88 emits dylibs that recent macOS dyld rejects withmis-aligned LINKEDIT string pool. That cost me an afternoon.2. Content snippets in the engine, and
type:email(30ee8b3)To show why a
content:search matched, the app needs the text around the match. Rather than a second scanner in the app,content_snippetsits next tonode_content_matchesinsearch-cacheand shares its chunked read and Rabin-Karp finder, so a snippet is found wherever the filter found a match. Case folding goes to a scratch buffer instead of in place, because the snippet is cut from those same bytes.The highlighted terms come from
content_terms_of_query, which walks the parsed query instead of re-parsing the text in the UI. Two behaviours fall out of using the real parser: a negated!content:no longer highlights anything, andcontent:"Bearer "keeps its trailing space.type:emailcovers.eml,.emlx,.emlxpart,.msgand.mbox, withmail/messageas synonyms. Apple Mail's own formats are the ones nobody finds by name.3. Filters as controls in the search bar (
f3f67b5)The rule these follow: a control edits the query text, it does not hold filter state of its own. So there is no second filtering path to keep in sync, the search bar stays the single source of truth, and the syntax stays visible — the user sees
type:imageappear and learns it.doc/pdf/presentation/spreadsheetrather than wideningtype:doc, which would silently change that filter for everyone.content:"…", beside the one for names.tauri-plugin-dialogwith onlydialog:allow-open.Each control reads back only the token it authored. A query it cannot represent faithfully — negated, duplicated, or inside a boolean group — reads as "custom" and is left untouched rather than misreported.
One thing to push back on: renaming "folder scope" to "search in" across the 15 locales is opinionated. It is confined to the i18n JSON files and trivial to drop if you would rather keep the original term.
Checks
npm test— 34 files, 292 tests, default pool and parallelismnpm run typecheck,npx prettier --check .cargo test -p search-cache— 880 + the integration suitescargo checkfor the app, withCargo.lockregenerated on the current versionNew tests cover the snippet scanner across a chunk boundary,
type:emailand its aliases, the query-token round trip for both controls, and the column-order storage.