overhaul: 02 design system - #16
Conversation
bf8dfc9 to
59ac30c
Compare
CS-5
left a comment
There was a problem hiding this comment.
Code review of the design-system layer. Verified by installing this branch, running pnpm check (green) and pnpm build, then reading the emitted dist/_astro/global.*.css, dist/styleguide/index.html and dist/sitemap-0.xml, and by compiling global.css against the pinned tailwindcss@4.3.3 to confirm what the utilities actually resolve to. Contrast numbers below come from this PR's own src/lib/contrast.ts.
The structure is good and the build-time contrast idea is the right instinct. Eleven findings, four of which change what ships:
Rendering / behavior
global.css— in-prose links get no underline:text-decoration-lineis never set, and preflight'sa { text-decoration: inherit }resolves it tonone. DESIGN.md §8 unimplemented, and links become color-only.global.css— addingsvgto the basemax-width: 100%rule clampsChalkOval'sw-[calc(100%+2rem)], so the oval renders word-width and offset 1rem left instead of ringing the word.SketchArrow.astro—direction="right"is dead: the inlinerotatestyle overrides the-rotate-45class, since Tailwind v4 rotation uses therotateproperty.global.css—--color-bodyand--text-bodyboth claim.text-body; the color wins, so the body size token has no usable utility (and the type-ramp row demonstrating it silently sets a color).
The guard itself
5. styleguide.astro — the contrast check reads hex literals duplicated into the page, not global.css, so a token edit on the CSS side alone leaves the build green against stale values.
6. DESIGN.md — Danger Orange's "corrected" 7.9:1 is the destructive token's number; #FB923C measures 6.6:1. The error crosses the AAA line, and the guard's AA-only floor can't catch it.
Smaller
7. /styleguide is noindex but still listed in dist/sitemap-0.xml — one-line filter in astro.config.ts.
8. spec-chip's border is opaque currentcolor, while its comment and DESIGN.md §8 both specify 40% alpha.
9. Callout renders <figcaption> with no <figure> anywhere on the page.
10. ambient-pool-neutral silently renders nothing unless composed with ambient-pool.
11. Swipe (7.6 vs 7.7) and Science Blue label (4.8 vs 4.9) doc figures are each 0.1 off the verifier.
Nothing here is structural — 1, 2 and 3 are the ones I'd fix before merge.
Generated by Claude Code
|
503465d to
9e65237
Compare
Turn DESIGN.md into working CSS: the full token set, fluid type scale, self-hosted fonts, program themes, the motif components, and a /styleguide page that proves all of it. Contrast is verified at build time, not by eye. /styleguide computes every pair required by DESIGN.md §9 and throws with the offending pairs listed if one drops below its floor, so a token edit cannot quietly break accessibility. That verification caught two defects in the spec, amended here per DESIGN.md §11: - Science Blue's fill used a white label, which measures 3.5:1 — below AA, on fills that carry chip-sized text. Changed to the brand near-black (4.9:1), which also makes blue consistent with every other fill. - body's stated ratio was "≈11.5:1"; it measures 10.2:1. Still AAA. Also added a destructive pair, which §8 required but the doc never valued, and pinned the highlighter swipe at 25% alpha — the only value in the documented range that keeps white text at AAA. Fonts are declared face by face rather than through the @fontsource CSS, so only latin subsets reach the build: five woff2 files, no cyrillic or greek. Program themes remap exactly four tokens — primary, primary-bright, primary-foreground, ring — diff-checked in the built CSS. The brief said three; DESIGN.md §2 says four, and without primary-bright a program page's links and stats would stay yellow on a green page. Hand-markup strokes carry pathLength="100" so the draw-on animation's dash math is in percent; a hard-coded length truncates any path longer than the guess, which is what first left the chalk ovals as open arcs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YRfxMh7FLjQtDbb1BEsCbR
Three DESIGN.md §8 rules were declared but not in force. In-prose links set only underline thickness and offset, and preflight's `text-decoration: inherit` resolved the line to `none`, so links were colour-only; `spec-chip` used an opaque `currentcolor` border where the doc asks for 40% alpha; and adding `svg` to the base `max-width: 100%` rule clamped ChalkOval's deliberate 2rem overshoot, since `max-width` cannot be overridden by a `width` utility. `SketchArrow direction="right"` was a silent no-op: Tailwind v4 implements `-rotate-45` with the individual `rotate` property, which the per-variant inline `style` overrode. Direction is now summed into that one value; mirroring stays a class because `scale` does not collide. `--color-body` and `--text-body` both claimed `.text-body` and the colour won, leaving the size token unreachable and the type-ramp row demonstrating a colour. Added `@utility text-copy` and pointed the ramp at it. `/styleguide`'s contrast gate compared a hand-typed copy of the palette against itself, so a token edited only in the stylesheet left the build green. `@/lib/tokens` now parses the `@theme` and `[data-theme]` blocks out of `global.css`, and the program themes' `primary-bright` accents are gated too — with the theme list read from the stylesheet, so a new program cannot ship an unchecked accent. Verified: dimming `--color-muted`, renaming a token, breaking the FLL accent, and changing the swipe alpha each now fail the build. DESIGN.md's accent ratios disagreed with the repo's own verifier. Danger Orange is 6.6:1, not 7.9:1 — the old number sat above the 7:1 AAA line while the real value sits below it. Corrected alongside Safety Yellow (9.8), Science Blue (5.9), destructive text (7.9) and the swipe (7.6), and the measured value now renders per theme on the page. `Callout` emitted `<figcaption>` with no `<figure>` anywhere on the page. It renders `<p>` by default; `as="figcaption"` is opt-in and the caller owns the figure. `ambient-pool-neutral` overrides only a `::before` background, so used alone it emitted no pseudo-element — documented as the modifier it is. Quality: the §2.3 engineering grid was defined twice, in CSS and as an inline style with a raw `#FAFAFA`, already disagreeing on alpha; it is now one `@utility engineering-grid` driven by custom properties off `--color-foreground`. The §13 stroke contract was duplicated across three hand-markup devices, which had already drifted on `vector-effect`; it is now `@utility hand-stroke` plus a shared tone mapping. RegistrationMarks encoded one bracket as four hand-computed paths. The four `public/image/svg/logo-*.svg` files were byte-identical to `src/assets/brand/` and unreferenced, deploying 48 KB unhashed. The page was `noindex` and in the sitemap — a "Submitted URL marked 'noindex'" error. Added a sitemap filter. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BX5PrKuYNRLVxiEj3eejhs
9e65237 to
32b3bd8
Compare
Layer 2 of the overhaul stack, on
overhaul/01-foundation.plan/02-design-system.md.DESIGN.mdturned into working CSS.DESIGN.md— see "Design amendments" below, it needs your eyes.What's here
Tokens (
src/styles/global.css) — the full §2–§4/§6 set as a Tailwind v4@themeblock: the recessed surface system, the AAA text ramp, fill/text accent pairs, sponsor tiers, the fluid type scale asclamp()between 360px and 1440px, the three-value radius scale, motion durations and easings.Program themes remap exactly four tokens —
primary,primary-bright,primary-foreground,ring— and nothing else, diff-checked in the built CSS.Fonts, self-hosted. Declared face by face rather than importing the
@fontsourceCSS, because those ship every subset: this waydistcontains 5 latinwoff2files and no cyrillic, greek, or vietnamese.src/styles/fonts.tsexports the two above-the-fold URLs for Phase 05's preload.Motifs as components so pages can't reinvent them:
Pattern(engineering grid + dimension ticks),GhostNumeral,RulerDivider,RegistrationMarks,TitleBlock,Callout, and the hand-markup set —ChalkOval,ChalkUnderline,SketchArrow, each with three distinct paths. Plus utilities for the pocket anatomy, accent rules, media frame, highlighter swipe, ambient pools, spec labels and chips./styleguide— every token, ramp, and motif rendered once, with computed contrast ratios, and each section repeated underdata-theme="frc"and"fll". Noindexed.Contrast is verified at build, not by eye
The page computes every pair DESIGN.md §9 requires and throws with the offending pairs listed if one drops below its floor. Verified by temporarily dimming
bodyto#8A8A8A:A token edit can't quietly break accessibility now.
Design amendments (DESIGN.md §11) — please review
That verification caught two real defects in the spec:
#FAFAFAon#3B82F6measures 3.5:1, and blue fills carry chip-sized text (calendar/event chips, data UI). Changed the label to the brand near-black#171717→ 4.9:1, which also stops blue being the one fill with a white label. Science Blue's own hex is untouched, so its reservation as the future light-theme primary stands.body's documented ratio was wrong — "≈11.5:1" vs. a measured 10.2:1. Still AAA; the decision holds, only the number changed.Two gaps filled:
destructivepair (#DB262F/#FAFAFAfill at 4.6:1,#FCA5A5text at 8.0:1). §8 requires "the destructive text token" but no value existed anywhere in the doc.card). §2.13 asked for this to be verified on/styleguide; the page now prints the measured numbers.Also corrected Danger Orange's stated 8.0:1 to the measured 7.9:1.
Deviations from the phase brief
primary-bright. The doc wins (§11) — and without it, links and stat numerals on a green FRC page would stay yellow.src/components/ui/primitives/, which Phase 03 also populates. DESIGN.md §2.12 calls them primitives; the conventions README arrives with Phase 03.@typescript-eslint/no-unsafe-returnis off for.astro.astro-eslint-parserdoesn't type template JSX, so everyitems.map(() => <El />)resolves aserrorand trips the rule. Frontmatter — where the real logic lives — stays fully typed, andastro checktype-checks templates properly. The config comment names the sibling rules that may need the same treatment, and says not to blanket-off the family.TitleBlock,Callout) are done.Notes
Two mechanics worth knowing, both of which bit during implementation: hand-markup strokes carry
pathLength="100"so the draw-on dash math is in percent — a hard-coded length silently truncates any longer path, which is what first left the chalk ovals as open arcs — and the ovals and underlines usevector-effect="non-scaling-stroke", since their SVG is stretched non-uniformly over whatever word it wraps.I rendered the page at 1280px and 390px and iterated on it; both hold up, and the contrast table scrolls inside its own container rather than the page.
Generated by Claude Code