fix(homepage): uniform 4-column grid, hold the status gutter, unleak AI Systems

The board's card width changed at every group boundary because `columns:` was
being tuned per group under the 2026-08-18 "columns = member count" rule. That
rule is retired: it sets `lg:grid-cols-N` for one group, so it fixes that
group's CARD WIDTH, not its density. Measured on the live board, Notes rendered
a single 1464px card, News and Media 728px, Eval & Retrieval 286px, everything
else 360px. All 20 groups are now `columns: 4` and every card renders at 360.

`.service-name` reserved a 78px status gutter with `padding-right` and relied
on `overflow: hidden` to hold it, but overflow clips at the PADDING box — the
gutter was spill room the title printed straight through. Six cards on the AI
tab rendered their name underneath their own status pill, measured by testing
the title text node's box against the status cluster's. The intended ellipsis
never fired either: it is painted by whichever block's own line overflows, and
that is the anonymous box around the bare title text node, which does not carry
`overflow`. The gutter holds by wrapping now, and the description opts back out
of it with a negative margin since the pill only ever covers the first line.

Scriberr's `homepage.group=AI Systems` named a group absent from `layout:`, so
it had no `tab:` and rendered on all four tabs — the same defect as UltraSeedbox
in 2026-08-18, arriving this time from a container label. Relabelled to
`AI - Audio Tools`, where the other ASR seats already live. The
`homepage.group=AI Systems` sample in the repo-root CLAUDE.md was the source of
the name and now carries the constraint, plus the fact that a labelled
container is discovered from any of the five configured engines and must not
also be listed in services.yaml.

Also: descriptions clamped to three lines so a five-line outlier stops dragging
its row 50px taller than its neighbours; icon ramp overridden off slate-400 ->
slate-700, which was sinking the bottom half of every glyph into the card fill;
bookmark groups and Jellyfin's trailing stream rows brought into the card and
eyebrow vocabulary; group gap 10px -> 22px now that width no longer separates
them.

The icon override sits on `html[class]`, not `:root`: Homepage sets the same
variables on `.theme-slate`, which is on the <html> element, and a class beats
`:root` on the same element.

Verified with Playwright against the live board — per-group card width, card
height spread, and a geometric title-vs-status collision check, before and
after. Before/after captures: http://10.100.10.50:8090/b/homepage-relayout/
This commit is contained in:
vh
2026-08-24 08:15:02 -07:00
parent 32349b7653
commit f6f2f69649
8 changed files with 623 additions and 77 deletions
+42 -24
View File
@@ -74,30 +74,50 @@ useEqualHeights: false
# If you add an AI group, place it by asking "would I click this?", not by
# how central the service is to the fleet.
#
# COLUMN COUNTS ARE NOT A STYLE CHOICE — they are the member count.
# `columns: N` lays the group out N-per-row and leaves the remainder of the
# last row as dead space. A 1-member group at columns:4 renders one card and
# three empty cells, which is what made this dashboard look ragged before
# 2026-08-18. Rule: set columns to the member count, or to the divisor that
# leaves the smallest remainder. Re-check when a group gains or loses a
# service — `GET /api/services` prints the live per-group counts.
# COLUMNS ARE 4 EVERYWHERE. DO NOT TUNE THEM PER GROUP.
#
# EVERY GROUP NEEDS A `tab:` — including bookmark groups. A group with no tab
# assignment renders on ALL FOUR TABS. That is how UltraSeedbox ended up
# repeated at the bottom of every tab (fixed 2026-08-18); it is Homepage
# behaviour, not a bug, and it will happen again to the next group added
# without a tab.
# `columns: N` is not a density dial — it sets `lg:grid-cols-N` on that one
# group, so it fixes the CARD WIDTH for that group alone. Varying it per group
# means the card width changes every time you cross a group boundary, and a
# page whose grid keeps resizing as you scroll reads as broken layout even when
# every individual group is fine.
#
# The predecessor rule here was "columns = the member count", written to avoid
# the dead cells a 1-member group leaves in a 4-wide row. It trades one flaw
# for a worse one: at columns:1 a single service becomes a 1500px-wide bar
# holding six words, and at columns:2 a three-member group orphans its third
# card onto a half-empty row. Measured on the live board 2026-08-24 — the
# Notes, News, Media, AI - Image & Media and AI - Audio Tools groups were all
# rendering cards two to four times wider than the groups above and below them.
#
# A short last row is what a grid looks like. A card that is wider than its
# neighbours is what a mistake looks like. Uniform wins.
#
# Homepage's own responsive ramp (`grid-cols-1 md:grid-cols-2 lg:grid-cols-N`)
# still collapses this to 2-up and 1-up on narrow viewports, so 4 is a desktop
# maximum, not a hard floor.
#
# EVERY GROUP NEEDS A `tab:` — including bookmark groups, and including groups
# that arrive from a `homepage.group=` container label rather than from this
# file. A group with no tab assignment renders on ALL FOUR TABS. That is how
# UltraSeedbox ended up repeated at the bottom of every tab (fixed 2026-08-18)
# and how Scriberr's `AI Systems` label did the same from 2026-08-23 (fixed
# 2026-08-24 by relabelling it into `AI - Audio Tools`). It is Homepage
# behaviour, not a bug, and it will happen again to the next container labelled
# with a group name that does not appear below.
# `GET /api/services` prints the live group list — anything in it that is not a
# key here is currently leaking onto all four tabs.
layout:
Notes:
icon: mdi-note-text-outline
tab: Main
style: row
columns: 1
columns: 4
News:
icon: mdi-rss
tab: Main
style: row
columns: 2
columns: 4
Monitoring:
icon: mdi-chart-line
tab: Main
@@ -108,19 +128,16 @@ layout:
tab: Main
style: row
columns: 4
# 2 wide, not 3 — Plex and Jellyfin carry stat-block widgets that get
# squeezed to unreadable at 4-across, and 4 members / 3 columns orphaned
# Jellyfin onto a row of its own.
Media:
icon: mdi-play-box-multiple
tab: Main
style: row
columns: 2
columns: 4
Games:
icon: mdi-gamepad-square
tab: Main
style: row
columns: 1
columns: 4
# Bookmarks. Listed here for the tab pin above all else — without it this
# group appears on every tab. `style: row` also turns the eight entries
# from full-width stacked bars into a compact grid.
@@ -141,13 +158,14 @@ layout:
icon: mdi-image-multiple
tab: AI
style: row
columns: 2
columns: 4
# Mixed: YT Voice Clipper has an audition console, Parakeet is an API.
# Scriberr joined here 2026-08-24 (it was the stray `AI Systems` group).
AI - Audio Tools:
icon: mdi-waveform
tab: AI
style: row
columns: 2
columns: 4
# Below here: model seats whose href is a vLLM `/docs` page. Status at a
# glance is the whole value; you consume these through the gateway, not by
# clicking them.
@@ -160,7 +178,7 @@ layout:
icon: mdi-scale-balance
tab: AI
style: row
columns: 5
columns: 4
AI - Speech (TTS):
icon: mdi-account-voice
tab: AI
@@ -183,12 +201,12 @@ layout:
icon: si-proxmox
tab: Infrastructure
style: row
columns: 3
columns: 4
Infra - IRV:
icon: mdi-brain
tab: Infrastructure
style: row
columns: 2
columns: 4
Infra - ESH:
icon: si-proxmox
tab: Infrastructure