feat(filters+ui): per-element author/publisher filters, playlist toggles, Up Next API, reorder UI, dashboard badges

Phases 4 + 5 of the metadata overhaul (plan §4, §5) plus §3 handler
wiring.

Filters (§4.1): author_filter/publisher_filter now match per-element
against the authors[]/publishers[] arrays — a co-authored book matches
any one of its authors — with comma-joined params acting as
OR-within-facet multi-select (string_to_array + cross unnest). Legacy
single-value params unchanged on the wire; series/genre/language
filters stay on their varchar columns; narrator filter already
per-element.

Collections API (§3.3-3.5): collection detail carries viewer +
hide_finished into the listing query and exposes up_next (visible-
library-enforced reading-queue head); PUT
/api/collections/:id/items/order (owner-only, full-order rewrite);
UpdateCollectionRequest gains hide_finished/show_up_next ([]
string last-value-wins for the hidden-false-sibling checkbox pattern)
merged server-side OVER stored view_settings so imported_from_series
provenance survives edits; EditCollectionModal renders the toggles
from view_settings.

Dashboard (§5.5): sections JSON carries is_playlist/complete
(dashboard.ts is the AGENTS-documented JSON-render exception; first
paint stays templ). Fully-read playlists render '✓ Playlist complete'
instead of an empty rail; imported playlists get a 📖 Playlist badge.

Frontend: book detail shows subtitle + full authors + Explicit badge;
edit modal gains subtitle, comma-separated authors/publishers (client-
split to arrays exactly like narrators), step=any series number,
explicit checkbox; collection book cards get playlist move ↑/↓ buttons
persisting the full order via the reorder endpoint (templ partial
re-render on failure — DOM order is client-local, API order is truth).

AGENTS.md gotcha #3 amended per plan §3.7: user collections are
dashboard sections by default; the show_on_dashboard premise is
retired.

Verified: full affected integration sweep green (pins widened for
up_next/is_playlist/complete — changelog entries in
docs/contract_pins.md); unit suite green; go build + templ + vite
green.
This commit is contained in:
John O'Keefe committed 2026-10-19 15:43:59 -04:00
1 parent 1dbb0ce406
commit f94e7472b4
20 files changed
+1711 -1339

No files matched your search

+13 -3
View File
@@ -323,9 +323,13 @@ templ MetadataEditorModal(book handlers.MediaDetail) {
<label class="block text-xs font-semibold uppercase tracking-wide mb-1" style="color: var(--text-secondary);">Title</label>
<input type="text" name="title" value={ book.Title } class="input" />
</div>
<div>
<label class="block text-xs font-semibold uppercase tracking-wide mb-1" style="color: var(--text-secondary);">Subtitle</label>
<input type="text" name="subtitle" value={ textToString(book.Subtitle) } class="input" />
</div>
<div>
<label class="block text-xs font-semibold uppercase tracking-wide mb-1" style="color: var(--text-secondary);">Author</label>
<input type="text" name="author" value={ textToString(book.Author) } class="input" />
<input type="text" name="authors" value={ authorsFormValue(book.Authors, textToString(book.Author)) } class="input" placeholder="Comma-separated names"/>
</div>
<div>
<label class="block text-xs font-semibold uppercase tracking-wide mb-1" style="color: var(--text-secondary);">Description</label>
@@ -400,7 +404,7 @@ templ MetadataEditorModal(book handlers.MediaDetail) {
<div x-show="openSections.publication" x-transition class="p-4 space-y-3 border-t" style="border-color: var(--border);">
<div>
<label class="block text-xs font-semibold uppercase tracking-wide mb-1" style="color: var(--text-secondary);">Publisher</label>
<input type="text" name="publisher" value={ textToString(book.Publisher) } class="input" />
<input type="text" name="publishers" value={ authorsFormValue(book.Publishers, textToString(book.Publisher)) } class="input" placeholder="Comma-separated publishers"/>
</div>
<div>
<label class="block text-xs font-semibold uppercase tracking-wide mb-1" style="color: var(--text-secondary);">Date Published</label>
@@ -446,7 +450,7 @@ templ MetadataEditorModal(book handlers.MediaDetail) {
<div class="grid grid-cols-3 gap-3">
<div>
<label class="block text-xs font-semibold uppercase tracking-wide mb-1" style="color: var(--text-secondary);">Number</label>
<input type="number" name="series_number"
<input type="number" name="series_number" step="any"
value={ fmtNumber(book.SeriesNumber.Float64) }
class="input" />
</div>
@@ -524,6 +528,12 @@ templ MetadataEditorModal(book handlers.MediaDetail) {
<span>Abridged edition</span>
</label>
</div>
<div>
<label class="flex items-center gap-2 text-sm" style="color: var(--text-primary);">
<input type="checkbox" name="explicit" checked={ book.Explicit } class="h-4 w-4 accent-current"/>
<span>Explicit content</span>
</label>
</div>
if book.AudioDuration() > 0 {
<div>
<label class="block text-xs font-semibold uppercase tracking-wide mb-1" style="color: var(--text-secondary);">Duration (scanned)</label>