Files
headquarter/openspec/specs/web-ui-spacing-typography-rework-pass2.md
T

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 280px sidebars with the --sidebar-width token.
  • Unify .form-group and .form-field into a single pattern.
  • Consolidate status badges/chips into a single .status-badge family.
  • 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 .btn or .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-md
  • padding: "...".p-2, .p-3, .p-4
  • marginBottom: "...".mb-2, .mb-3, .mb-4
  • background: "var(--panel)".card, .card-sm, .card-md
  • borderRadius: "0.375rem" / 10px.rounded-md, .rounded-lg
  • border: "1px solid var(--border)".card already provides it
  • fontSize: "0.875rem".text-sm
  • fontFamily: "monospace".font-mono
  • flex: 1.flex-1
  • overflow: "auto".overflow-auto
  • textAlign: "center".text-center
  • justifyContent: "space-between".justify-between
  • alignItems: "center".items-center
  • cursor: "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-1 through .p-5, .px-*, .py-*
  • .m-0, .mb-1 through .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-active utilities.
  • Replace inline sticky dialog-actions styles with the existing .dialog-actions class plus a .sticky-footer modifier 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" }} with style={{ width: "var(--sidebar-width)", minWidth: "var(--sidebar-width)" }} or a .sidebar class.
  • Replace list-item inline spacing with .sidebar-item utilities.

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.small or .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-group exists in global.css and is widely used.
  • .form-field exists with nearly identical rules and is used in newer components.

Target state

  • Keep .form-group as the canonical class.
  • Deprecate .form-field by 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:

  1. 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);
    }
    
  2. Then migrate callers from .form-field to .form-group in 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-icon for icon-only buttons

Because renaming every caller is risky, the worker should:

  1. Add the aliases in CSS so both old and new class names work.
  2. Update only the refactored components to use the new .btn* classes.
  3. Leave other callers untouched to keep the diff focused.

8. Acceptance Criteria

  • All six target components have significantly fewer inline style blocks.
  • No hardcoded 280px sidebar widths remain; var(--sidebar-width) is used.
  • New layout/spacing/typography/visual utilities from §6 are present.
  • .form-group and .form-field share a single rule block.
  • .status-badge family exists and is used by at least one caller.
  • Button aliases (.primary-button, .secondary-button, .ghost-button) map to .btn primitives.
  • npm run typecheck passes in apps/web.
  • npm run lint passes in apps/web.
  • No visual regressions in Tool Workshop and Config Profiles pages.

9. Verification Plan

  1. Run cd apps/web && npm run typecheck.
  2. Run cd apps/web && npm run lint.
  3. Count inline style={{ blocks in the six target components before/after; expect ≥50% reduction overall.
  4. Search for 280px in apps/web/src; expect zero matches (except in comments/docs).
  5. Open Tool Workshop and Config Profiles in browser/dev server and verify layout.
  6. Check that .status-badge renders 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.