feat(browse): sticky tabs + per-tab search bar (server-side, scope-aware)
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:
@@ -11,7 +11,7 @@ export const usePostsStore = defineStore('posts', () => {
|
||||
const cursor = ref(null)
|
||||
const { loading, error, run } = useAsyncAction()
|
||||
const done = ref(false)
|
||||
const filters = ref({ artist_id: null, platform: null })
|
||||
const filters = ref({ artist_id: null, platform: null, q: null })
|
||||
|
||||
// In-context (anchored) view: bidirectional cursors so the feed can load
|
||||
// newer posts on scroll-up and older posts on scroll-down around a post.
|
||||
@@ -32,6 +32,7 @@ export const usePostsStore = defineStore('posts', () => {
|
||||
if (cursor.value) q.cursor = cursor.value
|
||||
if (filters.value.artist_id != null) q.artist_id = filters.value.artist_id
|
||||
if (filters.value.platform) q.platform = filters.value.platform
|
||||
if (filters.value.q) q.q = filters.value.q
|
||||
return q
|
||||
}
|
||||
|
||||
@@ -47,6 +48,7 @@ export const usePostsStore = defineStore('posts', () => {
|
||||
filters.value = {
|
||||
artist_id: newFilters?.artist_id ?? null,
|
||||
platform: newFilters?.platform ?? null,
|
||||
q: newFilters?.q ?? null,
|
||||
}
|
||||
_reset()
|
||||
await loadMore()
|
||||
@@ -78,6 +80,7 @@ export const usePostsStore = defineStore('posts', () => {
|
||||
const p = { ...extra }
|
||||
if (filters.value.artist_id != null) p.artist_id = filters.value.artist_id
|
||||
if (filters.value.platform) p.platform = filters.value.platform
|
||||
if (filters.value.q) p.q = filters.value.q
|
||||
return p
|
||||
}
|
||||
|
||||
@@ -93,6 +96,7 @@ export const usePostsStore = defineStore('posts', () => {
|
||||
filters.value = {
|
||||
artist_id: newFilters?.artist_id ?? null,
|
||||
platform: newFilters?.platform ?? null,
|
||||
q: newFilters?.q ?? null,
|
||||
}
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
Reference in New Issue
Block a user