# `[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-`, not `status-`.** 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.