feat(filters): the rule builder — composed engines over the field registry (faceted plan Phase 9)
- Field registry (services/filter_registry.go): ~55 typed fields — every book column, file/lifecycle facts, the three HOLDING STRUCTURES (library, collections with excluded-aware membership, series with FULL primary+alternate_info membership), and the whole user-relative class (status, progress, last read, my rating, review, notes/highlights presence, note/highlight text, time spent, pages read, collections, added by) reading the VIEWER's rows. Operators are typed constants; negation is an operator. - Composed engines (services/filter_query.go): one Go clause builder over raw pgx feeds the search (identical SearchMediaItemsUnifiedRow shape), the field-aware suggestions, and a true-COUNT live count. Mandatory WHERE head everywhere (family lens + integrity facts). Values are always bound parameters — no user string reaches a field/operator position, ever. - Wire: the qf JSON param (validated strictly — unknown fields/operators/malformed values are 400); qf wins over flat params on hand-crafted URLs; flat deep links keep working unchanged. - Endpoints: GET /api/media-items/filter-count (live count); field-values autocomplete upgraded in place — any registry field key, qf binds the OTHER rules (symmetric under-filter suggestions, zero-match values never offered); classic six without a tree keep their static pinned contract. - UI: the drawer becomes the rule builder — the ONLY filter UI (templ markup, Alpine tree state, sanctioned island; grouped field menus, operator menus with red negations, field-aware value menus, live count, chips per rule with red negations, URL rehydration, smart serialization flat-when-simple else qf). - Tests: composed semantics (AND/OR/negation/nesting/enums/bools/ numbers/dates/empty), every user-relative field class with the my-data weld (two readers, different worlds), note-text substring pin (catharsis ≠ Carthage), family lens (hidden libraries nowhere, 403s), suggestion-under-rules exactness, qf validation + precedence, flat-vs-tree equivalence; registry completeness unit test.
This commit is contained in:
1 parent
ebb7f4a5b0
commit
4ee24e7152
18 files changed
+3788
-376
No files matched your search
+8
-6
@@ -176,12 +176,14 @@ const setupHTMXListeners = (): void => {
|
||||
});
|
||||
};
|
||||
|
||||
// Suggestion fetches — field-value autocomplete (author=, genre=, …)
|
||||
// on the search endpoint — are background hints for typed inputs. A
|
||||
// failed hint must never interrupt the user with a toast: it logs to
|
||||
// the console only. The grid's real search (q=/sort= params) is NOT a
|
||||
// suggestion fetch and keeps its toasts.
|
||||
const suggestionFieldRe = /[?&](author|genre|tags|narrator|series|language)=/;
|
||||
// Suggestion fetches — field-value autocomplete on the search endpoint
|
||||
// (author=, genre=, note_text=, collection=, … any filter-registry
|
||||
// field key) — are background hints for typed inputs. A failed hint
|
||||
// must never interrupt the user with a toast: it logs to the console
|
||||
// only. The grid's real search (q=/sort= params) is NOT a suggestion
|
||||
// fetch and keeps its toasts.
|
||||
const suggestionFieldRe =
|
||||
/[?&](author|narrator|contributor|publisher|imprint|edition|genre|tags|story_arc|age_rating|title|subtitle|language|format_group|mime_type|manga_type|reading_direction|series|note_text|highlight_text|collection|library|added_by)=/;
|
||||
function isSuggestionFetch(input: Parameters<typeof fetch>[0]): boolean {
|
||||
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
||||
if (!url.includes("/api/media-items/search?")) return false;
|
||||
|
||||
Reference in new issue
Block a user