Skip to content

The rest of the roadmap: a second ground, real shadows, one table owner, arrow keys, tests, one brand colour - #6

Merged
datlechin merged 6 commits into
mainfrom
feat/landing-roadmap
Aug 15, 2026
Merged

The rest of the roadmap: a second ground, real shadows, one table owner, arrow keys, tests, one brand colour#6
datlechin merged 6 commits into
mainfrom
feat/landing-roadmap

Conversation

@datlechin

Copy link
Copy Markdown
Member

Everything left that did not need a screenshot, a benchmark or sponsor copy.
Six items, one PR, each its own commit.

1. The page had one background for sixteen sections

This was flagged critical in the very first audit and never touched.

Over roughly ten thousand pixels of scroll the only rhythm device was hairline
density, so a reader could not tell from peripheral vision whether they had
moved from Architecture to Agents to Mobile. That is the difference between
restrained and monotonous. --card, --secondary and --accent were declared
in the stylesheet and used zero times, which is how long this had been the
intention.

Two grounds, not five. raised goes to the four sections that already read as
inset: Safe Mode, the depth index, the prices, the questions.

Measured rather than assumed — every foreground token was checked against the
new surface, the tightest being --muted-foreground-subtle at 4.61:1, which
still clears AA. [data-tone="raised"] sits after .dark because both are
specificity (0,1,0) and the tie breaks on source order.

Pricing is wrapped in SectionShell to get there, which it should have been
since #2. It was the one landing section rendering its own header, and it had
already drifted: extra spacers the shell has no concept of, a headline missing
text-pretty. Every shell change had been silently skipping the highest-intent
section on the page.

2. shadow-sm is a 3px blur on a 1216px-wide image

An order of magnitude too small, which is exactly why the hero read as a sticker
laid on the page rather than a window above it. Nine images across three files.

Replaced with the three-layer ramp every reference Mac-app site converged on
independently: tight contact shadow, mid lift, one large soft ambient.
Warm-tinted rather than neutral black.

drop-shadow rather than box-shadow because the shipped PNGs have an alpha
channel
— I checked — so the captures carry real transparent macOS squircle
corners and the shadow follows the actual silhouette. The border and radius come
off for the same reason: border-radius is a circular arc tracing a continuous
curve, and the join is visible.

3. The three tables had already disagreed

Not about typography — they differ there on purpose — but about the one thing
the hairline system exists to settle. Columns are structure and take the heavier
rule; rows are data and take the hairline. The spec table did that. The plan
comparison drew its column separators with the row weight
, so the one table
where columns carry the entire meaning drew them faintest.

DataTable owns the shell, the two weights by name, and a required
caption — the only reliable way to keep every data table announcing what it
is before a screen reader starts reading cells.

Worth flagging because it typechecks either way: two className="…${TOKEN}…"
strings in spec-strip were double-quoted rather than template literals, so they
would have shipped the literal text as a class name. Caught by grepping the
compiled stylesheet for the token names.

4. Arrow keys across the database tiles

Twenty-seven tiles; reaching one meant Tab twenty-seven times. Up and down move
a whole row, read off COLS at the current breakpoint so the two cannot
disagree. Focus clamps rather than wraps — wrapping a 2D grid on Left at the
start of a row lands you at the end of the previous one, which reads as focus
jumping backwards.

Purely additive: every tile stays in the tab order. The usual composite
pattern would leave one, but that needs role="grid" with row and gridcell
children, and this is one flat CSS grid whose borders are computed by index —
row wrappers would break the layout outright. Adding the roles without the
structure would describe a widget that is not there.

depth-grid and faq do not get this: they contain zero interactive
elements, so there is nothing to move between. The plan named all three.

5. Nothing from the last four PRs had a test

Survivable while the ssr job did not run. Not survivable now that it does,
because it is the only thing that sees the rendered DOM and it was still
checking the page as it looked before any of this.

Nine assertions, all on things a typecheck cannot see, each guarding a defect
this work actually shipped and had to repair — every rule numbered (AccentLine
was not), rule-inset-host present (without it half the ordinals were 32px
off), four routes to /download, both blocking objections before the feature
tour, the AI answer between Agents and Safety, more than one ground, Agents
adjacent to Safety, data-row on more than twenty elements, and plan
availability as words.

6. Four values claimed to be the brand colour

--primary #f68001, a theme-color meta #FFAA46, #ffaa46 across three OG
templates, a manifest declaring #000000. None agreed, none referenced the others.

theme-color was wrong in kind: it tints the browser's own chrome, so an accent
there paints the address bar a colour appearing nowhere on the page — and one
value cannot serve a site that follows the OS. Now two media-scoped tags
carrying the actual backgrounds, both computed from tokens.

The OG templates keep #ffaa46 deliberately. It corresponds to the dark
theme's --primary-strong, which computes to #ffa65e — 10.4:1 against the card
ground versus 10.2:1, a difference nobody can see. Changing it would regenerate
thirty-six committed PNGs through Chromium for nothing. What those files needed
was a reference, not a new value.

Still outstanding, unchanged

The hero recapture, the cold-start measurement, and eight sponsor descriptions.
All three need an artifact or a fact I do not have, and I will not invent any of
them.

Verification

typecheck, build and pint clean. 105 tests / 1849 assertions with
REQUIRE_SSR=1, up from 96. Rendered HTML checked for all six: four raised
sections, app-plate on hero and both comparison shots, zero shadow-sm left
on any screenshot, three tables through the primitive, two media-scoped
theme-color tags, and one main / one h1 / one header still.

Sixteen sections shared one background. Over roughly ten thousand pixels of
scroll the only rhythm device was hairline density, so a reader could not tell
from peripheral vision whether they had moved from Architecture to Agents to
Mobile — the difference between restrained and monotonous. `--card`,
`--secondary` and `--accent` were declared in the stylesheet and used zero
times, which is how long this had been the plan.

Two grounds, not five. `raised` goes only to the four sections that already read
as inset: Safe Mode, the depth index, the prices and the questions.

The token is checked, not assumed. Every foreground was measured against the new
surface: the tightest is `--muted-foreground-subtle` at 4.61:1, which still
clears AA. `[data-tone="raised"]` has to sit after `.dark` in source order —
both are specificity (0,1,0) and the tie breaks on position — and it needs only
one rule because `--surface-raised` is itself theme-swapped.

Pricing is wrapped in `SectionShell` to get there, which it should have been all
along. It was the one landing section that rendered its own header, and it had
already drifted: extra spacers the shell has no concept of, and a headline
missing `text-pretty`. Every future shell change had been silently skipping the
highest-intent section on the page.
`shadow-sm` is a 3px blur. On an image rendered 1216px wide that is an order of
magnitude too small, which is why the hero read as something laid on the page
rather than a window sitting above it. Nine images across three files used it.

The replacement is a three-layer ramp, which is the shape every reference
Mac-app site converged on independently: a tight contact shadow, a mid lift, and
one large soft ambient. Warm-tinted rather than neutral black, so it sits in the
page's own light instead of muddying it.

`drop-shadow`, not `box-shadow`, because the captures carry real transparent
macOS squircle corners — I checked, the shipped PNGs have an alpha channel — so
the shadow follows the actual silhouette. A box-shadow would draw a rectangle
behind a rounded image.

The border and radius come off for the same reason: the image already has its
own corners, and `border-radius` is a circular arc tracing a continuous curve,
which is visibly wrong where the two meet.
Three sections carry real tabular data and each hand-rolled its own table shell.
That is tolerable where they genuinely differ — the spec result set, the Safe
Mode ladder and the plan comparison have different typography and density on
purpose, and a primitive that parameterised all of it would be harder to read
than the markup it replaced.

What was not tolerable is that they had already disagreed on the thing the
hairline system exists to settle. Columns are stable structure and take the
heavier rule; rows are data and take the hairline. The spec table did that. The
plan comparison drew its column separators with the row weight — so the one
table where the columns carry the entire meaning was the one drawing them
faintest.

`DataTable` owns exactly that: the shell, the two weights by name, and a
required `caption`. Requiring the caption is the only reliable way to keep it
true; a table without one gives a screen reader no idea what it is about to read
cells from.

The interpolation in spec-strip is worth noting because it typechecks either
way: two `className="…${TOKEN}…"` strings were double-quoted rather than
template literals, so they would have shipped the literal text as a class name.
Caught by grepping the compiled stylesheet for the token names.
Twenty-seven tiles, and reaching the one you want meant Tab twenty-seven times.
Arrow keys now move between them, up and down by a whole row, Home and End to
the ends. The column count is read off `COLS` at the current breakpoint so the
two can never disagree.

Purely additive: every tile stays in the tab order. The usual composite-widget
pattern would take them all out and leave one, but that needs `role="grid"` with
`row` and `gridcell` children before assistive technology can make sense of what
happened — and this is one flat CSS grid whose borders are computed by index, so
row wrappers would break the layout outright. Adding the roles without the
structure would describe a widget that is not there, which is worse than
describing nothing.

Focus clamps at the ends rather than wrapping. Wrapping a two-dimensional grid
on Left at the start of a row lands you at the end of the previous one, which
reads as focus jumping backwards for no reason.

The other two grids the plan named do not get this: `depth-grid` and `faq`
contain zero interactive elements, so there is nothing to move between.
Nothing shipped in the last four PRs had a test. That was survivable while the
`ssr` job did not run; it is not survivable now that it does, because the job is
the only thing that sees the rendered DOM and it was checking the page as it
looked before any of this.

Nine assertions, all on the real server-rendered output, all on things a
typecheck cannot see: Tailwind silently drops an unknown utility, a CSS counter
class is just a string, and a section that stops rendering leaves no trace in
`tsc`.

Each one guards a defect this work actually shipped and had to repair:

- Every full-bleed rule carries the counter, because AccentLine did not and the
  first rule of every section went unnumbered.
- `rule-inset-host` is present, because without it half the ordinals rendered
  32px off and inside the content column.
- At least four routes to /download exist, because the page once offered two
  with three thousand words between them.
- Both blocking objections precede the feature tour, and the AI answer sits
  between Agents and Safety rather than after both.
- The page has more than one ground.
- Agents is adjacent to Safety, and Mobile follows Pricing.
- `data-row` appears on more than twenty elements, because it once appeared on
  two that could not take focus.
- Plan availability exists as words, because the checkmark is aria-hidden and
  every included feature announced as an empty cell.
…ser chrome

Four values claimed to be the brand colour: `--primary` at #f68001, a
`theme-color` meta at #FFAA46, #ffaa46 across three OG templates, and a manifest
declaring #000000. None of them agreed and none referenced the others.

`theme-color` was wrong in kind, not just in value. It tints the browser's own
chrome, so an accent there paints the address bar a colour that appears nowhere
on the page — and a single value cannot be right for a site that follows the
OS. It is now two media-scoped tags carrying the actual backgrounds, #ffffff and
#0a0a0a, both computed from the tokens rather than picked. The manifest follows.

The OG templates keep #ffaa46 deliberately. It corresponds to the dark theme's
`--primary-strong`, which computes to #ffa65e — 10.4:1 against the card ground
versus 10.2:1, a difference nobody can see. Changing it would mean regenerating
thirty-six committed PNGs through Chromium for nothing. What those files needed
was not a new value but a reference, so the next person knows the literals are
tokens that could not reach the stylesheet, and that touching them means
re-running `og:generate`.
@datlechin
datlechin merged commit 4f7e906 into main Aug 15, 2026
5 checks passed
@datlechin
datlechin deleted the feat/landing-roadmap branch August 15, 2026 03:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant