Skip to content

UI Pass 2 and sync/process improvements#11

Merged
mstrhakr merged 20 commits into
mstrhakr:masterfrom
Wutname1:UI-Pass2
Jun 7, 2026
Merged

UI Pass 2 and sync/process improvements#11
mstrhakr merged 20 commits into
mstrhakr:masterfrom
Wutname1:UI-Pass2

Conversation

@Wutname1

@Wutname1 Wutname1 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Theme: UI Pass 2 — settings cleanup, dashboard clickability, richer book detail, smarter library filters/search, and a handful of sync/data correctness fixes.

Settings & Wizard

  • Tidied settings spacing, dropped read-only Storage wizard step (ccb1122)
  • Plainer settings wording (9caf1e3)
  • Sync settings now apply without restart (1e0d5be)
  • Setup wizard asks about auto-downloading new books (c019a26)
  • Fixed settings section links jumping to top (6aeda0e)

Dashboard & Navigation

  • All dashboard summary cards now clickable (0a2ad84)
  • Dashboard auto-refresh, clickable New card, mobile nav + table fixes (162b01e)
  • Diagnostics book links now open the book (8b2958a)

Library & Book Detail

  • "Download" leads on rows that aren't downloaded (4249c6e)
  • New libraries tag audiobooks with series info by default (4101184)
  • File info block + collapsible description on book detail (5d4220e)
  • Library presence filters (disk + per-destination) (4be08d1)
  • Search by series and ASIN in addition to title/author (078f8d8)

Sync & Data

  • New Metadata Repair sync phase re-tags books missing ASIN (0ffa189)
  • Library total now used as destination coverage denominator (ca3fad3)
  • Fixed column order in live-appended completed/failed download rows (0c91856)

Wutname1 and others added 16 commits May 22, 2026 06:43
…rows

The SSE handler in base.html inserted dynamically-appended rows with ASIN
in the first column and Title in the second, but the completed and failed
table headers (in downloads.html and dashboard_downloads.html) order
columns as Title, ASIN. Rows added on download completion appeared with
swapped values until a full page refresh re-rendered them server-side.

Reorder the inserted cells to match the header order, and run the values
through escHTML to match the rest of the SSE-driven UI.
The library search input previously matched only the title and author
columns, so a query like "Dungeon Crawler" returned only the books
where the series name happened to also appear in the title.

Extend the SQLite and PostgreSQL WHERE-clause builders to also LIKE
match the series and asin columns. Series is stored as a plain string
column on books so no schema changes are needed.
The dashboard and destinations page computed each destination's
coverage % as item_count / books_with_status_complete, capped at 100.
When a destination held more items than the DB tracked as complete —
common when destination items linger across status transitions or
when reconciliation hasn't refreshed the book records — the cap
silently turned every card into "100%", hiding real coverage gaps
(e.g. ABS at 636 items, Plex at 629, against ~650 in the library).

Switch the denominator to the total book count so the metric reads
as "fraction of the library present in this destination". The
totalBooks count is far more stable than the complete count across
sync transitions, so the cap rarely kicks in for legitimate state.
Books downloaded under the historical Basic tag profile (or restored
from older runs) ship without an ASIN atom embedded in the m4b,
which makes Audiobookshelf's library-matcher unable to identify
them and forces a manual re-download to fix.

Introduce a new Metadata Repair phase that runs as part of the
full sync pipeline (and can be triggered standalone via the
existing /api/sync/phase/:phase endpoint):

- ffprobes every complete book's file and compares the embedded
  ASIN atom to the DB record
- when missing or stale, re-runs EmbedMetadata with the
  AudiobookRich profile regardless of the user's tag_profile
  setting, since the whole point of this job is to make ASIN
  matching work
- enriches via Audnexus when available so genre/copyright/etc.
  match what the original download would have written; falls back
  to the DB-only field set when audnexus is unreachable
- writes the new file to a sibling temp path and atomically
  renames over the original, so a crashed ffmpeg never corrupts
  the source file

ffmpeg uses -c copy so the actual operation is a fast remux with
no re-encode. Phase is skipped (not failed) when ffmpeg isn't
available on the host.
The library page had no way to ask "which of my books aren't on
Plex/ABS yet?" — presence was visible only as per-row chips, with
no way to slice the table by it. Status tabs were the only filter
dimension, and presence didn't fit there anyway since a book can
be Complete and missing from a destination at the same time.

Add three orthogonal presence dimensions to BookFilter:

  OnDisk                  *bool   — file_path != ''
  PresentInDestinations   []id    — EXISTS row with sync_state in synced|syncing
  MissingFromDestinations []id    — NOT EXISTS the above

Wire them through:

- BookFilter struct and both WHERE-clause builders (sqlite, postgres),
  using EXISTS subqueries that hit the (destination_id, sync_state)
  index on book_library_destinations.
- handleLibrary URL params: ?on_disk=yes|no plus a dynamic
  ?presence_<dest_id>=in|out for each enabled destination.
- Filter bar dropdowns: one for disk state, one per enabled
  destination, generated from the live destinations list so the UI
  tracks whatever the user has configured.
- Status tabs stay status-only — presence is now a filter, not a
  tab, so it composes with status (e.g. "Complete AND missing
  from ABS") instead of being mutually exclusive.

Also drop the surface-2 background from .presence-chip so the row
chips inherit from the table cell instead of painting their own
fill.
Two additions to the book detail panel:

1. File info block (only when the book is on disk)

   New ProbeFile method on the FFmpeg wrapper that runs ffprobe with
   -show_format -show_streams -show_chapters in a single invocation
   and returns a FileProbe carrying container/codec technicals
   (format, codec, bitrate, sample rate, channels, channel layout,
   duration), chapter count, embedded-artwork flag, and the raw
   format-level tag map.

   handleBookDetail composes a bookFileInfoView from that probe plus
   os.Stat (size, modified time), the filesystem path, and the
   account's activation bytes (fetched from the audible client when
   authenticated — surfaced here because they're what was used to
   decrypt this file, useful for re-decryption diagnostics).

   The template renders it as a meta-grid mirroring the existing
   metadata stack, with the raw container tags tucked inside a
   <details> summary so encoder/major_brand/compatible_brands/asin
   etc. stay one click away. Container-identity tags bubble to the
   top of that list; everything else sorts alphabetically.

   Every field is independently optional — a degraded probe still
   renders size + modified date + path so the block degrades
   usefully instead of disappearing.

2. Collapsible description

   Long Audible descriptions clamp to 9.5rem with a fade-out
   gradient over the bottom 3rem; a "Show more" button toggles to
   "Show less". The script hides the toggle when the clamped
   content already fits (scrollHeight <= clientHeight) so short
   descriptions don't get a dangling affordance.

Wires ffmpeg through web.NewServer so handlers can call ProbeFile
directly — previously ffmpeg lived only inside DownloadManager and
SyncService.
Three issues touching the dashboard, sidebar, and library table that
all show up the moment a user reaches for a phone or does a sync.

1. Dashboard didn't update after sync. The summary panel listened
   for an undefined "queue:last" event, so its only refresh path
   was the 12s poll — meaning the "Library Items" / "New" counters
   stayed stale for up to 12s after the user kicked a Quick Sync
   that surfaced new books.

   Add "sync:done from:body" to the dashboard summary and downloads
   hx-trigger lists, and have the SSE sync event handler in
   base.html fire that event whenever sync transitions out of the
   running state. The dashboard refetches immediately when sync
   ends instead of waiting for the next poll tick.

2. The "New" stat card on the dashboard was a non-interactive div
   even though "go look at my N new books" is the obvious next
   action. Wrap it in an anchor to /library?status=new with no
   text-decoration; subtle hover ring keeps the affordance visible
   without trumpeting it.

3. Mobile sidebar wasn't usable. The existing 768px breakpoint
   flipped the sidebar into a horizontal bar that wrapped six nav
   links and consumed half the screen. Replace with an off-canvas
   drawer: hamburger button in the topbar, body.sidebar-open
   slides the drawer in via transform, a backdrop closes on click,
   nav links auto-close on activation, Escape dismisses.

4. Library table wasn't usable on mobile either. Nine columns can't
   reflow on a phone-width viewport without horizontal scroll. At
   ≤768px, the table flips to a card layout: cover floats left,
   title/author/series stack as block lines, and the dense bits
   (duration, purchased, status, presence chips) pack into a single
   wrapping inline-flex row. Actions clear the float into a
   full-width footer with a top border. Named column classes
   (col-title, col-author, …) added to the row template so future
   column reorders don't break the layout.
The left-rail "On this page" highlight used an IntersectionObserver that
only saw the sections whose visibility changed in each batch, so clicking
a link (or scrolling) could snap the highlight back to the top section.
Replaced it with a position-based scroll-spy that evaluates every section
on each scroll, anchors to the last section at the bottom of the page, and
locks itself while a click-driven smooth scroll animates so it can't fight
the click. Also fixed the post-auth focus helper, which looked for a
non-existent "<name>-settings" element id instead of "section-<name>".

Files: internal/web/templates/settings.html

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New installs now default to the Audiobook-rich tag profile, which embeds
series, series-part, and asin atoms so apps like Audiobookshelf can group
series automatically. The profile is additive (it keeps the album=title
workaround that stops Plex collapsing a series), so the switch is safe for
existing libraries too. Added a DefaultTagProfile constant and routed all
fallbacks (unset, nil DB, unknown value) through it.

Files: internal/audio/tag_profile.go, internal/audio/tag_profile_test.go

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Added a toggle on the final setup step (on by default) that controls
whether books found during a sync are queued for download right away. The
choice is wired to the existing "Start sync" form and saved when setup
finishes, so new users get to opt in or out instead of inheriting a silent
default.

Files: internal/web/templates/setup.html, internal/web/setup_handlers.go

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The sync schedule, enable-sync toggle, sync mode, and auto-download setting
already had live setters on the scheduler, but the settings handler only
wrote them to the database and asked for a restart because the web server
never received a scheduler reference. Wired the scheduler into the web
server so saving those settings reconfigures the running scheduler
immediately. Added a mutex to the scheduler since its fields are now
touched by both the cron goroutine and the web handler. Worker counts are
the only setting that still needs a restart (the pipeline goroutines are
sized once at startup), and the save message now says so specifically.

Files: internal/scheduler/cron.go, internal/web/server.go, cmd/server/main.go

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reworded settings copy to read more simply: renamed "Auto-add new books to
queue" to "Automatically download new books", simplified the worker hints
and sync mode labels, refreshed the tag profile help to lead with the new
default, and updated the page subtitle. Also removed the now-incorrect
"applies after a restart" note from the Sync section (those settings apply
live) and tightened the surviving worker-count restart note.

Files: internal/web/templates/settings.html

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Every "At a glance" card now links to its matching library filter:
Library Items to the full library, Downloaded to complete, In Progress to
downloading, and Failed to failed (New already linked). They reuse the
existing stat-card-link style, so there is no underline and the same hover
and focus treatment applies.

Files: internal/web/templates/dashboard_summary.html

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On every library view, books that aren't downloaded now show Download (or
Retry, for failed books) as the primary button, with View moved into the
More menu. Downloaded and unavailable books keep View as the primary
button since there is nothing to download. The cover and title stay
clickable, so book details are always one click away.

Files: internal/web/templates/library_row.html

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Diagnostics page links to books by ASIN, but the book detail handler
only parsed numeric IDs, so it fell through to a bare error render that
showed just the page shell. The handler now accepts either a numeric book
ID or an ASIN (numeric IDs and ASINs never overlap), so those links open
the correct book.

Files: internal/web/server.go

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… step

The setup wizard's Storage step only displayed the container's mount paths,
which can't be edited from the app, so it was removed. Those paths still
appear (read-only) under Settings > System Information, which now explains
that they come from the container's volume mounts. Also fixed uneven gaps
between the Library/Sync/Performance cards, which were touching because
they sit inside one form that the settings grid's gap couldn't reach.

Files: internal/web/templates/setup.html, internal/web/setup_handlers.go,
internal/web/templates/settings.html, internal/web/server.go

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR delivers a second UI polish pass across the web app (setup wizard, settings, dashboard, library, and book detail) and pairs it with several sync/scheduling and data-correctness enhancements (metadata repair phase, richer search/presence filtering, and improved destination coverage math).

Changes:

  • Improves onboarding + navigation UX (wizard step cleanup, dashboard cards clickable, mobile nav drawer, settings scroll behavior).
  • Expands library/book-detail UX (presence filters, richer search, row action tweaks, file-info panel, collapsible descriptions, mobile-friendly library rows).
  • Adds/adjusts sync + backend behavior (metadata repair sync phase, live-apply sync scheduler settings, destination coverage denominator update, new DB filter support + tests, tag profile default update).

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
internal/web/templates/setup.html Removes storage wizard step and adds auto-download-new-books toggle on final step.
internal/web/templates/settings.html Updates settings copy, section focusing, and replaces TOC scroll-spy implementation.
internal/web/templates/library.html Adds disk/presence filters and consolidates HTMX includes via .lib-filter.
internal/web/templates/library_row.html Refines table column classes for responsive layout; makes Download/Retry primary for undownloaded rows.
internal/web/templates/dashboard.html Refreshes summary/download panels on sync:done events in addition to polling.
internal/web/templates/dashboard_summary.html Makes “At a glance” stat cards clickable links into library filters.
internal/web/templates/book_detail_panel.html Adds “File info” block and collapsible description behavior.
internal/web/templates/base.html Adds mobile nav drawer + backdrop; triggers sync:done and hardens dynamic row HTML escaping.
internal/web/static/style.css Styles clickable stat cards, mobile nav drawer/backdrop, responsive library “card view”, and book detail additions.
internal/web/setup_handlers.go Updates setup step list and persists final-step auto-queue setting.
internal/web/server.go Threads scheduler/ffmpeg into web server; updates destination coverage math; adds library presence filters; adds ASIN support for book detail; applies sync settings live via scheduler; adds file-probe view model.
internal/web/destinations_handlers.go Updates destination coverage denominator to use total library size.
internal/scheduler/cron.go Adds mutex protection for live scheduler reconfiguration and cron entry updates.
internal/library/sync.go Introduces Metadata Repair sync phase and optional dependency wiring for ffmpeg/audnexus.
internal/library/metadata_repair.go Implements metadata repair pass that probes tags and re-embeds AudiobookRich atoms when needed.
internal/database/sqlite.go Extends search fields and adds on-disk + per-destination presence filters in WHERE builder.
internal/database/postgres.go Mirrors sqlite search/presence filter behavior for Postgres.
internal/database/interface.go Extends BookFilter with on-disk and destination presence predicates.
internal/database/books_presence_filter_test.go Adds coverage tests for the new presence filters (SQLite).
internal/audio/tag_profile.go Changes default tag profile behavior to prefer AudiobookRich for new/unknown settings.
internal/audio/tag_profile_test.go Updates tests for the new default tag profile behavior.
internal/audio/probe_tags.go Adds ffprobe helpers for reading tags and full file probe details for UI.
cmd/server/main.go Wires optional sync dependencies and updates web server constructor args.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/library/metadata_repair.go
Comment thread internal/web/templates/settings.html
Comment thread internal/web/templates/setup.html Outdated
@mstrhakr mstrhakr self-assigned this Jun 7, 2026
@mstrhakr mstrhakr merged commit 6bebb86 into mstrhakr:master Jun 7, 2026
3 checks passed
@Wutname1 Wutname1 deleted the UI-Pass2 branch June 10, 2026 18:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants