# `[2026-08-24]` Homepage: remote-label consumption re-verified, then the board relaid out on a uniform grid Prompted by the operator: *"Homepage on esh-vm-docker lists remote dockers and can absolutely consume their labels, please verify again. I am still unsatisfied with the layout and aesthetics."* ## The verification — the operator was right, and the record now says so **Homepage on `esh-docker-vm` discovers services by container label from all five Docker engines in `conf/docker.yaml`, not just its own.** This is not an inference; `GET /api/services` returns every card's `server` field, and the 2026-08-24 snapshot resolves to: | `server` | host | label-discovered services | |---|---|---| | `ana-pfi-docker` | 10.250.50.70 | 30 | | `irv-ml1-docker` | 10.100.79.3 (over WireGuard) | 15 | | `ana-ml2-docker` | 10.250.50.54 | 14 | | `esh-vm-docker` | 10.0.50.45 (the dashboard's own host) | 13 | | `nh3-pfi-docker` | 10.100.50.40 | 2 | **74 of 107 cards are label-discovered, and only 13 of those come from the dashboard's own engine** — the other 61 are read off four remote hosts, including irv-ml1 across the WireGuard tunnel. The remaining 33 carry `server: null`: those are the manual `services.yaml` entries — hardware, BMCs, hypervisors, printers, and user-level systemd services that have no container to label in the first place. **That null is the only thing "not label-driven" about this dashboard**, and it is a property of the entry, not of the host it points at. ⚠ If a future session doubts this again, the check is one command and takes two seconds — do not reason about it from the docs: ```bash curl -s http://10.0.50.45:5100/api/services \ | jq -r '.[].services[] | .server' | sort | uniq -c ``` ## What was actually wrong with the layout Measured with Playwright against the live board (per-group `card=` width, card height spread, and a geometric title-vs-status overlap test), not judged by eye: - **Card width changed at every group boundary.** `columns:` is not a density dial — it sets `lg:grid-cols-N` for one group, so it fixes that group's card width. Notes rendered a single **1464px** card; News and Media **728px**; Eval & Retrieval **286px**; everything else 360px. Scrolling the page, the grid resized five times. - **Long names printed underneath their own status pill.** Measured by re-injecting the old rule and testing the title text node's box against the status cluster's box: **6 cards, all on the AI tab** — 3 in Inference, 2 in Dormant, 1 in Eval & Retrieval; zero on the other three tabs, which is why it survived earlier passes. Root cause is a genuinely counter-intuitive one: the rule reserved a 78px gutter with `padding-right` and relied on `overflow: hidden` to hold it, but **overflow clips at the padding box, not the content box** — so the reserved gutter was spill room the title rendered straight through. The intended `text-overflow: ellipsis` never fired either, because the ellipsis is painted by whichever block's own line overflows, and here that is the anonymous box around the bare title text node, which does not carry `overflow`. - **`AI Systems` / Scriberr was on all four tabs** — the 2026-08-18 UltraSeedbox bug recurring, this time arriving from a container label rather than from `settings.yaml`. - **Icons were grey smudges.** Homepage masks every glyph over `--color-logo-start/stop`; stock slate-400 → slate-700 sinks the bottom half of each icon into the card fill. - Bookmark groups and Jellyfin's trailing stream rows were the two components the theme had never reached. ## The fixes `stacks/homepage/conf/settings.yaml` — **all 20 groups to `columns: 4`.** `stacks/homepage/theme/australis.css.in` → rebuilt → `conf/custom.css`: gutter held by wrapping, description clamped to 3 lines (floor still 2), icon ramp overridden, bookmark + trailing-widget components themed, group gap 10px → 22px. `stacks/scriberr/compose.yaml` — `homepage.group` → `AI - Audio Tools`, container recreated on ana-ml2. After: **every group renders at card=360**, and the collision count is zero. Before/after, all four tabs: `http://10.100.10.50:8090/b/homepage-relayout/` (24h TTL; also on the standing link board). ## ⚠ Three traps worth carrying forward 1. **"Columns = member count" is RETIRED** (it was the 2026-08-18 rule). It was avoiding dead cells in a short last row and bought a worse defect. A short last row is what a grid looks like; a card wider than its neighbours is what a mistake looks like. 2. **A `:root` override of a Homepage theme variable is silently ignored.** Homepage sets `--color-logo-*` on `.theme-slate`, and that class is on the `` element — the same element `:root` matches. `.theme-slate` (0,1,0) beats `:root` (0,0,1), so the override does nothing and looks like the variable is not the one in play. `html[class]` (0,1,1) wins, and does not hard-code which `theme-*` class is active. Specificity alone is not enough either: a custom property resolves from the *nearest* ancestor that sets it, so the override has to land on ``, not on `
`. 3. **The post-recreate tab-bar loss is INTERMITTENT, not guaranteed.** The 2026-08-19 note reads as though every recreate costs up to an hour of broken render. This recreate came up correct within 10 seconds — fresh payload on the first poll, all four tabs clickable a minute later. Recreate, *check*, and only then walk away if it is actually in the broken state. Also re-confirmed, since the change depended on it: **a `settings.yaml` edit needs a container recreate, not a restart.** `docker restart homepage` left the old `"columns":1` payload embedded in the served HTML with the correct file mounted and readable inside the container; `compose up -d --force-recreate` cleared it immediately. ## Deliberately not done — operator's call The Main tab still opens on three sparse bands: **Notes** (1 member) and **Games** (1) each burn a full 4-wide row, and **News** has 2. Merging Notes + News, or folding Games into Apps, would tighten the top of the page — but that is information architecture, not layout, and the group names are the operator's. Surfaced rather than done. → **Resolved in pass 2 below**, where the operator delegated the naming ("re-categorize however you want"). Notes + News became `Daily`, Games folded into `Apps`, and the `AI - Audio Tools` placement in this pass was superseded (Scriberr moved on to `AI - Studios`). --- # `[2026-08-24, pass 2]` Recategorised on "do I open this?", API groups collapsed Operator, after seeing pass 1: *"You can re-categorize however you want. service networking tab is uneven, you can split out the adguard cards, etc. most of the issues are that tools I use and have a UI are interspersed with API endpoints which are largely informational only. They might even go in their own cards or start collapsed."* ## The axis Every group is now either **tools** (expanded, top of tab) or **endpoints** (an API, a broker, an agent — `initiallyCollapsed: true`, bottom of tab). A collapsed group still renders its eyebrow and rule, so presence costs one line instead of two rows. Second, quieter rule that fell out of the same pass: **a group's members should all carry a widget or none should.** A stat strip adds ~50px, so one widget card in a row of plain ones opens a void under the plain ones — which is most of what made the 13-member `Service Networking` band look broken. ## Shape - **Main** — `Daily` (Memos, Miniflux, Nevermore, SearXNG — replaces the 1-card Notes and 2-card News bands), `Monitoring`, `Apps` (12; absorbed the 1-card `Games` band), `Media`, `UltraSeedbox`. - **AI** — `AI - Gateways & Chat` (8) and `AI - Studios` (6) expanded; then `AI - Inference` (7), `AI - Eval & Retrieval` (4), `AI - Speech (TTS)` (4), `AI - Audio Tools` (2), `AI - Dormant` (6) all collapsed. - **Toolchain** — `DNS & Filtering` (3), `Reverse Proxies` (2), `Compose Consoles` (5), `Toolchain` (3), `Agents (no UI)` (6, collapsed). - **Infrastructure** — unchanged; every card there is already a console. Measured after: every group `card=360`, and `DNS & Filtering` and `Reverse Proxies` both `h=134..134` — dead flush. ## ⚠ The move that made it affordable **The sixteen GPU-backed model seats were NOT relabelled.** `homepage.group` is read at container **creation**, so renaming `AI - Inference` to something clearer would have meant recreating six vLLM seats plus four eval seats plus four TTS engines — multi-minute model reloads on endpoints peers reach through the gateway. Order plus `initiallyCollapsed` buys the same separation for free, so the names stay ugly on purpose. **Do not spend that recreate on a label.** 28 containers *were* relabelled — all cheap web services — via five rerunnable elway playbooks, `playbooks/homepage-regroup-