Skip to content

chore(tailwind): upgrade apps/sim to Tailwind v4 - #7419

Merged
waleedlatif1 merged 8 commits into
stagingfrom
worktree-shadcn-cn-migrate
Sep 3, 2026
Merged

chore(tailwind): upgrade apps/sim to Tailwind v4#7419
waleedlatif1 merged 8 commits into
stagingfrom
worktree-shadcn-cn-migrate

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Upgrade apps/sim from Tailwind v3.4 to v4.3 — CSS-first config, tailwind.config.ts deleted
  • Bump tailwind-merge v2 → v3 in @sim/emcn (v2 encodes Tailwind v3's utility surface and would stop resolving conflicts for anything v4 added or renamed); apps/docs now re-exports the same cn, so there is one merge engine instead of two
  • Intended to be visually inert; every claim below was verified by building the stylesheet both ways and diffing computed styles in a headless browser, not by reading the upgrade guide

Parity evidence

check result
Preflight (bare elements, all computed properties) 112/112 identical
Border-width combinations (border border-b-0, border border-2, …) 10/10 identical
Real class strings, computed styles across light/dark/touch 4,673 strings, no visual differences
tailwind-merge v2 vs v3, harvested class strings 19,247 strings, 3 diffs — all inert (below)
tailwind-merge v2 vs v3, pairwise base+override combinations 69,900 pairs, 0 diffs

The 3 merge-engine diffs are one mechanism: v3 treats bare outline and outline-<n> as a single class group, so outline outline-2 collapses to outline-2. In v4 .outline and .outline-1 compile byte-identically, and .outline-2 sets the same outline-style with a wider outline-width, so the dropped class contributed nothing. v3 is the more correct of the two here.

v4 defaults deliberately pinned

Taking these would have changed rendering:

  • --font-sans — v4 changed its own default to an -apple-system stack, and Preflight resolves the document font through it
  • --text-xs--line-heightxs is the one custom size that collides with Tailwind's scale and inherits its ratio (16.5px → 14.67px)
  • The 24 stock palette colours the app uses — v4 re-authored the palette in oklch, which is visibly more saturated on P3 displays. Opacity modifiers verified pixel-identical afterwards.
  • Breakpoints pinned in px — v4's defaults are rem, which shifts every breakpoint for anyone whose browser font size is not 16px

v3 behaviours restored that v4 silently changed

Each of these was a real, user-visible regression caught during verification:

  • hover: on touch devices — v4 gates hover behind @media (hover: hover), which killed 64 hover classes on touch. Restored with @custom-variant hover (&:hover). The app keeps a separate hover-hover variant for the places where that gating is actually wanted.
  • blur-0 — removed in v4, leaving an icon permanently blurred on hover. Renamed to blur-none (9 sites).
  • space-y-* specificity — v4 wraps the selector in :where(), dropping it to 0-0-0 so a child's own mt-* now wins (2 layouts).
  • Preflight — v4 drops or changes seven v3 behaviours no diff would surface. Restored: button cursor: pointer (plus v3's :disabled companion), form-control background, ::placeholder colour, <th>/<td>/<option> padding, [type=search] normalisation, <dialog> centring.

Two things worth reviewer attention

  • borderWidth.DEFAULT has no v4 theme key. Confirmed against the v4 docs — there is no --default-border-width. A PostCSS pass (lib/postcss/hairline-border-width.mjs) rewrites Tailwind's own 1px output through --border-width in place. Re-declaring the utilities in a trailing @layer — the obvious fix — makes border beat border-2 and border-t-0, because it lands after Tailwind's output at equal specificity. The plugin preserves Tailwind's ordering, specificity and variant coverage, and is covered by 18 fixture tests.
  • rounded-sm / rounded-md are NOT renamed. @tailwindcss/upgrade would rewrite all 146 sites to rounded-xs; that is wrong here because this app overrides --radius-sm/--radius-md. Verified identical as-is.

Type of Change

  • Chore (dependency/tooling upgrade)

Testing

  • Full suite: 40,590 tests passing across 2,934 files, 19/19 tasks
  • bun run check:audits: 45/45 · bunx turbo run type-check: 26/26 · bun run lint: 26/26
  • Production builds of apps/sim and apps/docs: clean; hairline plugin verified present in shipped CSS

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

Migrates apps/sim from Tailwind v3.4 to v4.3 (CSS-first config) and replaces
clsx + tailwind-merge with the cn package in @sim/emcn and apps/docs.

The upgrade is intended to be visually inert. Parity was verified by building
the stylesheet both ways and diffing computed styles in a real browser:

- Preflight: 112/112 bare elements identical, after restoring seven v3
  behaviours v4 dropped (button cursor, form-control background, placeholder
  colour, table/option padding, search-field normalisation, dialog centring)
- Utilities: every class the app uses renders pixel-identically
- Border widths: 10/10 combinations identical
- Class strings: 699 changed strings run through both merge engines, 0 diffs

Pinned against v4 defaults that would otherwise shift rendering: --font-sans
(v4 changed its own default stack), --text-xs--line-height, and the 24 stock
palette colours the app uses (v4 re-authored the palette in oklch, which is
visibly more saturated on P3 displays).

borderWidth.DEFAULT has no v4 theme key, so a PostCSS pass rewrites Tailwind's
own border-width output through --border-width. Re-declaring the utilities in a
trailing @layer instead would make `border` beat `border-2` and `border-t-0`.

rounded-sm and rounded-md are deliberately NOT renamed: this app overrides that
radius scale, so the standard v3->v4 rename would change their values.

cn is compiled ahead of time (cn build --full) so the config compiler stays out
of the browser bundle; check:cn-tables fails CI if the tables drift.
@waleedlatif1
waleedlatif1 requested a review from a team as a code owner September 2, 2026 21:58
@vercel

vercel Bot commented Sep 2, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
docs Ready Ready Preview Sep 3, 2026 6:47pm UTC

Request Review

…-migrate

# Conflicts:
#	apps/sim/app/workspace/[workspaceId]/home/components/suggested-actions/suggested-actions.tsx
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile-apps

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@greptile-apps

greptile-apps Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR migrates apps/sim to Tailwind CSS v4 while preserving the existing visual behavior.

  • Moves Tailwind configuration into CSS-first theme, source, plugin, and variant declarations.
  • Adds a PostCSS pass that preserves token-driven hairline borders.
  • Upgrades and consolidates the shared Tailwind class-merging implementation.
  • Updates affected utilities and component class names for Tailwind v4 compatibility.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains in the eligible follow-up review scope.

No blocking failure remains.

Important Files Changed

Filename Overview
apps/sim/app/_styles/globals.css Replaces the deleted TypeScript Tailwind configuration with CSS-first Tailwind v4 sources, theme values, variants, plugins, and compatibility styles.
apps/sim/lib/postcss/hairline-border-width.mjs Adds a targeted post-processing pass that rewrites generated one-pixel border utilities to use the application’s hairline width token.
apps/sim/postcss.config.mjs Wires the Tailwind v4 PostCSS plugin before the new hairline border transformation.
packages/emcn/src/lib/cn.ts Moves the shared class-name helper to tailwind-merge v3 while retaining the custom font-size class group.
apps/docs/lib/utils.ts Re-exports the design system’s shared class-name helper instead of maintaining a second merge engine.
apps/sim/tailwind.config.ts Removes the Tailwind v3 JavaScript configuration after migrating its behavior into the application stylesheet.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Sources[App and package source classes] --> Tailwind[Tailwind CSS v4 PostCSS plugin]
  Theme[CSS-first theme and variants] --> Tailwind
  Tailwind --> Hairline[Hairline border-width PostCSS pass]
  Hairline --> CSS[Application stylesheet]
  Components[Sim and docs components] --> CN[Shared emcn class merger]
  CN --> Sources
Loading

Reviews (7): Last reviewed commit: "Merge remote-tracking branch 'origin/sta..." | Re-trigger Greptile

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 278 files

Confidence score: 4/5

  • .agents/skills/design-taste-frontend/SKILL.md still contains canonical examples importing motion/react, so copied samples can fail with an unresolved motion module; replace every sample import with the repository’s declared dependency.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".agents/skills/design-taste-frontend/SKILL.md">

<violation number="1" location=".agents/skills/design-taste-frontend/SKILL.md:7">
P2: The repo override still leaves canonical samples importing `motion/react`, so copying any of those examples into this repo can fail with an unresolved `motion` module. Replace every sample import with the declared `framer-motion` import.</violation>
</file>

Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.

Fix all with cubic | Re-trigger cubic

Comment thread .agents/skills/design-taste-frontend/SKILL.md
Comment thread .gitignore Outdated
Was an unanchored `.cn-check/`, which would ignore a directory of that name
anywhere in the tree; the check script only ever creates it under
packages/emcn.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile-apps

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found across 278 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.

Re-trigger cubic

Found by rebuilding the v3 stylesheet from this same tree and diffing computed
styles for all 4,674 class strings the app actually uses — combinations, not
just individual utilities, which is where these only show up.

- leading-*/text-* precedence: v4 routes a text-* utility's line-height through
  --tw-leading, so a leading-* class now wins at every breakpoint. v3 let a
  responsive text-lg reassert its own 28px leading; restate that explicitly.
- overflow-wrap: v4 emits the arbitrary property before `break-words`, flipping
  the winner from `anywhere` to `break-word`. Drop the redundant utility.
- z-9999999 and duration-[30ms] never took effect under v3 (outside the
  generated scale; rejected as ambiguous). v4 honours both, which would change
  stacking order and a transition from 150ms to 30ms. Removed.
- note-block focus ring: outline-<n> set width only in v3, so this drew
  nothing; v4 also sets outline-style. Removed to keep v3 rendering.

Every remaining computed-style difference is now verified non-visual: colour
notation (oklch/oklab, pixel-identical), gradient interpolation, mask-composite
keyword aliasing, and outline-none vs outline-hidden.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile-apps

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 280 files

Confidence score: 5/5

  • apps/sim/app/(landing)/components/content-post-page/content-post-page.tsx applies sm:leading-7 at sm and wider, changing the 18px description line-height from the stated 150% (27px) to 28px; remove the override or retain the previous 150% value to avoid a minor visual regression.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/sim/app/(landing)/components/content-post-page/content-post-page.tsx">

<violation number="1" location="apps/sim/app/(landing)/components/content-post-page/content-post-page.tsx:82">
P3: At `sm` and wider, `sm:leading-7` overrides the existing `leading-[150%]`, changing the 18px description from 27px to 28px line-height. Remove this override or use the previous 150% value to preserve the stated visual parity.</violation>
</file>

Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.

Fix all with cubic | Re-trigger cubic

An independent parity audit across light, dark and touch contexts found four
user-visible differences the earlier desktop-only checks could not see.

- hover: v4 wraps `hover:`/`group-hover:`/`peer-hover:` in
  `@media (hover: hover)`, so 64 hover-family classes were dead on touch
  devices — row highlights, reveal-on-hover icons, nav menus. This codebase
  already has `hover-hover` for the cases that want that gating, so plain
  `hover:` was meant to apply everywhere. Restored with `@custom-variant hover`.
- blur-0 no longer exists in v4. Two sites used `group-hover:blur-0` to clear a
  base `blur-[2px]`, leaving the icon permanently blurred. Renamed to
  `blur-none`.
- space-y-* dropped from specificity 0-3-0 to `:where()` 0-0-0 and moved to the
  preceding sibling, so a child's own `mt-*` now wins. Two layouts grew 4-8px;
  removed the child margins v3 was overriding.
- Breakpoints: v4 emits rem, which resolves against the browser's initial font
  size rather than `html { font-size }`, so a reader on a larger default text
  size got the mobile layout at desktop widths. Pinned back to v3's px.

Hardening from the same pass:
- `::before`/`::after`/`::backdrop` border-color — v4 leaves them
  `currentColor` and the app's `*` rule did not cover pseudo-elements.
- apps/docs was missing the `cn-tables.ts` source exclusion, so its bundle
  carried phantom `float-left`, `clear-end` and every `mix-blend-*`.
- Hairline plugin: narrowed to border-width longhands (it could rewrite an
  `outline-width` sharing a rule), stopped `\b1px\b` splicing into `0.1px`, and
  skipped node_modules stylesheets. Added 18 fixture tests.
- 23 golden-case tests for `cn`, pinning the font-size class group.
- The tables drift check now derives its flags from `cn:build` so the two
  cannot diverge, uses the local bin instead of `bun x`, and writes to tmpdir.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile-apps

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found across 283 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.
You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.

Re-trigger cubic

apps/sim still imported clsx directly in 8 files, so the repo carried two
class-name paths — one that resolves Tailwind conflicts and one that does not.
Converts them all to `cn` and removes the dependency.

clsx joins; cn merges. The two differ only where a call emits conflicting
classes, so every call site was checked rather than assumed. Of 19 sites with
2+ class tokens, 6 could conflict:

- 4 already resolve the same way, because cn keeps the class CSS source order
  was picking anyway (cursor-grab/pointer, opacity-0/100, text-secondary/tertiary)
- 2 are mutually exclusive ternaries in output-panel; only one branch ships

The seventh is a latent bug rather than a merge difference: workflow-item's
overlay sets `pointer-events-none` in its base and `pointer-events-auto` when
the context menu is open, but Tailwind emits `pointer-events-none` last, so it
always won — that button has never been clickable while the menu is open, only
visible. Left rendering as-is with a comment; enabling it is a real fix but not
a rendering-neutral one.

Verified all four runtime branches of that call still yield
`pointer-events-none`, and the built stylesheet is byte-identical.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile-apps

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found across 284 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.
You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.

Re-trigger cubic

The cn swap did not earn its keep. Benchmarked on this repo's own 4,673 class
strings, with both libraries' caches warm — which is what a re-rendering UI is
almost always in — cn is 4.2x faster per call (4.7ns vs 19.5ns). On a heavy
5,000-call render that is a 0.074ms saving: below anything perceivable, and far
below React's own reconciliation. The 13.7x cold-path win only applies to
strings neither cache has seen.

Against that it cost 2.1 KB gzip over tailwind-merge v3, paid on every cold
load on the critical path to first paint. cn trades gzip-compressibility for
parse speed: its tables are pre-encoded data that gzip cannot squeeze, where
tailwind-merge's config is repetitive JS that it can.

Reverting also drops a dependency published eight days ago onto a recycled npm
name, and lets the 7-day supply-chain gate go back on for the whole tree.

Keeps everything the migration was actually worth: Tailwind v4, and a single
class-name path now that apps/sim's direct clsx imports are gone. The 23 golden
cases pass unchanged against tailwind-merge, so the merge semantics are
identical; the built stylesheet is byte-for-byte the same.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile-apps

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found across 279 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.
You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.

Re-trigger cubic

@waleedlatif1 waleedlatif1 changed the title chore(tailwind): upgrade to v4 and swap clsx/tailwind-merge for cn chore(tailwind): upgrade apps/sim to Tailwind v4 Sep 3, 2026
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found across 279 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.
You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.

Re-trigger cubic

@waleedlatif1
waleedlatif1 merged commit 39307bd into staging Sep 3, 2026
33 checks passed
@waleedlatif1
waleedlatif1 deleted the worktree-shadcn-cn-migrate branch September 3, 2026 18:55
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