Visual modernization: Inter, true-neutral ramp, retuned type scale, real design system page - #815
Merged
Merged
Conversation
Groundwork for the visual modernization pass. Pure addition and repair —
nothing here changes how the app looks yet.
Design system (/d):
- Rebuild as a real inventory: foundations (ramps with contrast readouts,
type scale, radius, shadow, focus), typography, every button variant,
forms, feedback/overlays, layout primitives, and the recurring page
patterns that are not components yet. Every element is imported from
where it lives so the page cannot drift from reality.
- Self-contained light/dark toggle that restores on unmount.
- Gate the route behind REACT_APP_VERCEL_ENV !== "production" (the house
idiom, per PosthogWrapper) plus a noindex meta.
New src/lib/designTokens.js: single source of truth that tailwind.config.js
requires and /d imports, so the swatches cannot lie.
Pin the TextResizer font. This hidden element measures node label text for
getSize(), whose output lands in every node's data. findFontData() only
reads a rule whose selector is exactly `node`, but toTheme() emits node
styles under `:childless` — so fontData is {} for every chart and the
element was inheriting the UI font. Without this pin, changing the app
typeface would silently resize nodes on any chart whose CSS reads
data(shapeWidth). No-op today; load-bearing next commit.
Bug fixes, all verified dead in the current build:
- Dialog.tsx: a missing space glued `ff-dialog` onto the next class,
producing `ff-dialogdata-[state=open]:...`. Cost overflowV dialogs their
open animation AND broke Sandbox.tsx's `.ff-dialog` lookup, so the
sandbox upgrade prompt fired over open dialogs instead of waiting.
- Select.tsx: `focus:show-md` (typo for shadow-md), `rounded-xs` (not a
Tailwind 3 class), and a duplicated `flex`.
- LogIn.tsx: `text-md`, which has no key in the scale.
- ImportDataDialog.tsx: `italics` typo.
- Typography.tsx: `text-${size}` cannot be extracted by Tailwind's scanner;
it only worked because every value happened to appear as a literal
elsewhere. Replaced with a static map before the page pass can remove the
last literal and turn a latent bug into a live one.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The app read as dated for four measurable reasons, all of them tokens
rather than markup:
- The `neutral` ramp was warm/mauve, not neutral. Every stop was
red-dominant (#fcf8fb, #f3eef0, #362e2e), which is why neutral surfaces
looked visibly pink. 571 usages, all fixed by one config change.
- The type scale ran a full step large with 1.16 body leading
(base was 19px/22px), making everything chunky and hard to read.
- IBM Plex Sans carried the whole interface, with font-bold 72 times.
- No radius or shadow discipline; borderRadius and boxShadow were not in
the config at all.
Tokens now live in src/lib/designTokens.js, which tailwind.config.js
requires and /d renders, so the design system page cannot drift.
Type:
- Inter for the interface, self-hosted and subset to latin + latin-ext
with the opsz axis pinned to 14 — 874KB TTF down to a 101KB woff2 that
keeps the full 100-900 weight range. Preloaded, declared in index.html.
Verified: full glyph coverage for fr/de/es/pt-br; ko/hi/zh fall through
to system faces exactly as they did with Plex.
- IBM Plex Sans stays on the Google CDN as the display face (wordmark,
page titles), because the same family name is a legacy chart font and
customCssOnly charts depend on document-level availability.
- slang.css sets `html { font-family }` after Tailwind's preflight at
equal specificity, so it — not tailwind.config.js — was deciding the UI
font. Overridden via :root, which beats slang's :where(:root).
- Scale retuned to 12/14/16/18/20/24/30/36/48 with real body leading
(1.16 -> 1.5) and optical tracking at display sizes.
Colour: neutral ramp swapped for a true neutral; background #FAFAF7 ->
#FAFAFA; foreground unified at #18181B across Tailwind and slang, whose
hand-typed *Hsl siblings were updated with it. neutral-500 on white goes
3.9:1 -> 4.83:1, so Label (previously 2.3:1) now passes WCAG AA.
blue/purple/green/orange deliberately untouched — purple is the pricing
identity and blue reaches the frozen pricing surface for no visible gain.
Focus ring unified on blue-600; it previously shipped as three different
values in three spellings.
/pricing is repaired, not restyled. Six sites used arbitrary values that
do not move with a scale change — Pricing2.tsx:398 would have inverted
from 18/19/23 to 18/16/18. Each was re-pointed at on-scale sizes that
reproduce the shipped rendering. The "/" optical nudge was re-measured in
the browser (the old 50/33px pair at -4px sat 2px below the price's
optical centre; 48/24px needs -7px for the same 2px). min-h-[210px] was
measured and kept: content is 183px, so the floor stays active and the
card outline is unchanged.
Also fixed four text controls that sat under the 16px iOS auto-zoom
threshold (two pushed there by this change, two already broken).
Verified: 25 suites / 564 unit tests, 18 e2e, and all 10 template visual
goldens pass UNCHANGED — chart rendering is untouched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Merged
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 "fresh coat of paint" pass. Two commits, deliberately separated — the first is a pure addition + bug fixes and is safe to ship on its own.
Why it looked dated — measured, not vibes
Every cause was a token, not markup.
Settings.tsxis already clean Tailwind; it looked old because of what it was pointing at.neutralramp was warm/mauve. Every stop was red-dominant (#fcf8fb,#f3eef0,#362e2e) — which is whybg-neutral-100buttons on/sread visibly pink. 571 usages.basewas 19px/22px).font-bold72 times.borderRadius/boxShadowweren't in the config at all, androunded-xs(not a Tailwind 3 class) was silently dead.Labelwas 2.3:1 contrast — failed WCAG AA, visibly washed out on/o.What changed
Type. Inter for the interface, self-hosted and subset to latin + latin-ext with the
opszaxis pinned to 14 — 874KB TTF → 101KB woff2 keeping the full 100–900 weight range. IBM Plex Sans stays as the display face for the wordmark and page titles. Scale retuned to 12/14/16/18/20/24/30/36/48 with body leading 1.16 → 1.5.Colour. True-neutral ramp; background
#FAFAF7→#FAFAFA;foregroundunified at#18181Bacross Tailwind and slang (whose hand-typed*Hslsiblings were drifting).neutral-500on white goes 3.9:1 → 4.83:1, soLabelnow passes AA. blue/purple/green/orange deliberately untouched — purple is the pricing identity and blue reaches the frozen pricing surface for no visible gain.Design system.
/drebuilt from 81 lines into a real inventory — foundations with live contrast readouts, every button variant × size × state, forms, overlays, and the recurring page patterns that aren't components yet. Everything is imported from where it lives, so it can't drift. Tokens live insrc/lib/designTokens.js, whichtailwind.config.jsrequires and the page renders. Gated onREACT_APP_VERCEL_ENV !== "production".Two traps worth knowing about
slang.csswas deciding the UI font, nottailwind.config.js. It setshtml { font-family }after Tailwind's preflight at equal specificity. Editing the Tailwind config alone would have changed nothing. Overridden via:root, which beats slang's:where(:root).TextResizerwas inheriting the page font to measure node widths.toTheme.tsemits node styles under:childless, butfindFontDatamatchesselectorText === "node"exactly — sofontDatais{}for every chart. Changing the typeface would have silently resized nodes on any chart whose CSS readsdata(shapeWidth). The visual suite could never have caught this: it renders/f, which mounts<Graph>withoutGraphWrapper, so the resizer isn't even on the page. Font is now pinned inTextResizer.module.css— a no-op today, load-bearing forever after. Please don't remove it./pricingis repaired, not restyledSix sites used arbitrary values that don't move with a scale change.
Pricing2.tsx:398would have gone from 18/19/23 to 18/16/18 — an inverted responsive ramp. Each was re-pointed at on-scale sizes reproducing the shipped rendering.Two were settled by measuring in the browser rather than arguing:
/optical nudge: the old 50/33px pair at-4pxsat 2px below the price's optical centre, so 48/24px needs-7pxfor the same 2px.sm:min-h-[210px]kept unchanged. Card content measures 183px now vs ~192px before — the floor was already active, dead space grew ~9px, and deleting it would have made the cards visibly shorter. Now carries a comment explaining it's a deliberate visual floor, not a grid equaliser.Also fixed
Five latent class-name bugs, all verified dead in the current build — including
Dialog.tsx's missing space, which was gluingff-dialogonto the next class and therefore breakingSandbox.tsx's lookup, so the sandbox upgrade prompt fired over open dialogs instead of waiting. Plus four text controls sitting under the 16px iOS auto-zoom threshold (two pushed there by this change, two already broken).Verification
pnpm -F app check/pnpm -F api checkpnpm -F app testpnpm -F app e2epnpm -F app visual@font-facepresent;/dabsent from main bundleManual pass in light and dark across
/,/d,/s,/o,/l,/pricing. Glyph coverage measured per locale: 0 missing for fr/de/es/pt-br; ko/hi/zh fall through to system faces exactly as they did with Plex.Pricing2.tsx:386gained a border after itsshadow-md-only separation stopped readingTwo follow-ups, neither in this diff: CLAUDE.md is stale — it says 13 templates (actually 10) and 23 suites/322 tests (actually 25/564). And the two commits are separated on purpose, so a squash-merge would collapse that.
🤖 Generated with Claude Code