Zugangsdaten and Infrastruktur split the same subject by storage mechanism —
SecretVault vs App\Support\Settings — instead of by what each field
configures, which is not a distinction an operator setting up Stripe or DNS
should have to know or care about. One page now, grouped by integration:
Zahlungen (Stripe), DNS (Hetzner), Monitoring, VPN/WireGuard, SSH-Identität. A
secret and a plain setting sit side by side within a section; the difference
stays visible — a vault entry is write-only and shows only an outline, a
setting shows its value in full — as a property of the field, not a reason
for a separate page.
Both storage mechanisms are unchanged underneath: this is a presentation
change, not a data migration. The vault keeps its own gate (secrets.manage +
a recently confirmed password); plain settings keep theirs (hosts.manage
alone). Reachable with either capability, since the two are no longer two
pages a role happens to see one, both, or neither of — every section and
every save action still checks its own capability server-side.
admin.secrets and admin.infrastructure redirect here permanently rather than
404ing a bookmark. The nav entry that replaces both is visible to either
capability — Navigation's capability field now accepts an array meaning "any
of these", not only a single ability.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The panel had been built by reading the template and believing the result.
Every round of review found the same class of defect, so this round the
template was rendered in a browser and measured with getComputedStyle, and the
implementation measured the same way — two number columns instead of an
opinion.
That immediately settled a disagreement: a review of the template's SOURCE said
the metric grid used a 20px gap. Rendered, it is 14px. The measurement wins.
Brought to the template's figures: label 11.5px (it was 11px in three separate
places, which is how it drifted — it is now one .lbl rule), value tracking
-0.02em, unit weight 450, metric row 6px above and 14px between, page head
centred with a 14px gap, grid gap 14px, columns switching at 1101/561px and the
h1 at 901px to match the template's own breakpoints, ring 62px, bar 5px.
The shared button now takes its height from min-height (40px) rather than
vertical padding, at 14px/600 with 0 18px padding and a 10px radius — a button
keeps its height whatever sits inside it, icon, spinner or bare text.
Also here, from the same round of review:
- The chart's blue border was Tailwind's own `ring` utility colliding with a
component class of the same name. Renamed to `.metric-ring`. This was
dismissed once as a screenshot artefact; it was real.
- Page titles lost the `sm:text-3xl` (40px) an earlier bulk edit had appended
to 23 of them. The template's h1 is 30px.
- The users table has its actions back — edit, suspend, lock and delete — for
every seat that is not the owner, with the owner refused in the action itself
and not merely hidden in the markup.
DemoCustomerSeeder writes one complete customer as real rows: instance,
subscription, six seats across four roles, a backup, current-period traffic and
thirty days of samples with a deliberate wobble and one day at 286/288 checks.
Nothing in the panel is drawn from a fixture any more, so anything missing
shows up as missing. Removing the demo is one deletion.
Verified: 607 tests, and a Codex comparison of the two measurement sets —
"a person would call them the same design", the only difference a 1px gap.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The console was a second product. admin-tokens.css held a complete dark set —
its own surfaces, its own orange, its own status triad — so every shared
component had to work in two worlds, and the two halves of the application
drifted apart in ways nobody could see from inside either one. That file now
carries density and nothing else: smaller type, tighter rows, tighter corners.
Colour comes from one place.
The tokens themselves are rebuilt around what the redesign settled on:
- Radii scaled to object size (9/11/16/22) instead of one value everywhere,
which is what made the interface read as a design-system specification
rather than as a product.
- Warm, broad, low shadows. A neutral grey drop shadow belongs to a different
design language and reads as cold on this ground.
- --accent-press, which several rules already referenced and nothing defined.
An undefined var() does not make a browser skip the declaration; it computes
to , and for background-color that is transparent — the accent button
lost its fill on hover and left white text on white.
- No serif. A serif headline is what made every page read as a document, which
is the impression the whole redesign exists to remove. --font-serif now
points at the sans so nothing breaks while call sites are cleaned up.
The two shells are one shell with two configurations, and both finally have a
mobile navigation: the sidebar used to simply disappear below 900px, leaving no
way to move around the application at all on a phone.
The customer dashboard is bound to real records — instance, seats, traffic,
backups, contract — instead of the fixtures it shipped with. Where something is
not measured, storage consumption, it says what was contractually agreed rather
than inventing a figure. This is the sheet a customer forwards to their auditor.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>