feat(browse): sticky tabs + per-tab search bar (server-side, scope-aware)
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 25s
CI / integration (push) Successful in 3m10s

The Browse tab nav scrolled away (operator didn't know it existed) and
Posts had no search. Roll the tab strip + a shared search field into one
sticky block pinned under the 64px TopNav.

- Posts gains server-side text search: PostFeedService.scroll()/around()
  + /api/posts accept q (ILIKE over post_title OR description), applied
  INSIDE the artist/platform WHERE so search stays scoped to the active
  filter. Scope shown as clearable chips next to the search field.
- Artists/Tags search consolidates into the sticky bar: their inner
  search boxes are removed; they react to route.query.q (q is deep-
  linkable, e.g. /browse?tab=posts&q=foo). Platform/kind filters stay.
- Posts empty state now distinguishes 'no matches' from 'no posts yet'.

Tests: posts q-search matches title|description and stays artist-scoped
(service); q passthrough (api).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-12 00:04:06 -04:00
parent 90c68f8b2a
commit 2c544ad5af
9 changed files with 237 additions and 44 deletions
+3 -2
View File
@@ -17,6 +17,7 @@ async def list_posts():
cursor = args.get("cursor") or None
artist_id_raw = args.get("artist_id")
platform = args.get("platform") or None
q = (args.get("q") or "").strip() or None
limit_raw = args.get("limit", "24")
direction = args.get("direction", "older")
around_raw = args.get("around")
@@ -56,7 +57,7 @@ async def list_posts():
if around_id is not None:
result = await svc.around(
post_id=around_id, artist_id=artist_id,
platform=platform, limit=limit,
platform=platform, q=q, limit=limit,
)
if result is None:
return _bad("not_found", status=404, detail=f"post id={around_id}")
@@ -64,7 +65,7 @@ async def list_posts():
try:
page = await svc.scroll(
cursor=cursor, artist_id=artist_id,
platform=platform, limit=limit, direction=direction,
platform=platform, q=q, limit=limit, direction=direction,
)
except ValueError as exc:
# Service raises ValueError for malformed cursors only;