Commit graph

33 commits

Author SHA1 Message Date
Barely Removable
2a4236efea Drop a redundant inline margin from the Storage fallback note
All checks were successful
Deploy / deploy (push) Successful in 24s
The container-disk-usage note carried style="margin-bottom:0", which
326ab6c had already covered with `.meter-legend + .empty`. The <p> is
that rule's adjacent sibling in the rendered output (the Jinja {% if %}
around it emits no element), so the inline copy was doing nothing and
violated CLAUDE.md's no-static-inline-style rule for no benefit.

Verified the computed margin-bottom is still 0 with the attribute gone.
The eight remaining inline styles are the kinds the rule allows —
Jinja-computed meter widths and threshold colours, and display:none on
elements dashboard.js reveals.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 08:11:27 -07:00
Barely Removable
3c931873b1 Keep the status pills on exactly one row at every width
All checks were successful
Deploy / deploy (push) Successful in 21s
The strip wrapped to two or three rows on narrow screens. It's now
nowrap with flex: 1 1 0 pills, so all six share the row equally and
shrink together instead of spilling onto a second line.

Six pills can't hold full detail in 375px, so detail degrades inside
the pill rather than the strip breaking: the stat drops at 1024px (the
dot already carries up/down), then below 560px the name swaps to a
short label — Son, Rad, Prow, SAB, Taut, Ombi. Without that swap the
names ellipsised to ~23px, about two characters, which identifies
nothing. The full name stays in the DOM at font-size: 0 so screen
readers still announce it, and it's on the pill's title attribute.

The dot and the pill itself never go, so no service is ever hidden —
which was the point of the old wrapping rule.

Verified one row with nothing clipped and no page overflow at 320, 375,
430, 560, 620, 700, 820, 1024, 1100 and 1400px.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 08:06:31 -07:00
Barely Removable
41205be83c Bump to v1.5.0 for deploy
All checks were successful
Deploy / deploy (push) Successful in 23s
Covers the Status section move; the date is unchanged since 1.4.0 hasn't
left today.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 08:00:03 -07:00
Barely Removable
377524c037 Move the service pills out of the header into a Status section
All checks were successful
Deploy / deploy (push) Successful in 22s
The pills occupied a second full-width row of the header at every width,
which is a lot of permanent chrome for something you glance at. They now
open <main> as their own "Status" section, directly above Right now —
where you'd look for them, and consistent with every other band on the
page carrying a heading.

The header drops to a single row (brand left, clock and settings right),
so it no longer needs flex-wrap or the order: 1/2/3 shuffle that kept
the pill row beneath the brand. Pills go to var(--tap) height now
they're content rather than header furniture, and left-align with every
other section instead of centring.

Behaviour is unchanged: the pills keep their svc-<key> ids, so the
polling in dashboard.js needed no edit, and they still wrap rather than
scroll so no service is ever hidden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 07:59:26 -07:00
Barely Removable
f572d95bd0 Make the listen port configurable via PORT
All checks were successful
Deploy / deploy (push) Successful in 22s
app.py's dev server and gunicorn both hardcoded 5000, which is awkward
locally — macOS ControlCenter already holds that port, so the app can't
bind on the dev laptop without editing source.

Both now read PORT, defaulting to 5000 so EXPOSE, the compose
`${HOST_PORT}:5000` mapping and the deployed container are unchanged.
Read at run time rather than import time so a dev harness can assign a
free port.

Also tracks .claude/launch.json with autoPort set. It doesn't actually
launch on this machine — the preview sandbox can't read files under
~/Desktop — so CLAUDE.md notes that running from a shell with an
explicit PORT is the working local path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 07:51:07 -07:00
Barely Removable
95b7ce7c03 Bump to v1.4.0 and document the local all-offline baseline
All checks were successful
Deploy / deploy (push) Successful in 24s
The footer version is the honest check that a deploy landed, so cut
1.4.0 for the Storage table.

Also record something that has now cost two sessions: the dev machine
isn't on the media stack's network, so every service is offline and the
live-poll endpoints 500 on a timer. That's the normal local baseline,
not a regression. Notes the monkey-patch pattern for exercising a card's
real rendering, plus the two traps that go with it — Jinja's auto_reload
follows app.debug (off), so template edits need a restart rather than a
browser reload, and LOGIN_PASSWORD has to be set explicitly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 07:46:29 -07:00
Barely Removable
08c71ba68d Give Storage its own row and rebuild the disk list as a table
All checks were successful
Deploy / deploy (push) Successful in 23s
Ten disks stacked at --row-h each turned one third of the "Right now"
strip into a ~950px tower. The row is a grid, so Now playing and
Downloads stretched to match and held roughly 1500px of empty card
between them.

The information needed width, not a shorter list. "Right now" drops to
two columns and Storage moves to a full-width row beneath it, where the
same ten disks are a six-column table — disk, used, size, free, usage
bar, temp — at ~270px total. Nothing is hidden and nothing scrolls
except the table itself, which gets its own overflow container below
720px rather than wrapping the sizes into mush.

Also: the array total moves to a bar above the table and the summary
into the card header, so the hero figure isn't repeating what every row
already says. The header summary is a sentence rather than a badge, so
it needs .card-meta-wrap to opt out of the global nowrap — without it
that one string pushed the whole page sideways on a phone.

Local-fallback (no per-disk data) and error states keep the previous
hero-figure treatment. CLAUDE.md now carries the dead-space rule this
came from.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 06:52:34 -07:00
Barely Removable
326ab6cee5 Move remaining static inline styles into dashboard.css, bump Now Playing text size
Three inline style="..." attributes were pure styling with no per-request
data behind them (a hero-block spacing tweak, a trailing note's margin, a
gap between two stacked rows) — moved each into dashboard.css as a scoped
structural selector so future adjustments happen in one place instead of
being split between the template and the stylesheet.

Also bumped .stream-title/.stream-user/.stream-icon in the Now Playing
card one step up the existing --fs-* scale (13px->15px, 12px->13px,
15px->18px) rather than introducing a one-off size.

Documented the "styles belong in CSS, not inline" rule in CLAUDE.md,
with the actual exception: per-request computed values (a meter's width,
a threshold colour) and JS-toggled initial visibility still can't live
in a static stylesheet and are fine to stay inline.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 06:27:43 -07:00
Barely Removable
1cc5a247cf Fetch per-disk temp and spin state for the Storage card
All checks were successful
Deploy / deploy (push) Successful in 28s
The GraphQL query only asked for aggregate array fsSize/fsFree, so the
per-disk breakdown added in templates/dashboard.html (commit 5045316)
had no data to render. Now pulls name/device/temp/isSpinning for data
disks, cache disks, and parity disks, and shapes them into the
disks/caches/parities lists the template expects.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 06:21:41 -07:00
Barely Removable
5045316fa5 Match Ombi's Show more button to the request pill height
The button used the default 38px .show-more height, shorter than the
68px (--row-h) request pills above it, so it read as a smaller
tacked-on control rather than one more row in the list. Scoped to a
new .show-more-row modifier rather than changing .show-more globally —
the Discover cards' Show more buttons sit under much taller RT items
and are correctly compact at 38px.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 06:19:41 -07:00
Barely Removable
4b56f1fba9 Bump to v1.3.0 for deploy
All checks were successful
Deploy / deploy (push) Successful in 29s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 23:03:35 -07:00
Barely Removable
bd292fbe32 Rebuild request rows to match indexer rows
The Requests and Indexers cards sit side by side but read as two
unrelated lists: requests led with a type badge, indexers with a status
dot, and the rows drifted out of alignment once the share bar landed.

Give a request row the same anatomy as an indexer row — status dot,
title over a dimmed detail line, badge on the right — folding the type
into the detail line and mapping request status onto the existing
dot colours (a new .warn light covers pending). Both row types now
share a --row-h floor so the two cards line up row for row; the share
bar's top margin is trimmed so an indexer row reaches that height
without overshooting it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 23:02:36 -07:00
Barely Removable
909a0b568c Show per-indexer grab share and rebuild the Indexers card as a list
All checks were successful
Deploy / deploy (push) Successful in 26s
The Indexers card sat beside Requests as a stretched, vertically centred
grid of pills — mostly dead space once the Ombi list was expanded, and it
said nothing about which indexer was actually doing the work.

- Fetch Prowlarr's indexerstats over a rolling 30-day window and merge
  per-indexer queries/grabs into the summary. Cached 10 min on success and
  never on failure, so a stats outage can't blank the column after it
  recovers or take the up/down list down with it.
- Rank indexers busiest-first by grab share (query share as fallback);
  queries mostly count enabled indexers, grabs count who delivered.
- One indexer per line, matched to the Ombi request rows: same padding,
  gap, radius and two-line body, plus a share bar scaled to the busiest
  indexer and an Online/Offline badge the 30s poll keeps current.
- Let that row size cards to their own content instead of stretching.

Bump APP_VERSION to 1.2.0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 22:28:39 -07:00
Barely Removable
2cc62979a2 Update CLAUDE.md for the static-asset split and version footer
All checks were successful
Deploy / deploy (push) Successful in 40s
The architecture notes still described one :root token block shared
inside the three templates, which stopped being true when CSS and JS
moved to static/. Document base.css as the single source of truth, the
four-hue + color-mix rule that replaced hardcoded literals, and the
<body data-*> convention for passing server config to JS.

Also documents the footer version line under the container-restart
section it exists to serve — it is the quickest honest check that a
deploy landed, given the Unraid Docker tab's cached container IDs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 14:36:14 -07:00
Barely Removable
fd973dd3f7 Unstick the header and give service pills their own row
All checks were successful
Deploy / deploy (push) Successful in 51s
Letting the six pills share row one with the brand and clock meant that
once they stopped fitting, the header wrapped into ragged rows and
orphaned the clock onto a line of its own. Commit to two deliberate rows
at every width instead: identity and controls on top, pills centered on
a full-width row beneath. The <=700px block already did exactly this, so
this promotes that established pattern to all widths and drops the rules
it no longer needs to restate.

Also drops position:sticky — a status bar isn't worth permanent screen
real estate, and the header now scrolls away with the page.

Verified at 1554/1000/375px: brand and controls share a row, 0 pills
clipped, no horizontal page overflow, position computes to static.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 14:32:52 -07:00
Barely Removable
18c7719cd7 Extract CSS/JS to static files, wrap header pills, add version footer
All checks were successful
Deploy / deploy (push) Successful in 23s
The palette lived in a :root block duplicated across all three
templates, so every reshade meant three edits — and drifted twice in
practice (the favicon hardcoded its own hex; stale danger-border rgba
literals survived two palette changes). Move tokens to a single
static/css/base.css, derive every dim/border tint from the four source
hues with color-mix, and point the favicon at one static/favicon.svg.

Also splits the 1558-line dashboard.html into template + static assets:
662 lines of CSS and 396 of JS were re-sent uninlined on every load
with no browser caching, which costs real time on the remote/cellular
access this app is built for. Templates drop to 501/42/107 lines.
Server-rendered config (timezone, script_root) now arrives via
<body data-*>; wiring URL_BASE to request.script_root also fixes /api/*
fetches for a future subpath mount, which the hardcoded "" would break.

Header service pills now wrap instead of scrolling — they were clipped
at narrow widths, and a hidden pill is a service whose status you
cannot see. Verified 0 clipped and no page overflow at 320/375/700/1280.

Footer gains a version and date so the running build is identifiable
without trusting the Unraid Docker tab's cached container IDs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 14:27:52 -07:00
Barely Removable
faf25a811a Make Watch insights cards match in height
All checks were successful
Deploy / deploy (push) Successful in 22s
Most played rows carry a two-line title+username layout while Top
viewers/TV/movies rows are single-line, so the two card rows ended up
visibly different heights even though each pair matched within its own
row. Give .top-item a fixed min-height so every row — and therefore
every card, all four hold exactly 3 items — lines up the same.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 14:04:47 -07:00
Barely Removable
0d63339490 Keep Requests/Indexers cards always equal height
All checks were successful
Deploy / deploy (push) Successful in 23s
Revert to the default grid stretch for this row so Ombi and Prowlarr
always match height exactly, and center the indexer grid vertically
within the taller Prowlarr card so any leftover space splits evenly
top/bottom instead of collecting as dead space at the bottom.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 14:03:26 -07:00
Barely Removable
ecf3209abd Enlarge Prowlarr indexer pills
All checks were successful
Deploy / deploy (push) Successful in 21s
Bigger padding, font size, and status dot, plus a wider minimum column,
so the indexer grid reads less cramped next to the Ombi request list.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 14:01:17 -07:00
Barely Removable
2e58d9e26f Cap Ombi requests to 5 with a Show more toggle
Some checks failed
Deploy / deploy (push) Has been cancelled
An unbounded request list made the Ombi card grow far taller than the
Prowlarr indexer grid beside it, leaving the row looking lopsided even
after the earlier align-items fix. Reuse the same show-more pattern
already used for the Rotten Tomatoes lists instead of an internal
scrollbar, so both cards land at a comparable height.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 14:00:54 -07:00
Barely Removable
bf10f438d2 Rework dashboard color palette and fix Prowlarr card layout
All checks were successful
Deploy / deploy (push) Successful in 25s
Switch from the warm graphite/orange theme to a cooler charcoal/indigo
palette with a red (not pink/purple) danger color, and restyle error
banners to sit on the normal card surface with an accent stripe instead
of washing the whole box in color. Also fix the Requests/Indexers row
stretching the Prowlarr card to match Ombi's longer request list,
leaving a large blank area under the indexer grid.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 13:26:56 -07:00
Barely Removable
a30cb4a30e Document the reverse proxy setup and container-restart gotcha
All checks were successful
Deploy / deploy (push) Successful in 22s
Records what wasn't discoverable from the repo alone after today's
outage: Cox blocks inbound port 80 so certs must use DNS-01, the apex
proxy host's destination has drifted before, the NPM UI can show a cert
as valid/in-use while nginx presents none on disk, and restarting via
the Unraid Docker UI throws "No such container" after every deploy
because Compose gives the container a fresh ID each time.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 13:02:52 -07:00
Barely Removable
600a48da93 Persist settings.json across deploys via a data volume
All checks were successful
Deploy / deploy (push) Successful in 21s
The previous commit made Settings-saved API keys override .env, but the
key still never survived: settings.json was written to the container
filesystem, and every deploy runs `up -d --build`, which recreates the
container and discards it. Saving a rotated SABnzbd key and restarting
therefore still fell back to the stale .env value.

SETTINGS_FILE is now configurable and pointed at /data/settings.json on
a ./data bind mount, so runtime settings live on the host. The deploy
rsync also excludes data/ — it runs --delete and would otherwise wipe
the directory for not being in the repo, reintroducing the same bug.

This affected everything the Settings page writes, not just API keys:
timezone and a UI-changed login password were being reverted on every
deploy too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 09:34:46 -07:00
Barely Removable
20f348eb9e Fix Settings-saved API keys never actually being used
All checks were successful
Deploy / deploy (push) Successful in 22s
Every *_API_KEY was read from the env var once at startup and never
checked against settings.json again, so saving a new key on the
Settings page updated the file but had zero effect on real requests —
reported live as "SABnzbd rejected the API key" right after rotating
and saving a new one there. A saved key now overrides the .env value
at startup, matching what the Settings page's own toast already says
("restart the container to apply").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 09:29:39 -07:00
Barely Removable
018bc58bbf Switch palette to graphite & orange
All checks were successful
Deploy / deploy (push) Successful in 27s
Replaces the slate/indigo palette with a warmer graphite base and an
orange accent (#e07a35), per user's pick from a side-by-side comparison
of five directions. Same token structure, same contrast bar (all pairs
still >= 4.5:1) — only the hex values changed, across dashboard, login
and settings plus the three embedded favicons.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 08:20:40 -07:00
Barely Removable
339b9f3310 Redesign dashboard: single neutral theme, urgency-ordered layout
Replaces the five novelty themes with one professional dark theme built
on a shared token set (surfaces, 6-step type scale, semantic colours,
44px tap target) used across dashboard, login and settings.

Layout now matches the cache tiers in app.py: a "Right now" strip with
live streams, the download queue and array usage sits above the fold,
with library, discovery, requests and watch insights below. Previously
the page opened on rarely-actionable disk stats and buried active
streams under twenty recommendation rows.

Also from the design review:
- Discover lists show 5 with a Show more toggle instead of 10 each
- RT descriptions render inline and clamped, not hover-only tooltips
  that touch devices could never reach
- Adding a title is now two-step (tap to arm, tap to confirm) since it
  queues a real download
- role=alert on errors, aria-live on polled regions, semantic dl/dt/dd
  stats, labelled search inputs
- every text/surface pair verified at >= 5:1 contrast

Drops the dead delete-modal JS, which threw a TypeError on every load,
and the stale root-level template copies that Flask never rendered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 08:12:41 -07:00
Barely Removable
951914dd69 Redact API keys from error technical-details disclosure
All checks were successful
Deploy / deploy (push) Successful in 27s
SABnzbd and Tautulli embed their API key in the request URL, so it
was showing up verbatim in the raw exception text under "Technical
details" on the dashboard — visible to anyone with the study-room
password. Added a redact_secrets filter that strips apikey/token/
password query params before the raw string is rendered.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 05:11:28 -07:00
Barely Removable
2e1f8efa33 Fix unreachable mobile Settings link and raw error tracebacks
All checks were successful
Deploy / deploy (push) Successful in 2m6s
Settings lived inside .header-right, which is hidden below 600px,
leaving no way to reach it on mobile. It's now a standalone 44x44px
button next to the title.

Dashboard error cards showed raw Python/urllib3 exception strings.
Added a friendly_error() filter that classifies the failure (timeout,
connection refused, DNS, auth, 4xx/5xx, TLS) into plain-English copy,
with the raw string still available behind a details disclosure.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 23:59:07 -07:00
9804283396 Add CLAUDE.md documenting architecture and deploy pipeline
All checks were successful
Deploy / deploy (push) Successful in 1m54s
2026-08-25 23:36:06 -07:00
5baba883f1 Install rsync in job container before sync step
All checks were successful
Deploy / deploy (push) Successful in 34s
2026-08-25 23:23:34 -07:00
33a8654c73 Retrigger deploy after runner mount fix
Some checks failed
Deploy / deploy (push) Failing after 13s
2026-08-25 23:15:44 -07:00
54c0598582 Add Forgejo deploy workflow
Some checks failed
Deploy / deploy (push) Failing after 13s
2026-08-25 22:59:56 -07:00
c1e30bc8f0 Initial commit: Arr Summary source from server 2026-08-25 22:59:31 -07:00