Files
esh-pfi-infrastructure/persistent-memory.d/2026-08-19-homepage-skyfall-theme.md
T
vh bc3aada73a memory: snapshot — .internal DNS live, waterland containerised, homepage themed
Captures a long infra session: fleet *.internal DNS (git-sourced, 42 names,
three resolvers including a new colo one), waterland studio containerised on
irv-ml1, Homepage cleaned up and themed with Australis Skyfall over an
Arbo-generated background, and four unmanaged stacks adopted into stacks/.

Four detail files added. Auto-archived 4 entries to archival-memory.md
(Recent decisions 2, Tried and abandoned 2); 5 held back by the open-deferred
guard rather than moved.

Also records three operator-owned open items: the colo DNS repoint, the
static-v6 convention, and the deliberately belayed AI-tab Dormant regrouping.
2026-08-19 01:20:53 -07:00

146 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# `[2026-08-19]` Homepage cleaned up, then themed with Australis Skyfall + an Arbo-generated background
Commits `9d92c4b`, `c3de7db`, `45c1995`, `f38cf69`, `df68dd2`.
## The cleanup (three real defects)
- **UltraSeedbox rendered on all four tabs.** The bookmark group had no entry in
`settings.yaml`'s `layout:` block at all, and Homepage's documented behaviour
is that a group with no `tab:` is shown on **every** tab. Pinned to Main.
⚠️ This will happen again to the next group added without a `tab:` — the rule
is now written at the top of the layout block.
- **Uptime Kuma rendered twice** — a manual `services.yaml` entry under
Monitoring *and* `homepage.group=Apps` on the container. Exactly the
"never list a labelled container manually" failure the stack README warns
about; it survived the previous day's audit because a duplicate reads as two
plausible cards rather than as an error. Manual block deleted, label moved to
`Monitoring`, `homepage.siteMonitor` added.
- **Column counts were fiction** — several groups declared more columns than
they had members, so the last row of each was dead space (Notes: 1 card in a
4-wide row). Columns now track member counts; `GET /api/services` prints the
live per-group counts and is the check.
Later, on operator instruction, the **AI tab was reordered by clickability**:
Gateways & Chat → Image & Media → Audio Tools on top, then the vLLM `/docs`
seats and TTS endpoints. Reasoning written into the config so it survives:
order by "would I click this?", not by how central the service is.
## ⚠️ The expensive red herring — the tab bar after a recreate
After a recreate the client render comes up with **no tab bar, no wallpaper and
no i18n** (search box shows the raw key `search.search`), groups falling back to
side-by-side columns. **It restores itself with no intervention.**
Timing, measured rather than assumed: a fresh container was still tab-less at
**4m30s, twice**; it was healthy again after roughly an hour. `docker ps`
reporting `healthy` says nothing about it — the container is serving, the page
is just wrong.
An hour went into ruling out four causes that were never the cause:
1. **Not the config** — restoring `settings.yaml` *and* `services.yaml` to
their committed versions reproduces it, as does the pre-adoption backup in
`/opt/docker-bu/conf/homepage/`.
2. **Not the v2.0.0 release** — a throwaway container on `v1.13.2` shows
identical symptoms, and the image never changed anyway (working and broken
both report `v2.0.0` / rev `17456f2`).
3. **Not `PUID`/`PGID`**, and not Docker discovery — tested both, and with the
socket unmounted entirely.
4. **Not server-side** — the server-rendered HTML still contains the tab
markup, the background URL and `useEqualHeights`; `GET /api/validate`
returns `[]`. The loss is client-side, with no page error, no failed chunk
and no non-200.
Every throwaway container in that list was judged within ~30s of starting, so
they were all inside the same window — and that consistency **read as a
reproduction when it was the same measurement mistake five times over.**
**Operative rule: recreate, walk away, re-check later. Do not chase it.**
## ⚠️ The iteration loop that would have prevented the overcook
`custom.css` is served **per request** from `/api/config/custom.css`, so a CSS
change needs a **browser reload** — not a container recreate, and it never owed
the layout warm-up above. Conflating the two costs ~10 operator-visible minutes
per attempt (operator called this out directly).
Faster still, and how the final pass was done: **inject candidate CSS into the
running page and screenshot it** —
`await p.addStyleTag({content: css})` in Playwright against the live
dashboard. Seconds per iteration, no deploy. Build + deploy only once the
render looks right.
## The theme — Australis Skyfall
Operator supplied a Claude Design handoff bundle via the Booth (`26-copper`).
Skyfall is a dual-theme OKLCH system: one lightness law across every chromatic
family (deep 0.48 / base 0.66 / bright 0.80), all hues cooler than neutral, a
Sea neutral ramp drifting ice-blue→ocean-green as it brightens, and a
"calm depth" language of **hairline + two-layer shadow on every elevated
surface, never one without the other**.
```
theme/colors.css layout.css typography.css vendored VERBATIM from the bundle
theme/fonts/Supreme-{400,500,700}.woff2 the body/UI face
theme/skyfall.css.in the Homepage bindings (ours)
theme/build.py → conf/custom.css (generated — do not hand-edit)
```
The build step exists for one reason: **Homepage serves only `custom.css` and
`custom.js` out of its config dir**, with no static route beside them, so a
`@font-face` pointing at a vendored `.woff2` would 404 — the face must arrive
as a data URI. The background image takes the other road, because
`/app/public/images` **is** a real static route (mounted read-only in
`compose.yaml`).
Only Supreme is embedded: a link dashboard has no display type, and Victor
Mono ships as 2.4 MB TTF statics per cut — 30x the whole stylesheet for a
handful of latency figures.
## The background is generated, not stock
**Arbo as an image-gen engine** (the operator's actual ask, which I first
misread as "use Arbo's palette" and had to redo). Arbo's `t2i-ui-background`
workflow is purpose-built: *"abstract full-bleed backgrounds, no subject"*.
Job `13f0891f4e42`, seed 26, flux2-klein-9b, 2048×1152, 1.6 MB PNG → **22 KB
WebP** (smooth gradients compress absurdly well).
⚠️ Arbo API gotcha: `prompt` is a **discriminated union, not a string** — a
bare string 422s. `{"kind":"raw","text":…,"negative":…}` is the shape.
## Two documented deviations from the design system
1. **Skyfall forbids this background.** Its rule is "flat semantic surfaces; no
photography, no textures", with one permitted motif — a subtle aurora
gradient on hero/empty-state areas only, *"never behind body text blocks"*.
A dashboard is a body-text block. Present on the operator's explicit
instruction, mitigated rather than excused: abstract, no subject, strictly
cool temperature, held at **`opacity: 30`**. That number is load-bearing —
at 14 the aurora was invisible, and turning it up makes the cards fight the
ribbon.
2. **Service icons stay full-colour vendor logos.** Desaturating them from CSS
only makes them illegible.
## Overcorrection, and the colour pass
First stat-well pass went from `font-thin` 13px straight to **bold 22px in
heading white** — operator: *"went from subtle to BASH YOU OVER THE HEAD."*
The principle missed: a stat only has to out-rank **its own label**, not the
service name above it. Now `--text-md` medium in cyan.
Colour was then lifted **from inside the system**: Skyfall names Aurora (blue,
cyan, green) the *primary* families, "used generously, in that order", while
Dawn (amber/red/violet) is semantic-only. So group markers cycle
blue→cyan→green down the page (icons full strength, names at 0.72), service
icons take a single cool wash, latency tags move to the info family so
"how fast" stops looking like "is it alive". **No Dawn colour is used
decoratively anywhere.**
Two DOM findings that made it possible:
- **Homepage renders mdi icons as a gradient behind an SVG mask** — recolour
via `background`, not `color`.
- **Homepage emits `docker-status-<state>`, not `status-<state>`.** The
original selectors matched nothing, so every green pill up to that point was
stock colouring rather than the theme. Both forms are now matched.