fix(filters): builder UX round one — incomplete rules, group removal, field menu (user report)

- Root bug: incomplete rules (the ready rule, every new group's first
  rule) were sent to the server — the live-count fetch toasted a 400
  and every suggestion fetch failed silently (why author typing showed
  no dropdown). Fix: completedTree() prunes incomplete rules and empty
  groups; the count fetch skips when nothing is complete, suggestion
  fetches send only the other COMPLETED rules, and serialization wires
  only completed rules (an unfinished rule was never a filter).
- Group remove button was x-show'd away on depth-0 groups — exactly
  where user groups live. Always visible now.
- Field menu: filter input at the top (Enter prevented), no-match
  state; group headers restyled as flush-left tinted sticky bands with
  options tabbed in beneath (user's layout pick).
- toast.ts: filter-count fetches join the suggestion-fetch exemption
  (a failed background hint never interrupts).
This commit is contained in:
John O'Keefe committed 2026-10-20 15:04:19 -04:00
1 parent 4ee24e7152
commit 04b36f0563
6 files changed
+134 -23

No files matched your search

+6 -1
View File
@@ -186,7 +186,12 @@ 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;
// The builder's live-count endpoint is a background hint too — a
// failed count must never interrupt with a toast.
if (!url.includes("/api/media-items/search?") && !url.includes("/api/media-items/filter-count?")) {
return false;
}
if (url.includes("/api/media-items/filter-count?")) return true;
return suggestionFieldRe.test(url) && !/[?&](q|sort)=/.test(url);
}