Skip to content

feat(website): ATC theme spike — aviation yellow, Archivo Black, scope navy - #1058

Merged
blove merged 24 commits into
mainfrom
blove/threadplane-theme-update-55baa6
Sep 8, 2026
Merged

feat(website): ATC theme spike — aviation yellow, Archivo Black, scope navy#1058
blove merged 24 commits into
mainfrom
blove/threadplane-theme-update-55baa6

Conversation

@blove

@blove blove commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Reskins the marketing website to the visual language of ATC — Live Air Traffic Radio (App Store id6755132266). Colours were sampled from the app's own screenshots rather than approximated: aviation yellow #FFAF00, frequency-strip amber #FFB700, scope navy #15253E, LIVE red-orange #FF3200, near-black #0A0A0A.

Built in libs/design-tokens rather than as a website-local override, so the spike measures what adopting the theme actually costs.

The one decision everything follows from

#FFAF00 on white is 1.84:1. Yellow cannot be a text, link or icon colour, so this is a remap of token roles, not a substitution of values:

Role Was Now
--color-accent (links, focus, borders) #004090 #15253E 15.37:1 — higher than what it replaces
--color-signal / --color-accent-light #64C3FD #FFAF00 fill only, always under ink at 10.73:1
focus ring navy stays navy a yellow ring on white is 1.84:1 and invisible

Typography

EB Garamond → Archivo Black (display), Inter → Archivo (text). Archivo Black ships a single weight and no italic, so 48 font-weight declarations and 4 font-style: italic declarations were removed rather than left to synthesize.

Inter is deliberately retained behind a new --font-diagram token. Diagram geometry is pinned to Inter's metrics — EnterpriseArchitecture.tsx pins every rectangle to an 8px grid and e2e/home-architecture.spec.ts measures each rendered text run against its card. Retyping diagrams would have forced a geometry rework larger than the retheme. The carve-out is 3 declarations, and that e2e is the gate proving it held.

Surfaces

New [data-ui="section"][data-surface="signal"] scope re-points the whole --color-* set for the yellow hero block, mirroring the existing data-surface="dark" mechanism. The hero closes with a full-bleed scope-navy frequency strip. Below it the page returns to white — yellow reappears only as the primary button, a rule, or a highlighter wash.

Bugs found and fixed along the way

  • A dangling --ds-font-serif reference survived the rename in workspace.css. An undefined custom property in a font-family list is invalid at computed-value time, so those headings lost their font entirely. Added the inverse guard — ds-var-contract.spec.ts only checked that names don't vanish, never that consumer references resolve.
  • dark.ts's accent derives from accentLight and flipped on its own, while five sibling tints were hardcoded blue. Guarded, mutation-verified.
  • card.spec.ts compares the Satori card palette against theme.css at test time, so declaring the divergence intentional in prose would just have left nx test website red for the whole arc. Card colours follow the retheme; only its bundled TTF is out of scope.

Test Plan

  • libs/design-tokens — 99/99
  • apps/website — 1367/1367
  • Playwright e2e — 128 passed, 1 skipped
  • home-architecture.spec.ts passes unchanged — the diagram carve-out held
  • home-hero.spec.ts passes — the taller hero did not push the demo past its 0.25 IntersectionObserver threshold
  • nav-height.spec.ts — 8/8 after the logo mark became a squircle
  • Measured on the rendered page: hero bg rgb(255,175,0); H1 on hero, hero button and nav button all 10.73:1; diagram text computed family is Inter; no horizontal overflow at 1280px or 375px

Known follow-ups

Deliberately out of scope, named so the divergence reads as a decision: the OG/social-card fonts (bundles its own EBGaramond-Bold.ttf), generate-whitepaper.ts and its committed preview HTML/PDFs, and icon.svg (favicon stays navy). The cockpit and example apps do not import tokens.css at all, so they are unaffected.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Sep 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
threadplane Ready Ready Preview Sep 8, 2026 5:17am UTC

Request Review

blove and others added 23 commits September 7, 2026 22:10
Reskins the website to the visual language of ATC — Live Air Traffic Radio:
aviation yellow #FFAF00, Archivo Black display type, and instrument-panel
data strips. Built in libs/design-tokens rather than as a website override,
so the spike measures what adopting the theme actually costs.

The central finding is that the accent role has to split: #FFAF00 on white
is 1.84:1, so yellow is a fill colour only and the interactive ink moves to
the app's scope navy #15253E (15.37:1, higher than the #004090 it replaces).
The focus ring deliberately stays navy on light surfaces.

Records the load-bearing risk: the homepage architecture diagram e2e added
by #1048 measures text runs against their cards after document.fonts.ready,
and Archivo's metrics differ from Inter's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diagrams do not take the brand face. They stay clean and minimal on Inter
through a new --font-diagram token, for two reasons: a heavy grotesk fights
a 16-node drawing, and the diagram geometry is tuned to Inter's metrics.

That second reason is the load-bearing one. EnterpriseArchitecture pins every
rectangle to an 8px grid and home-architecture.spec.ts measures each rendered
text run against its card, so swapping diagrams to Archivo would force a
geometry rework that could exceed the retheme itself. Keeping Inter makes it
zero.

The carve-out is three declarations — .arch-figure text in landing.css, and
the two sans rules in the docs diagram kit — which the mechanical
--font-inter rename must send to --font-diagram rather than --font-sans.
Section 8 now treats the overflow e2e as the gate on that carve-out holding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eleven tasks, TDD where there is something to assert: the three value-pinning
token specs are updated before the values move, so each colour change has a
red test first.

Notable ordering constraints baked into the plan:

- dark.ts needs no edit to `accent` (it derives from accentLight) but does
  need lines 29-33, which hardcode the old blue. Task 2 makes that a step of
  its own with a grep that proves no blue survives.
- The font-var rename and the --font-diagram carve-out land in ONE commit.
  Split apart, a blind find-and-replace sends the diagram rules to Archivo and
  the overflow e2e can fail at 390px only.
- The site is visibly broken between Task 4 and Task 5, which the plan says
  out loud so nobody stops to debug it.

Selectors verified against the source rather than guessed: the logo mark takes
the signal via `color` (PlaneMark fills with currentColor), and the docs active
item is [data-docs-navlink][data-active], already at docs.css:917.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nk and yellow signal

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A guard that compares values at test time cannot be satisfied by declaring
the divergence intentional in prose. card.spec.ts reads theme.css and asserts
card/tokens.ts still matches it, so the earlier scope boundary just left
nx test website red for the rest of the arc.

Colours are hex literals and cost nothing to move; the font genuinely needs a
bundled TTF. Splitting those.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ccent tints

The Satori social card hand-copies the light palette as hex literals, because
Satori cannot read CSS variables, and card.spec.ts reads theme.css at test
time and asserts the copy still matches. So the retheme left two guards red:
ink and accent had moved in the tokens and not in the card. A spec that
compares values at test time cannot be satisfied by declaring the divergence
intentional in prose — the only way to make it green is to move the card, so
the card's ink, accent, accent-surface and accent-border now follow the ATC
palette. Only the card's bundled font stays out of scope.

Also adds the guard the retheme was missing: darkOverrides.accent derives from
brand.accentLight and flips on a repoint, while the four accent tints beside it
are hardcoded rgba() and do not. The new dark-theme test derives the expected
RGB triple from the token rather than pinning a second literal, so it moves
with the palette; it was mutation-checked by reverting accentGlow to the old
blue and confirming it goes red.

The rest is honesty upkeep on comments the retheme falsified: a dark test named
for a blue accent it no longer asserts, an accent-family comment, and the
generator docstring that still called the cockpit fallback divergence "minor
drift" when those fallbacks are now a different hue entirely. base.ts hoists
AVIATION_YELLOW and SCOPE_NAVY so signal cannot drift from accentLight, nor
scope from accent. No emitted value changes except #ffc233 -> #FFC233.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ssed

Task 2's review found the plan named only 3 of 24 hardcoded old-brand sites in
apps/website/src. The rest would have survived the retheme as blue islands.

Includes the one site that must NOT take the yellow: ChatLandingCodeShowcase's
--chat-user-bg sits directly above --chat-user-color: #ffffff, so white on
aviation yellow would be 1.84:1. It takes scope navy instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rate aliases

A reviewer proved by mutation that deleting all five brand names
(--ds-signal, --ds-signal-strong, --ds-scope, --ds-alert, --ds-ink) from
css-vars.ts left the full design-tokens suite green: the existing
ds-var-sources-agree.spec.ts only checks that tokens.css is a superset of
cssVars(), which is correct for the type scale but wrong for brand colors,
which have no reason to exist in one emitter and not the other.

- Add a scoped reverse check to ds-var-sources-agree.spec.ts asserting
  cssVars('light') emits all five brand names, without making the whole
  comparison bidirectional (which would break on the legitimate type-scale
  superset).
- Fix the ds-var-contract.spec.ts docstring, which claimed the list is
  derived purely from a grep of consumer usage — false for the five new
  brand names (zero references outside libs/design-tokens/ today) and a
  trap that would lead someone to "clean up" and delete the guard.
- Document the deliberate --color-signal/accent-light and
  --color-scope/accent duplication in theme.css, and add a Brand
  sub-header to the --ds-* block in tokens.css / tokens-dark.css so the
  generated output explains itself to a website/cockpit author.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…or diagrams

Renames fontSerif (EB Garamond) to fontDisplay (Archivo Black) and
repoints fontSans at Archivo instead of Inter. Adds a new fontDiagram
token that keeps Inter: EnterpriseArchitecture.tsx pins every rectangle
to an 8px grid and home-architecture.spec.ts measures each rendered
text run against its card after document.fonts.ready, so retyping
diagram text to Archivo would silently overflow the cards and force a
geometry rework larger than the retheme itself. Diagram surfaces read
as information, not brand, and deliberately stay off the brand face.

Regenerates theme.css/tokens.css/tokens-dark.css from the new
typography values and updates the token<->CSS parity and --ds-* var
contract specs accordingly. apps/website and libs/workspace-react
still reference the retired --font-garamond/--font-inter names; that
rename is a separate follow-up task and is expected to leave the
website visually broken until it lands.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
theme.css emits --font-diagram as a raw 'Inter, ...' stack, which next/font's
hashed family never matches. If layout.tsx stops supplying the var, diagrams
silently fall back to system-ui and every glyph width shifts — defeating the
carve-out the token exists for. Only the architecture e2e would notice.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ut onto --font-diagram

The design-tokens generator no longer emits --font-garamond or --font-inter,
so every consumer reference in apps/website and libs/workspace-react moves to
the new names. layout.tsx's next/font `variable` fields move with them; the
faces themselves are swapped in a later task.

The diagram carve-out lands here rather than as a follow-up because
--font-inter was a single variable doing two jobs: ~100 body-text rules and
the diagram text. Renaming it alone would retype the architecture figure and
the docs diagram kit from Inter to Archivo, changing every glyph width.
EnterpriseArchitecture.tsx pins its rectangles to an 8px grid and
e2e/home-architecture.spec.ts measures each rendered text run against its
card, so that would land as a red e2e — possibly only at the 390px viewport.
Splitting the two commits would mean deliberately committing a broken
geometry, so the three diagram declarations move to --font-diagram (Inter,
retained for exactly this reason) in the same change:

  .arch-figure text                                    (landing.css)
  .tp-diagram-node[data-title="sans"] .tp-diagram-title (docs.css)
  .tp-diagram-meta                                      (docs.css)

Diagram rules already on --font-mono are untouched; mono was never Inter.

ui.css's FONTS note gains the constraint that makes --font-diagram fragile:
theme.css emits it as a raw `Inter, ...` stack, which next/font's hashed
family never matches, so the var has to keep being supplied by next/font in
layout.tsx or diagrams fall back to system-ui silently.

nx test website and nx test workspace-react pass, but these are jsdom suites
that do not resolve CSS custom properties — they cannot detect a wrong font
var. The architecture e2e is the real check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two documentation fixes from the Task 4 review, both about the diagram
carve-out that landed in the previous commit.

The fontDiagram docblock explained why diagrams keep Inter but not what keeps
them on it: on apps/website the var has to be supplied by next/font in
layout.tsx, because the raw `Inter` literal theme.css emits never matches
next/font's hashed family name. Cross-referenced to the FONTS note in
apps/website/src/styles/ui.css, which now carries the same warning.

The ds-var-contract docstring called `--ds-font-serif` "consumer-referenced"
in a paragraph that otherwise says the greps for these names come up empty,
which read as a contradiction. Reworded to what it actually is: a name this
list previously guarded and the retheme retires.

Note the review's stated premise was not quite right — `--ds-font-serif` is
not reference-free. libs/workspace-react/src/styles/workspace.css still names
it as the fallback behind var(--font-display), so the docstring now says so
explicitly instead of dropping the fact, and scopes the zero-reference claim
to the reserved names it was always about (verified: 0 hits each for
--ds-signal, --ds-signal-strong, --ds-scope, --ds-alert, --ds-ink,
--ds-font-display, --ds-font-diagram outside libs/design-tokens/).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Task 4 retired --ds-font-serif from the token sources; Task 5's rename swept
--font-garamond in the same declaration but left the --ds-* fallback pointing
at a name that no longer exists.

The failure mode is silent and worse than it looks: an undefined custom
property inside a font-family list is invalid at computed-value time, so the
whole declaration is dropped and .workspace-prose headings lose their font
entirely. There is no partial fallback to the first entry in the list.

ds-var-contract.spec.ts guarded only the other direction — that a name
consumers use does not vanish. Adds the inverse check, which fails if any
consumer references a --ds-* the generator does not define. Mutation-verified:
restoring the bug turns it red with the exact name.

--ds-surface-subtle is listed as a known pre-existing dangling reference
(already broken at 218ac1c) so it stays visible rather than being hidden by
the new guard.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
layout.tsx now loads Archivo_Black for --font-display and Archivo for
--font-sans via next/font/google, matching the ATC-derived token
library. EB_Garamond is gone.

Inter stays loaded here too, feeding --font-diagram. It cannot be
supplied by theme.css's raw `Inter, system-ui, sans-serif` literal:
next/font registers the family under a hashed name, so that literal
never matches it and would silently fall back to system-ui, changing
every diagram glyph width. Diagram geometry in
apps/website/e2e/home-architecture.spec.ts is pinned to Inter's
metrics on an 8px grid, so Inter must keep coming from next/font.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…trip

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e hero

The Task 8 rule replaced the wash with an ink underline globally, but
.marker-highlight is not hero-only — /langgraph, /chat, /ag-ui and /render all
use it on white canvas, where they would have picked up an accent-coloured
underline nobody asked for.

A yellow wash is the better answer anyway: it is literally what a highlighter
does, and it is the correct use of aviation yellow as a fill under near-black
ink rather than as a text colour. The ink underline is now scoped to the
signal block, where a yellow wash would be yellow on yellow.

Also records in the plan that nx test website does not type-check — a step
written as "watch the test fail" on a union change passes vacuously, because
vitest skips types and React forwards unknown props to the DOM.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… surfaces

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
DocsControlPlane.spec.tsx guards 'rounded sidebar states without a left
marker' from #963. The step would have overridden that deliberate decision to
achieve something the retheme had already achieved: --color-accent-surface is
now a yellow tint and --color-accent is scope navy, so the active item is
already a soft yellow pill with navy ink.

A plan written days earlier does not get to overrule a design decision the
codebase deliberately guards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ction

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… and italic

`--font-display` is Archivo Black, loaded by next/font at `weight: '400'`.
Google Fonts ships it in exactly one weight and no italic, so every rule that
asked for anything else was making the browser synthesize the face — smearing
an already-black glyph, or shearing it into a fake oblique. jsdom cannot
measure either, so nothing caught it.

- Removed 48 `font-weight` declarations (500–800) from rules whose own block
  sets `font-family: var(--font-display)`: pages.css 21, landing.css 15,
  marketing.css 5, docs.css 4, ui.css 2, chrome.css 1. The face has one weight,
  so declaring it is noise. `.hero-heading` keeps its explicit `400` and the
  comment documenting the constraint at the most-read site. Rules on
  `--font-sans` and `--font-mono` are untouched: Archivo and JetBrains Mono are
  genuinely multi-weight.
- Removed `font-style: italic` from the four display-face rules in landing.css
  (`.wp-cover-title`, `.final-cta-heading`, `.final-cta-mark`,
  `.proof-strip-watermark`). The faux oblique was most visible on the two that
  set it at 210px and up to 300px.

The logo mark was aviation yellow on the white nav and footer — 1.84:1, below
even the 3:1 floor for meaningful non-text graphics, and the exact thing this
theme's own thesis forbids. It is now the ATC app icon in miniature: a
signal-yellow squircle with an ink glyph, 10.73:1, inverting to an ink tile
inside the signal scope so it does not vanish on the yellow block.

The tile uses `box-sizing: border-box` rather than `content-box`. The logo sets
the nav's content-row height at the two smaller breakpoints, so padding on a
content-box icon inflates the whole nav — measured 58/66/81 to 64/72/81 against
a `--nav-h` that e2e/nav-height.spec.ts pins to the rendered nav within 1px.
Border-box insets the glyph instead, leaving every `--nav-h` offset untouched;
all 8 nav-height e2e tests pass. The stale comment above the rule (the glyph
"inherits the wordmark's color"; "the wordmark is Garamond") is rewritten.

Also in libs/design-tokens:

- The dangling-`--ds-*` guard only walked four hand-listed directories, missing
  libs/chat, libs/example-layouts, libs/design-tokens, apps/website/public and
  apps/website/e2e. Widened to the whole repo with node_modules/.next/dist/
  coverage excluded. This surfaced 13 pre-existing dangling names, all from the
  committed generator artifact apps/website/public/whitepapers/chat-preview.html;
  they are listed in KNOWN_PRE_EXISTING and attributed in the comment so they
  stay visible rather than hidden. The guard also had to stop grepping its own
  docblocks, which quote retired names as prose.
- `--ds-font-serif`'s comment claimed a surviving reference in workspace.css; a
  later commit repaired that, so it now has none.
- Aviation yellow on the dark ground is 10.24:1 on `canvas` and 9.24:1 on
  `surface`, not 8.33:1 — that figure is the ratio against the website's scope
  navy, a different surface. Corrected, and the deliberate website/dark.ts
  surface divergence is now documented from both sides.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The open-source strip landed on main after this branch renamed the font
custom properties, so it referenced --font-garamond and --font-inter, which no
longer exist. An undefined custom property in a font-family declaration is
invalid at computed-value time, so both rules would have shipped as silent
system-font fallbacks that look almost right.

.open-source-strip-line also puts an italic <em> inside what would have been
the display face, and Archivo Black has no italic. It is body-scale editorial
copy rather than display type, so it moves to --font-sans, where the italic is
real: Archivo now loads style ['normal','italic']. That also retires the faux
italics the sans face had been synthesizing all along, since Inter was loaded
normal-only too.

Adds font-vars.spec.ts, which fails if any stylesheet references a --font-*
var layout.tsx does not supply. This exact bug happened twice in one day — the
rename, and then a PR that landed in between — and nothing caught it: jsdom
does not resolve custom properties and the Next build does not type-check CSS.
Mutation-verified.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@blove
blove force-pushed the blove/threadplane-theme-update-55baa6 branch from 72cb41a to a9b787f Compare September 8, 2026 05:13
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

@blove
blove merged commit aa88fe6 into main Sep 8, 2026
71 checks passed
blove added a commit that referenced this pull request Sep 8, 2026
)

* docs: spec — Reliability as a scope band, compatibility as its own section

Three measured defects: the works-with logos have no colour treatment at all
so dark marks sit invisible on navy; the proof cells kept neutral
rgb(28,28,28) surfaces after #1058 moved the ground to scope navy; and the
hero strip is the same navy as the section that starts 78px later, so it
cannot close the yellow block the way it was designed to.

The aviation frame is Vx versus Vy — best angle clears the obstacle in front
of you and leaves you lower ten minutes later; best rate is what actually gets
you to altitude. It lands in the aside line, not the heading, so the working
argument and the copy guard are left alone.

Fixes are subtractive: card chrome off the figures, one pitch-ladder divider
instead of four background layers, and the logo row out of the section
entirely. Moving that row to a light ground retires the CSS filter an earlier
draft needed — the marks are drawn for light backgrounds, so on white there is
nothing to normalise and nothing to guard.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: implementation plan for the Reliability scope redesign

Six tasks, TDD where there is something to assert. Notable details the plan
pins down rather than leaving to discovery:

- The masthead needs margin-top: calc(-1 * var(--spacing-section-y)). Section
  sets its own top padding, so a first child would sit below it with a navy gap
  above — defeating the point of moving the strip to the seam.
- The ordering assertion uses getAttribute('class') for the SVG, because
  className on an SVG element is an SVGAnimatedString and comparing it to a
  string fails confusingly.
- "+ 4 more" becomes "+ 3 more": a comment in Reliability.tsx documents the
  four as Azure OpenAI, Pydantic AI, Microsoft Agent Framework and AWS Strands,
  and Strands is now named in the runtimes group — it was already cited by name
  in a reliability receipt, so hiding it in a count was odd.
- The watermark is explicitly protected. No task touches it and it sits in the
  same stretch of CSS as the chrome being removed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(website): the hero's trust strip becomes the proof band's masthead

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): give back the padding the masthead's negative margin took

A negative margin-top on an in-flow first child drags every following sibling
up with it, not just itself. So pulling the masthead onto the section's top
edge also removed the proof band's entire top padding — the eyebrow would have
sat flush against the bar. The matching bottom margin returns exactly what the
top pulled away.

Verified in a real layout engine, since jsdom applies no CSS: at 1280px the
masthead is flush with both the hero's bottom and the section's top (0 and 0)
and the heading keeps 143px of clearance.

Also corrects two comment claims: the bar is flat --color-scope while the band
is a gradient that only reaches that value at its bottom, and that difference
is what stops the bar dissolving into the band.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): the dark band's surfaces join its ground in the navy family

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(website): proof figures sit on the ground, not in cards

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): re-tune the proof grid's whitespace now that the boxes are gone

gap: 20px was a card-to-card gutter. With the cards removed, whitespace is the
only grouping cue left, and 20px was no larger than the gaps INSIDE a cell —
so wherever the grid stacks (≤900px and ≤560px) the rhythm argued that a
caption belonged to the numeral below it rather than above.

Now 44px 40px: the row gutter beats the intra-cell spacing 44 to 12, and the
column gutter is the old card padding expressed as column-gap, which is what
column-gap is for. That also drops a padding-right that was being applied to
the last cell too, shaving the row's right edge out of line with the receipts
border beneath it.

Also corrects two comments the previous commits made stale: the rationale cited
proof cards that no longer exist (--color-surface now dresses the secondary
button and the focus halo in that band), and a note 130 lines away still called
the dark scope's surface "dark grey".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(website): the proof band is framed as a climb, with a pitch ladder

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(website): compatibility becomes its own light section, grouped

The works-with row closed the dark reliability band, where the vendor
marks were invisible: .reliability-logo set only width/height/object-fit,
so Anthropic's #181818 mark sat on navy and Google's full-colour one was
the loudest thing in a band meant to be quiet. The section was also
carrying two arguments at once — trustworthy, and compatible.

The row now has its own light section, which is what makes the marks
legible: they are drawn for light grounds and need no treatment there,
so there is no CSS filter to guard. It is grouped (providers, runtimes,
protocols) instead of a flat run, AWS Strands is promoted out of the
hidden count (a reliability receipt already names it), and the
compatibility-not-endorsement claim is now visible text rather than
alt="" plus a spec comment.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): name all twelve integrations instead of hiding three in a count

The compatibility section named nine integrations and closed the Agent
runtimes group with a "+ 3 more" badge. The three it hid were Azure
OpenAI, Pydantic AI and Microsoft Agent Framework — and Azure OpenAI is
a model provider, not a runtime, so the section told a reader there were
three more runtimes when one of them was not. The badge's trigger was
`group.label === 'Agent runtimes'`, a string comparison against display
copy: rewording the label would have made it silently vanish. All three
marks were already on disk, so all twelve are now named in the group
each actually belongs to and COMPATIBILITY_MORE_COUNT is gone.

The spec could not see content disappear. Every test iterated
COMPATIBILITY_GROUPS — the same constant the component renders from — so
deleting the entire Protocols group, LangGraph and AG-UI, the two with
shipped first-party adapters, left all five tests green. A new test pins
the shape independently: three groups by name, twelve items, and four
entries looked up by literal text. Verified non-vacuous by deleting the
Protocols group and watching only that test fail.

The group lists had also lost their accessible name. The old markup
pointed each <ul> at a label via aria-labelledby; the rewrite left the
label in a bare <p> with no association, and the test that replaced the
old assertion was named for checking it without doing so. Each list now
takes its name from the sibling label through an id derived from that
label, so a reword cannot leave the list unnamed, and the spec asserts
getByRole('list', { name }) for every group.

Also: the adapter-guide CTA gains a weight bump and a hover colour (on
this light ground --color-accent is navy against near-black ink, a very
small hue step at 11px with no underline); the pitch-ladder comment no
longer claims a horizon the drawing does not have; and the ladder's
hairlines take vector-effect: non-scaling-stroke so they survive the
svg scaling down below its 700px viewBox. That property is not
inherited, so it sits on the lines rather than the <g> elements.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: record that Task 5's '+ N more' badge was superseded during execution

The badge could not be stated honestly: it rendered only under Agent runtimes
while one of the three hidden entries, Azure OpenAI, is a model provider. All
three marks already existed on disk, so the shipped version names all twelve
and deletes the count.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): the pitch ladder spans the band instead of floating left

Capped at 700px and left-aligned inside a 1120px container, the instrument's
waterline landed ~210px left of the band's centre and the whole thing read as
stray marks rather than an attitude indicator. Full width puts the waterline
on the container's centre line (640/640 measured) and makes it what the
comment always claimed it was: a divider that happens to mean something.

The strokes hold their weight at any scale because vector-effect:
non-scaling-stroke is on the lines, not the groups — vector-effect is not an
inherited property, so on the groups it would have been a no-op that looked
correct.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): guard the new section in the e2e spine, and correct three stale documents

The Compatibility section shipped as a new top-level block with no e2e
presence. The spine spec enumerates the homepage by heading id and asserts
document order, but `compatibility-heading` was never added — so the section
could have been reordered or deleted from page.tsx outright with all 128
Playwright tests and the whole unit suite still green (the unit spec renders
the component in isolation and never asserts it is mounted). Verified by
mutation: commenting out <Compatibility /> now fails the spine spec on
`locator('#compatibility-heading')` not being attached.

A comment in Reliability.tsx inverted the colour relationship it explained.
It described the masthead bar as --color-scope flat against a band gradient
"that only reaches that value at its bottom", which was true when written; a
later commit moved the band to #0B1622 → #0F1C2E, which never reaches
--color-scope #15253E, so the bar is now lighter than every part of the band.

The plan and the design spec still described a design that did not ship: cell
padding that became the grid's column-gap, a ladder max-width cap that was
removed and stroke rules that gained vector-effect, and a compatibility list
of eight marks with a "+ 4 more" badge where twelve are now named with no
count constant.

Also: the 900px .proof-strip-grid gap was left at 32px while the cells grid
was re-tuned to a 44px row gutter, inverting the grouping the cells comment
argues for; an orphan .compatibility-name class with no stylesheet rule and a
.proof-strip-cell positioning context whose ::before was deleted are gone; the
customer-claim copy guard is restored to Reliability.spec.tsx alongside its
copy in Compatibility.spec.tsx; the Compatibility region's aria-labelledby is
now pinned; and the proof cells list gained aria-label="Published scores" so
it is not an unnamed list beside a named one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: record the FinalCTA dark-variant check the token change implied

Four pages share that component and inherited the darker ground. Verified on
the rendered /langgraph rather than reasoned about: the secondary button was
the same grey-black-on-navy mismatch the proof cards had, and unlike the cards
it still exists — so it is the one element the change visibly improves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: spec — the proof band becomes a preflight checklist

Pilots run checklists, and the band's most useful half turns out to be the
part still outstanding: 11 ticked rows for what Threadplane provides, 8
unticked for what stays yours, phrased in the docs' own words. The Yours boxes
never fill — that is the argument, not an oversight. Beneath, the audit
numbers become an Airworthiness block where the response IS the figure.

Every row was checked against the docs rather than written from memory, and
that caught three errors. "Destructive actions — HELD FOR APPROVAL" was FALSE:
<chat> does not render the interrupt panel, and the docs say so outright. It
became two Yours rows. "Errors & retry", which had been held back as
unverified, is the opposite — zero-config retry across five classified error
kinds, now the first row. Keyboard and screen reader stays off: four
components document a11y, there is no audit, and reduced motion exists only in
code with nothing to link.

Supersedes the pitch ladder from the scope-redesign spec — the checklist is
the aviation device now, and two would be one too many.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: implementation plan for the preflight checklist

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(website): the preflight checklist rows, with a proof link each

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(website): the preflight checklist component

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(website): checklist styles in, figure-card and ladder styles out

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(website): the proof band is a preflight checklist

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): retire the orphaned receipts and re-derive the Angular range

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(website): make the Angular derivation guard actually bite

The plan's version asserted the response contains the first and last
supported major, which the literal '20-22' satisfies just as well as the
template does. It would only have failed once someone bumped a major --
by which point the homepage had already shipped the wrong range.

Stub the dependency to [41,42,43] instead and assert the value follows.
Mutation-verified: hardcoding the string fails with
'expected 20-22 to be 41-43'.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): separate the Airworthiness heading from the columns

The margin was on the list, so the heading sat flat against the last
Threadplane row -- measured 0px between them -- while being pushed away
from the rows it labels. Measured 38px after.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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