Commit 39307bd
authored
chore(tailwind): upgrade apps/sim to Tailwind v4 (#7419)
* chore(tailwind): upgrade to v4 and swap clsx/tailwind-merge for cn
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.
* chore(tailwind): anchor the cn-tables scratch dir ignore
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.
* fix(tailwind): close the remaining v3/v4 rendering differences
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.
* fix(tailwind): restore v3 hover, blur and breakpoint behaviour
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.
* refactor(emcn): finish the cn migration by dropping clsx
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.
* refactor(emcn): use tailwind-merge v3 instead of the cn package
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.1 parent 17bfb0b commit 39307bd
279 files changed
Lines changed: 1658 additions & 1285 deletions
File tree
- .agents/skills/design-taste-frontend
- .claude/rules
- .cursor/rules
- apps
- docs
- app
- lib
- sim
- app
- (interfaces)/chat/components
- input
- message-container
- message
- components
- (landing)
- components
- content-author-page
- content-index-page
- content-post-page
- hero/components
- hero-chat-loop
- hero-platform-loop
- hero-visual
- lightbox
- enterprise/components
- enterprise-platform-loop
- feature-graphics
- files/components
- feature-graphics
- knowledge/components
- feature-graphics
- logs/components
- feature-graphics
- pricing/components/pricing-card
- solutions
- components/feature-graphics
- finance/components/feature-graphics
- tables/components/feature-graphics
- workflows/components/feature-graphics
- _styles
- unsubscribe
- workspace/[workspaceId]
- components
- conversation-list-item
- error
- inline-rename-input
- message-actions
- presence
- resource-tile
- resource/components
- action-bar
- resource-header
- resource-options
- files/components/file-viewer
- rich-markdown-editor
- home
- components
- message-content
- components
- agent-group
- chat-content
- message-sources
- question
- source-card
- source-chip
- special-tags
- mothership-chat
- mothership-view/components
- resource-content
- components
- browser-session
- generic-resource-content
- resource-registry
- search-sources
- suggested-actions
- user-input/components
- mic-button
- prompt-editor
- user-message-content
- integrations/components/integrations-showcase
- knowledge
- [id]
- [documentId]
- components
- chunk-editor
- document-tags-modal
- components
- action-bar
- add-connector-modal
- add-documents-modal
- base-tags-modal
- connectors-section
- edit-connector-modal
- components/create-base-modal
- logs
- components
- dashboard
- components
- status-bar
- workflows-list
- log-details
- components
- file-download
- trace-view
- settings
- billing/credit-usage
- components
- activity-log
- api-keys
- billing
- byok
- general
- inbox/components
- inbox-settings-tab
- inbox-task-list
- mcp
- member-list
- settings-resource-row
- terminal
- workflow-mcp-servers
- secrets/[credentialId]/components/secret-usage-panel
- tables/[tableId]/components
- column-config-sidebar
- column-dropdown
- enrichment-details
- enrichments-sidebar
- select-field
- table-action-bar
- table-filter
- table-grid
- cells
- headers
- workflow-sidebar
- upgrade/components
- comparison-table
- plan-card
- w
- [workflowId]
- components
- action-bar
- chat
- components/output-select
- connection-block-selector
- note-block
- panel
- components
- deploy/components/deploy-modal/components
- api
- chat
- general
- components
- editor
- components
- connection-blocks
- components/field-item
- sub-block
- components
- code
- combobox
- credential-selector
- grouped-checkbox-list
- knowledge-tag-filters
- long-input
- messages-input
- short-input
- skill-input
- starter
- table
- text
- tool-input
- components/code-editor
- variables-input
- subflow-editor
- toolbar
- search-replace
- terminal
- components
- output-panel
- toggle-button
- variables
- wand-prompt-bar
- workflow-block
- components
- preview
- components
- preview-editor
- preview-workflow/components/block
- sidebar
- components
- search-modal
- components
- command-chrome
- command-items
- settings-sidebar
- sidebar-section
- status-notice
- workflow-list
- components
- folder-item
- workflow-item
- workspace-header
- components/pending-invitations
- blocks
- components
- agent-stream
- emails/_styles
- permissions
- pii
- settings
- ui
- ee
- access-control/components
- audit-logs/components
- data-drains/components
- data-retention/components
- organization-usage/components
- whitelabeling/components
- workspace-forking/components
- fork-activity-panel
- fork-excluded-workflows
- fork-file-tree
- fork-resource-picker
- fork-sync
- lib/postcss
- packages
- emcn
- src
- components
- badge
- button-group
- button
- checkbox
- chip-date-picker
- chip-dropdown
- chip-input
- chip-modal
- chip-switch
- chip-tag
- chip
- code
- combobox
- info-card
- info
- input
- modal
- popover
- secret-reveal
- slider
- switch
- tab-strip
- tag-input
- textarea
- time-picker
- toast
- tooltip
- lib
- workflow-renderer/src
- lib
- note
- subflow
- workflow-block
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
113 | 113 | | |
114 | 114 | | |
115 | 115 | | |
116 | | - | |
| 116 | + | |
117 | 117 | | |
118 | 118 | | |
119 | 119 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
49 | | - | |
| 49 | + | |
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
43 | | - | |
| 43 | + | |
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
38 | | - | |
| 38 | + | |
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
50 | | - | |
| 50 | + | |
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
2 | | - | |
3 | | - | |
4 | 1 | | |
5 | | - | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
6 | 11 | | |
7 | | - | |
8 | | - | |
9 | | - | |
| 12 | + | |
10 | 13 | | |
11 | 14 | | |
12 | 15 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
26 | | - | |
27 | 26 | | |
28 | 27 | | |
29 | 28 | | |
| |||
35 | 34 | | |
36 | 35 | | |
37 | 36 | | |
38 | | - | |
39 | 37 | | |
40 | 38 | | |
41 | 39 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
124 | 124 | | |
125 | 125 | | |
126 | 126 | | |
127 | | - | |
| 127 | + | |
128 | 128 | | |
129 | 129 | | |
130 | 130 | | |
| |||
171 | 171 | | |
172 | 172 | | |
173 | 173 | | |
174 | | - | |
| 174 | + | |
175 | 175 | | |
176 | 176 | | |
177 | 177 | | |
| |||
214 | 214 | | |
215 | 215 | | |
216 | 216 | | |
217 | | - | |
| 217 | + | |
218 | 218 | | |
219 | 219 | | |
220 | 220 | | |
| |||
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
68 | 68 | | |
69 | 69 | | |
70 | 70 | | |
71 | | - | |
| 71 | + | |
72 | 72 | | |
73 | 73 | | |
74 | 74 | | |
| |||
Lines changed: 2 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
115 | 115 | | |
116 | 116 | | |
117 | 117 | | |
118 | | - | |
| 118 | + | |
119 | 119 | | |
120 | 120 | | |
121 | 121 | | |
122 | 122 | | |
123 | | - | |
| 123 | + | |
124 | 124 | | |
125 | 125 | | |
126 | 126 | | |
| |||
0 commit comments