fix(web): scan progress — correct percentages, no layout shift, refresh-survivable

Bug: the admin 'Scanning Libraries' widget fed the server's 0-1
progress straight into percent strings and bar widths, so the overall
and per-library percentages capped at '1%' and the bars never filled.
Both now scale by 100 (matching the setup page, which was already
correct).

Bug: scan state only reached clients via websocket events, so a page
opened (or refreshed) mid-scan showed nothing until the next tick —
and the admin widget never re-attached at all. The worker now exposes
active jobs (with their library_id so rows keep their names), served
as GET /api/scanner/active (admin, re-attaches the widget + polling)
and GET /api/scanner/active-summary (any user, restores the sidebar
indicator on load). Idle hides both, exactly as before.

Annoyance: the sidebar's 'Scanning… N%' was a block element between
the logo and the nav, shoving every menu entry down while a scan ran.
The percent now lives inside the fixed-height logo row next to the
spinner (spinner last, so growing digits extend leftward); nothing
below it moves.
This commit is contained in:
John O'Keefe committed 2026-10-15 08:29:04 -04:00
1 parent 9bc149f3d9
commit fd6f0e3353
11 files changed
+245 -38

No files matched your search

+19 -13
View File
@@ -24,11 +24,24 @@ templ Header(user User, currentPath string) {
@Icon("book-open", "h-6 w-6")
</span>
<span class="font-bold text-lg tracking-tight" style="color: var(--text-primary)">Bookhoard</span>
<!-- Scanning indicator: lives INSIDE the fixed-height logo row so
it can never push the nav entries down. Spinner sits last so
the growing percent extends leftward, not into the spinner. -->
<div
x-show="scanning"
x-cloak
x-transition
class="ml-auto flex items-center gap-1.5 shrink-0"
aria-live="polite"
>
<span
x-show="scanProgress > 0"
class="text-xs font-medium"
style="color: var(--text-secondary);"
x-text="scanProgress + '%'"
></span>
<svg
x-show="scanning"
x-cloak
x-transition
class="h-4 w-4 animate-spin ml-auto"
class="h-4 w-4 animate-spin"
style="color: var(--accent);"
fill="none"
viewBox="0 0 24 24"
@@ -36,15 +49,8 @@ templ Header(user User, currentPath string) {
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</a>
<span
x-show="scanning && scanProgress > 0"
x-cloak
x-transition
class="block px-5 py-1.5 text-xs font-medium -mt-1"
style="color: var(--text-secondary);"
x-text="'Scanning… ' + scanProgress + '%'"
></span>
</div>
</a>
<!-- Primary navigation -->
<nav class="flex-1 px-3 py-4 space-y-0.5 overflow-y-auto">
<a href="/dashboard" class={ activeClass(currentPath, "/dashboard") }>