chore(tailwind): upgrade apps/sim to Tailwind v4 - #7419
Conversation
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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…-migrate # Conflicts: # apps/sim/app/workspace/[workspaceId]/home/components/suggested-actions/suggested-actions.tsx
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
Greptile SummaryThe PR migrates
Confidence Score: 5/5The PR appears safe to merge because no blocking failure remains in the eligible follow-up review scope. No blocking failure remains.
|
| 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
Reviews (7): Last reviewed commit: "Merge remote-tracking branch 'origin/sta..." | Re-trigger Greptile
There was a problem hiding this comment.
1 issue found across 278 files
Confidence score: 4/5
.agents/skills/design-taste-frontend/SKILL.mdstill contains canonical examples importingmotion/react, so copied samples can fail with an unresolvedmotionmodule; 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
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.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
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.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
1 issue found across 280 files
Confidence score: 5/5
apps/sim/app/(landing)/components/content-post-page/content-post-page.tsxappliessm:leading-7atsmand 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.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
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.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
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.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
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
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
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
Summary
apps/simfrom Tailwind v3.4 to v4.3 — CSS-first config,tailwind.config.tsdeletedtailwind-mergev2 → v3 in@sim/emcn(v2 encodes Tailwind v3's utility surface and would stop resolving conflicts for anything v4 added or renamed);apps/docsnow re-exports the samecn, so there is one merge engine instead of twoParity evidence
border border-b-0,border border-2, …)tailwind-mergev2 vs v3, harvested class stringstailwind-mergev2 vs v3, pairwise base+override combinationsThe 3 merge-engine diffs are one mechanism: v3 treats bare
outlineandoutline-<n>as a single class group, sooutline outline-2collapses tooutline-2. In v4.outlineand.outline-1compile byte-identically, and.outline-2sets the sameoutline-stylewith a wideroutline-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-systemstack, and Preflight resolves the document font through it--text-xs--line-height—xsis the one custom size that collides with Tailwind's scale and inherits its ratio (16.5px → 14.67px)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 separatehover-hovervariant for the places where that gating is actually wanted.blur-0— removed in v4, leaving an icon permanently blurred on hover. Renamed toblur-none(9 sites).space-y-*specificity — v4 wraps the selector in:where(), dropping it to 0-0-0 so a child's ownmt-*now wins (2 layouts).cursor: pointer(plus v3's:disabledcompanion), form-control background,::placeholdercolour,<th>/<td>/<option>padding,[type=search]normalisation,<dialog>centring.Two things worth reviewer attention
borderWidth.DEFAULThas 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 own1pxoutput through--border-widthin place. Re-declaring the utilities in a trailing@layer— the obvious fix — makesborderbeatborder-2andborder-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-mdare NOT renamed.@tailwindcss/upgradewould rewrite all 146 sites torounded-xs; that is wrong here because this app overrides--radius-sm/--radius-md. Verified identical as-is.Type of Change
Testing
bun run check:audits: 45/45 ·bunx turbo run type-check: 26/26 ·bun run lint: 26/26apps/simandapps/docs: clean; hairline plugin verified present in shipped CSSChecklist