Files
headquarter/ui-audit-spacing-typography.md

26 KiB
Raw Permalink Blame History

Headquarter Web UI — Spacing, Typography & Visual Rhythm Audit

Scope: apps/web/src/components/**/*, apps/web/src/pages/**/*, apps/web/src/components/app-shell.tsx, apps/web/src/styles/*.css, apps/web/src/styles/pages/*.css, and mobile-specific components under apps/web/src/components/features/mobile.

Date: 2026-06-16

Method: Static source review; no runtime screenshots. Line numbers reference the current state of the repository.


1. Critical Issues

These are problems that break layouts, make the UI unusable on mobile, or create accessibility failures.

1.1 Missing CSS for heavily-used component classes

A large number of class names are referenced in JSX/TSX but have no rules in any stylesheet. Browsers will render them as unstyled inline elements, causing broken spacing, unreadable text, and invisible interactive states.

Class(es) Used in (examples) Impact
.mobile-detail-view, .mobile-detail-header, .mobile-detail-field, .mobile-detail-field-label, .mobile-detail-field-value, .mobile-detail-code components/features/mobile/mobile-detail-view.tsx (lines 3091), pages/WorkspacesPage.tsx (detail view), ToolWorkshopMobileView.tsx, ConfigProfilesMobileView.tsx The entire mobile detail view has no layout styling; fields stack without gaps, labels/values are indistinguishable, code blocks overflow.
.mobile-list-search, .mobile-list-search-input components/features/mobile/mobile-list-view.tsx (lines 3742) Search input is unstyled on mobile list views.
.btn, .btn-primary, .btn-secondary, .btn-sm pages/WorkspacesPage.tsx:150,157,183, components/features/workspace/workspace-tools-panel.tsx:26,56, components/features/workspace/workspace-card.tsx:74, components/features/project/ProjectCard.tsx:131 Buttons fall back to native browser styling; spacing and sizing are inconsistent with .primary-button/.secondary-button.
.link-button components/features/session/session-card.tsx:187 Session URL button has no styling.
.checkbox-label components/features/tool/tool-starter.tsx:280, components/features/session/create-session-form.tsx:368, components/features/tool/instance-list.tsx:345,490, components/features/config-profiles/ConfigProfileEditorPanel.tsx:170 Checkbox pills rely only on inline styles; base class is undefined.
.hint components/features/tool/tool-starter.tsx:312, components/features/session/create-session-form.tsx:398 Helper text is unstyled and hard to read.
.form-error, .error-message components/features/tool/tool-starter.tsx:318, components/features/tool/start-tool-modal.tsx:92, components/features/workspace/workspace-create-form.tsx:296, pages/ProfilePage.tsx:177, pages/ProjectSettingsPage.tsx:51,122, components/features/project/repositories-settings-tab.tsx:62, components/features/project/repository-create-dialog.tsx:384, components/features/tool/instance-list.tsx:249 Error states have no visual treatment (color, padding, background).
.alert, .alert-error pages/WorkspacesPage.tsx:97,165 Alert banners are unstyled.
.loading-state pages/WorkspacesPage.tsx:100,178, pages/WorkspaceDetailPage.tsx:27 Loading states are unstyled plain text.
.text-muted components/features/mobile/mobile-detail-view.tsx:30 Undefined; should be .muted.
.badge, .badge-success, .badge-secondary components/features/mobile/mobile-detail-view.tsx:3336 Badge classes used but never defined.
.key-signing, .key-verification, .signature-result, .verify-result components/features/ssh-keys/SSHKeyList.tsx (lines 79161) Signing/verification sections have no spacing or typography rules; the SSH key page is visually broken below the public-key copy area.
.form-input components/features/config-profiles/ConfigProfileEditorPanel.tsx (multiple lines) Inputs in config profile editor use .form-input, which is not defined.

Recommendation: Add a single “missing primitives” pass to global.css or utilities.css defining .btn, .btn-primary, .btn-secondary, .btn-sm, .link-button, .checkbox-label, .hint, .form-error/.error-message, .alert/.alert-error, .loading-state, .badge*, .form-input, and the entire .mobile-detail-* block. Then remove the duplicated inline style={{...}} blocks from components.

1.2 Mobile detail view is completely unstyled

  • File: components/features/mobile/mobile-detail-view.tsx (lines 5391)
  • Issue: The component defines .mobile-detail-view, .mobile-detail-header, .mobile-detail-fields, .mobile-detail-field, .mobile-detail-field-label, .mobile-detail-field-value, and .mobile-detail-code, but no stylesheet contains these selectors. The result is a raw HTML-like stack with no padding, no separators, no label/value hierarchy, and unbounded <pre> blocks.
  • Recommendation: Add to utilities.css:
    .mobile-detail-view { display:flex; flex-direction:column; min-height:100%; background:var(--bg); }
    .mobile-detail-header { position:sticky; top:0; z-index:10; display:flex; align-items:center; gap:var(--space-3); padding:var(--space-3); background:var(--panel); border-bottom:1px solid var(--border); }
    .mobile-detail-header-content { flex:1; min-width:0; }
    .mobile-detail-title { margin:0; font-size:var(--font-size-lg); }
    .mobile-detail-subtitle { margin:var(--space-1) 0 0; color:var(--muted); font-size:var(--font-size-sm); }
    .mobile-detail-actions { display:flex; gap:var(--space-2); }
    .mobile-detail-action {  }
    .mobile-detail-fields { display:flex; flex-direction:column; gap:var(--space-3); padding:var(--space-3); }
    .mobile-detail-field { display:flex; flex-direction:column; gap:var(--space-1); padding:var(--space-3); background:var(--panel); border:1px solid var(--border); border-radius:10px; }
    .mobile-detail-field-label { font-size:var(--font-size-xs); text-transform:uppercase; letter-spacing:0.05em; color:var(--muted); }
    .mobile-detail-field-value { font-size:var(--font-size-base); color:var(--ink); word-break:break-word; }
    .mobile-detail-code { overflow:auto; max-width:100%; font-size:var(--font-size-sm); }
    

1.3 Mobile navigation and FAB collision

  • File: styles/utilities.css (lines 24482474) and styles/global.css (lines 11531205)
  • Issue:
    • .mobile-nav is fixed bottom:0, height:64px, with padding-bottom: env(safe-area-inset-bottom, 0).
    • .start-tool-fab on mobile is positioned bottom: 5rem; right: 1rem (global.css:1201).
    • .mobile-edit-actions is position: fixed; bottom: 64px (utilities.css:2272).
    • .shell-content.mobile adds padding-bottom: calc(1.25rem + 64px) (utilities.css:2412).
  • Impact: The FAB, bottom sheets, edit actions, and mobile tab bar can overlap depending on safe-area insets. The 5rem magic number does not account for env(safe-area-inset-bottom) and may collide with the nav on devices with home indicators.
  • Recommendation: Replace magic numbers with a CSS custom property: --mobile-nav-height: calc(64px + env(safe-area-inset-bottom, 0px)). Then set .start-tool-fab { bottom: calc(var(--mobile-nav-height) + var(--space-3)); }, .mobile-edit-actions { bottom: var(--mobile-nav-height); }, and .shell-content.mobile { padding-bottom: var(--mobile-nav-height); }.

1.4 Unreadable type scale and cramped touch targets

  • Files: styles/tokens.css (lines 4045), styles/utilities.css (lines 25012526), multiple component inline styles
  • Issue:
    • The token scale defines only six font sizes (--font-size-xs through --font-size-2xl) and no line-height tokens.
    • Many inline styles use fontSize: "0.7rem" (e.g. ConfigProfileListSidebar.tsx:82,95), which is below the 11 px effective minimum recommended for mobile legibility.
    • Several buttons use min-height derived only from padding, not the design-system touch target. The global rule button, a, input, select, textarea, [role="button"] { min-height: 44px; min-width: 44px; } (utilities.css:2501) is too broad and can distort form elements.
  • Recommendation: Add --line-height-* tokens and a --touch-target: 44px token. Replace inline fontSize: "0.7rem" with var(--font-size-xs) (or larger). Narrow the broad min-height/min-width selector to interactive controls only, not every input/select/textarea.

1.5 Sidebars use fixed pixel widths and inline styles

  • Files:
    • components/features/config-profiles/ConfigProfileListSidebar.tsx (lines 2223): style={{ width: "280px", minWidth: "280px" }}
    • components/features/tool-workshop/ToolTypeListSidebar.tsx (lines 2223): style={{ width: "280px", minWidth: "280px" }}
    • styles/global.css (line 35): .shell-body { grid-template-columns: 230px 1fr; }
  • Issue: These values are hardcoded in pixels/rem strings inside components and CSS. They do not respond to breakpoints and are inconsistent with the shell sidebar width.
  • Recommendation: Define --sidebar-width: 280px in tokens and use it consistently. On small desktop/large tablets (≤860 px) the shell collapses to a top nav, but the config-profiles/tool-workshop pages keep their 280 px sidebars, causing horizontal overflow.

2. Major Inconsistencies

2.1 Mixed px/rem/em units and magic numbers

The codebase uses px, rem, em, and string literals in inline styles interchangeably.

Location Value Problem
styles/syntax-highlight.css:46,87,125 font-size: 14px Hardcoded px in code areas; should use --font-size-sm.
styles/syntax-highlight.css:21,29,55,69,104,110,115 px-based paddings Should be --space-* tokens.
styles/global.css:36 height: calc(100vh - 57px) Magic number 57 px assumes header height; fragile if header wraps.
styles/global.css:12 padding: 0.85rem 1.25rem Non-token values; closest tokens are --space-3/--space-4.
styles/global.css:50 padding: 0.65rem 0.75rem Non-token; should be --space-2/--space-3.
styles/global.css:187 min-height: 38px Below --touch-target; should be 44 px.
styles/global.css:269 font-size: 1.6rem One-off size; should map to a token or be removed.
styles/global.css:623628 min-width: 400px, max-width: 600px, max-height: 80vh, border-radius: 8px Modal sizing inconsistent with .dialog rules above.
styles/pages/git-history.css:69 padding: 0.15rem 0.4rem Should be --space-1/--space-2.
styles/pages/sessions.css:64 bottom: calc(100% + 0.25rem) Should be --space-1.
styles/pages/sessions.css:67 min-width: 12rem Should be a token or --space-* composition.
styles/utilities.css:2448 height: 64px Should be a token.
styles/utilities.css:2459 padding: 8px 12px Should be --space-2/--space-3.
styles/utilities.css:2461 font-size: 11px Hardcoded px; should be --font-size-xs.
styles/utilities.css:2465 min-width: 64px Should be a touch-target token.
Inline styles in manifest-editor.tsx, ConfigProfileEditorPanel.tsx, ToolTypeEditorPanel.tsx, ToolTypeListSidebar.tsx, ConfigProfileListSidebar.tsx, git-mount-editor.tsx Dozens of style={{ gap:"0.5rem", padding:"1rem", borderRadius:"0.375rem" }} Bypass tokens entirely.

Recommendation: Run a pass to replace ad-hoc px/rem values with --space-* and --font-size-* tokens. Create additional tokens where the current scale is insufficient (--space-7: 2.5rem, --space-12: 6rem, --radius-sm, --radius-md, --radius-lg, --radius-full).

2.2 Duplicated and overlapping responsive breakpoints

  • Files: styles/global.css (lines 198, 330, 517), styles/utilities.css (many @media (max-width: 767px)), styles/pages/workspaces.css (@media (max-width: 767px)), styles/pages/sessions.css (@media (max-width: 767px)), styles/pages/workspace-detail.css (@media (max-width: 767px))
  • Issue: The codebase mixes 767px, 768px, 860px, and 1024px breakpoints with no clear mobile/desktop boundary. For example:
    • .shell-body collapses at 860px (global.css:517) and again at 767px (global.css:198).
    • .settings-layout collapses at 768px (global.css:742) and is overridden again at 767px (utilities.css).
    • .card-grid goes 1-column at <768px but .workspaces-grid stays multi-column until <767px, causing a 1 px gap in behavior.
  • Recommendation: Standardize on the token breakpoints (--bp-sm: 480px, --bp-md: 768px, --bp-lg: 1024px, --bp-xl: 1280px) and use min-width mobile-first queries. Remove the 860px one-off breakpoint.

2.3 Token scale is incomplete

  • File: styles/tokens.css (lines 2431, 4045)
  • Issue:
    • Spacing jumps from --space-6: 2rem to --space-8: 3rem to --space-10: 4rem; no --space-7, --space-9, --space-12.
    • No border-radius tokens (--radius-sm, --radius-md, --radius-lg, --radius-full).
    • No line-height tokens.
    • No shadow/elevation tokens; shadows are copy-pasted (box-shadow: 0 1px 0 rgba(0,0,0,0.02), 0 6px 18px rgba(0,0,0,0.08), 0 4px 14px rgba(0,0,0,0.25), etc.).
    • No touch-target or nav-height tokens.
  • Recommendation: Expand tokens.css to include these primitives and refactor existing rules to consume them.

2.4 Two competing modal/dialog systems

  • File: styles/global.css (lines 394451 and 619643)
  • Issue: There are two separate modal implementations:
    • .dialog-overlay + .dialog / .dialog-lg (rounded, centered, responsive)
    • .modal-overlay + .modal-content (older, square, fixed min-width: 400px)
  • Impact: Different paddings, border radii, widths, and mobile behaviors. Components use both (SessionCard uses .modal-overlay, instance-list.tsx uses .dialog-overlay).
  • Recommendation: Deprecate .modal-* and migrate all modals to .dialog* tokens. Add a single .modal-content → .dialog mapping for backward compatibility, then refactor callers.

2.5 Inline styles bypass the design system

Dozens of components embed style={{...}} objects that duplicate layout primitives.

  • High-volume offenders:
    • components/features/tool/manifest-editor.tsx — ~70 inline style props (cards, rows, gaps, padding, font sizes).
    • components/features/config-profiles/ConfigProfileEditorPanel.tsx — ~40 inline style props.
    • components/features/tool-workshop/ToolTypeEditorPanel.tsx — ~20 inline style props.
    • components/features/tool-workshop/ToolTypeListSidebar.tsx — sidebar dimensions and list item spacing inline.
    • components/features/config-profiles/ConfigProfileListSidebar.tsx — same pattern.
    • components/features/git/git-mount-editor.tsx — many inline flex/gap/padding rules.
  • Impact: Prevents theming, makes responsive overrides impossible, and scatters magic numbers across the codebase.
  • Recommendation: Replace inline styles with utility classes (.stack, .stack-sm, .row, .card, .form-group, .form-row, .p-3, .rounded-lg, etc.) and add missing utilities as needed.

2.6 Page-specific CSS files repeat global patterns

  • Files: styles/pages/projects.css, styles/pages/sessions.css, styles/pages/ssh-keys.css, styles/pages/workspace-detail.css, styles/pages/workspaces.css, styles/pages/git-history.css
  • Issue: Each page file redeclares card spacing, grid gaps, responsive overrides, and form field patterns that already exist (or should exist) in global.css/utilities.css. For example:
    • .repo-block, .workspace-chip, .instance-card, .settings-section all duplicate background: var(--panel); border: 1px solid var(--border); border-radius: 10/12/14px;.
  • Recommendation: Consolidate card variants into global.css using .card, .card-sm, .card-lg, and .card-elevated modifiers. Reserve page files for truly page-specific layout (e.g., .history-container two-column grid).

3. Minor Polish

3.1 Misaligned icons and nav density

  • File: components/app-shell.tsx (lines 1468) and styles/global.css (lines 1277)
  • Issue:
    • .shell-header uses padding: 0.85rem 1.25rem while .shell-content uses padding: 1.25rem. The header baseline does not visually align with the content grid.
    • .nav-item uses gap inherited from .shell-nav (gap: 0.4rem) plus border-radius: 10px; active and hover states have inconsistent text colors (#f7fff7 vs var(--primary-fg)).
    • .nav-section-title has margin-top: 0.5rem and padding: 0.25rem 0.75rem while utilities.css redeclares it with padding: var(--space-2) var(--space-4) and font-size: var(--text-xs) (an undefined variable; should be --font-size-xs).
  • Recommendation: Use --space-3/--space-4 consistently for header and content padding. Unify nav active/hover colors to token values. Fix --text-xs typo in utilities.css.

3.2 Page header spacing

  • File: styles/global.css (lines 320337)
  • Issue: .page-header uses gap: var(--space-3) and wraps to column on mobile, but the bottom margin/rhythm is inconsistent across pages. Dashboard home sections use .page-header inside .card, while other pages use it at the section root.
  • Recommendation: Add .page-header { margin-bottom: var(--space-4); } and a nested variant .card .page-header { margin-bottom: var(--space-3); }.

3.3 Card gaps and grid inconsistencies

  • File: styles/global.css (lines 230261) and styles/pages/workspaces.css (lines 1236)
  • Issue:
    • .home-summary-grid, .home-project-grid, .home-session-grid use gap: var(--space-4).
    • .card-grid uses gap: var(--space-4) and breakpoint-driven columns.
    • .workspaces-grid uses gap: var(--space-4) on desktop and var(--space-3) on mobile.
    • .sessions-grid uses gap: var(--space-4).
  • Impact: Mostly consistent, but the workspace card internal gap (workspace-card { gap: var(--space-4); }) combined with the grid gap creates double spacing relative to project/session cards.
  • Recommendation: Standardize all list/grids on --space-3 mobile and --space-4 desktop. Reduce workspace card internal gap to --space-3.

3.4 Form spacing

  • File: styles/global.css (lines 428451)
  • Issue: .form-field and .form-group both exist with nearly identical rules (display:flex; flex-direction:column; gap: 0.35rem). .form-field is newer and used in Settings; .form-group is legacy and used in SSH keys, dialogs, and many inline forms.
  • Recommendation: Merge into a single .form-field class. Provide .form-row for horizontal groups (already partially defined) and .form-actions for button rows. Add margin-bottom: var(--space-4) to each field for vertical rhythm.

3.5 Status badges use arbitrary colors

  • File: styles/utilities.css (lines 23562396) and styles/global.css (lines 10921131)
  • Issue: Status badges are defined in both files with overlapping status names but different background rules. .instance-chip also duplicates status colors.
  • Recommendation: Consolidate status colors into a single .status-badge component with .status-badge--running, .status-badge--error, etc., and reuse it for chips.

3.6 Notification center positioning

  • File: components/features/notification/notification-center.tsx (lines 4259)
  • Issue: Dropdown position is computed in JavaScript and applied via style={dropdownStyle}. On mobile, left/right: 1rem is hardcoded in JS; on desktop, it uses window.innerWidth - rect.right.
  • Recommendation: Use CSS anchoring or simpler absolute positioning relative to .notification-center so the dropdown responds to tokens and does not need resize listeners.

3.7 Profile page desktop card lacks profile-specific CSS

  • File: pages/ProfilePage.tsx (lines 134180)
  • Issue: Uses .profile-avatar-section, .avatar-preview, .avatar-image, .avatar-placeholder which are not defined in any stylesheet. The desktop profile form will render unstyled.
  • Recommendation: Add profile avatar styles or reuse a generic .avatar component.

3.8 Terminal page uses hardcoded dark-theme values

  • File: styles/utilities.css (terminal section, lines ~12001900)
  • Issue: Terminal colors (#1e1e1e, #2d2d2d, #3e3e3e, #d4d4d4) are hardcoded and do not adapt to the light/dark theme tokens. This is acceptable for a terminal surface, but many spacing values (8px, 4px, 2px, 16px) are also hardcoded.
  • Recommendation: Keep terminal colors as exceptions, but convert spacing to tokens for consistency.

4. Priority-Ranked Action List

P0 — Fix before any further mobile work

  1. Add missing primitive styles (global.css/utilities.css)

    • .mobile-detail-* full block
    • .btn, .btn-primary, .btn-secondary, .btn-sm
    • .link-button
    • .checkbox-label
    • .hint
    • .form-error/.error-message
    • .alert, .alert-error
    • .loading-state
    • .badge, .badge-success, .badge-secondary
    • .form-input
    • .mobile-list-search, .mobile-list-search-input
    • .key-signing, .key-verification, .signature-result, .verify-result
    • Profile avatar classes (.profile-avatar-section, .avatar-preview, etc.)
  2. Unify mobile nav / FAB / bottom-sheet spacing

    • Introduce --mobile-nav-height: calc(64px + env(safe-area-inset-bottom, 0px)).
    • Update .start-tool-fab, .mobile-edit-actions, .shell-content.mobile, .mobile-bottom-sheet, and .mobile-action-sheet to use it.
  3. Fix critical accessibility issues

    • Ensure all interactive controls meet min-height: 44px; min-width: 44px without distorting text inputs.
    • Add visible :focus states to .nav-item, .mobile-nav-item, .tab, .tree-entry, .button, and .form-field input.
    • Replace font-size: 0.7rem inline styles with --font-size-xs (or larger).

P1 — Tokenize and consolidate

  1. Expand tokens.css

    • Add --space-7, --space-9, --space-12.
    • Add --radius-sm: 6px, --radius-md: 10px, --radius-lg: 14px, --radius-xl: 16px, --radius-full: 999px.
    • Add --line-height-tight, --line-height-normal, --line-height-relaxed.
    • Add --shadow-sm, --shadow-md, --shadow-lg, --shadow-xl.
    • Add --touch-target: 44px and --sidebar-width: 280px.
  2. Standardize breakpoints

    • Replace 767px, 768px, 860px with --bp-sm, --bp-md, --bp-lg, --bp-xl.
    • Prefer mobile-first min-width queries.
    • Remove the redundant .shell-body collapse at 860px.
  3. Refactor inline styles into utility classes

    • Start with the highest-volume offenders: manifest-editor.tsx, ConfigProfileEditorPanel.tsx, ToolTypeEditorPanel.tsx, ToolTypeListSidebar.tsx, ConfigProfileListSidebar.tsx, git-mount-editor.tsx.
    • Replace style={{ display:"flex", gap:"0.5rem" }} with .row.row-sm or .row.gap-sm utilities.

P2 — Merge duplicated systems

  1. Merge .form-field and .form-group into one component class with consistent vertical rhythm.

  2. Deprecate .modal-overlay/.modal-content in favor of .dialog-overlay/.dialog and migrate callers.

  3. Consolidate status badges/chips into a single .status-badge family and reuse across sessions, instances, and workspaces.

  4. Consolidate card patterns across page CSS files into global.css modifiers (.card-sm, .card-lg, .card-elevated).

P3 — Polish and documentation

  1. Add design-system documentation comments at the top of tokens.css listing the spacing/type/radius/shadow scales and naming rules.

  2. Add a lint rule or code-review checklist to prevent new inline style={{...}} layout declarations and undefined class names.


Summary of Files Most Likely to Need Changes

File Lines to review Why
apps/web/src/styles/tokens.css 192 Incomplete token scale; needs spacing, radius, line-height, shadow, touch-target, and nav tokens.
apps/web/src/styles/global.css 11206 Many magic numbers, duplicate modal/dialog systems, missing primitive classes, inconsistent breakpoints.
apps/web/src/styles/utilities.css 13270 Overly broad touch-target rules, undefined --text-xs, missing .mobile-detail-*, duplicated badge/status styles.
apps/web/src/styles/pages/*.css all Heavy duplication of card/grid patterns; should use shared utilities.
apps/web/src/components/app-shell.tsx 1150 Header/nav padding misalignment, mobile nav/FAB collision.
apps/web/src/components/features/mobile/mobile-detail-view.tsx 195 Component references unstyled classes.
apps/web/src/components/features/mobile/mobile-list-view.tsx 1115 Search input unstyled.
apps/web/src/components/features/tool/manifest-editor.tsx ~70 inline styles Highest-volume inline-style offender.
apps/web/src/components/features/config-profiles/ConfigProfileEditorPanel.tsx ~40 inline styles Second-highest inline-style offender.
apps/web/src/components/features/tool-workshop/ToolTypeEditorPanel.tsx ~20 inline styles Third-highest offender.
apps/web/src/components/features/tool-workshop/ToolTypeListSidebar.tsx 1150 Hardcoded 280 px sidebar; inline styles.
apps/web/src/components/features/config-profiles/ConfigProfileListSidebar.tsx 1160 Hardcoded 280 px sidebar; inline styles.
apps/web/src/components/features/git/git-mount-editor.tsx Many inline styles Complex mount editor with inline flex/gap/padding.
apps/web/src/components/features/ssh-keys/SSHKeyList.tsx 1180 Signing/verification sections have no CSS.
apps/web/src/pages/ProfilePage.tsx 130180 Desktop profile card uses unstyled avatar classes.
apps/web/src/pages/WorkspacesPage.tsx 90110 Uses .alert alert-error and .loading-state without styles.

End of audit. No code changes were made; this is an analysis-only report.