chore: inline css -> tailwind, font tweaks (sizing, spacing, face, line height)#1878
Merged
jonathanlab merged 10 commits intomainfrom Apr 24, 2026
Merged
chore: inline css -> tailwind, font tweaks (sizing, spacing, face, line height)#1878jonathanlab merged 10 commits intomainfrom
jonathanlab merged 10 commits intomainfrom
Conversation
adamleithp
approved these changes
Apr 24, 2026
jonathanlab
approved these changes
Apr 24, 2026
Contributor
jonathanlab
left a comment
There was a problem hiding this comment.
LGTM, thanks!
qq: if we're using text-[13px] everywhere, why not add a custom tailwind token for it?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two big things on an initial styling pass:
Aside from a little bit of right-sizing (labels, paragraph treatment, etc), it shouldn't look like much has changed... which is intentional. This was mostly a chore to get stuff in a place they'll be easier to tweak in the coming weeks.
Note: This was a moderately-moderated Claude task. It was intentionally pretty conservative so it shouldn't cause any visual regressions.
Before / after
(The after has more padding in the main column on the left/right sides of the main description.)
I also asked it to add some rules to
CLAUDE.mdto follow some best practices around this stuff.Original plan and summary of output is below, for those interested.
Inline Styles Migration & Line Height Tightening
Scope
apps/code/src/renderer/containstyle={{}}(~600+ instances)width: someNumber,transform: \…${x}px…``), library config (CodeMirror/xterm theme objects), and complex computed cases alone — convert only what's truly staticList.tsxchange (leading-5→leading-snug) across the codebase via a Tailwind v4 theme override + a sweep of arbitrary text sizesPhase 1 — Global line-height tightening
Two-pronged so it works for both named utilities (
text-sm,text-base) and arbitrary sizes (text-[13px]).1a. Override Tailwind v4 font-size token line-heights in
globals.cssTailwind v4 ships paired font-size + line-height tokens. Override the line-height side so every
<element className="text-sm">automatically gets the tighter leading. In apps/code/src/renderer/styles/globals.css, add a@themeblock:1b. Sweep
text-[Npx] leading-5→text-[Npx]text-[13px]etc. don't pair with theme tokens, so each call site needs an explicit leading. Sweep across the codebase:text-[13px] leading-5→text-[13px](~50 files, all from the size="1" codemod)text-[12px] leading-5→text-[12px](handful)text-[11px] leading-5→text-[11px](handful)leading-5standalone (without text-[Npx]) alone — those have intentional 20px valuesleading-tight/leading-relaxed/leading-[1.35]etc. cases alone — those are explicit overrides1c. Update List.tsx sizeStyles
Now that named utilities ship with the right defaults, simplify:
"2": "text-sm"→"text-sm"(theme provides snug)"3": "text-base"→"text-base"(theme provides snug)Phase 2 — Inline-style codemod
Add
apps/code/scripts/migrate-inline-styles.ts(next to the existingmigrate-radix-text-to-tailwind.ts) using ts-morph. The codemod walks everystyle={{...}}JSX attribute, examines each property, and:style={...}attribute if empty after removalclassName(handle StringLiteral, TemplateLiteral, JsxExpression — same logic as the previous codemod)Mapping table (a non-exhaustive set, focused on what's actually used)
Conversions only apply when the property value is a literal (string or number) matching a recognized pattern:
color: "var(--gray-N)"text-(--gray-N)(Tailwind v4 CSS-var shorthand)color: "var(--<scale>-<N>)"(red, green, blue, accent, …)text-(--<scale>-<N>)backgroundColor: "var(--gray-N)"bg-(--gray-N)(and other scales)padding: "var(--space-N)"p-{1,2,3,4,6,8,10,12,16}(Radix space-N → Tailwind, see scale below)paddingTop/Right/Bottom/Left(var)pt/pr/pb/pl-Npadding: <Npx>or<N>p-[Npx]arbitrarymargin/marginTop/...(var or px)m-Nflex: 1flex-1flexShrink: 0shrink-0flexGrow: 0/1grow-0/growminWidth: 0min-w-0minHeight: 0min-h-0width: "100%"w-fullheight: "100%"h-fulloverflowY: "auto"/overflowX: "auto"/overflow: "auto"overflow-y-autoetc.display: "flex"/"inline-flex"/"none"/"block"flex/inline-flex/hidden/blockcursor: "pointer"/"default"/"help"/"not-allowed"cursor-pointeretc.textAlign: "left"/"center"/"right"text-left/center/righttextTransform: "uppercase"/"lowercase"uppercase/lowercasefontWeight: 400/500/600/700font-normal/font-medium/font-semibold/font-boldopacity: 0.5/0.6/1opacity-50/opacity-60/opacity-100userSelect: "none"select-nonepointerEvents: "none"pointer-events-noneposition: "absolute"/"relative"/"fixed"/"sticky"absolute/relative/fixed/stickyborderRadius: "var(--radius-N)"rounded-(--radius-N)border: "1px solid var(--gray-N)"border border-(--gray-N)borderTop/Bottom/...: "1px solid var(--gray-N)"border-t border-t-(--gray-N)etc.whiteSpace: "nowrap"/"pre"/"pre-wrap"whitespace-nowrap/pre/pre-wrapwordBreak: "break-word"/"break-all"break-words/break-allboxSizing: "border-box"box-borderlistStyleType: "disc"/"decimal"list-disc/list-decimalRadix space → Tailwind spacing mapping
--space-11--space-22--space-33--space-44--space-56--space-68--space-710--space-812--space-916What the codemod will NOT touch
style={{ ...style, foo: 1 }}) — leave as-iscolor: cond ? "x" : "y") — leave as-is unless trivially literalwidth: \${n}px`,transform: ...`) — leave as-isfontFamily— leave alone (deferred; mostly already handled or library-specific)fontSize— already migrated in the previous passlineHeight— already migrated in the previous passnode_modules/,tests/,scripts/editorTheme.ts,useCodePreviewExtensions.ts,TerminalManager.ts)Codemod flow
flowchart TD Walk[Walk every JsxAttribute name='style'] --> Object{ObjectLiteralExpression?} Object -->|no| Skip Object -->|yes| ForEachProp[For each PropertyAssignment] ForEachProp --> Lookup{Property in MAPPING_TABLE?} Lookup -->|no| KeepProp[Keep property in style] Lookup -->|yes| Literal{Value is literal?} Literal -->|no| KeepProp Literal -->|yes| AddClass[Add Tailwind class to pending list] AddClass --> RemoveProp[Remove property from style] RemoveProp --> NextProp KeepProp --> NextProp NextProp --> Done{All props done?} Done -->|no| ForEachProp Done -->|yes| MergeClasses[Merge pending classes into className] MergeClasses --> CheckEmpty{style now empty?} CheckEmpty -->|yes| RemoveStyleAttr[Delete style attribute] CheckEmpty -->|no| Save RemoveStyleAttr --> Save[Save file]After codemod
biome check --write --unsafeon touched files (sorts merged Tailwind classes)tsc --noEmitto confirm no type regressionsPhase 3 — Hand-edit the hard files
The codemod's skip log will list every property it couldn't convert. For these ~8-10 files, hand-edit the parts that the codemod skipped where conversion is still safe:
meta.colorternaries that the codemod skips. Static parts get codemodded automatically.statusInfo.colorpatterns → CSS-var-driven className usingstyle={{ "--bg": color }}+bg-(--bg). Or accept staying inline since color is data-driven.gridTemplateColumns→grid-cols-[28px_1fr_auto](static). Pulse-dot dynamic color stays inline.paddingLeft: depth * N→style={{ paddingLeft: depth * 12 }}stays (dynamic). Caret rotate stays.width: \${w}px`` stays.calc(100% - ...)all stay inline. Static parts get codemodded.top: y/left: xstay.flexShrink: 0and other static parts get codemodded.Keycapis a small design system bundled in one style block. Extract to a single component with internal Tailwind classes.For each: I read the current state, apply the conversions the codemod missed, and confirm visually-equivalent output.
Phase 4 — Strengthen
CLAUDE.mdReplace the existing minimal section at lines 175-177 of CLAUDE.md:
### Tailwind over inline styles Always write Tailwind classes for styling wherever possible.…with a fuller version that defines the boundary clearly:
Order of execution
What stays inline (intentionally)
style={{ fontSize: size }}in DotsCircleSpinner.tsx — dynamicstyle={{ ...callerStyle, ... }}spread — caller intentRisk & mitigation
List.tsxalready validated the direction.Migration Result
Final stats
style={{}}instances (~70% reduction)What got done
Phase 1 — Line-height tightening
@themeblock overrides Tailwind v4's font-size line-height tokens (--text-{xs,sm,base,lg,xl,2xl}--line-height) so every<element className="text-sm">automatically gets the tighter leading. Body sizes use snug (1.375), heading sizes stay tight (1.25).text-[Npx] leading-5→text-[Npx]across the renderer (108 files via per-line awk that required both patterns on the same line).leading-snugfrom sizes 2 and 3 since the theme override now provides it.Phase 2 — Inline-style codemod
style={{...}}attribute, converts known static properties via a mapping table, removes them from the style object, merges new classes intoclassName, and deletes thestyleattribute when emptied.text-(--gray-N)shorthand), spacing (margins/paddings/gaps/inset/top/right/bottom/left), sizing (with px/percent/vw/vh support), flex/grid/display/overflow/position/cursor/text/white-space/word-break/font-weight/opacity/userSelect/pointerEvents/visibility/box-sizing/list-style/object-fit/z-index, plus border / borderRadius / background tokens."6px 10px"→px-[10px] py-[6px]).classNameafter the codemod added one to a call site.Phase 3 — Hand-edits for the hard files
tracking-[0.06em],italic,grid-cols-[28px_1fr_auto],rounded-full${isCancel ? "bg-(--gray-3)" : ...}instead of inline ternariesfont-monoandfont-[var(--code-font-family)]for monospace text[all:unset]for the rare CSS-reset caselast:border-b-0 even:bg-(--gray-1) odd:bg-(--gray-2)to replaceindex < ... && index % 2animate-spinto replace inlineanimation: "spin 1s linear infinite"m-autoinstead ofmargin: "auto auto"max-h-[calc(80vh-120px)]for arbitrary calc expressionsPhase 4 —
CLAUDE.mdruleclassNameandstyleprops.What remains inline (intentional, ~163 instances)
editorTheme.ts,useCodePreviewExtensions.ts,TerminalManager.ts(CodeMirror / xterm options — not React style)style={{ color: meta.color }},background: PULSE_COLOR[status],boxShadow: 0 0 0 3px color-mix(...)— values from datafontSize: "inherit"in custom buttons inside text — intentional cascadestyle={{ ...callerStyle, foo: 1 }}) — caller-passed style needs to mergeCodemod artifact
apps/code/scripts/migrate-inline-styles.ts is kept as a reusable artifact next to the existing
migrate-radix-text-to-tailwind.ts. It supports--dry-run,--verbose(skip log with grouping + samples), and--files=<glob>for narrowed runs. If new inline styles creep in despite the CLAUDE.md rule, you can re-run it any time.