chore(tailwind): upgrade to v4 and swap clsx/tailwind-merge for cn - #7419
chore(tailwind): upgrade to v4 and swap clsx/tailwind-merge for cn#7419waleedlatif1 wants to merge 4 commits into
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 the Sim application to Tailwind CSS v4 while preserving the existing theme and compatibility behavior, and replaces the existing class-merging stack with precompiled
Confidence Score: 5/5The PR appears safe to merge because no blocking failure remains. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| apps/sim/app/_styles/globals.css | Defines the Tailwind v4 CSS-first sources, theme values, variants, compatibility styles, and pinned v3 defaults. |
| apps/sim/lib/postcss/hairline-border-width.mjs | Rewrites Tailwind hairline border declarations after compilation while preserving generated selector ordering. |
| apps/sim/postcss.config.mjs | Runs Tailwind’s PostCSS integration before the hairline compatibility transformation. |
| packages/emcn/src/lib/cn.ts | Exposes the shared class merger backed by precompiled lookup tables. |
| packages/emcn/cn.config.mjs | Extends class conflict handling for the repository’s custom typography scale. |
| scripts/check-cn-tables.ts | Regenerates and compares class-merger tables to prevent committed configuration drift. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
Sources[Application and package class names] --> Scan[Tailwind v4 source scanning]
Theme[CSS-first theme and variants] --> Build[Tailwind PostCSS compilation]
Scan --> Build
Build --> Hairline[Hairline border-width rewrite]
Hairline --> CSS[Shipped application stylesheet]
Config[cn.config.mjs] --> Tables[Precompiled cn tables]
Tables --> Merge[Shared cn runtime]
Merge --> Sources
Check[check:cn-tables] --> Tables
Reviews (3): Last reviewed commit: "fix(tailwind): close the remaining v3/v4..." | 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.
3 issues found across 280 files
Confidence score: 3/5
- In
wand-prompt-bar.tsx, removing the high stacking layer can let positioned editor content cover the prompt bar and intercept clicks; preserve the overlay with a valid v4 z-index utility. - In
action-bar.tsx, removingdata-action-menu-readymakes the bar retract at Tailwind’s 150ms default instead of 30ms, creating a noticeably slower interaction; keepduration-[30ms]on the base class. - In
content-post-page.tsx,sm:leading-7changes the description’s wider-screen line-height from the stated 150% value, causing a small visual regression; remove the override or retain the prior 150% setting.
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>
<file name="apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx">
<violation number="1" location="apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx:425">
P2: When `data-action-menu-ready` is removed, this element loses the base 30ms duration and falls back to Tailwind’s 150ms default, making the action bar retract five times slower. Keep `duration-[30ms]` on the base class while retaining the `duration-100` ready override.</violation>
</file>
<file name="apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/wand-prompt-bar/wand-prompt-bar.tsx">
<violation number="1" location="apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/wand-prompt-bar/wand-prompt-bar.tsx:83">
P2: When the prompt bar overlaps the editor, removing its high stacking layer leaves it at `z-index: auto`, so later positioned editor content can cover the bar and intercept clicks. Preserve the layer with a valid v4 utility or the appropriate shared z-index token.</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
| 'relative flex flex-row items-center gap-[2px]', | ||
| isSwell && [ | ||
| 'pointer-events-none h-full opacity-0 transition-opacity duration-[30ms] [transition-timing-function:cubic-bezier(0.23,1,0.32,1)]', | ||
| 'pointer-events-none h-full opacity-0 transition-opacity [transition-timing-function:cubic-bezier(0.23,1,0.32,1)]', |
There was a problem hiding this comment.
P2: When data-action-menu-ready is removed, this element loses the base 30ms duration and falls back to Tailwind’s 150ms default, making the action bar retract five times slower. Keep duration-[30ms] on the base class while retaining the duration-100 ready override.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx, line 425:
<comment>When `data-action-menu-ready` is removed, this element loses the base 30ms duration and falls back to Tailwind’s 150ms default, making the action bar retract five times slower. Keep `duration-[30ms]` on the base class while retaining the `duration-100` ready override.</comment>
<file context>
@@ -422,7 +422,7 @@ export const ActionBar = memo(
'relative flex flex-row items-center gap-[2px]',
isSwell && [
- 'pointer-events-none h-full opacity-0 transition-opacity duration-[30ms] [transition-timing-function:cubic-bezier(0.23,1,0.32,1)]',
+ 'pointer-events-none h-full opacity-0 transition-opacity [transition-timing-function:cubic-bezier(0.23,1,0.32,1)]',
'group-data-[action-menu-ready]:pointer-events-auto group-data-[action-menu-ready]:opacity-100 group-data-[action-menu-ready]:duration-100',
]
</file context>
| 'pointer-events-none h-full opacity-0 transition-opacity [transition-timing-function:cubic-bezier(0.23,1,0.32,1)]', | |
| 'pointer-events-none h-full opacity-0 transition-opacity duration-[30ms] [transition-timing-function:cubic-bezier(0.23,1,0.32,1)]', |
| '-translate-y-3 absolute right-0 bottom-full left-0 gap-2', | ||
| 'rounded-lg border bg-background shadow-lg', | ||
| 'z-9999999 transition-all duration-150', | ||
| 'transition-all duration-150', |
There was a problem hiding this comment.
P2: When the prompt bar overlaps the editor, removing its high stacking layer leaves it at z-index: auto, so later positioned editor content can cover the bar and intercept clicks. Preserve the layer with a valid v4 utility or the appropriate shared z-index token.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/wand-prompt-bar/wand-prompt-bar.tsx, line 83:
<comment>When the prompt bar overlaps the editor, removing its high stacking layer leaves it at `z-index: auto`, so later positioned editor content can cover the bar and intercept clicks. Preserve the layer with a valid v4 utility or the appropriate shared z-index token.</comment>
<file context>
@@ -80,7 +80,7 @@ export function WandPromptBar({
'-translate-y-3 absolute right-0 bottom-full left-0 gap-2',
'rounded-lg border bg-background shadow-lg',
- 'z-9999999 transition-all duration-150',
+ 'transition-all duration-150',
isExiting ? 'opacity-0' : 'opacity-100',
className
</file context>
| 'transition-all duration-150', | |
| 'z-[9999999] transition-all duration-150', |
| </h1> | ||
| <p | ||
| className='mt-4 text-[var(--text-body)] text-base leading-[150%] tracking-[0.02em] sm:text-lg' | ||
| className='mt-4 text-[var(--text-body)] text-base leading-[150%] tracking-[0.02em] sm:text-lg sm:leading-7' |
There was a problem hiding this comment.
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.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/app/(landing)/components/content-post-page/content-post-page.tsx, line 82:
<comment>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.</comment>
<file context>
@@ -79,7 +79,7 @@ export function ContentPostPage({
</h1>
<p
- className='mt-4 text-[var(--text-body)] text-base leading-[150%] tracking-[0.02em] sm:text-lg'
+ className='mt-4 text-[var(--text-body)] text-base leading-[150%] tracking-[0.02em] sm:text-lg sm:leading-7'
itemProp='description'
>
</file context>
| className='mt-4 text-[var(--text-body)] text-base leading-[150%] tracking-[0.02em] sm:text-lg sm:leading-7' | |
| className='mt-4 text-[var(--text-body)] text-base leading-[150%] tracking-[0.02em] sm:text-lg' |
Summary
apps/simfrom Tailwind v3.4 to v4.3 — CSS-first config,tailwind.config.tsdeletedclsx+tailwind-mergewith thecnpackage in@sim/emcnandapps/docsParity evidence
border border-b-0,border border-2, …)cnvstailwind-mergev3 over harvested class pairscntables vs runtime-compiledv4 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)Two things worth reviewer attention
borderWidth.DEFAULThas no v4 theme key. 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.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.Preflight compatibility
v4 drops or changes seven v3 Preflight behaviours that no diff would show. Restored: button
cursor: pointer(plus v3's:disabledcompanion), form-control background,::placeholdercolour,<th>/<td>/<option>padding,[type=search]normalisation,<dialog>centring.Bundle
cnis compiled ahead of time (cn build --full) so its config compiler stays out of the browser.@sim/emcngoes 7,037 → 11,033 B gzip (+4.0 KB) for a ~30× faster merger with no first-call compile; usingcn/configinstead would have been +9.1 KB and ~3.7 ms of hydration work.check:cn-tablesfails CI if the tables drift fromcn.config.mjs.Known behaviour difference
note-block-view.tsxhasfocus-visible:outline-2 focus-visible:outline-current/50with nooutlineclass. In v3 that drew nothing (outline-<n>set width only); in v4 it also setsoutline-style: solid, so a keyboard-focus ring now appears on the two note edit buttons. Left as-is — it is the code's plain intent and keyboard-only. Say the word and I'll drop the dead classes to preserve v3 exactly.Supply chain
cnis added tominimumReleaseAgeExcludes. Its npm name was recycled in 2026, so every usable version is younger than the 7-day gate and it can never pass on its own. Pinned to an exact version in both consumers;bun.lockcarries the integrity hash and CI installs--frozen-lockfile. The exclude can be removed in a follow-up now that the lockfile entry exists.Type of Change
Testing
bun run check:audits: 46/46 (includes the newcheck:cn-tables)bunx turbo run type-check: 26/26 ·bun run lint:check: 26/26apps/simandapps/docs: clean; hairline plugin verified present in shipped CSSChecklist
🤖 Generated with Claude Code