Skip to content

fix: replace the dead text-md and font-regular typography classes - #2629

Open
camielvs wants to merge 1 commit into
masterfrom
fix/dead-typography-classes
Open

fix: replace the dead text-md and font-regular typography classes#2629
camielvs wants to merge 1 commit into
masterfrom
fix/dead-typography-classes

Conversation

@camielvs

Copy link
Copy Markdown
Collaborator

Resolves B2 of #2626.

The bug

textVariants in src/components/ui/typography.tsx mapped size="md" to text-md and weight="regular" to font-regular. Neither is a Tailwind v4 token — the default theme defines --text-xs/sm/base/lg/xl/2xl and --font-weight-light/normal/semibold/bold. There is no --text-md and no --font-weight-regular.

A class with no matching token compiles to nothing. So every <Text>, <Paragraph>, <Heading level={1}> and <Link> on the default size/weight emitted a class that did not exist, and inherited its font-size and weight from an ancestor instead. It failed silently, and was copied from typography.tsx into link.tsx before anyone noticed.

The fix

before after
textVariants.size.md text-md text-base
textVariants.weight.regular font-regular font-normal
linkVariants.size.md text-md text-base
AppMenu CopyText text-md text-base

text-base is 1rem/1.5 and font-normal is 400 — identical to what Preflight already gives an element that inherits (html { line-height: 1.5 }, h1..h6 { font-size: inherit; font-weight: inherit }, no body font-size). So on its own this change is a visual no-op except where an ancestor overrode font-size or weight; there, the element used to pick up the ancestor's value and would now snap to 1rem/400.

Preserving current rendering

I walked every .tsx in src with the TypeScript AST, tracking each typography primitive's ancestor chain — including ancestors contributed by wrapper components that put a font utility on the element wrapping {children} (InfoBox's body is text-sm, TooltipContent is text-xs, DialogTitle is text-lg font-semibold, TableHead is font-medium, …). 13 elements across 9 files would have changed. Each now states the value it previously inherited:

  • InfoBox body (text-sm) → size="sm"PipelineValidationList ×2, ExamplePipelines ×2, FeaturedExamples, IOSection ×2, RemoteAuthErrorView, PipelineRun ×2, RunViewV2 ×2. (A sibling in PipelineValidationList already wrote size="sm" explicitly, confirming the intent.)
  • InfoBox with className="text-xs"size="xs" — the three inline <Link>s in ManualSubmissionInstructions.
  • TooltipContent (text-xs) → size="xs"BetaFeatureWrapper, matching its two siblings.
  • DialogTitle (text-lg font-semibold) → size="lg" weight="semibold"ComponentQuickDetailsDialog.
  • TableHead (font-medium) → className="font-medium"TableVisualizer ×2. Text's weight scale has no medium step, so this one cannot be expressed as a prop.

Cases that look like nesting but are not, and were verified to need no change:

  • asChild (<Button asChild><Link>, <DialogDescription asChild><Paragraph>) — Radix merges onto the same element, and cn(variants, className) puts the incoming class last, so twMerge lets the wrapper's text-xs/text-sm win.
  • CopyText forwards className into its inner <Text>, so all 9 call sites are same-element merges; verified per-site with a twMerge probe.
  • [&_.text-sm]:text-xs! (FlexNodeDetails, RecentRunsContent) keys off the emitted class name and only matches .text-sm. Confirmed no selector anywhere in src keys on .text-base or .font-normal.

The guard

src/components/ui/typography.test.tsx reads the token tables Tailwind actually compiles from (node_modules/tailwindcss/theme.css + src/styles/global.css, plus tailwind.config.js only when global.css carries an @config directive — see #2628), renders every step of Text, Paragraph, Heading and Link, and asserts each emits a font-size/weight class that resolves to a real token. A final pair of cases bans the two dead class names from src outright.

Mutation-tested: restoring text-md/font-regular in typography.tsx fails 6 of the 23 cases — both the scale assertions and the ban, independently.

Note

The issue proposed enforcing this with an ESLint no-restricted-syntax rule. That would not have worked: no-restricted-syntax is already configured for REACT_COMPILER_ENABLED_GLOBS, which explicitly includes src/components/ui/typography.tsx and src/routes/**, and flat config replaces a rule's options rather than merging them — so the new rule would be silently disabled in the very file it protects. CI also runs pnpm run lint without --max-warnings, so its "warn" severity could not fail the build. A test achieves the intent without either hole.

Verification

  • pnpm run typecheck — clean
  • pnpm run lint — clean
  • pnpm run test — 192 files, 1989 passed
  • AST scan re-run post-fix: no unintended nested sites remain

🤖 Generated with Claude Code

@camielvs
camielvs requested a review from a team as a code owner August 14, 2026 00:10
@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown

🎩 Preview

A preview build has been created at: fix/dead-typography-classes/7ff795a

@camielvs

Copy link
Copy Markdown
Collaborator Author

Follow-up: added a medium weight variant

Tailwind defines --font-weight-medium: 500, but textVariants.weight skipped it — the scale went light (300) → regular (400) → semibold (600) → bold (700). So callers who wanted 500 reached for className="font-medium" instead of the prop, at 6 sites, 4 of which predate this PR:

site what it is
TaskNodeCard.tsx:238 "Beta" pill
SidebarSection.tsx:27 section heading
DashboardComponentsView.tsx:65 uppercase eyebrow label
DashboardComponentsView.tsx:153 folder name
TableVisualizer.tsx ×2 table column headers (introduced earlier in this PR)

weight="medium" is now a variant and all six use it. This also removes the last raw-className weight override in the codebase, which matters beyond tidiness: a weight set via className only wins by twMerge ordering, and that indirection is what let font-regular go unnoticed in the first place.

Also converted two className="font-light" sites (TaskNodeCard.tsx:248, :256) that duplicated the already-existing light variant.

Unchanged rendering

Each conversion was checked with a twMerge probe comparing the old variant-plus-className string against the new one. All five distinct patterns resolve to an identical class set:

IDENTICAL  TableVisualizer      font-medium -> font-medium
IDENTICAL  TaskNodeCard beta    font-medium -> font-medium
IDENTICAL  TaskNodeCard light   font-light  -> font-light
IDENTICAL  SidebarSection       font-medium -> font-medium
IDENTICAL  Dashboard eyebrow    font-medium -> font-medium

An AST re-scan confirms no weight className remains on any Text/Paragraph/Heading in src. The guard test now covers medium alongside the other four steps.

pnpm run typecheck, lint, format clean; pnpm run test 192 files / 1990 passed.

@camielvs
camielvs force-pushed the fix/dead-typography-classes branch from 03eb0b0 to 415d64a Compare August 17, 2026 17:01

Copy link
Copy Markdown
Collaborator Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@morgan-wowk morgan-wowk left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Agent review. The core dead-class swap (text-mdtext-base, font-regularfont-normal) is correct, and the 13 same-set preservation edits check out. But flipping the defaults from 'inherit' to explicit 16px/400 has a blast radius beyond what was audited (inline).

Comment thread src/components/ui/link.tsx
@camielvs
camielvs force-pushed the fix/dead-typography-classes branch from 6f2bdf2 to a18debe Compare August 18, 2026 18:18
@camielvs

camielvs commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator Author

^ I've checked every call site relying on the default size/weight and found 10 more that were inheriting a smaller size and now jump to 16px:

  • Table rows: folder and pipeline names in FolderRow and PipelineRow
  • Inline text mid-sentence: ComponentDetail, ComponentSpecProperty, Import, DebugPanel, ReplaceConfirmationContent
  • The AI chat markdown surface: body links in renderMarkdown (your example — confirmed)
  • ActionButton's optional label, latent since no caller passes it today

All now pass their size explicitly. Two unrelated things in the diff also fixed: the beta badge had lost its dark-mode styling, and DashboardComponentsView kept a redundant font-medium that was failing prettier --check.

`text-md` and `font-regular` resolve to no Tailwind token, so they compiled to
nothing and the affected elements silently inherited their font-size/weight.
Replaces them with the real tokens (`text-base`, `font-normal`) and adds a test
so an invented scale step cannot ship again.

Where an ancestor overrode font-size or weight, the previously-inherited value
is now made explicit at the call site so rendering is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@camielvs
camielvs force-pushed the fix/dead-typography-classes branch from 0c3fb41 to 7ff795a Compare August 18, 2026 18:53
@camielvs
camielvs requested a review from morgan-wowk August 18, 2026 18:54
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.

2 participants