Fix four things found by looking at the tabbed page
All checks were successful
CI / naming (push) Successful in 3s
CI / frontend (push) Successful in 12s
CI / migrations-mysql (push) Successful in 43s
CI / backend (push) Successful in 4m18s

SOFTWARE OPENED COLLAPSED. The installed-software panel was collapsible and
started that way, because it used to be low-priority reference data near the
bottom of one long page. It is now the Software tab's whole reason to exist,
and somebody who clicked that tab has already said what they came for. Made
it neither collapsible nor collapsed.

TRACKED SERVICES VANISHED FROM METRICS. The group is decided by the SERVER, so
a new frontend against a server still serving the old vocabulary puts a panel
in a group the build does not render, and it is fetched, held, and shown
nowhere - with nothing logged. Moving services out of 'actions' is exactly that
shape. The client now falls back to 'details' for any group it does not render,
the same way the server does, so a version-skewed pair degrades to a misplaced
card instead of a missing one.

A TABLE PANEL WITH NOTHING IN IT HID ITSELF. panelVisible honoured `empty` for
list panels and ignored it for tables, for no reason anybody wrote down. A
declared empty message is an answer - "No installed software reported." - where
an absent card is a question. It matters more now that a panel can be the only
thing in a tab, because the tab disappears with it.

THE TAB STRIP HAD A VERTICAL SCROLLBAR. Per the overflow spec, when one axis is
not `visible` the other computes from `visible` to `auto`, so `overflow-x: auto`
alone made the strip scrollable vertically too and the -1px margin the tabs use
to sit on the border was enough to trip it.

Also moved .detail-full into the global stylesheet next to .content-grid, per
frontend/CLAUDE.md: it is detail-page layout, and the other four detail pages
need it when they adopt tabs.

Scope: product
This commit is contained in:
cproudlock
2026-08-31 15:01:14 -04:00
parent f4b2ed7613
commit c71183375f
7 changed files with 70 additions and 16 deletions

View File

@@ -191,18 +191,30 @@ const props = defineProps({
const panels = ref([])
// The server resolves every panel's group before we see it, so an unknown or
// absent one is already 'details' by the time it arrives here. Filtering can
// therefore be a plain equality check and nothing falls through the gap.
// The groups this build renders. A panel in any other group would be fetched,
// held, and shown on no tab at all.
//
// The server normalises group before we see it, so a version-matched pair
// never lands here. A MISMATCHED pair does: deploy a new frontend against a
// server still serving an older vocabulary and every panel in a group that
// has since been renamed or removed silently disappears. That is what happened
// to tracked services when it moved out of 'actions' - the backend still said
// 'actions', this build had no such tab, and the card was simply gone with
// nothing logged. Fall back the same way the server does rather than drop it.
const RENDERED_GROUPS = ['details', 'software', 'metrics', 'activity']
function groupOf(panel) {
return RENDERED_GROUPS.includes(panel.group) ? panel.group : 'details'
}
const groupedPanels = computed(() =>
props.group ? panels.value.filter((p) => p.group === props.group) : panels.value
props.group ? panels.value.filter((p) => groupOf(p) === props.group) : panels.value
)
// Which groups actually have a panel with data, so a parent can hide a tab
// rather than offering an empty one.
const populatedGroups = computed(() => {
const seen = new Set()
panels.value.forEach((p) => { if (panelVisible(p)) seen.add(p.group || 'details') })
panels.value.forEach((p) => { if (panelVisible(p)) seen.add(groupOf(p)) })
return seen
})