Search
The header and full results page share server-side search. The URL carries committed filters and sorting; the browser holds only one page of results.
Request path
Header suggestions debounce 150 ms and request
/api/search?quick=true&limit=5. Enter opens full results. The Search page
normally uses quick text matching too, with a 30-row page and stacked filters. Initial
rows come from the server page; later URL/actor changes and the search-refresh event fetch
the API.
querySearchPage applies reviewScope, text matching, AND-ed
filters, sorting, pagination, and a count. Quick text uses index fields. Full scalar-text
search and nested field filters/sorts can join the current snapshot. Active extra columns
extract only requested nested values. Matching is not performed by downloading every
review into the browser.
Filter contract
search-fields.ts describes available field paths, types, choices, and display
values. search-filters.ts validates up to 100 filter rows and 10 sorts. Modes
include contains, starts, ends, exact, between, empty, and present; exclusion negates a
row. Between is inclusive. Field names and values are validated before SQL construction;
LIKE wildcards are escaped.
Admin mail attachment
An admin attaching mailed PDFs loads the selected current review at action time, uploads
the PDF, and sends a document command with that version. Search list rows do not carry the
complete editable review. Open links use reviewKey UUIDs.
Changing search
Keep index-only header requests small. Add fields through the registry and filter/display
handling, then check both initial server rows and API refresh. Query-level tests cover
scope and nested predicates; browser tests cover URL controls.
/api/search/fields exposes the registry without authentication; the app also
imports the registry directly.