14 KiB
OpenSpec Spec: Web UI Component Cleanup — Inline Styles, Forms, Badges
Change
web-ui-spacing-typography-rework — Pass 2: Component Cleanup
Parent Proposal
openspec/proposals/web-ui-spacing-typography-rework.md
Parent Spec
openspec/specs/web-ui-spacing-typography-rework.md
Status
spec
1. Scope of This Spec
This spec covers Pass 2 (Component Cleanup) only:
- Refactor static inline styles in the highest-volume editor/sidebar components into token-based utility classes.
- Replace hardcoded
280pxsidebars with the--sidebar-widthtoken. - Unify
.form-groupand.form-fieldinto a single pattern. - Consolidate status badges/chips into a single
.status-badgefamily. - Add the missing utility classes needed by the refactor (
.stack-*,.row-*,.card-*,.form-section,.form-row,.form-help,.text-error,.muted,.ghost-button,.secondary-button,.dialog-actions).
Out of scope for Pass 2:
- Token additions (Pass 1, already done).
- Missing primitive classes like
.btnor.alert(Pass 1, already done). - Modal/dialog unification (Pass 3).
- Card consolidation across page CSS files (Pass 3).
- Breakpoint standardization beyond sidebar width (Pass 3).
2. Components to Refactor
The following components have high inline-style counts and must be refactored to use utility classes. Only static layout styles should move to CSS; dynamic values (e.g. drag-over background, computed widths) may remain inline.
2.1 apps/web/src/components/features/tool/manifest-editor.tsx (~70 inline styles)
Typical patterns:
display: "flex", gap: "..."→.row,.row-sm,.row-md,.stack,.stack-sm,.stack-mdpadding: "..."→.p-2,.p-3,.p-4marginBottom: "..."→.mb-2,.mb-3,.mb-4background: "var(--panel)"→.card,.card-sm,.card-mdborderRadius: "0.375rem"/10px→.rounded-md,.rounded-lgborder: "1px solid var(--border)"→.cardalready provides itfontSize: "0.875rem"→.text-smfontFamily: "monospace"→.font-monoflex: 1→.flex-1overflow: "auto"→.overflow-autotextAlign: "center"→.text-centerjustifyContent: "space-between"→.justify-betweenalignItems: "center"→.items-centercursor: "pointer"→.cursor-pointer
Required new utilities:
.row,.row-sm(gap:--space-2),.row-md(gap:--space-3),.row-lg(gap:--space-4).stack,.stack-sm,.stack-md,.stack-lg,.stack-xl.items-start,.items-center,.items-end.justify-between,.justify-center,.justify-end.flex-1,.flex-none.text-center,.text-left,.text-right.font-mono.cursor-pointer.overflow-auto,.overflow-hidden.p-1through.p-5,.px-*,.py-*.m-0,.mb-1through.mb-6,.mt-*,.ml-*,.mr-*.w-full,.min-w-0,.max-w-prose,.max-w-form.rounded-sm,.rounded-md,.rounded-lg,.rounded-xl,.rounded-full
2.2 apps/web/src/components/features/config-profiles/ConfigProfileEditorPanel.tsx (~51 inline styles)
Same utility patterns as above. In addition:
- Replace inline success-banner styles with
.alert-success(add if missing). - Replace inline drag item styles with
.drag-item,.drag-item-activeutilities. - Replace inline sticky dialog-actions styles with the existing
.dialog-actionsclass plus a.sticky-footermodifier if needed.
2.3 apps/web/src/components/features/tool-workshop/ToolTypeEditorPanel.tsx (~20 inline styles)
Same patterns as ConfigProfileEditorPanel. The empty-state and form header styles should use shared utilities.
2.4 apps/web/src/components/features/tool-workshop/ToolTypeListSidebar.tsx (~12 inline styles)
- Replace
style={{ width: "280px", minWidth: "280px" }}withstyle={{ width: "var(--sidebar-width)", minWidth: "var(--sidebar-width)" }}or a.sidebarclass. - Replace list-item inline spacing with
.sidebar-itemutilities.
2.5 apps/web/src/components/features/config-profiles/ConfigProfileListSidebar.tsx (~14 inline styles)
- Same sidebar width change as ToolTypeListSidebar.
- Replace inline badge styles with
.badge/.badge-secondary. - Replace inline delete button styles with
.ghost-button.smallor.btn-icon.
2.6 apps/web/src/components/features/git/git-mount-editor.tsx (~31 inline styles)
Same stack/row/card/gap/padding/margin utilities as manifest-editor.
3. Sidebar Width Tokenization
Both sidebars currently hardcode 280px. Change to use the token introduced in Pass 1.
Option A: Inline style with CSS variable
<div style={{ width: "var(--sidebar-width)", minWidth: "var(--sidebar-width)" }}>
Option B: Utility class
.sidebar {
width: var(--sidebar-width);
min-width: var(--sidebar-width);
display: flex;
flex-direction: column;
border-right: 1px solid var(--border);
background: var(--panel);
}
Use Option A if the surrounding layout already has custom styles; use Option B if the sidebars can share more structure. The spec requires at minimum that 280px is replaced by var(--sidebar-width).
4. Form Pattern Unification
Current state
.form-groupexists inglobal.cssand is widely used..form-fieldexists with nearly identical rules and is used in newer components.
Target state
- Keep
.form-groupas the canonical class. - Deprecate
.form-fieldby making it an alias:
.form-field {
/* alias for backward compatibility */
composes: form-group; /* not supported in plain CSS; instead duplicate the rule or remove .form-field usage */
}
Because plain CSS does not support composes, do one of:
- Preferred: keep both selectors in the same rule block:
.form-group, .form-field { display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-4); } - Then migrate callers from
.form-fieldto.form-groupin a follow-up cleanup task (not this pass).
Add missing form utilities
.form-section {
padding: var(--space-4);
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius-md);
}
.form-section > h4:first-child,
.form-section > h3:first-child {
margin-top: 0;
}
.form-row {
display: flex;
align-items: flex-start;
gap: var(--space-3);
}
.form-row .form-group {
flex: 1;
margin-bottom: 0;
}
.form-help {
font-size: var(--font-size-sm);
color: var(--muted);
line-height: var(--line-height-normal);
}
.text-error {
color: var(--danger);
font-size: var(--font-size-sm);
}
5. Status Badge Consolidation
Current state
Status colors are duplicated across .status-badge (utilities.css), .instance-chip (global.css), and inline styles.
Target state
Create a single .status-badge family in global.css:
.status-badge {
display: inline-flex;
align-items: center;
gap: var(--space-1);
padding: var(--space-1) var(--space-2);
border-radius: var(--radius-full);
font-size: var(--font-size-xs);
font-weight: 600;
text-transform: capitalize;
line-height: var(--line-height-tight);
background: var(--bg);
color: var(--muted);
border: 1px solid var(--border);
}
.status-badge::before {
content: "";
width: 6px;
height: 6px;
border-radius: 50%;
background: currentColor;
}
.status-badge--running,
.status-badge--success {
background: var(--success-light);
color: var(--success);
border-color: color-mix(in srgb, var(--success) 30%, transparent);
}
.status-badge--error,
.status-badge--failed {
background: var(--danger-light);
color: var(--danger);
border-color: color-mix(in srgb, var(--danger) 30%, transparent);
}
.status-badge--warning {
background: var(--warning-light);
color: var(--warning);
border-color: color-mix(in srgb, var(--warning) 30%, transparent);
}
.status-badge--info,
.status-badge--pending,
.status-badge--idle {
background: var(--info-light);
color: var(--info);
border-color: color-mix(in srgb, var(--info) 30%, transparent);
}
Then update callers to use .status-badge + .status-badge--<variant> instead of .instance-chip or inline status colors. Keep .instance-chip as a deprecated alias for one pass if it is widely referenced; do not delete it yet.
6. Required New Utility Classes
Add to apps/web/src/styles/utilities.css unless they fit better in global.css.
6.1 Layout utilities
.row {
display: flex;
align-items: center;
gap: var(--space-3);
}
.row-sm { gap: var(--space-2); }
.row-md { gap: var(--space-3); }
.row-lg { gap: var(--space-4); }
.row.items-start { align-items: flex-start; }
.row.items-center { align-items: center; }
.row.items-end { align-items: flex-end; }
.stack {
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.stack-sm { gap: var(--space-2); }
.stack-md { gap: var(--space-3); }
.stack-lg { gap: var(--space-4); }
.stack-xl { gap: var(--space-5); }
.justify-between { justify-content: space-between; }
.justify-center { justify-content: center; }
.justify-end { justify-content: flex-end; }
.flex-1 { flex: 1; }
.flex-none { flex: none; }
.w-full { width: 100%; }
.min-w-0 { min-width: 0; }
.max-w-prose { max-width: 65ch; }
.max-w-form { max-width: 800px; }
6.2 Spacing utilities
.m-0 { margin: 0; }
.mt-1 { margin-top: var(--space-1); }
.mt-2 { margin-top: var(--space-2); }
.mt-3 { margin-top: var(--space-3); }
.mt-4 { margin-top: var(--space-4); }
.mt-5 { margin-top: var(--space-5); }
.mt-6 { margin-top: var(--space-6); }
.mb-0 { margin-bottom: 0; }
.mb-1 { margin-bottom: var(--space-1); }
.mb-2 { margin-bottom: var(--space-2); }
.mb-3 { margin-bottom: var(--space-3); }
.mb-4 { margin-bottom: var(--space-4); }
.mb-5 { margin-bottom: var(--space-5); }
.mb-6 { margin-bottom: var(--space-6); }
.ml-auto { margin-left: auto; }
.mr-auto { margin-right: auto; }
.p-1 { padding: var(--space-1); }
.p-2 { padding: var(--space-2); }
.p-3 { padding: var(--space-3); }
.p-4 { padding: var(--space-4); }
.p-5 { padding: var(--space-5); }
.px-2 { padding-left: var(--space-2); padding-right: var(--space-2); }
.px-3 { padding-left: var(--space-3); padding-right: var(--space-3); }
.px-4 { padding-left: var(--space-4); padding-right: var(--space-4); }
.py-1 { padding-top: var(--space-1); padding-bottom: var(--space-1); }
.py-2 { padding-top: var(--space-2); padding-bottom: var(--space-2); }
.py-3 { padding-top: var(--space-3); padding-bottom: var(--space-3); }
.py-4 { padding-top: var(--space-4); padding-bottom: var(--space-4); }
6.3 Typography utilities
.text-xs { font-size: var(--font-size-xs); }
.text-sm { font-size: var(--font-size-sm); }
.text-base { font-size: var(--font-size-base); }
.text-lg { font-size: var(--font-size-lg); }
.text-xl { font-size: var(--font-size-xl); }
.font-mono { font-family: "IBM Plex Mono", monospace; }
.font-medium { font-weight: 500; }
.font-semibold { font-weight: 600; }
.text-center { text-align: center; }
.text-left { text-align: left; }
.text-right { text-align: right; }
6.4 Visual utilities
.cursor-pointer { cursor: pointer; }
.overflow-auto { overflow: auto; }
.overflow-hidden { overflow: hidden; }
.rounded-sm { border-radius: var(--radius-sm); }
.rounded-md { border-radius: var(--radius-md); }
.rounded-lg { border-radius: var(--radius-lg); }
.rounded-xl { border-radius: var(--radius-xl); }
.rounded-full { border-radius: var(--radius-full); }
6.5 Card utilities
.card-sm {
padding: var(--space-3);
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius-md);
}
.card-md {
padding: var(--space-4);
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius-md);
}
.card-lg {
padding: var(--space-5);
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
}
Ensure existing .card rules remain backward-compatible.
7. Button Class Alignment
The codebase uses many ad-hoc button class names: .primary-button, .secondary-button, .ghost-button, .button-secondary, .delete-btn.
For Pass 2, align the most common ones to the new .btn primitives where feasible:
.primary-button→ keep as alias to.btn.btn-primary.secondary-button/.button-secondary→ keep as alias to.btn.btn-secondary.ghost-button→ define explicitly or alias to.btn.btn-secondary.btn-icon→ add a modifier.btn.btn-iconfor icon-only buttons
Because renaming every caller is risky, the worker should:
- Add the aliases in CSS so both old and new class names work.
- Update only the refactored components to use the new
.btn*classes. - Leave other callers untouched to keep the diff focused.
8. Acceptance Criteria
- All six target components have significantly fewer inline style blocks.
- No hardcoded
280pxsidebar widths remain;var(--sidebar-width)is used. - New layout/spacing/typography/visual utilities from §6 are present.
.form-groupand.form-fieldshare a single rule block..status-badgefamily exists and is used by at least one caller.- Button aliases (
.primary-button,.secondary-button,.ghost-button) map to.btnprimitives. npm run typecheckpasses inapps/web.npm run lintpasses inapps/web.- No visual regressions in Tool Workshop and Config Profiles pages.
9. Verification Plan
- Run
cd apps/web && npm run typecheck. - Run
cd apps/web && npm run lint. - Count inline
style={{blocks in the six target components before/after; expect ≥50% reduction overall. - Search for
280pxinapps/web/src; expect zero matches (except in comments/docs). - Open Tool Workshop and Config Profiles in browser/dev server and verify layout.
- Check that
.status-badgerenders correctly in at least one place.
10. Next Phase
After this spec is approved, create tasks for Pass 2 implementation, then delegate to sdd-apply.