feat(filter): label endpoint names id values for client-held trees

The SSR embed (65f257f3) covers trees the server renders; one path
still fell through to the flawed fallback — applying a SAVED filter
from a cold bookshelf. That page was rendered before any filter
existed (nothing embedded), and the fallback reused the suggest facet,
whose rows are MEDIA-CONSTRAINED: "feed in [A,B,C] AND title contains
Chapter" labeled only the feeds with a matching episode (the exact
live repro from the original bug report).

- GET /api/filter/labels?field=X&ids=a,b,c resolves names straight
  from the entity tables via the shared ResolveFieldLabels core
  (extracted from FilterTreeLabels): no media join, no tree
  constraint. RLS/visibility ride the request identity — a
  zero-episode subscribed feed resolves its title (pinned; the facet
  path structurally could not), an unsubscribed feed id stays unnamed
  (pinned), non-id fields are 400 (pinned), ids cap at 100.
- Client: resolveIdLabels/resolveLabelsAsync use ONLY the endpoint;
  the quiet suggest-fetch mode is deleted (its only caller was the
  flawed fallback). The saved-filter apply flow resolves through the
  endpoint and rebuilds the chips bar when labels land — chips render
  raw ids only while the round trip is in flight, never stuck.
- 188 web tests green; Go endpoint pins + filter/episode slices green;
  contract_pins documents the endpoint and the fallback removal.
This commit is contained in:
John O'Keefe committed 2026-11-01 13:40:25 -05:00
1 parent a20741ccc9
commit 96a40f7d88
8 files changed
+274 -78

No files matched your search

+18
View File
@@ -685,3 +685,21 @@ born-reversed chain/page pins replace the former native expectations
pin from 81e54f89 was corrected while flipping: the API envelope's
match list is NOT chain-ordered and never was — order pins live on the
SSR page and the reader chain.
## 2026-11-01 — label endpoint replaces the suggest-facet fallback
`GET /api/filter/labels?field=X&ids=a,b,c` names id-kind filter values
from their source tables (entity rows only — no media join, no tree
constraint; visibility/RLS ride the request identity). Non-id fields
are a 400; ids truncate at the same 100 cap the SSR embed uses.
The client's label resolution (`resolveIdLabels`/`resolveLabelsAsync`)
now uses ONLY this endpoint. The former fallback — the quiet suggest
fetch through the MEDIA-CONSTRAINED facet — is deleted: it labeled only
the ids whose media matched the tree's sibling rules (the live feed
repro), which mattered exactly where the SSR embed can't reach:
applying a SAVED filter from a cold bookshelf. The apply flow now
resolves through the endpoint and rebuilds the chips bar when labels
land. Pinned (TestFilterLabelsEndpoint): a zero-episode feed resolves
its title, comma-joined ids resolve together, an unsubscribed feed id
stays unnamed (RLS), non-id fields are 400.