fix(filter-builder): id labels resolve at render — refresh keeps names
Refresh showed raw uuids in id-kind value cells (feed/library/ collection/added_by). Two defects, one story: 1. Only ids persist in filter trees; the client's id→label cache dies with the page. The quiet re-resolution fetch reused the suggest facet, whose rows are MEDIA-CONSTRAINED (INNER JOIN media_items + the tree's sibling rules as a constraint) — so an id whose media doesn't match got no row and no label. Live repro: "feed in [A,B,C] AND title contains Chapter" labeled one feed of three (live-request evidence: 200 with total:1). A label is a property of the entity row, not of what currently matches. Fix: pages that render a tree resolve labels SERVER-SIDE at render and embed the map on the mount (data-id-labels) — the collection editor (stored tree) and the bookshelf (URL qf). New SearchService.FilterTreeLabels reads the entity tables with no media join and no tree constraint; scoping rides the request identity through the scoped handle (podcast_feeds/collections RLS — an invisible id resolves to nothing; pinned), libraries mirror the facet's visibility join. A feed with ZERO episodes resolves its title — pinned; that case was structurally impossible before. 2. The bookshelf drawer never hydrated at page load at all: the only load-time hydrate calls live in the PARENT's x-init (initBookshelf), and Alpine initializes parents before nested components — builderData() was null there, so hydrateFromForm no-oped and a URL-qf refresh left the drawer with an empty seeded rule while the grid stayed filtered. The drawer now hydrates in its OWN init when the mount carries no data-tree; the init-order contract is pinned in vitest (185/185). The quiet suggest fetch remains only as a fallback for trees the SSR never saw; embedded labels make it a no-op on refresh. Interactive menus are untouched (their constrained, count-bearing behavior is correct for choosing). Pins: TestRulesPageEmbedsIdLabels (zero-episode feed labels at render, bookshelf qf library label, hidden library NOT labeled); filter/episode-tree/search-contract/RLS/playlist container slices green; contract_pins entry documents the embed contract.
This commit is contained in:
1 parent
c448fa438f
commit
65f257f377
12 files changed
+533
-80
No files matched your search
@@ -297,6 +297,32 @@ func collectionDataFromRow(collection database.Collections, collectionID string,
|
||||
// detail page and the books-grid partial: smart evaluation, a dashboard
|
||||
// query section, or manual membership. Callers resolve the scope:
|
||||
// libUUID/libScope/isAll/scoped per their own resolution rules.
|
||||
// resolveTreeLabelsJSON — SSR id-label resolution for a page-rendered
|
||||
// filter tree: walks the tree's id-kind rules, resolves uuids to display
|
||||
// names through the search engine (RLS/visibility ride the request
|
||||
// identity), and returns the JSON map embedded into the builder mount.
|
||||
// Best-effort: any failure yields "{}" and the page renders with the
|
||||
// client's fallbacks intact (2026-11-01, filter_labels.go for the WHY).
|
||||
func resolveTreeLabelsJSON(c *echo.Context, cfg *Config, userID string, tree *services.FilterTree) string {
|
||||
if tree == nil || len(tree.Children) == 0 {
|
||||
return "{}"
|
||||
}
|
||||
userUUID, err := uuid.Parse(userID)
|
||||
if err != nil {
|
||||
return "{}"
|
||||
}
|
||||
labels := cfg.MediaHandler.SearchService().FilterTreeLabels(
|
||||
c.Request().Context(), pgtype.UUID{Bytes: userUUID, Valid: true}, tree)
|
||||
if len(labels) == 0 {
|
||||
return "{}"
|
||||
}
|
||||
b, merr := json.Marshal(labels)
|
||||
if merr != nil {
|
||||
return "{}"
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (cfg *Config) collectionBooksForScope(c *echo.Context, userUUID uuid.UUID, collUUID pgtype.UUID, collection database.Collections, collectionID string, libUUID pgtype.UUID, libScope pgtype.UUID, scoped bool, visibleIDs []pgtype.UUID) (books []handlers.BookInfo, isSmart bool, errCode string) {
|
||||
colService := cfg.CollectionHandler.CollectionService()
|
||||
isSmart = services.IsCollectionSmart(collection)
|
||||
|
||||
Reference in new issue
Block a user