Preferences search¶
Flutter uses its own available-control catalogue in
flutter/lib/ui/preferences_catalogue.dart and pure matching in
flutter/lib/model/preferences_search.dart. Add a catalogue entry and matching
ValueKey to each new Preferences row; actual-widget coverage rejects uncovered
rows, checkbox captions and missing targets. Connection/profile entries follow
the current capabilities and state. Search keeps the existing settings form
mounted, reveals controls inside its scroll view and opens the existing CalDAV
form for a field result without clearing entered credentials. Local saves and
Retry still belong to Workspace. The Flutter catalogue does not expose absent
desktop controls; those remain tracked in issue #41. Native Android search/reveal
and Theme controls have actual FFI/capture evidence; wider Android scenarios,
iOS execution and the separate browser client search remain open.
Native Flutter runs the shared mail-content::fuzzy::WordMatcher through the
existing native request boundary. Catalogue encoding, decoding and ranking run
off the UI thread without account, database or provider capacity. One native
search slot bounds matching independently; the view coalesces one running query
and its latest replacement, rejects stale catalogue/input replies and offers
Retry after a search failure. It preserves shared Latin accent folding,
Japanese/Hangul recomposition, Nucleo abbreviations, OSA typos and exact tokens
containing numbers. Exact labels rank above supporting text. Queries stop at 256
Unicode characters, fuzzy word comparisons at 64 characters and encoded
catalogues at 2 MiB; exact long words and prefixes remain searchable.
The isolated Flutter preview uses the Dart diacritic library with literal,
prefix and subsequence matching. Common accent, abbreviation and mixed-numeric
cases are tested there too. This preview fallback does not correct typos or
guarantee native Unicode recomposition/rank scores. It is not the installed
client matcher or the separate browser client. Actual native bridge/widget tests
exercise the shared matcher separately from preview pointer/keyboard evidence.
The remaining details describe the desktop implementation.
Preferences search uses a static catalogue of section titles, actual control captions, descriptions and common synonyms. Search includes controls in account, calendar, Google, profile and backup forms. It indexes no personal names, addresses, passwords, tokens or saved configuration values.
The common matcher normalises Latin accents, preserves other scripts and requires
every distinct query word. Exact section titles come first, followed by exact
control captions, caption prefixes and weighted word matches. Title words have
more weight than control captions, tab names, descriptions or synonyms. The
selected matcher uses Nucleo abbreviations with RapidFuzz OSA correction,
including adjacent transpositions. A query word spelled like a real catalogue
word, or a prefix of one, is not also treated as a typo of a different word, so
"shared profile" finds Profiles and sync rather than the local Profiles card
through "saved". Words with no catalogue spelling keep typo correction.
Numeric words match exactly. Stable title ordering resolves ties. matches_with
retains the pure word-scoring boundary. The word matcher does not bridge
abbreviations across separate caption words.
Static text is normalised once. Results are calculated when the input changes and reused during drawing and observations. The ignored ranking measurement checks complete warmed searches separately from native input and rendering; run it only after other builds stop.
Each result is one section, ranked as above. Within it, every control caption is scored with the same word matcher, counting only exact, prefix and abbreviation matches; a typo correction finds the section but never names a control. The caption matching the most query words wins, then the one whose words read as the query in order, then the lowest score, then the shorter caption. It is named when it matches at least half of the query words and more of them than the section title does, so "shared profile" names Check for shared profiles after Google sign-in and "new mail interval" names Check for new mail, while "backup", "profile workspace" and the synonym "retention" open their sections at the top.
Selecting a named result opens the section, then reveals the control. The reveal
is a widget operation over actual layout: it finds the caption's rendered text
inside the Preferences scroller (settings-scroll), scrolls only when the
caption is not fully visible and focuses a text field on the caption's row or
directly below it with no other caption between. Checkboxes, buttons and pick
lists are scrolled to but not focused. A caption that is not laid out after about
250 ms, such as Retry Google cleanup while Google is connected, leaves the
section open at its top. Newer navigation or a new query discards a pending
reveal. Selecting a result never changes a preference.
The revealed control gets a 2 px accent outline with a faint tint: a labelled
text field with its label, otherwise the caption's own button or row (the
innermost enclosing container that adds only padding) or the caption itself, as
for a checkbox inside a card. The outline is a second stack layer inside the
scroller, so it scrolls with the content, draws above it and never captures
input. It clears after 1.8 seconds, or on the next click, key press or wheel
scroll. The layer is always present, as an empty space when idle, so the
Preferences widget tree and its focus survive the outline appearing.
Coverage is enforced by tests. coverage.rs renders every Preferences tab in
empty, connected, enrolled, disconnected-Google and several backup states,
collects every caption the scroller lays out, and fails when one is not a
catalogue title or control, a whole-word phrase in a description, or a
documented data, count, binding or sample caption. The same test fails when a
catalogue control is never laid out, unless it is listed with the state that
shows it. Form fields in dialogs and pick-list options live in descriptions,
because they are searchable but cannot be revealed in place.
Tests also exhaustively destructure Preferences, compare its serialised keys
with search coverage and documented exclusions, and audit nested
network/configuration schemas. Excluded fields are layout/sidebar state, mailbox
sorting, per-message image decisions, provider receipts, credential identifiers,
legacy OAuth data and the old calendar interval. Password controls are indexed by
caption only.
Browser and Flutter clients have no Preferences search yet. Those parity gaps
remain open in TODO and docs/CLIENT_PARITY.md; the desktop catalogue does not
establish client parity.