ui_primitives overhaul — verified brief, tech design, implementation plan
Status: planned. Verified against
main@2cf277b(2026-07-18). All file:line references below were re-checked against that commit; when the code drifts, re-verify before implementing.
1. Problem statement
web/src/components/ui_primitives/ is a naming layer over MUI, not a design
system. The import policy is enforced (never import raw MUI outside
ui_primitives/ and editor_ui/) and holds in practice — a sweep found zero
raw TextField/Select imports in app code. But the visual decisions were
never made, so a screen built 100% from primitives still looks amateur by
default: fields with mismatched variants, invisible panels, four label
treatments, and per-component invented heights.
The measure of success: a bare
<Panel><FlexColumn gap={3}><TextInput label="Name" /><SelectField label="Kind" options={...} /></FlexColumn></Panel>
with no styling props renders as a professional form — visible panel, two identically-styled fields, labels above controls, one control height.
Reference implementation of the target look: the storyboard header in
web/src/components/storyboard/StoryboardBoard.tsx (as of 2cf277b). It
hand-builds everything this plan systematizes: a local Field wrapper
(StoryboardBoard.tsx:69) putting labels above controls, uniform 36px control
heights via CSS override (StoryboardBoard.tsx:131-133), an uppercase
.group-label (:112-116), a responsive .header-grid (:102-111), and an
overflow: visible workaround for Panel clipping the floating label
(:96-98). Note it does not solve everything — its Panel is still the
invisible default background. Once the primitives are fixed, migrate this file
to the shared versions and delete the local workarounds (Fix 4).
2. Defect inventory (verified)
Every claim below was verified against the codebase; corrections from the original audit draft are marked ⚠.
2.1 No control-level tokens
tokens.ts exports FONT_WEIGHT, FONT_SIZE_SANS/FONT_SIZE_MONO,
TYPOGRAPHY, MOTION, reducedMotion, Z_INDEX, BORDER_RADIUS, and
scrollbar helpers — but no control height, control padding, or field radius.
Every primitive invents its own:
| Component | Value | Source |
|---|---|---|
| EditorButton | 24 / 28 raw px |
editor_ui/EditorButton.tsx:55 |
| Editor node controls | 28px |
theme.editor.heightNode (ThemeNodetool.tsx:70), used at themes/components/editorControls.ts:108 |
| Inspector controls | 32px |
theme.editor.heightInspector (ThemeNodetool.tsx:71), used at editorControls.ts:129 |
| TagInput | 48px |
ui_primitives/TagInput.tsx:51 |
| SearchInput radius | 8px |
ui_primitives/SearchInput.tsx:48 |
| Editor control radius | 6px |
theme.editor.controlRadius (ThemeNodetool.tsx:76) |
| MuiButton radius | theme.rounded.buttonLarge = 6px |
ThemeNodetool.tsx:241 |
Consequences: a compact EditorButton (24px) next to a node-canvas select
(28px) misaligns by 4px; next to an inspector select by 8px. Three independent
radius sources for controls. A second, parallel control scale lives on
theme.editor that EditorButton does not read. EditorButton.tsx:65 does
height: size ? undefined : height — passing size silently discards the
density height.
Off-grid values inside the primitives whose own docs ban them
(spacing.ts:23 forbids 0.25/0.75/1.25/2.5/5 steps; canonical scale is
[0, 0.5, 1, 1.5, 2, 3, 4, 6, 8] units of 4px):
Slider.tsx:45—13pxvertical padding (normal density)NodeSlider.tsx:56—3pxmargin-top (compact)Panel.tsx:39—comfortable: 2.5units;Panel.tsx:151computespaddingValue / 2⚠ (a computed halving, not a literal 1.25 — yields 1.25 units = 5px forcomfortable)
⚠ Additional offenders the original draft missed (same class of defect, fold into the Fix 1 sweep):
| File | Defect |
|---|---|
TabGroup.tsx:86,105 |
minHeight: isSmall ? "36px" : "42px" — 42 is off-grid |
Chip.tsx:64 |
height: "22px" — off-grid |
UndoRedoButtons.tsx:51-52,68,73 |
padding: 6px, border-radius: 6px, 24px box |
MenuItemPrimitive.tsx:46,114,131,135 |
border-radius: 4px literals, min-height: 32px, min-width: 28px |
MobileBottomSheet.tsx:45-46,62-63 |
16px corner radii, 40px/4px grab handle |
WarningBanner.tsx:73 |
border-radius: 8px |
Breadcrumbs.tsx:56 |
border-radius: 4px |
ZoomControls.tsx:34 |
minWidth: "45px" — off-grid |
InfoTooltip.tsx:77 |
font-size: 14px — off the type scale |
NotificationBadge.tsx:56 |
font-size: 10px — off the type scale |
2.2 Unrelated defaults across primitives
TextInput.tsx:52-53→variant="outlined",size="medium"SelectField.tsx:98-99→size="medium",variant="standard"
A no-props TextInput above a no-props SelectField renders a boxed field above
an underline-only field — the default composition is broken. Of 29 real
SelectField call sites, 17 rely on the standard default and 12 pass
variant="standard" redundantly (7 of those in SettingsMenu.tsx alone).
Surfaces:
Panel.tsx:89-90→bordered=false,background="default". Darkbackground.defaultis#08090A(paletteDark.ts:268) — same as the page. A default Panel is invisible. (Corrected during PR 3: the “42 call sites” here was a<Panelprefix-grep artifact matchingPanelLeft,PanelHeadline, etc. — the primitive is actually used at 13 sites across 10 files. Also found: Panel’s numericborderRadius: 6went through the sx multiplier and rendered 36px, not 6px.)Surface.tsx:64-68→background="paper",bordered=false,elevation=0with explicitboxShadow: "none". Different background AND a different radius source (theme.rounded[...],Surface.tsx:92-95) than Panel (theme.shape.borderRadius,Panel.tsx:135).
size semantics diverge: TextInput maps compact → MUI small
(TextInput.tsx:73); SelectField’s standard variant barely responds to
size; EditorButton discards its height when size is passed (see 2.1).
Flex containers: FlexRow.tsx:55, FlexColumn.tsx:53, Stack.tsx:61 all
default gap/spacing to 0. ~374 of ~1010 usages (37%) rely on that
default, so it cannot be changed — form spacing must come from the Fix 4
composition primitives instead.
2.3 Four label treatments, none shared
- MUI floating label —
TextInput, hand-correcting MUI’s transform (TextInput.tsx:95-96translate(14px, 17px)), sized viatheme.fontSizeNormal || "15px"⚠ (token with fallback, not a hardcoded 15px),opacity: 0.6(TextInput.tsx:89-92). - MUI
InputLabel—SelectField.tsx:123-129, same 15px, no opacity, no transform fix. Renders differently from TextInput’s at rest. Label.tsx—Typography component="label"(:81), 13px, weight 500,text.secondary, bakedmarginBottom: theme.spacing(0.5)= 2px (:88).FormField.tsx:85-96— reimplements the same thing with its ownTypography(does NOT useLabel), duplicating the required-asterisk logic (FormField.tsx:98-108vsLabel.tsx:95-104).
The theme sets a fifth treatment on MuiFormLabel
(ThemeNodetool.tsx:249-262): capitalize, 13px, padding: 0 0 8px 0 —
which TextInput/SelectField then override back to 15px, defeating the theme’s
label token exactly where labels matter.
The floating-label choice is also a bug class: the shrunk label renders above
the input box, and Panel.tsx:136 is overflow: hidden, so a TextInput at
the top of a Panel gets its label clipped. This shipped (the storyboard
title) and is worked around in StoryboardBoard.tsx:96-98 with
overflow: visible.
Escape hatches are asymmetric: SelectField has hideLabel
(SelectField.tsx:48); TextInput has none — the storyboard Field wrapper
exists partly because you cannot stop TextInput from floating its label.
2.4 No usable form composition primitive
FormFieldexists but has exactly 1 consumer (components/collections/CollectionForm.tsx);STRATEGY.md:47counts 14+ places doing label+input+helper by hand.FormFielddouble-labels: it renders its own label while TextInput/SelectField render theirs.- No
FormRow,FormSection,FieldGroup, or grid primitive. Only flex helpers — which default togap=0(2.2). - Spacing ownership is incoherent:
SelectField.tsx:117comments “spacing is the parent’s job” whileLabel.tsx:88,FormField.tsx:120,AutocompleteTagInput.tsx,EmptyState,ProgressBar,SectionHeader, andPanelbake their own margins. Stacking three fields yields four different gaps. TextInput additionally inherits MUI’s default helper-text margin (3px 14px 0) which nothing overrides — off-grid and different from SelectField’smt: 0.5(SelectField.tsx:157).
2.5 The type scale is fake
Text.tsx:14 advertises 8 sizes; ThemeNodetool.tsx:39-46 collapses them to
4 real values: giant = bigger = big = 18px; normal = 15px;
small = 13px; smaller = tiny = tinyer = 11px.
<Text size="tiny"> and <Text size="smaller"> are indistinguishable.
Usage counts across web/src (for migration sizing): small 277, normal 71,
big 42, smaller 39, tiny 36, bigger 17, tinyer 4, giant 2 — 488 total. The
aliased names (bigger, tiny, tinyer, giant) cover 59 call sites.
Nothing consumes the legacy CSS vars (--fontSizeTiny etc.) directly.
2.6 Zero-opinion passthroughs masquerading as primitives
Box.tsx— 2-line re-export. (Acceptable as an import-policy shim, but should say so.)muiReexports.ts:107-130re-exportsAccordion/AccordionSummary/AccordionDetails,Tabs/Tab,ToggleButton/ToggleButtonGroup,Modal,Fade,Toolbar,LinearProgressunder a header comment (:8-10) claiming “the MUI theme overrides in ThemeNodetool already style them”. ⚠ The theme’s actual override set is larger than the original draft claimed —MuiCssBaseline,MuiTypography,MuiButton,MuiFormLabel,MuiFormControl,MuiPopover,MuiModal,MuiToolbar,MuiDialogTitle,MuiTooltip,MuiDividerinThemeNodetool.tsx, plusMuiOutlinedInput,MuiSelect,MuiPaper,MuiMenu,MuiMenuItem,MuiSwitchfromeditorControls.ts— but the core claim holds: there is noMuiTabs,MuiToggleButton,MuiAccordion, orMuiLinearProgressoverride anywhere inthemes/. Those four render stock MUI.- Contrast:
MuiTooltip(ThemeNodetool.tsx:301-319) gets backdrop blur, a divider border, and a layered shadow — more surface craft than any panel, card, or input receives.
Inputs have no default field background: only editor-scoped controls get
Paper.overlay, gated on the nt-editor-control marker class
(editorControls.ts:19-23, class defined in
constants/editorUiClasses.ts:10). SearchInput.tsx:49 gives itself
action.hover — so search boxes read as fields and plain text inputs don’t.
Paper.overlay exists in both schemes (#17181B dark, #F0EDE6 light), so
a default field background is safe in both.
3. Tech design
3.1 Control tokens (CONTROL)
Add to ui_primitives/tokens.ts:
/**
* Control-chrome tokens: the five control heights, the two horizontal
* paddings, and the single field radius. Every interactive control in
* ui_primitives/ and editor_ui/ derives its box from these.
*/
export const CONTROL = {
/** px, all on the 4px grid */
height: {
xs: 24, // toolbar-density buttons (EditorButton compact)
sm: 28, // node-canvas controls, EditorButton normal
md: 32, // inspector controls
lg: 36, // default form controls (TextInput, SelectField, SearchInput)
xl: 44, // touch targets (MobileBottomSheet actions, TagInput)
},
paddingX: { compact: 8, normal: 12 }, // px, on-grid
radius: BORDER_RADIUS.md, // 6px — matches editor controlRadius + buttonLarge
} as const;
Rationale for five steps rather than the three the draft proposed
(28/36/44): the editor’s two densities (28 node, 32 inspector) are real,
shipped, and correct for a dense canvas — collapsing them into a 3-step scale
would either restyle the whole editor or leave theme.editor as a second
unreconciled scale, which is the defect we’re fixing. Five named steps, one
source.
Radius: BORDER_RADIUS.md (6px) — the value the editor controls and
MuiButton already use, so the flip is churn-free for the editor and only
moves SearchInput (8→6). (Alternative considered: sm/4px, rejected —
it would restyle every editor control and button for no gain.)
Wiring:
theme.editor.heightNode/heightInspector/controlRadius(ThemeNodetool.tsx:69-79) become derived:`${CONTROL.height.sm}px`,`${CONTROL.height.md}px`,CONTROL.radius.ThemeNodetool.tsxmay import fromui_primitives/tokens.ts(tokens has no theme dependency beyond types — no cycle).theme.d.tsneeds no change; the shape oftheme.editoris unchanged.EditorButton:density === "compact" ? CONTROL.height.xs : CONTROL.height.sm; thesizeprop mapssmall → sm,medium → mdinstead of discarding the height (EditorButton.tsx:65bug).TextInput/SelectField:compact/small→CONTROL.height.sm, default →CONTROL.height.lg, applied asminHeighton the input root (leavemultilinefree to grow, as the storyboard CSS does atStoryboardBoard.tsx:135).SearchInput: radius →CONTROL.radius; height →lg(orsmwhen its compact variant is set).TagInput:48px→CONTROL.height.xl(44) asminHeight(chips can wrap taller).- Off-grid fixes in the same pass:
Slider13px→12px,NodeSlider3px→4px,Panelcomfortable: 2.5→3(=SPACING.lg) and replace thepaddingValue / 2computed bottom padding withsnapSpacing, plus the 2.1 ⚠-table sweep (TabGroup 42→40, Chip 22→24 orpillheight token, radius literals →BORDER_RADIUS.*, font literals →FONT_SIZE_SANS.*).
3.2 One label treatment
Standardize on label above the control: fontSizeSmall (13px), weight
500, text.secondary, no text-transform, 4px gap to the control. This is
what Label.tsx already implements and what the storyboard Field wrapper
hand-rolls.
Labelbecomes the single renderer. AddhtmlForsupport (already has the asterisk); change its bakedmarginBottomfrom 2px to 4px (theme.spacing(1)) — the one component allowed to own that gap.TextInput: stop forwardinglabelto MUI. Render<Label htmlFor={id}>above the field; generateidviaReact.useId()when the caller passes none, solabel/input association survives. Delete the floating-label correction sx (TextInput.tsx:88-101). AddhideLabelfor parity with SelectField (renders the label visually hidden but keepsaria-label).SelectField: drop theInputLabelpath (SelectField.tsx:123-129); render the sharedLabelabove.hideLabelkeeps its contract.FormField: useLabel; delete its own Typography block and asterisk duplicate (FormField.tsx:85-108).- Theme: remove
textTransform: "capitalize"and the 8px padding fromMuiFormLabel(ThemeNodetool.tsx:249-262) — labels are no longer MUI-managed; keep the 13px/500 rules for any residual MUI-internal labels. - Outlined inputs no longer need the label notch; MUI renders no legend when no label is passed, so nothing to patch.
- This kills the clipped-label bug class. The
overflow: visibleworkaround inStoryboardBoard.tsx:96-98and its.field-labelCSS go in Fix 4’s migration.
Call-site impact: ~56 TextInput label= sites (17 files) and 29 SelectField
label= sites keep their code unchanged — the prop’s meaning changes from
“floating label” to “label above”, which is the point. Visual diff at every
one of those sites is expected and desired.
3.3 Coherent defaults
SelectField: defaultvariant="outlined". Remove the 12 redundant explicitvariant="standard"props in the same PR (they only restated the default; keeping them would fork the look). Thestandardvariant stays supported but undocumented-discouraged; if post-flip audit finds no legitimate use, delete it in a follow-up.- Inputs get a default field background:
Paper.overlayon the input root (both TextInput and SelectField, outlined variant), matching the editor controls’ treatment. ChangeSearchInputfromaction.hovertoPaper.overlayso all fields read identically. Panel: defaultbackground="paper"andbordered=true; radius source moves totheme.rounded(aligning withSurface— one radius source for surfaces). Risk: 42 call sites, none passbackground. Docked workspace panels (PanelLeft,PanelBottom,WorkspaceShell,PanelToolbar) plausibly want the flush look — audit all 42 during the flip and pinbackground="default" bordered={false}(or introducevariant="docked") where flush is intentional. Everything else inherits the visible default.FlexRow/FlexColumn/Stack: keepgap=0(37% of ~1010 usages rely on it). Form spacing is owned by the Fix 4 primitives.EditorButtonsizefix lands with Fix 1 (same file).
3.4 Form composition primitives
FormFieldrework: owns the label (viaLabel), the 4px label gap, and a helper/error line with a fixed on-grid margin (theme.spacing(1)), overriding MUI’s3px 14px 0helper margin. Provides aFormFieldContext { controlId, labelless: true };TextInputandSelectFieldconsume it and suppress their own labels + adopt the id — no double labels by construction, no convention to remember.FormSection: optional group label (uppercase,smaller/11px,letterSpacing: "0.08em",text.disabled— the storyboard.group-labeltreatment,StoryboardBoard.tsx:112-116) above aFlexColumn gap={3}body.FormGrid: n-column CSS grid (gridTemplateColumnsfrom acolumnsprop, defaultminmax(0, 1fr) 260pxtwo-column like.header-grid),gapfromSPACING, stacking to one column below astackBelowbreakpoint prop.- Proof migrations:
StoryboardBoard.tsx(delete localField, the.header-grid/.group-label/.field-label/36px CSS, and theoverflow: visibleworkaround),CollectionForm.tsx(the one existing FormField consumer), andWorkflowForm.tsx.
3.5 Honest type scale
Five real sizes, five names:
| name | px | change |
|---|---|---|
| giant | 22 | gets a real value (was 18; 2 call sites, both want display size) |
| big | 18 | unchanged |
| normal | 15 | unchanged |
| small | 13 | unchanged |
| smaller | 11 | unchanged |
Codemod the aliases with ast-grep (see §4 tooling note; pattern
<Text size="tiny" $$$REST>$$$C</Text> verified to match): bigger→big
(17 sites), tiny→smaller (36), tinyer→smaller (4) — mechanical, zero
visual change since the values were already identical. Then narrow TextProps["size"] to the five names and
delete fontSizeGiant/fontSizeBigger/fontSizeTiny/fontSizeTinyer from
the theme after adding fontSizeGiant: "22px" — grep confirmed nothing
consumes the legacy CSS vars directly. Text.tsx’s size map shrinks to
five entries. Review the 2 giant call sites for the 18→22 bump.
3.6 Passthrough honesty
For each re-export in muiReexports.ts:107-130 that the theme does not
style, add the missing override with tokens (preferred over relabeling —
the primitives-first policy funnels all usage through these re-exports, so a
theme override fixes every consumer at once):
MuiTabs/MuiTab:minHeight: CONTROL.height.lg, label 13px/500, 2px indicator (matchTabGroup’s look so the two tab systems agree).MuiToggleButton:CONTROLheight/radius/padding.MuiAccordion:Paper.paperbackground, divider border, radiustheme.rounded.md, no default elevation shadow.MuiLinearProgress: 4px track,BORDER_RADIUS.pill, primary bar (matchProgressBar).
Rewrite the muiReexports.ts header comment to state which exports are
themed and which are deliberate raw escape hatches (MuiAutocomplete,
MuiDialog at :132-141 already are). Document Box.tsx as an
import-policy shim.
4. Implementation plan
Codemod tooling: the mechanical rewrites in PRs 3 and 5 (and future
large migrations — this repo refactors heavily) use ast-grep, driven by
the official agent skill vendored at .claude/skills/ast-grep/ (plus
ast-grep-outline for structural surveys). Run it via
npx --yes --package @ast-grep/cli ast-grep <args> — it is not a repo
dependency. For multi-step, test-gated migrations beyond single-pattern
rewrites (e.g. the raw-MUI → primitives sweep STRATEGY.md tracks), evaluate
the Codemod CLI + MCP stack (npx codemod ai), which layers AST inspection
and codemod tests on top of ast-grep.
Six PRs, in order. 1–3 change no call-site code (PR 3 removes 12 redundant
props); every existing screen improves without migration. Each PR:
cd web && npm run typecheck && npm run lint && npm test, plus
npm run lint:design, plus the visual-suite step below.
PR 1 — control tokens (mechanical)
- Add
CONTROLtotokens.ts; export from the barrel; unit test alongsidetokens.test.ts(all heights on 4px grid, radius is a--rounded-*var). - Wire:
EditorButton(incl. thesize-discards-height fix),theme.editorderivation inThemeNodetool.tsx,TextInput,SelectField,SearchInput,TagInput. - Off-grid sweep:
Slider,NodeSlider,Panelpadding, plus the ⚠ table in 2.1 (TabGroup, Chip, UndoRedoButtons, MenuItemPrimitive, MobileBottomSheet, WarningBanner, Breadcrumbs, ZoomControls, InfoTooltip, NotificationBadge). - Add a
DesignTokens.ControlStorybook story (heights/paddings/radius swatches) next to the existingDesignTokens.*stories.
Acceptance: an EditorButton (normal), a compact TextInput, a compact SelectField, and a node-canvas select in one FlexRow share one height (28px), one radius, one horizontal padding; grep gates (§5) pass.
PR 2 — one label treatment
Label: addhtmlFor; margin 2px→4px.TextInput: label-above viaLabel+useId; delete floating-label sx; addhideLabel.SelectField: dropInputLabel; label-above viaLabel.FormField: render viaLabel; delete duplicate asterisk logic.- Theme: trim
MuiFormLabeloverride. - Tests: update
TextInput.test.tsx(label association viagetByLabelTextmust still pass — it proves thehtmlForwiring); add the missingSelectField.test.tsx(render, a11y, ref, label association,hideLabel).
Acceptance: TextInput, SelectField, and a Field-wrapped model select render
pixel-identical labels; no label renders outside the control’s box (Panel
overflow: hidden no longer clips anything).
PR 3 — coherent defaults
SelectFielddefaultvariant="outlined"; delete the 12 redundantvariant="standard"props (SettingsMenu.tsx×7,LayerRow,GetVariableBody,PropertiesPanel,WorkersPanel,SearchProviderSetupDialog).- Default field background
Paper.overlayfor TextInput/SelectField; switchSearchInputfromaction.hover; verify both schemes (dark#17181B, light#F0EDE6). Paneldefaultsbackground="paper",bordered=true; radius fromtheme.rounded; audit all 42 call sites and pin the docked panels (PanelLeft,PanelBottom,PanelToolbar,WorkspaceShell, timeline/ sketch inspectors as judged) to the flush look explicitly.- Update
Panel.test.tsxdefaults; visual-QA the 17 SelectField sites that relied onstandard.
Acceptance: the bare no-props composition from §1 renders as a visible bordered panel containing two identically-styled fields.
PR 4 — form composition primitives
- Rework
FormField(context-based label suppression); addFormSection,FormGrid; barrel exports; tests per the STRATEGY.md new-primitive checklist (render + axe + ref for each); Storybook stories. - Migrate
StoryboardBoard.tsxheader — delete localField, the.header-grid/.group-label/.field-label/minHeight: 36pxCSS, and theoverflow: visibleworkaround. - Migrate
CollectionForm.tsxandWorkflowForm.tsxas proofs.
Acceptance: storyboard header is visually unchanged (within visual-suite tolerance) but contains zero local form CSS.
PR 5 — honest type scale
- Theme:
fontSizeGiant: "22px"; deletefontSizeBigger/fontSizeTiny/fontSizeTinyerkeys (and their CSS-var emission). - Codemod
size="bigger|tiny|tinyer"→ canonical (59 sites incl.giantreview); narrowTextProps["size"]; updateText.test.tsx. - Review the 2
giantsites at 22px.
PR 6 — passthrough honesty
- Theme overrides for
MuiTabs/MuiTab,MuiToggleButton,MuiAccordion,MuiLinearProgressper §3.6. - Rewrite the
muiReexports.tsheader comment; documentBox.tsxas a shim.
5. Verification
- Per PR:
cd web && npm run typecheck && npm run lint && npm testandnpm run lint:design. - Visual regression:
npm run test:visual(Playwright,web/tests/visual/, committed baselines, 1% diff tolerance). PRs 1–3 and 5–6 intentionally change rendering — regenerate baselines with--update-snapshots, review the image diffs in the PR, and say so in the PR body.design-system.spec.tscovers the primitives gallery and is the primary gate;settings.spec.tscatches the SelectField flip. - Manual before/after screenshots per PR: storyboard header (
/workspacestoryboard tab), node inspector,SettingsMenu,CollectionForm— in both color schemes. - Grep gates after PR 1 (run in
web/src/components/ui_primitives/andeditor_ui/, excluding__tests__/and*.md):- no
minHeight: "2[248]px"/height: "2[24]px"control literals - no
border-?[rR]adius: "?[468](px)?"literals (useBORDER_RADIUS/CONTROL.radius) - no
13px/3px/5px/42px/45pxbox values (exception: the 7px/3px vertical paddings inside TextInput/SelectField/SearchInput — they are derived from the CONTROL heights to make MUI’s content box land exactly on 36/28px, not layout spacing) - no
font-size: 1[04]pxliterals
- no
- After PR 2:
grep -rn "MuiInputLabel" web/src/components/ui_primitives/returns nothing. - After PR 4:
grep -n "header-grid\|group-label\|field-label" web/src/components/storyboard/StoryboardBoard.tsxreturns nothing.
6. Risks and open decisions
- Panel default flip — resolved in PR 3. The audit found 13 real sites
(not 42); 10 docked inspector/transcript sites were pinned to the flush
look explicitly, content panels (cost estimate, storyboard header) inherit
the new visible default. Rendered radius corrected from the accidental
36px to a true 6px (
theme.rounded.md). - Label-above changes every labelled field’s geometry (~85 sites gain ~17px of height). Dialogs and dense inspectors may need spot re-layout; the visual suite will surface them.
standard-variant removal is deferred until post-flip audit; the variant remains functional in PR 3.- TagInput at 44px vs 36px: chosen 44 (
xl) to keep the touch-friendly chip row; revisit if it reads oversized next to 36px fields in forms. giantat 22px restyles 2 call sites; trivially reviewable.- Line numbers in §2 are pinned to
2cf277b; PRs land sequentially and each shifts the next PR’s targets — re-grep, don’t trust stale lines.