Skip to content

Wire catalog cascade into GET /library/ via fuzzySearchLibrary (BS#972)#973

Merged
jakebromberg merged 1 commit into
mainfrom
bugfix/972-cascade-catalog-route
May 20, 2026
Merged

Wire catalog cascade into GET /library/ via fuzzySearchLibrary (BS#972)#973
jakebromberg merged 1 commit into
mainfrom
bugfix/972-cascade-catalog-route

Conversation

@jakebromberg
Copy link
Copy Markdown
Member

Summary

The catalog-track-search cascade (CTALML /lookup) lived only inside searchLibrary, which GET /library/search calls. Catalog clients (dj-site useSearchCatalogQuery, iOS searchLibrary) hit GET /library/ instead, which calls fuzzySearchLibrary and tops out at tsvector + trigram. Flipping CATALOG_TRACK_SEARCH_CTA_ENABLED / CATALOG_TRACK_SEARCH_DISCOGS_ENABLED to true in prod had no observable effect on the catalog page or the iOS app, and matched_via was always absent from AlbumSearchResult[] responses — making three already-merged client surfaces (MatchedTrackBadge, modern MatchedTrackChips, classic MatchedTrackChips) dormant.

This PR pushes the cascade down one layer:

  • searchLibraryByCTARaw and searchLibraryByTrackRaw are new, returning TaggedLibraryViewEntry[] (i.e. LibraryArtistViewEntry plus an optional matched_via). The existing searchLibraryByCTA / searchLibraryByTrack are kept as enriched-shape wrappers so request-line callers (artistPlusAlbum, swappedInterpretation, songAsArtist, trackOnCompilation) are unaffected.
  • Track 2's LRU cache moves to the raw layer so both catalog and request-line callers share one cache.
  • searchLibraryBothMode now owns the full tsvector → trigram → CTA → LML cascade and returns TaggedLibraryViewEntry[].
  • fuzzySearchLibrary's both-mode branch (the dj-site shape, where artist_name === album_title) routes through searchLibraryBothMode and inherits the cascade. Split-field queries stay on the legacy fuzzy path — out of scope.
  • serializeLibraryArtistViewEntry carries matched_via through to the wire (LibraryArtistViewResponse now declares the optional field).

Both feature flags default off, so a deployment that hasn't opted in is byte-identical to before. This is wiring only — no behavior change without a flag flip.

Acceptance criteria (from #972)

  • GET /library/?artist_name=q&album_title=q&n=10 with CATALOG_TRACK_SEARCH_CTA_ENABLED=true returns matched_via: [{ title, source: "cta" }, …] on tsvector+trigram 0-hits (new unit + integration tests assert this).
  • Same shape with CATALOG_TRACK_SEARCH_DISCOGS_ENABLED=true returns matched_via: [{ title, source: "discogs…" }] on CTA misses (new integration test against the existing Track 2 fixtures hits vi scose poise → Confield).
  • Both flags off: /library/ responses are byte-identical to today (cascade no-op verified by the existing library.spec.js + ranking suites).
  • Direct artist/album tsvector hits never carry matched_via (unit + integration test).
  • No regression in searchForAlbum request shape (code_letters lookup, on_streaming-only browse, missing-params 400 unchanged).
  • Integration test: with both flags on, /library/?artist_name=vi+scose+poise&album_title=vi+scose+poise returns the Confield row with matched_via.source matching discogs* (uses the existing Track 2 mock-LML fixture; no live LML required in CI).
  • Latency budget preserved: enabled-flag path stays within the existing LIBRARY_SEARCH_ENRICHMENT_BUDGET_MS artwork race; the cascade adds at most one CTA SQL + one LML /lookup per 0-hit (unchanged from /library/search).

Test plan

  • npm run test:unit (1991 passed; new fuzzySearchLibrary Both-mode cascade block + existing searchLibrary cascade block both green)
  • npm run typecheck (clean across all workspaces)
  • npm run lint (0 errors)
  • npm run format:check (clean)
  • npm run build (all workspaces)
  • npm run test:integration (new GET /library cascade — catalog route serves matched_via (BS#972) block plus existing library.spec.js regressions — exercised in CI via ci:testmock)

Related

Push the catalog-track-search cascade (CTA -> LML /lookup) down from
searchLibrary into searchLibraryBothMode so both /library/search and
/library/ (the catalog-facing route dj-site + iOS call) traverse it.
Catalog responses now carry `matched_via` for cascade-sourced hits.

Refactor: extract searchLibraryByCTARaw + searchLibraryByTrackRaw that
return TaggedLibraryViewEntry[] (LibraryArtistViewEntry + optional
matched_via), keeping the enriched wrappers for request-line callers.
The LRU cache for Track 2 moves to the raw layer so both call sites
share it. fuzzySearchLibrary's both-mode branch now returns the tagged
shape, which serializeLibraryArtistViewEntry preserves through to the
AlbumSearchResult wire format.

Behavior is byte-identical when CATALOG_TRACK_SEARCH_CTA_ENABLED and
CATALOG_TRACK_SEARCH_DISCOGS_ENABLED are both off (the default), so this
is the cascade wiring alone -- no behavior change without a flag flip.
@jakebromberg jakebromberg merged commit 7b16952 into main May 20, 2026
5 checks passed
jakebromberg added a commit that referenced this pull request May 21, 2026
`searchLibraryQueryEndpoint` runs its own field-aware ILIKE primary path,
so the Track 1 (CTA) + Track 2 (LML `/lookup`) cascade owned by
`searchLibraryBothMode` was unreachable from the modern Card Catalog. That
left modern dj-site without `matched_via` chips even after BS#972/#973
wired the cascade onto the classic-experience `/library/` route.

When the primary query returns no rows AND the parsed query is a single
bareword (no `artist:`/`album:`/`label:` qualifier, no quoted exact, no
NOT), fall through to `searchLibraryByCTARaw` + `searchLibraryByTrackRaw`
under the existing `CATALOG_TRACK_SEARCH_CTA_ENABLED` /
`CATALOG_TRACK_SEARCH_DISCOGS_ENABLED` flags. Apply `genre` / `format` /
`on_streaming` filters and the requested `sort` + `order` over the
bounded fallback list in-memory before returning. Cascade fallback is
single-page (`page > 0` stays empty) so the client doesn't keep
scrolling into a bounded list.

Both flags default to `false`, so when the operator hasn't opted in the
function path is byte-identical to today.

Closes #977
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Wire searchLibrary cascade into GET /library/ so catalog clients see matched_via

1 participant