Files
Paul LütznerandClaude Opus 5 1a8b212792 fix(settings): the inset that was only ever wrong on a desktop
Fourth report of the same complaint, and settings turned out not to be the
drifting side: _card.html.twig already goes through the chrome macros, and all
seventeen sections that use it measured identical to admin. Rewriting settings
to match would have been rewriting the correct half.

The drift was in the one section that bypasses the embed. Account health is a
FEED of per-issue cards rather than one card, so it hand-rolls its chrome, and
it carried `px-4 @2xl:px-0` on its heading and `px-4 py-4 @2xl:px-5` on its
bodies. Both are correct on a narrow pane and wrong on every desktop one, which
is exactly why four reports and three sweeps never found them: at the width the
earlier fixes were checked, nothing was wrong.

The guards now cover both trees, and gained two things worth more than the
sweep. A tree that matches zero files now FAILS — "the test is green" and "the
test is looking at anything" had quietly become different statements, which is
how settings stayed unguarded while the suite stayed green. And the rendered
check measures its reference from the card's PADDING box, so a decorative
border is not mistaken for an inset: health's severity stripe is border-l-4,
and making a heading agree with a border-inclusive edge would have required
px-5 — which the static guard rejects as a hand-rolled inset. Two guards that
cannot both be satisfied means the rule is wrong, not the markup.

Proven to discriminate rather than to silence: the stripe alone passes, and
both real regressions are still caught — the original heading bug now reporting
its true 16px rather than 20px inflated by that border.

The spec also breaks its own worker's account on purpose, because the section
only renders issue cards when there are issues — measuring the all-clear pane
and calling it coverage is how this gap opened in the first place.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 17:16:11 +02:00

62 lines
3.0 KiB
Twig

{#
The card chrome, defined once.
Settings sections and admin panels are the same object visually — a bordered,
rounded surface with a header band across the top and a body under it — and
they were drifting apart because each side hand-wrote the classes. Admin sat on
`bg-pane-soft` with an `h-11 px-5` summary; settings sat on `bg-surface` with a
`px-4 py-3.5` header. Nobody chose that difference, it just accumulated.
They cannot share a TEMPLATE: an admin panel is a native <details> so it can
collapse without JavaScript, and a settings card is a plain <section> that must
not gain button semantics. So they share the CLASSES instead. These macros
return class strings and nothing else, which keeps one definition of what a
card looks like while leaving each side its own element and its own behaviour.
Deliberately Tailwind classes rather than a CSS @utility: `border-line` and
`bg-surface` carry the theme's alpha vars, and hand-rolling their equivalents
in CSS is exactly the drift this file exists to stop.
Usage:
{% import '_partials/_card_chrome.html.twig' as chrome %}
<section class="{{ chrome.shell() }}">
<header class="{{ chrome.band() }}">…</header>
<div class="{{ chrome.body() }}">…</div>
</section>
In settings the card is usually already built: embed settings/_card.html.twig
rather than opening a shell by hand.
THREE TESTS HOLD THIS FILE'S FIGURES, and they exist because the owner reported
the drift four times — the fourth about admin and settings disagreeing with each
other, after each side had been fixed on its own:
tests/Infrastructure/Templating/CardsUseTheChromeMacrosTest.php
both trees, statically: nothing insets a card wider than px-4, including
behind a variant prefix like `@2xl:`. Sees the branches no run renders.
tests/e2e/admin-card-insets.spec.ts
tests/e2e/settings-card-insets.spec.ts
both trees, in a browser: band against body, card against card, and the
headings between the cards against all of them.
Change `px-4` here and all three fail, which is correct — it is the figure, and
moving it is a decision about both halves of the application at once.
#}
{# The card itself. `overflow-hidden` so a flush body — a divide-y list, a
header strip — is clipped by the radius instead of squaring off its corners. #}
{% macro shell() %}rounded-pane border border-line bg-surface overflow-hidden{% endmacro %}
{# The header band: heading, optional subtitle, optional trailing control. The
border-b is what separates it from the body, so a card whose body is flush
still reads as two parts. #}
{% macro band() %}px-4 py-3.5 border-b border-line{% endmacro %}
{# Body padding, for cards whose content does NOT bring its own inset.
Most settings bodies are lists or stacked rows that already pad themselves
with px-4 — those stay FLUSH, because padding them twice puts 32px between
the card edge and the first word. Use this only for bodies that are bare
content: a form, a group of controls. #}
{% macro body() %}px-4 py-5{% endmacro %}