Make the site consistent with itself: one header rhythm, one copy of each component, one value per role - #7
Merged
Conversation
Nine pages built the section header by hand, forty-one times between them, and every copy had drifted from the one `SectionShell` documents: - The retired `FullLine → h-4 → FullLine` / `FullLine → h-2 → FullLine` pattern was still intact on six pages — four rules where the system draws two, which is a 1px rule, 16px gap, 1px rule band. SectionShell's own docblock calls that a mis-rendered table border and says it was deleted. - None of them imported AccentLine, so the orange tick that marks the start of every section on the homepage was missing from all forty-one. - Every label, heading and lede used `pl-4`, so they sat flush against their rules with no vertical padding — while the homepage gives them `py-3` and `py-4 sm:py-5`. `pl-4` also means no *right* padding, so long text ran into the container rail. - Three h1 size ramps and three padding treatments across the eight pages that have an h1. The fix is one `HeaderStack`, shared. `SectionShell` renders it with an h2 and a landmark; `PageHeader` renders it with an h1; `SectionHeader` renders it alone for the two pages whose sections live inside conditional fragments that no component wrapper lines up with. Duplicating a rhythm is how a rhythm stops being one, so there is now exactly one copy of it. Compare's Verdict section had an eyebrow and no heading at all, unlike every other section on that page. It gets one. Twenty-two spacer divs are gone, including the last two in pricing, and `pl-4` survives only in the comments explaining why it went.
Four pieces of markup existed in two to eight copies each, and every set had drifted. **The Apple mark, eight copies in four shapes.** Four of them carried no size class at all, which inside a flex button means the browser guesses; half were missing `aria-hidden`, so a screen reader announced an unlabelled graphic in the middle of a button that already reads "Download for Mac". **The tick, three copies that disagreed on colour.** Two used `--primary-strong`; one used bare `--primary` at 2.62:1 — the failure `SectionLabel` was created to stop, surviving in the one glyph that is the sole carrier of meaning in a comparison column. That last point is why availability is now a single `Availability` component rather than a glyph you are trusted to pair with text. Both glyphs are `aria-hidden`, so a cell holding only one announces as empty — and an empty cell in a comparison table does not read as "no answer", it reads as "not included". The pricing table shipped exactly that, was fixed, and Compare lost the same fix partway through this very commit when its local wrappers were swapped out. A pair that must not be separated should not be two things. **The FAQ, presented two ways.** An open two-column grid on the homepage and `/faq`, a `<details>` accordion on the comparison and database pages — the two accordions byte-identical, and both invalid, since a `<dl>` may contain only `<dt>`, `<dd>` and `<div>`. Open won: these pages carry three to eight questions, so there is nothing to collapse for, and an answer behind a disclosure is one a search engine ranks lower and a skimming reader never sees. **`SectionBlock` and `Bullet`, byte-identical in Privacy and Terms.** Two places for one rule to drift, and two places to fix it when it did. The mobile nav keeps its own close icon. It shares a path with the "not included" cross and nothing else — 20px inheriting the button's colour versus 14px in muted-foreground — and sharing a path is not sharing a role.
**Four screenshot blocks fetched every variant.** Compare and DatabaseClient drew their product shots as a pair of `<picture>` elements toggled with `hidden dark:contents`. `display: none` does not cancel an in-flight image request, so every visitor downloaded both the light and the dark file — `themed-image.tsx`'s own docblock says this is why it exists and why it selects on `prefers-color-scheme` instead. Those pages also skipped the WebP ladder that had already been generated for the same screenshots, so they shipped raw PNG where the homepage ships WebP. **Compare's comparison table was a grid of divs.** No table semantics, so nothing tied a cell to its column, and it drew its column separators at the row weight — in the one table where the columns carry the entire meaning. It is now a `DataTable`, matching the plan comparison, and its scroll container is reachable by keyboard. **Three scrolling code blocks could not be reached at all.** A container that scrolls but holds nothing focusable is unreachable from the keyboard, which is WCAG 2.1.1. The two scroll containers that *do* hold focusable children — the billing toggle and the category rail — are fine as they are and deliberately stay that way. Inline code had five treatments, one of them still filling with `bg-black/5 dark:bg-white/10` after the rule migration had removed every other raw alpha pair on the site. It has one now, and `rounded-key` is the radius token that exists for exactly this size of thing.
**Raw alpha pairs are gone.** Twelve survived the two-weight rule migration, in five roles: two inset panel fills written as `gray-950/[2.5%]` and `gray-950/[0.02]`, two placeholder-tile hover borders that disagreed with each other in dark mode (`white/20` against `white/25` for the same tile in two files), and two blog row hovers hand-rolled in a neutral grey. The panels take `--surface-raised`, which is the same "one step off the base ground" the raised section tone uses and reads the same way from the other side. The tiles take the accent, like every other hover on the site. The blog rows take `[data-row]`, which is what it is for, and get focus parity as a side effect — they had none. **`transition-all` is gone.** Seven elements animated every animatable property to change two: a dropdown wanted opacity and transform, three logo marks wanted opacity and filter, and the pricing CTAs wanted opacity. Each now names what it animates and takes the motion tokens, which had almost no consumers. **Disabled is one value.** It was 40% in two places and 50% in two others. **The tint scale is written down.** Seven values in two syntaxes collapse to two roles — `/5` marks a surface as the featured one, `/10` fills a chip or a selected control — with the reasoning recorded in app.css. `safety.tsx` keeps its own finer ladder, and that is not drift: the tint there encodes how far up the six-level ladder a row sits, so it has to be finer and evenly spaced. **The prose link is one class list.** Nineteen call sites across six files wrote it out by hand, and two more variants existed that were underlined but not coloured, or coloured but not underlined.
One of the twenty-one carried extra layout classes, so the exact-string sweep skipped it. It composes now instead of restating.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A seven-lens audit across all ten pages, then the fixes. −219 lines net, and
every consistency counter that mattered is now zero.
The homepage had six passes of attention over six PRs. The other nine pages had
almost none, and that is exactly where the divergence had collected.
The header rhythm was hand-rolled forty-one times
Not one of the nine non-homepage pages used
SectionShell. Compare andDatabaseClient each built seventeen section headers by hand. Every copy had
drifted from the one the component documents:
FullLine → h-4 → FullLine/FullLine → h-2 → FullLinepatternwas still intact on six pages — four rules where the system draws two, which
renders as a 1px rule, 16px gap, 1px rule band.
SectionShell's own docblockcalls that a mis-rendered table border and says it was deleted.
AccentLine, so the orange tick that marks thestart of every section on the homepage was missing from all forty-one.
pl-4— flush against its rules with novertical padding, and no right padding either, so long text ran into the
container rail.
an h1.
There is now one
HeaderStack.SectionShellrenders it with an h2 and alandmark,
PageHeaderwith an h1,SectionHeaderalone for the two pages whosesections live inside conditional fragments no component wrapper lines up with.
Duplicating a rhythm is how a rhythm stops being one.
Compare's Verdict section had an eyebrow and no heading at all. It has one.
Four things existed in two to eight copies
The Apple mark, eight copies in four shapes. Four carried no size class at
all — inside a flex button that means the browser guesses — and half were
missing
aria-hidden, announcing an unlabelled graphic inside a button thatalready reads "Download for Mac".
The tick, three copies that disagreed on colour. One used bare
--primaryat 2.62:1, in the glyph that is the sole carrier of meaning in a comparison
column.
That is why availability is now one
Availabilitycomponent rather than a glyphyou are trusted to pair with text. Both glyphs are
aria-hidden, so a cellholding only one announces as empty — and an empty cell in a comparison table
does not read as "no answer", it reads as "not included". The pricing table
shipped that, was fixed in #3, and Compare lost the same fix midway through
this very branch when its local wrappers were swapped out. A pair that must
not be separated should not be two things.
The FAQ, two presentations. An open grid on the homepage and
/faq, a<details>accordion on Compare and DatabaseClient — byte-identical betweenthose two, and invalid in both, since a
<dl>may contain only<dt>,<dd>and
<div>. Open won: three to eight questions is nothing to collapse for, andan answer behind a disclosure ranks lower and is never skimmed.
SectionBlockandBullet, byte-identical in Privacy and Terms.Images and tables
Four screenshot blocks fetched both themes.
hidden dark:contents— anddisplay: nonedoes not cancel an in-flight request, which is the exact reasonthemed-image.tsxexists and says so in its docblock. Those pages also skippedthe WebP ladder that had already been generated for the same files.
Compare's comparison table was a grid of divs with no table semantics,
drawing its column separators at the row weight — in the one table where the
columns carry the entire meaning.
Three scrolling code blocks were unreachable by keyboard. A container that
scrolls but holds nothing focusable fails WCAG 2.1.1. The two that do hold
focusable children are fine and deliberately unchanged.
One value per role
pl-4on an elementgray-950/black/whitealphastransition-alltext-[Npx]disabled:opacityvalues<details>accordionsThe accent tint went from seven values in two syntaxes to two roles, written
down in
app.css:/5marks a surface as the featured one,/10fills a chipor a selected control.
safety.tsxkeeps its own finer ladder and that isnot drift — the tint there encodes how far up the six-level ladder a row sits,
so it has to be finer and evenly spaced.
The mobile nav also keeps its own close icon. It shares a path with the "not
included" cross and nothing else — 20px inheriting the button's colour versus
14px in muted-foreground — and sharing a path is not sharing a role.
Verification
typecheck, build and pint clean. 105 tests / 1849 assertions with
REQUIRE_SSR=1. Every page type re-rendered and checked:/,/terms,/privacy,/refund-policy,/download,/faq,/blog,/compare/dbeaverand
/postgresql-clienteach show one<main>, one<h1>, the accent tickpresent, zero doubled rules and zero accordions.