Skip to content

chore(gardening): adopt UI primitives + tone prop where the class delta is provably zero (18 swaps) - #2624

Merged
camielvs merged 14 commits into
masterfrom
automated-gardening/react/2026-W33
Aug 17, 2026
Merged

chore(gardening): adopt UI primitives + tone prop where the class delta is provably zero (18 swaps)#2624
camielvs merged 14 commits into
masterfrom
automated-gardening/react/2026-W33

Conversation

@camielvs

@camielvs camielvs commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

🤖 Automated draft PR — opened by the gardening skill. Nothing here auto-merges.
18 swaps in 14 files, every one provably identical computed CSS. The primitives category is
requiresVisualReview: true, so this PR deliberately applies only exact mappings and leaves every
judgement call to the decision queue. PR #2569's lesson — a <p><Paragraph> swap is not
automatically null — is honoured by proving the class delta from source before touching anything.

The proof this run is built on

Read from source, not assumed:

  1. textVariants fills in defaults. src/components/ui/typography.tsx:21Text defaults to
    tone="inherit" size="md" weight="regular" font="default", which emit
    text-foreground / text-md / font-regular / "".
  2. text-md and font-regular emit no CSS. Neither --text-md nor --font-weight-regular exists
    in the @theme inline block (src/styles/global.css:194), and neither is a Tailwind default
    (v4 ships text-base, font-normal). tailwind.config.js extends fontSize with only 2xs/3xs.
    So the only class a primitive adds with real CSS behind it is the tone colour.
  3. cn is twMerge(clsx(...)) (src/lib/utils.ts:4), so a trailing className="text-muted-foreground"
    already overrode the text-foreground that tone="inherit" injects. Moving that colour into
    tone="subdued" therefore produces the identical computed colour and removes a conflicting
    class pair.

That is what makes each swap below an exact mapping rather than a hopeful one.

Findings applied

Raw HTML → primitive (3)ui-primitives#typography

  • ConfirmationDialogs/shared.tsx:4<p className="text-muted-foreground">
    <Paragraph tone="subdued">. Renders <p> either way; classes identical.
  • PipelineRow.tsx:218<span className="text-xs text-muted-foreground">
    <Text size="xs" tone="subdued">. Text renders a <span> by default.
  • CodeViewer.tsx:63<span className="text-sm text-muted-foreground">
    <Text size="sm" tone="subdued">.

Hardcoded colour className → tone (12)ui-primitives#typography

  • DocsQuickLinks.tsx:81, InputDetails.tsx:132, InputDetails.tsx:155,
    MissingPipelineInputValueResolution.tsx:79, DashboardFavoritesView.tsx:151,
    DashboardRecentlyViewedView.tsx:23, DashboardRecentlyViewedView.tsx:100
    className="text-muted-foreground"tone="subdued"
  • NodeListItem.tsx:67className="text-destructive"tone="critical"
  • DashboardHomeView.tsx:65, :147 — → tone="subdued" className="shrink-0"
  • DashboardComponentsView.tsx:71 — → tone="subdued" className="tabular-nums"
  • DashboardComponentsView.tsx:153 — → tone="subdued" className="font-medium truncate"

Non-mappable classes (shrink-0, tabular-nums, font-medium truncate) stay in className untouched.

className="font-mono"font="mono" (3)ui-primitives#typography names this rule verbatim

  • AddGitHubLibraryDialogContent.tsx:42
  • ConduitDetails.tsx:109truncate flex-1 retained in className
  • OutputDetails.tsx:68 — the hardcoded text-gray-500 retained (no tone maps to it)

These three are class-equivalent only because font-regular is dead, and it is worth being explicit
about that dependency, because it is not the same argument as the other 15 swaps:

before: twMerge("text-foreground text-xs font-regular", "font-mono text-gray-500")
     → "text-xs font-mono text-gray-500"                   ← font-regular DROPPED
after:  twMerge("text-foreground text-xs font-regular !font-mono", "text-gray-500")
     → "text-xs font-regular !font-mono text-gray-500"     ← font-regular SURVIVES

twMerge drops font-regular against a later font-mono but does not dedupe it against
!font-mono, so the emitted class set genuinely changed here — only font-regular compiling to nothing
keeps the pixels equal. If --font-weight-regular is ever added to the theme, these three sites gain a
weight the raw className versions never had. font="mono" also introduces !important where the
className had none.

Findings applied 18
Files 14 (+25 / −19)
.tsx files scanned 1,331
Exact-mapping candidates found 18 of 18 applied
Confidence threshold 0.9 (primitives, config)
Validation pnpm run validate:test ✅ — 191 test files / 1,966 tests
React Compiler edits 0 (flagOnly: true; --promote-compiler not passed)

Reviewer checklist

  • Visually diffed the rendered output — no layout/spacing regression
    (mandatory: requiresVisualReview: true for primitives)
  • ⚠️ Self-review was skipped (review skill unavailable) — review this diff manually.
    The engine requires every PR to survive the review skill first, but that skill is
    disable-model-invocation and only a human can run it.
  • Spot-check one tone="subdued" site in both light and dark mode — the whole argument rests on
    text-muted-foreground being emitted identically, and tone is the only real class delta.
  • Confirm you agree text-md / font-regular are dead classes. The three font="mono" swaps
    depend on font-regular being dead (see above); the other 15 do not. If they are supposed to work,
    that is a separate (and more interesting) bug in typography.tsx — every <Text> in the app is
    currently rendering at inherited size and weight. This PR does not change that either way.

Deliberately not applied

Each of these is a real convention gap, but none is an exact mapping, so all are queued for a human.

  • 4 raw <p className="text-sm font-semibold"> (RegionInput.tsx:36, ConfigInput.tsx:67,
    GoogleCloudSubmitter.tsx:42, :58). <Paragraph size="sm" weight="semibold"> would add
    text-foreground. body does set text-foreground (global.css:294), so the computed colour is
    probably unchanged — but proving no intermediate ancestor recolours these four sites is exactly the
    ancestor-chain reasoning requiresVisualReview exists to send to a human. Not applied.
  • FlexNodeCard.tsx:54, :97 — the code says why: a user-configurable fontSize needs an inline
    style, which Text does not accept. KEEP (documented, correct).
  • Raw <p> with hardcoded palette coloursContextPanelProvider.tsx:32 (text-gray-500),
    ImportPipeline.tsx:232/:243 (text-green-500 dark:text-green-400, text-red-500 dark:…),
    ImportComponent.tsx:228/:231/:265/:283 (text-gray-500/600 + dark: pairs). No tone maps to
    a raw palette colour or a dark: variant pair. Needs a design-token decision first.
  • 13 Text/Paragraph sites in 11 files with text-gray-* / dark: pairs — same reason. The IDE
    already flags several as cssConflict (e.g. MissingPipelineInputValueResolution.tsx:55,
    ConduitDetails.tsx:81); those warnings are pre-existing, not introduced here.
  • 27 className="flex flex-col" in 19 files → BlockStack. blockStackVariants
    (layout.tsx:13) bases on flex flex-col w-full and defaults to items-start justify-start gap-0.
    w-full and items-start are real layout changes (a plain flex-col column stretches its children;
    items-start shrinks them to content width). Per-site visual review required.
  • 14 bare <p> (no className) in non-test source, 13 of them in ConfirmationDialogs/*
    (ReplaceConfirmation.tsx, BulkUpdateConfirmationDialog.tsx, UpgradeComponent.tsx).
    <Paragraph> would add text-foreground, and dialog bodies commonly inherit a muted colour from
    the shadcn DialogDescription wrapper — so this swap could visibly darken dialog copy. This is the
    single largest raw-<p> bucket and the one most likely to regress; it needs a look at the rendered
    dialogs, not a grep.
  • 10 raw <h1><h6> in 5 files, 6 <Text as="h*"> in 3 filesHeading. Heading
    (typography.tsx:124) accepts only children and level — it forces its own size/weight and
    accepts no className. Every site here carries classes it would drop. Not mappable.
  • src/components/ui/date-picker.tsx:42 — a genuinely exact <span className="text-muted-foreground">
    <Text tone="subdued">, but it lives inside a shadcn-derived primitive. Making one
    components/ui primitive depend on another is a design call, and this file is regenerable by the
    shadcn CLI. KEEP.

React Compiler — flag-only, nothing edited

flagOnly: true and promotionEnabled: false in .github/gardening-config.json, and
--promote-compiler was not passed. Reporting only:

  • Coverage: 783 / 1,331 src files (58.8%) across 75 enabled entries.
  • Promotion candidates: 0. Every directory annotated 0 useCallback/useMemo - ready to enable in
    react-compiler.config.js is already uncommented, i.e. already enabled. The remaining commented-out
    entries carry 12–190 useCallback/useMemo each and are explicitly not marked ready.
  • Largest coverage gaps (uncovered files by area): src/components/shared 206, src/utils 61,
    src/hooks 45, src/components/ui 37, src/services 22,
    src/providers/ComponentLibraryProvider 18, src/components/PipelineRun 16, src/agent 26 (incl.
    tools, agents).
    (src/models/** is in excludeGlobs and is excluded from that ranking.)
  • Manual-memoization removal candidates: 0 real ones. Only 6 call sites survive inside enabled dirs,
    and all 6 look load-bearing rather than redundant: 3 × useMemo wrapping a debounce factory
    (PipelineNotesEditor.tsx:20, PipelineDescriptionEditor.tsx:21, useSelectionBehavior.ts:41) where
    a fresh instance per render would break debouncing outright, and 3 × memo() on components whose
    referential stability matters to React Flow / syntax highlighting (GhostNode.tsx:25,
    CodeSyntaxHighlighter.tsx:18, CodeBlock.tsx:34). The compiler does not guarantee identity
    stability, so these are KEEPs, not cleanup debt.

New primitives

None proposed, none authored. Per the pillar spec, new primitives are flag-only and never written
without express permission — and nothing in this scan showed a recurring shape with no existing
primitive to cover it. The gaps found were all adoption gaps, not coverage gaps.

Method deviations, disclosed

  • Commits are one-per-file (14), not one-per-finding (18). Four files hold two findings of the same
    category; DashboardHomeView.tsx's two sites are byte-identical and were replaced together.
  • All 18 edits went through the Edit tool. No scripted applier was used in this pillar.

@camielvs camielvs added the automated-gardening Automated codebase gardening label Aug 13, 2026
@camielvs
camielvs requested a review from a team August 13, 2026 19:54
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown

🎩 Preview

A preview build has been created at: automated-gardening/react/2026-W33/f205023

Comment thread src/routes/v2/pages/Editor/nodes/IONode/context/OutputDetails.tsx
@camielvs
camielvs marked this pull request as ready for review August 13, 2026 22:54

@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. All 18 primitive/tone swaps verified zero class-delta on the current base (raw elements carried no handlers/aria; twMerge preserves the overriding colour class; the !font-mono delta is disclosed). Only caveat is merge-order coupling with #2629 for the 3 size-less raw→primitive swaps — you're owning merge order. LGTM.

camielvs commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator Author

Merge activity

  • Aug 17, 10:48 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Aug 17, 10:48 PM UTC: Graphite rebased this pull request as part of a merge.
  • Aug 17, 10:51 PM UTC: Graphite rebased this pull request as part of a merge.
  • Aug 17, 10:54 PM UTC: @camielvs merged this pull request with Graphite.

@camielvs
camielvs force-pushed the automated-gardening/react/2026-W33 branch from 4141066 to f699bce Compare August 17, 2026 22:48
…on copy

Raw <p className="text-muted-foreground"> -> <Paragraph tone="subdued">.
Emitted classes are identical (text-md/font-regular resolve to no CSS).
Ref: ui-primitives#typography
Raw <span className="text-xs text-muted-foreground"> -> <Text size="xs" tone="subdued">.
Text renders a span by default, so the element and classes are unchanged.
Ref: ui-primitives#typography
Raw <span className="text-sm text-muted-foreground"> -> <Text size="sm" tone="subdued">.
Ref: ui-primitives#typography
…ckLinks)

className="text-muted-foreground" -> tone="subdued". cn() is twMerge, so the
className already overrode tone's default text-foreground; the computed CSS is
unchanged and the conflicting pair is gone.
Ref: ui-primitives#typography
…(NodeListItem)

Ref: ui-primitives#typography
…tails)

Two sites. Ref: ui-primitives#typography
…PipelineInputValueResolution)

Ref: ui-primitives#typography
…rdFavoritesView)

Ref: ui-primitives#typography
…rdRecentlyViewedView)

Two sites. Ref: ui-primitives#typography
…rdHomeView)

Two sites; the non-mappable shrink-0 stays in className.
Ref: ui-primitives#typography
…rdComponentsView)

Two sites; tabular-nums / font-medium truncate stay in className.
Ref: ui-primitives#typography
…ddGitHubLibraryDialogContent)

Ref: ui-primitives#typography
…onduitDetails)

Ref: ui-primitives#typography
…utputDetails)

The hardcoded text-gray-500 has no tone equivalent and is left alone.
Ref: ui-primitives#typography
@camielvs
camielvs force-pushed the automated-gardening/react/2026-W33 branch from f699bce to f205023 Compare August 17, 2026 22:50
@camielvs
camielvs merged commit 4976b51 into master Aug 17, 2026
16 checks passed
@camielvs
camielvs deleted the automated-gardening/react/2026-W33 branch August 17, 2026 22:54
camielvs added a commit that referenced this pull request Aug 19, 2026
`tailwind.config.js` has been dead since the v4 upgrade. Tailwind v4 is CSS-first and does **not**
auto-load a JS config — it only reads one when a stylesheet asks for it with `@config`. `src/styles/global.css`
never did, so the `theme.extend.fontSize` entries in that file were never registered and `text-2xs` /
`text-3xs` compiled to nothing.

```diff
  @import "tailwindcss";

+ @config "../../tailwind.config.js";
+
  @plugin "tailwindcss-animate";
```

## Proof the directive takes effect

A byte-identical build is ambiguous on its own — it is also what a silently-ignored directive looks
like. So I added a probe usage (`className="… text-2xs text-3xs"`) to a real component and built the
app both ways:

| | `.text-2xs` | `.text-3xs` |
| --- | --- | --- |
| with `@config` | `font-size:.625rem` ✅ | `font-size:.5rem` ✅ |
| without `@config` | not emitted | not emitted |

The probe was reverted; it is not part of this diff.

## Proof nothing changes today

No file under `src` uses `text-2xs` or `text-3xs` — zero occurrences. With the probe removed, the built
stylesheet is **byte-identical** to `master`:

```
master:      161,345 bytes
this branch: 161,345 bytes   (cmp: identical)
```

That also settles the one real risk in loading a legacy config: its `content: ["./src/**/*.{js,jsx,ts,tsx}"]`
key narrows source detection from v4's automatic scanning. Identical output over a full app build means
no currently-used class lives outside that glob. Worth knowing if a class is ever added to a `.html`,
`.mdx`, or `.mjs` file — it would need adding to `content`.

## Why `@config` rather than moving the values into `@theme`

Chosen per request. The alternative — deleting `tailwind.config.js` and adding `--text-2xs: 0.625rem` /
`--text-3xs: 0.5rem` to the existing `@theme inline` block — would be the more idiomatic v4 shape and
would leave one place to look for theme values instead of two. Happy to switch if preferred.

## Context

Found while reviewing #2624, which independently discovered the same class of bug: `text-md` and
`font-regular` in `typography.tsx` also emit no CSS, because `--text-md` and `--font-weight-regular` are
not defined either. That one is a separate issue — those variants are referenced by every `<Text>` in the
app, so defining them **would** change rendered output, unlike this change.

## Reviewer checklist

- [ ] Agree `@config` is the direction, rather than folding the two steps into `@theme inline`
- [ ] Sanity-check that a legacy `content` glob is acceptable (see above)

## Validation

- `pnpm vite build` ✅ — output byte-identical to `master`
- `pnpm prettier --check src/styles/global.css` ✅
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automated-gardening Automated codebase gardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants