fix(ui): mobile layout overflows across admin, reader, and book pages

Every fix is a minimal class-level change; desktop layouts are untouched
because each fixed row already fit on one line there.

- progress: CFI string is one unbreakable token — it inflated the page
  layout width to 398-501px (whole-page zoom-out on phones) and rendered
  debug-looking text edge to edge. min-w-0 + break-all lets it wrap.
- audio reader: settings strip min-content (~470px) exceeded the 390px
  viewport, clipping Skip/Sleep and — because the page ICB inflated —
  pushing the chapters drawer (w-full of the viewport) past the screen
  edge so its timestamps clipped too. flex-wrap fixes both symptoms.
- book detail edit-metadata modal footer: Rescan/Reset/Write-to-file row
  overflowed the modal; clipped buttons were unreachable. flex-wrap.
- bookshelf save-filter modal footer: three buttons overflowed both
  screen edges. flex-wrap.
- admin libraries: rows overprinted (badge chips shrink-0 vs actions
  shrink-0, no wrap) — 'manga⟳Scan▸1 folders⌄Manage'. flex-wrap stacks
  actions under the name on phones.
- admin podcasts: maintenance table forced a 892px page (44% zoom).
  overflow-x-auto on the card scrolls the table inside it instead.
- stats: 4-pill range row forced 421px; page permanently ~7% zoomed out.
  flex-wrap.
- podcast feed detail: label+select pairs now travel in their own flex
  pairs so flex-wrap can never split 'Order' from its select; ids and
  htmx change-triggers preserved.
- cosmetics: conflicts stat labels tracking-wide→normal (UNRESOLVED
  clipped), archived file path line-clamp-3, rules-page Smart badge
  wraps cleanly, dashboard View All shrink-0 nowrap (was wrapping to
  two lines).
This commit is contained in:
John O'Keefe committed 2026-11-02 09:30:55 -05:00
1 parent e3ca3426ba
commit 88adf30aa9
24 files changed
+109 -103

No files matched your search

+1 -1
View File
@@ -80,7 +80,7 @@ func Stats(user User) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, ") · totals derive from your devices' sync pushes, not a stopwatch.</p><div class=\"flex gap-2 mt-4\" id=\"stats-tab-row\"><button data-tab=\"reading\" @click=\"selectTab('reading')\" class=\"stats-tab-btn btn btn-secondary text-sm\">Reading</button> <button data-tab=\"library\" @click=\"selectTab('library')\" class=\"stats-tab-btn btn btn-secondary text-sm\">Library</button></div></div><!-- READING tab — behavior stats from sync pushes (all libraries) --><div id=\"stats-reading-section\"><div class=\"card p-4 mb-6 flex flex-wrap items-center justify-between gap-3\"><div class=\"flex gap-2\" id=\"stats-range-selector\"><button data-range=\"7\" @click=\"selectRange('7')\" class=\"stats-range-btn btn btn-secondary text-sm\">Last 7 days</button> <button data-range=\"30\" @click=\"selectRange('30')\" class=\"stats-range-btn btn btn-secondary text-sm\">30 days</button> <button data-range=\"90\" @click=\"selectRange('90')\" class=\"stats-range-btn btn btn-secondary text-sm\">90 days</button> <button data-range=\"year\" @click=\"selectRange('year')\" class=\"stats-range-btn btn btn-secondary text-sm\">This year</button></div><p class=\"text-xs\" style=\"color: var(--text-secondary)\" id=\"stats-window-label\"></p></div><div class=\"grid grid-cols-2 lg:grid-cols-5 gap-4 mb-8\"><div class=\"stat-card\"><h3 class=\"text-xs font-semibold uppercase tracking-wide mb-2\" style=\"color: var(--text-secondary)\">Reading Time</h3><p id=\"stats-total-minutes\" class=\"text-3xl font-bold\" style=\"color: var(--accent)\">–</p></div><div class=\"stat-card\"><h3 class=\"text-xs font-semibold uppercase tracking-wide mb-2\" style=\"color: var(--text-secondary)\" id=\"stats-pages-card-label\">Pages</h3><p id=\"stats-total-pages\" class=\"text-3xl font-bold\" style=\"color: var(--accent)\">–</p></div><div class=\"stat-card\"><h3 class=\"text-xs font-semibold uppercase tracking-wide mb-2\" style=\"color: var(--text-secondary)\">Streaks</h3><p id=\"stats-streaks\" class=\"text-lg font-bold leading-tight mt-1\" style=\"color: var(--text-primary)\">–</p></div><div class=\"stat-card\"><h3 class=\"text-xs font-semibold uppercase tracking-wide mb-2\" style=\"color: var(--text-secondary)\">Books Finished</h3><p id=\"stats-books-finished\" class=\"text-3xl font-bold\" style=\"color: var(--accent)\">–</p></div><div class=\"stat-card\"><h3 class=\"text-xs font-semibold uppercase tracking-wide mb-2\" style=\"color: var(--text-secondary)\">Most Active</h3><p id=\"stats-most-active\" class=\"text-sm font-bold leading-snug mt-2\" style=\"color: var(--text-primary)\">–</p></div></div><div class=\"grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8\"><div class=\"card p-6\"><h3 class=\"text-sm font-semibold uppercase tracking-wide mb-4\" style=\"color: var(--text-secondary)\">Daily Reading Minutes</h3><div class=\"h-64\"><canvas id=\"stats-daily-minutes-chart\"></canvas></div></div><div class=\"card p-6\" id=\"stats-pages-chart-panel\"><h3 class=\"text-sm font-semibold uppercase tracking-wide mb-4\" style=\"color: var(--text-secondary)\" id=\"stats-pages-chart-label\">Pages per Day</h3><div class=\"h-64\"><canvas id=\"stats-daily-pages-chart\"></canvas></div></div><div class=\"card p-6 hidden\" id=\"stats-listening-chart-panel\"><h3 class=\"text-sm font-semibold uppercase tracking-wide mb-4\" style=\"color: var(--text-secondary)\">Listening per Day (hrs)</h3><div class=\"h-64\"><canvas id=\"stats-daily-listening-chart\"></canvas></div></div></div><div class=\"grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8\"><div class=\"card p-6\"><h3 class=\"text-sm font-semibold uppercase tracking-wide mb-4\" style=\"color: var(--text-secondary)\">By Library</h3><div id=\"stats-library-breakdown\" class=\"space-y-3\"><div class=\"text-center py-6 text-sm\" style=\"color: var(--text-secondary)\">Loading…</div></div></div><div class=\"card p-6\"><h3 class=\"text-sm font-semibold uppercase tracking-wide mb-4\" style=\"color: var(--text-secondary)\">By Device</h3><p class=\"text-xs mb-3\" style=\"color: var(--text-secondary)\">Where the sync pushes came from — registered device names, plus a Web bucket for pushes that carry no device credential (the web reader).</p><div id=\"stats-device-breakdown\" class=\"space-y-3\"><div class=\"text-center py-6 text-sm\" style=\"color: var(--text-secondary)\">Loading…</div></div></div><div class=\"card p-6\"><h3 class=\"text-sm font-semibold uppercase tracking-wide mb-4\" style=\"color: var(--text-secondary)\">Most Read</h3><div id=\"stats-top-books\" class=\"space-y-3\"><div class=\"text-center py-6 text-sm\" style=\"color: var(--text-secondary)\">Loading…</div></div></div></div><div id=\"stats-empty\" class=\"card p-8 text-center hidden\"><p class=\"text-sm mb-2\" style=\"color: var(--text-primary)\">No reading sessions recorded in this range yet.</p><p class=\"text-xs\" style=\"color: var(--text-secondary)\">Sessions are captured every time a deviLine truncated
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, ") · totals derive from your devices' sync pushes, not a stopwatch.</p><div class=\"flex gap-2 mt-4\" id=\"stats-tab-row\"><button data-tab=\"reading\" @click=\"selectTab('reading')\" class=\"stats-tab-btn btn btn-secondary text-sm\">Reading</button> <button data-tab=\"library\" @click=\"selectTab('library')\" class=\"stats-tab-btn btn btn-secondary text-sm\">Library</button></div></div><!-- READING tab — behavior stats from sync pushes (all libraries) --><div id=\"stats-reading-section\"><div class=\"card p-4 mb-6 flex flex-wrap items-center justify-between gap-3\"><div class=\"flex flex-wrap gap-2\" id=\"stats-range-selector\"><button data-range=\"7\" @click=\"selectRange('7')\" class=\"stats-range-btn btn btn-secondary text-sm\">Last 7 days</button> <button data-range=\"30\" @click=\"selectRange('30')\" class=\"stats-range-btn btn btn-secondary text-sm\">30 days</button> <button data-range=\"90\" @click=\"selectRange('90')\" class=\"stats-range-btn btn btn-secondary text-sm\">90 days</button> <button data-range=\"year\" @click=\"selectRange('year')\" class=\"stats-range-btn btn btn-secondary text-sm\">This year</button></div><p class=\"text-xs\" style=\"color: var(--text-secondary)\" id=\"stats-window-label\"></p></div><div class=\"grid grid-cols-2 lg:grid-cols-5 gap-4 mb-8\"><div class=\"stat-card\"><h3 class=\"text-xs font-semibold uppercase tracking-wide mb-2\" style=\"color: var(--text-secondary)\">Reading Time</h3><p id=\"stats-total-minutes\" class=\"text-3xl font-bold\" style=\"color: var(--accent)\">–</p></div><div class=\"stat-card\"><h3 class=\"text-xs font-semibold uppercase tracking-wide mb-2\" style=\"color: var(--text-secondary)\" id=\"stats-pages-card-label\">Pages</h3><p id=\"stats-total-pages\" class=\"text-3xl font-bold\" style=\"color: var(--accent)\">–</p></div><div class=\"stat-card\"><h3 class=\"text-xs font-semibold uppercase tracking-wide mb-2\" style=\"color: var(--text-secondary)\">Streaks</h3><p id=\"stats-streaks\" class=\"text-lg font-bold leading-tight mt-1\" style=\"color: var(--text-primary)\">–</p></div><div class=\"stat-card\"><h3 class=\"text-xs font-semibold uppercase tracking-wide mb-2\" style=\"color: var(--text-secondary)\">Books Finished</h3><p id=\"stats-books-finished\" class=\"text-3xl font-bold\" style=\"color: var(--accent)\">–</p></div><div class=\"stat-card\"><h3 class=\"text-xs font-semibold uppercase tracking-wide mb-2\" style=\"color: var(--text-secondary)\">Most Active</h3><p id=\"stats-most-active\" class=\"text-sm font-bold leading-snug mt-2\" style=\"color: var(--text-primary)\">–</p></div></div><div class=\"grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8\"><div class=\"card p-6\"><h3 class=\"text-sm font-semibold uppercase tracking-wide mb-4\" style=\"color: var(--text-secondary)\">Daily Reading Minutes</h3><div class=\"h-64\"><canvas id=\"stats-daily-minutes-chart\"></canvas></div></div><div class=\"card p-6\" id=\"stats-pages-chart-panel\"><h3 class=\"text-sm font-semibold uppercase tracking-wide mb-4\" style=\"color: var(--text-secondary)\" id=\"stats-pages-chart-label\">Pages per Day</h3><div class=\"h-64\"><canvas id=\"stats-daily-pages-chart\"></canvas></div></div><div class=\"card p-6 hidden\" id=\"stats-listening-chart-panel\"><h3 class=\"text-sm font-semibold uppercase tracking-wide mb-4\" style=\"color: var(--text-secondary)\">Listening per Day (hrs)</h3><div class=\"h-64\"><canvas id=\"stats-daily-listening-chart\"></canvas></div></div></div><div class=\"grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8\"><div class=\"card p-6\"><h3 class=\"text-sm font-semibold uppercase tracking-wide mb-4\" style=\"color: var(--text-secondary)\">By Library</h3><div id=\"stats-library-breakdown\" class=\"space-y-3\"><div class=\"text-center py-6 text-sm\" style=\"color: var(--text-secondary)\">Loading…</div></div></div><div class=\"card p-6\"><h3 class=\"text-sm font-semibold uppercase tracking-wide mb-4\" style=\"color: var(--text-secondary)\">By Device</h3><p class=\"text-xs mb-3\" style=\"color: var(--text-secondary)\">Where the sync pushes came from — registered device names, plus a Web bucket for pushes that carry no device credential (the web reader).</p><div id=\"stats-device-breakdown\" class=\"space-y-3\"><div class=\"text-center py-6 text-sm\" style=\"color: var(--text-secondary)\">Loading…</div></div></div><div class=\"card p-6\"><h3 class=\"text-sm font-semibold uppercase tracking-wide mb-4\" style=\"color: var(--text-secondary)\">Most Read</h3><div id=\"stats-top-books\" class=\"space-y-3\"><div class=\"text-center py-6 text-sm\" style=\"color: var(--text-secondary)\">Loading…</div></div></div></div><div id=\"stats-empty\" class=\"card p-8 text-center hidden\"><p class=\"text-sm mb-2\" style=\"color: var(--text-primary)\">No reading sessions recorded in this range yet.</p><p class=\"text-xs\" style=\"color: var(--text-secondary)\">Sessions are captured every tLine truncated
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}