feat(annotations + player): bookmark rows in the banner, stacked volume panel, badge parity for comics/manga

Annotations banner: audio/text bookmarks join notes and highlights —
third row kind (bookmark glyph indicator, H:MM:SS / Page N / Chapter N
friendly labels), edit-note/delete actions branch by kind, and the
banner's export now covers all annotations. The archived ledger
(ListDeletedAnnotationsForBook) already tombstoned bookmarks equally
and Restore/Purge take annotation_type=bookmark — verified live:
delete → restore brings the row back with its note intact.

Volume: the dragger sits BELOW the mute icon in the panel (stacked
flyout shape) with the horizontal slider from the last round.

Badges: the real fix for comics/manga — format_group only knows
'comic_archive' (manga_type is unreliable across sources), so the
comic/manga split is resolved from the LIBRARY type server-side.
handlers.MediaBadgeKind + BookInfo.Badge at every construction site
(dashboard SSR + sections API, collections, series, and the bookshelf
search grid — which was also missing FormatGroup entirely, so no card
ever badged there). Both renderers (templ + dashboard.ts) switch on
the resolved badge. Verified: All-Books grid renders 2 Comic + 3
Manga badges, dashboard the audiobook one.

Infra: header.templ main.js src gains ?v=<stamp> — browsers keyed an
intermediate bundle by URL and served it across rebuilds (the stale
'Add note' the user saw); bump the stamp when JS stale-shadows.
This commit is contained in:
John O'Keefe committed 2026-10-15 19:33:18 -04:00
1 parent 2c714dcb60
commit 89665fb2bc
19 files changed
+273 -92

No files matched your search

+7 -2
View File
@@ -482,9 +482,14 @@ func registerFrontendRoutes(cfg *Config) {
}
}
libTypes := map[string]string{}
for _, ld := range libRes.Libraries {
libTypes[ld.ID] = ld.TypeValue
}
userPgID := pgtype.UUID{Bytes: userUUID, Valid: true}
sectionData := handlers.MarkActiveConflictsSections(c.Request().Context(), cfg.Queries, userPgID, handlers.BuildSections(visibleSections, libraryID, activeLibType))
allSectionsData := handlers.BuildSections(allSections, libraryID, activeLibType)
sectionData := handlers.MarkActiveConflictsSections(c.Request().Context(), cfg.Queries, userPgID, handlers.BuildSections(visibleSections, libraryID, activeLibType, libTypes))
allSectionsData := handlers.BuildSections(allSections, libraryID, activeLibType, libTypes)
// "This week" widget: the user's rolling 7-day reading pulse for
// the CURRENTLY SELECTED library (the same resolution the sections