feat(facets): all-facets panel with under-filter counts (faceted plan Phase 7)

The 9 Batch A facets could not be SET from the web UI, the facets
endpoint had no in-app consumer, and the registry's libraryTypes hints
rendered nowhere. This phase closes all three.

- ONE UNION-ALL sqlc query (FacetCountsUnderFilters) computes all 14
  listable facets in a single round trip: each arm omits its OWN
  facet's filter block and carries every other registered facet's
  block (copied mechanically from SearchMediaItemsUnified) — classic
  faceted semantics: with filters active each facet counts under the
  others; with none, blocks are neutral and the counts are the v1
  global-per-library ones. One query serves both cases. The 14
  per-column Batch C queries are deleted.
- Endpoint: no facet param → the new all-facets shape
  ({facets: {param: [{value, display?, count}]}}), filters bound via
  the shared ParseMediaSearchParams, library_id optional (the
  All-Libraries view, family lens still enforced); facet=X keeps the
  pinned single-facet shape (now routed through the same query with
  neutral filters).
- SSR partial GET /media-items/facet-panel: templ renders the drawer's
  value lists — registry order, Batch D library-type scoping
  (FacetsForLibraryType: manga_type on manga, reading_direction on
  manga+comics, narrator on audiobooks; All view = union), prettified
  genre labels + language display names, active values marked. Family
  lens: out-of-set explicit library_id is 403.
- Drawer wiring: the panel fetches on drawer open and after every
  apply with the CURRENT filter query string; a value click toggles
  that facet's comma-joined filter (hidden input where the drawer has
  no field — same as deep links) and re-applies the form; chips and
  URL stay in sync. Values are server-rendered (rendering doctrine);
  bookshelf.ts owns only the click/fetch orchestration.
- prettifyGenre moves to internal/utils as the canonical
  implementation (services' display enrichment shares it; templ wraps
  it; the TS port in bookshelf.ts is unchanged).
- Drift guard extended: FacetCountsUnderFiltersParams must carry every
  registry facet's param, the bools as pgtype.Bool.

Tests: under-filter exactness (own-block omission, OR-within,
AND-across), family lens (hidden values absent; 403 on both shapes),
all-shape + pinned-shape coexistence, partial rendering with Batch D
scoping + active marking + 403. Full container suite green except the
documented pre-existing TestAdminCreateUser red.
This commit is contained in:
John O'Keefe committed 2026-10-20 08:24:36 -04:00
1 parent 6152137156
commit f52bb6f073
18 files changed
+3001 -911

No files matched your search

+23
View File
@@ -257,3 +257,26 @@ cover_image_path, has_conflict, format_group, badge`.
from the live server's scans started reaching the socket first.
Keepalive is the standard protocol-level ping the hub already sends.
Never-built behavior is not a pinnable contract.
## 2026-10-20 — Scanner Phase 7: all-facets shape + SSR facet panel
- `GET /api/media-items/facets` gains a SECOND shape: with NO `facet`
param it answers `{"facets": {<param>: [{"value","display?","count"},
…]}}` — every listable facet in one response, counts computed under
the ACTIVE FILTERS (the same query params the search endpoint takes;
each facet is counted under the others — its own filter does not
restrict its own list). `library_id` is optional on this shape:
absent = the All-Libraries view. Family-lens unchanged: an explicit
out-of-set `library_id` is 403 on both shapes.
- The PINNED single-facet shape (`?facet=X&library_id=…` →
`{facet, values[]}`) is byte-identical in semantics; its
implementation now routes through the same UNION-ALL query with
neutral filters (the 14 per-column SQL queries were deleted — no
wire change).
- NEW SSR partial: `GET /media-items/facet-panel` (session-cookie
auth, bookshelf-protected group) renders the drawer's value lists —
templ-rendered, registry order, library-type-scoped sections, active
values marked. An out-of-set explicit `library_id` is 403 HTML.
- The 14 per-column facet SQL queries are replaced by one
`FacetCountsUnderFilters` UNION-ALL statement; the registry drift
guard now reflects over both generated param structs.