bc3aada73a
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.
146 lines
7.5 KiB
Markdown
146 lines
7.5 KiB
Markdown
# `[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.
|