Redesign Media Centre as a teletext broadcast information service
Replaces the neutral dark card-based theme with "Page 100": teletext's
locked eight colours at full saturation on pure black, numbered pages
reachable by typing the number (or the r/g/y/c fastext keys), and no
borders, boxes, or radius anywhere — grouping is carried by full-bleed
colour fields and row weight instead.
Chosen via the impeccable skill's replacement-world flow: seven visual
candidates derived from the audience's own world, a mandatory roll
assigned Ceefax/US-cable-guide teletext, six catalog challengers weighed
against it, and the user locked the assigned direction on the decision
page (seed aab4510e).
- New templates for all nine pages (home/watch/library/discover/
requests/system/activity/login/settings) behind a shared _shell.html
carrying the direction contract and _macros.html for the row/bar/tile
vocabulary; deletes the old single-page dashboard.html and its
dedicated stylesheets/script.
- static/css/base.css + teletext.css: the locked palette, a two-height
type scale (Chivo Mono / DotGothic16), and the five disciplines the
system is held to (no in-between tone, no enclosure, one shared bar
ruler, trend only where real history exists, pages weighted by state).
- static/js/service.js: header clock, page-number jump, fastext keys,
two-tap arm/confirm add, guarded live polling — replaces dashboard.js.
- app.py: nav_items now carry page numbers; fastext defaults added to
the page-chrome context processor; version bumped to 2.0.0.
- DESIGN.md + .impeccable/design.json regenerated from the built world
by the documenter pass; PRODUCT.md and CLAUDE.md updated to describe
the new system in place of the retired one.
- Fixed a live bug found along the way: settings.html called
url_for('dashboard'), an endpoint removed earlier in the branch, which
would have 500'd the Settings page.
- Fixed a One-Ruler violation the finish review's recapture request
surfaced: --bar-cells disagreed with the bar() macro's hardcoded cell
count below the 760px breakpoint, so a fill could land mid-cell on a
phone. Removed the mobile override; the ruler is now 20 cells at every
width, matching the macro unconditionally.
Passed two inspection rounds (desktop + true-390px mobile, via an
iframe harness since macOS Chrome clamps --window-size) and a finish
review; QA screenshots and interview logs are gitignored as scratch,
not shipped in the repo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBw1QodG8ZMYHrGeQfqyDS
This commit is contained in:
parent
9a277b9031
commit
4b19cbdfb2
25 changed files with 3002 additions and 2343 deletions
129
CLAUDE.md
129
CLAUDE.md
|
|
@ -3,8 +3,13 @@
|
|||
Flask dashboard aggregating status from the user's self-hosted media stack
|
||||
— Sonarr, Radarr, SABnzbd, Tautulli, Prowlarr, Ombi — plus Unraid array
|
||||
stats and a Rotten Tomatoes "what's popular that I don't have yet" feed.
|
||||
Branded "Media Centre" — a single neutral, professional dark theme (the
|
||||
earlier five novelty themes were removed). Runs as one Docker container on
|
||||
Branded "Media Centre". Since v2.0.0 (2026-08-29) it presents as a
|
||||
**broadcast information service** in the teletext tradition: numbered
|
||||
pages you reach by typing the number, status carried by colour, and no
|
||||
borders or cards anywhere. This replaced — not refined — the earlier
|
||||
neutral dark card theme, which had itself replaced five novelty themes.
|
||||
See DESIGN.md for the visual system and the `static/css/base.css` header
|
||||
for the five disciplines it is held to. Runs as one Docker container on
|
||||
the user's Unraid home server. Server-side project directory is still named
|
||||
`greendale`; the container/repo are named `arr-summary`.
|
||||
|
||||
|
|
@ -33,66 +38,72 @@ the user's Unraid home server. Server-side project directory is still named
|
|||
- Rotten Tomatoes / IMDB are scraped directly (`requests` +
|
||||
`BeautifulSoup`, JSON-LD parsing), not via an official API — brittle
|
||||
to upstream HTML changes by nature.
|
||||
- **`templates/`** — Jinja2 templates (`dashboard.html`, `login.html`,
|
||||
`settings.html`). Stale duplicates of these once sat at the repo root;
|
||||
they were never rendered (Flask loads from `templates/`) and were deleted
|
||||
during the redesign.
|
||||
- **`templates/`** — Jinja2 templates. `_shell.html` is the root layout
|
||||
every page extends; `_macros.html` holds the shared fragments. Pages are
|
||||
`home.html` (P100), `watch.html` (101), `library.html` (102),
|
||||
`discover.html` (103), `requests.html` (104), `system.html` (105),
|
||||
`activity.html` (106), `settings.html` (900), plus `login.html`, which
|
||||
stands alone. The old single-page `dashboard.html` was deleted in the
|
||||
v2.0.0 redesign along with `dashboard.css`, `login.css`, `settings.css`
|
||||
and `dashboard.js`.
|
||||
- Templates carry markup only. CSS and JS live in `static/` and are
|
||||
linked via `url_for('static', ...)` so they stay correct behind a
|
||||
`SCRIPT_NAME` subpath. Server-rendered config (timezone, script root)
|
||||
reaches JS through `<body data-*>`, never string-interpolated into a
|
||||
script block.
|
||||
`SCRIPT_NAME` subpath. Server-rendered config (timezone, script root,
|
||||
the page-number map) reaches JS through `<body data-*>`, never
|
||||
string-interpolated into a script block.
|
||||
- **No `style="..."` attributes for anything static.** A one-off tweak
|
||||
(button height, a margin, a font size) belongs in the matching
|
||||
`static/css/*.css` file, scoped with a structural selector (an
|
||||
adjacent-sibling or `:last-child` rule tied to the surrounding markup)
|
||||
or a small modifier class — not inline on the element. The only
|
||||
legitimate inline `style=` left in `dashboard.html` are per-request
|
||||
computed values Jinja has to interpolate (a meter's `width: {{ pct
|
||||
}}%`, a colour chosen by threshold) and JS-toggled initial visibility
|
||||
(`display:none` on something `dashboard.js` reveals) — those can't
|
||||
live in a static stylesheet. Everything else should read as a class
|
||||
name, not a value.
|
||||
- **`static/css/base.css`** — the single source of truth for the design
|
||||
tokens, shared by all three templates: surfaces, a 6-step `--fs-*` type
|
||||
scale, semantic colours, and `--tap: 44px` for minimum touch targets.
|
||||
Only four hues are declared (`--accent`, `--ok`, `--warn`, `--danger`);
|
||||
every dim/border tint derives from them via `color-mix`. Reshading the
|
||||
app should be a four-line edit here — never hardcode a colour literal in
|
||||
a page stylesheet, which is exactly how the palette drifted before
|
||||
(a favicon with its own baked-in hex, and stale `rgba()` danger borders
|
||||
that survived two palette changes). Page-specific rules live alongside
|
||||
in `dashboard.css` / `login.css` / `settings.css`.
|
||||
- `dashboard.html` is ordered by urgency, deliberately mirroring the cache
|
||||
tiers in `app.py`: a "Right now" strip (live streams, download queue,
|
||||
array usage) sits above the fold, with library, discovery, requests and
|
||||
watch insights below it.
|
||||
- **Cards in a row must earn their height — never leave dead space.** A
|
||||
`.row` is a CSS grid, so every card stretches to the tallest one in that
|
||||
row. A card whose content outgrows its neighbours doesn't just get tall,
|
||||
it hollows out everything beside it. This has bitten once already: the
|
||||
per-disk Storage list stacked ten disks at `--row-h` each, turning one
|
||||
third of the "Right now" strip into a ~950px tower and leaving ~1500px of
|
||||
empty card across Now playing and Downloads.
|
||||
- Before adding a repeating list to a card in a multi-column row, check
|
||||
what the row's other cards hold at their **quietest** — one stream, an
|
||||
idle queue, zero pending requests — and keep the tallest card within
|
||||
roughly 1.5× of that.
|
||||
- When the content genuinely won't fit, the fix is **more width**: give
|
||||
the card its own full-width row (`grid-template-columns: 1fr`) and lay
|
||||
its list out in columns inside, or use more columns within the existing
|
||||
card. Ten items down one narrow column is three tidy rows of tiles once
|
||||
the card spans the page.
|
||||
- The fix is **never** an inner scroll area, a truncated list, or content
|
||||
hidden behind a toggle the user has to find — see the "nothing clipped
|
||||
or hidden" rule the header pills already follow.
|
||||
- Where a short card still can't be avoided, centre its content
|
||||
vertically rather than reaching for `align-items: start`; equal-height
|
||||
cards are a settled preference.
|
||||
- Degraded services render through the `service_error()` macro, which
|
||||
pairs `friendly_error` (human cause) with the raw exception behind a
|
||||
disclosure, passed through `redact_secrets` — SABnzbd and Tautulli put
|
||||
their API key in the request URL, so it appears in raw tracebacks.
|
||||
belongs in `static/css/teletext.css`, scoped with a structural selector
|
||||
or a small modifier class. The only legitimate inline `style=` is a
|
||||
per-request computed value Jinja has to interpolate — currently just
|
||||
the `bar()` macro's fill width.
|
||||
- `_shell.html`'s first `<body>` child is an HTML comment holding the
|
||||
**direction contract** for the visual world (thesis, own-world, story,
|
||||
first viewport, form, seed key). Leave it in place; it is what a later
|
||||
session audits the design against.
|
||||
- **`static/css/base.css`** — tokens and reset. Its header comment states
|
||||
the **five disciplines** the visual world is held to; read them before
|
||||
changing anything visual, and treat breaking one as a regression rather
|
||||
than a variation:
|
||||
1. **No in-between tone.** There are eight colours (the teletext
|
||||
primaries). A mid-grey is an ordered dither of two of them, never a
|
||||
ninth colour.
|
||||
2. **No enclosure.** Nothing is surrounded by a border or a box. There is
|
||||
zero `border-radius` in the codebase. Grouping is a coloured field row
|
||||
(`.tt-field`), indentation, and weight. This is the single thing the
|
||||
world most refuses — the previous design was built on bordered cards.
|
||||
3. **One ruler.** Every bar and percentage shares one scale
|
||||
(`--bar-cells`) and is drawn only by the `bar()` macro in
|
||||
`_macros.html`, so 60% on the array and 60% on an indexer are the same
|
||||
width and compare directly. Never restyle a bar per section.
|
||||
4. **Every reading carries its direction.** A value shows where it is
|
||||
heading. Only drawn where the backend genuinely holds a comparison —
|
||||
today that is SABnzbd speed against its stored 7-day average, and
|
||||
nowhere else. Do not invent a trend for data with no history.
|
||||
5. **Pages differ in stock.** A page holding critical state is set
|
||||
heavier than a quiet one (`.tt-field.warn` / `.tt-field.crit`).
|
||||
Uniform texture across all the pages is a failure.
|
||||
- Colour is **semantic, never decorative**: yellow is the service's own
|
||||
voice, cyan is a live value, green is healthy, red is off air, magenta
|
||||
means a human must decide (pending requests only), blue is a field
|
||||
ground. Reshading means editing the eight tokens here, nothing
|
||||
downstream.
|
||||
- Type is **one face at exactly two heights** — `--fs` (15px) and `--fs2`
|
||||
(30px, double height). Adding a third size breaks the system. The 16px
|
||||
literal on inputs is deliberate: it prevents iOS zoom-on-focus.
|
||||
- `--ink-dim` (58% white) is the **only** dimmed token allowed to carry
|
||||
text; it clears 4.5:1 on black. `--ink-dimmer` (30%) is for graphics
|
||||
only — bar troughs, the dither field, separators. Setting text in
|
||||
`--ink-dimmer` fails contrast, which is exactly the bug the redesign's
|
||||
first inspection round caught.
|
||||
- **Navigation is the page number.** Typing any three digits anywhere
|
||||
jumps to that page, and `r`/`g`/`y`/`c` fire the four fastext colour
|
||||
keys — both in `static/js/service.js`. `nav_items` in `app.py` is the
|
||||
source of truth for the numbers and is serialised into
|
||||
`<body data-pages>`.
|
||||
- **Nothing is clipped, hidden, or sticky.** All seven page numbers and
|
||||
all four fastext keys stay visible at every width, reflowing to another
|
||||
row rather than scrolling, truncating, or folding into a menu. No
|
||||
header is sticky.
|
||||
- **`export-to-unraid.sh`** — a pre-existing standalone deploy script
|
||||
(build image locally → tarball → rsync + `docker load` over SSH) that
|
||||
predates the Forgejo pipeline below. Still present, not removed, but the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue