feat(filters+nav): author navigation end-to-end — arrays on every media response, deep-linkable bookshelf filters, clickable authors
Completes the A2c acceptance scenario: click an author on a book detail page -> land on the bookshelf filtered to everything that author wrote (including their share of co-authored books). - Media detail + unscoped list resolved maps expose subtitle, explicit, authors, publishers (the library-scoped raw branch and the search endpoint already did — raw rows follow the SELECT). - SearchMediaItemsUnified SELECT carries the new columns; the search endpoint serializes the row directly, so clients get them there too. - book_detail.templ renders each author as a link to /bookshelf?author_filter=<name>. - Bookshelf honors filter params in the URL on load (author_filter, series_filter, genre_filter, publisher_filter, tags_filter, narrator_filter, language_filter): pre-fills the form, applies it to the first grid load. Comma-joined values are multi-select via the server-side string_to_array split. Filtered views are shareable. Pins widened: search rows (+7 keys), resolved keys (+4); changelog entries in docs/contract_pins.md. Android follow-up plan written to ~/.opencode/plan/bookhoard-app-v2-compat.md.
This commit is contained in:
1 parent
f94e7472b4
commit
7537063cb7
8 files changed
+851
-728
No files matched your search
@@ -662,6 +662,11 @@ func TestContractPin_SearchJSON(t *testing.T) {
|
||||
// EXPLAIN plan-identical) and the search endpoint serializes the
|
||||
// row directly, so they emit here.
|
||||
"abridged", "audio_metadata",
|
||||
// A2b/A2c (2026-10-19): + subtitle, explicit, authors,
|
||||
// authors_search, publishers, publishers_search, narrators_search
|
||||
// (docs/contract_pins.md changelog).
|
||||
"subtitle", "explicit", "authors", "authors_search",
|
||||
"publishers", "publishers_search", "narrators_search",
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/api/media-items/search?q=%s&library_id=%s", setup.Server.URL, url.QueryEscape(fx.itemTitle), fx.libraryID)
|
||||
@@ -739,10 +744,10 @@ func TestContractPin_MediaListBothBranches(t *testing.T) {
|
||||
"library_type_name_2",
|
||||
}
|
||||
resolvedKeys := []string{
|
||||
"id", "library_id", "title", "author", "isbn", "description",
|
||||
"id", "library_id", "title", "subtitle", "author", "authors", "isbn", "description",
|
||||
"file_path", "file_size", "mime_type", "cover_image_path",
|
||||
"series", "series_number", "tags", "asin", "date_published",
|
||||
"publisher", "contributors", "language", "edition", "page_count",
|
||||
"publisher", "publishers", "explicit", "contributors", "language", "edition", "page_count",
|
||||
"genre", "created_at", "updated_at", "format_group",
|
||||
"manga_type", "reading_direction",
|
||||
"series_count", "volume", "imprint", "age_rating", "web_url",
|
||||
|
||||
Reference in new issue
Block a user