docs(openspec): add web ui spacing/typography proposal, spec, tasks and audit
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
# SDD Proposal: Web UI Spacing, Typography & Visual Rhythm Rework
|
||||
|
||||
## Status
|
||||
**Phase:** proposal
|
||||
**Date:** 2026-06-16
|
||||
**Owner:** el Gentleman
|
||||
**Based on:** Scout audit `ui-audit-spacing-typography.md`
|
||||
|
||||
---
|
||||
|
||||
## User Story
|
||||
|
||||
As a Headquarter user, I want the web UI to feel consistent, readable, and polished across desktop and mobile, so that I can navigate and manage workspaces, projects, and sessions without fighting cramped layouts, broken mobile views, or inconsistent spacing.
|
||||
|
||||
---
|
||||
|
||||
## Problem Statement
|
||||
|
||||
A comprehensive audit of `apps/web/src` found systemic spacing, typography, margin, and visual-hierarchy issues:
|
||||
|
||||
- **Missing primitive styles**: dozens of widely-used class names (`.btn`, `.form-error`, `.alert`, `.badge`, `.mobile-detail-*`, etc.) are referenced in TSX but have no CSS rules, causing broken or unstyled UI on multiple pages.
|
||||
- **Completely unstyled mobile detail view**: `mobile-detail-view.tsx` ships layout classes that do not exist in any stylesheet.
|
||||
- **Mobile layout collisions**: the bottom nav, start-tool FAB, and mobile edit sheets can overlap and do not account for safe-area insets.
|
||||
- **Inconsistent design tokens**: the token scale lacks border-radius, line-height, shadow, touch-target, and nav-height primitives. Mixed `px`/`rem`/`em` values and inline `style={{...}}` blocks bypass the token system.
|
||||
- **Duplicated systems**: two modal/dialog implementations, duplicated `.form-field`/`.form-group` patterns, duplicated status-badge/chip styles, and overlapping breakpoints (`767px`, `768px`, `860px`).
|
||||
- **Heavy inline-style usage**: editors and sidebars embed layout CSS directly in JSX, making responsive overrides and theming impossible.
|
||||
|
||||
This is not a single bug; it is a design-system hygiene issue that affects nearly every page.
|
||||
|
||||
---
|
||||
|
||||
## Goals
|
||||
|
||||
1. Establish a complete, single-source-of-truth token scale for spacing, typography, radius, shadow, line-height, touch targets, and layout heights.
|
||||
2. Add every missing primitive component style so that referenced classes actually render correctly.
|
||||
3. Fix mobile layout collisions by deriving FAB/bottom-sheet offsets from a shared nav-height token.
|
||||
4. Replace ad-hoc `px`/`rem` values and inline styles with token-based utility classes where feasible.
|
||||
5. Consolidate duplicated patterns: one modal/dialog system, one form-field pattern, one status-badge family, one card family.
|
||||
6. Standardize breakpoints to the existing token scale (`--bp-sm`, `--bp-md`, `--bp-lg`, `--bp-xl`).
|
||||
7. Leave the UI visually improved without changing behavior or user-facing features.
|
||||
|
||||
---
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- No new user-facing features or workflows.
|
||||
- No changes to backend APIs, database schema, or authentication.
|
||||
- No redesign of the information architecture or navigation structure.
|
||||
- No replacement of Tailwind or CSS-modules; we stay in the existing global-CSS architecture.
|
||||
- No runtime theme-engine rewrite; light/dark tokens remain as-is.
|
||||
|
||||
---
|
||||
|
||||
## High-Level Approach
|
||||
|
||||
Split the work into three reviewable passes to stay under the 400-line review budget:
|
||||
|
||||
### Pass 1: Foundations (P0)
|
||||
- Expand `tokens.css` with missing primitives.
|
||||
- Add missing primitive classes to `global.css`/`utilities.css` (buttons, alerts, errors, badges, form inputs, loading states, checkbox labels, hints, mobile detail view, etc.).
|
||||
- Introduce `--mobile-nav-height` and fix FAB/bottom-sheet/shell-content collisions.
|
||||
- Fix the most critical inline font-size violations (`0.7rem` → tokens).
|
||||
- Add focused `:focus` visibility to interactive controls.
|
||||
|
||||
### Pass 2: Component Cleanup (P1)
|
||||
- Refactor the highest-volume inline-style offenders into utility classes:
|
||||
- `manifest-editor.tsx`
|
||||
- `ConfigProfileEditorPanel.tsx`
|
||||
- `ToolTypeEditorPanel.tsx`
|
||||
- `ToolTypeListSidebar.tsx`
|
||||
- `ConfigProfileListSidebar.tsx`
|
||||
- `git-mount-editor.tsx`
|
||||
- Replace hardcoded `280px` sidebars with `--sidebar-width` token.
|
||||
- Unify `.form-field`/`.form-group` and migrate callers.
|
||||
- Consolidate status badges/chips into a single `.status-badge` family.
|
||||
|
||||
### Pass 3: Consolidation & Polish (P2)
|
||||
- Deprecate `.modal-*` in favor of `.dialog-*` and migrate callers.
|
||||
- Consolidate card patterns across page CSS files into `global.css` modifiers.
|
||||
- Standardize breakpoints to `--bp-*` tokens and remove the `860px` one-off.
|
||||
- Add token documentation and a lightweight review checklist.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Which pass should include the mobile detail view styles?**
|
||||
- Recommendation: Pass 1, because it is currently broken.
|
||||
2. **Should we keep the existing `.utilities.css` naming convention or introduce BEM-style modifiers (`.card--elevated`, `.status-badge--error`)?**
|
||||
- Recommendation: introduce BEM-style modifiers only for the new consolidated families; keep existing utilities untouched to minimize churn.
|
||||
3. **Is the terminal page's hardcoded dark palette acceptable long-term, or should it become theme-aware?**
|
||||
- Recommendation: keep terminal colors hardcoded for now; only convert spacing to tokens.
|
||||
4. **Should we add a lint rule to block new inline `style={{...}}` layout declarations?**
|
||||
- Recommendation: add a code-review checklist in Pass 3, not a blocking lint rule, to avoid false positives.
|
||||
|
||||
---
|
||||
|
||||
## Risks
|
||||
|
||||
| Risk | Severity | Mitigation |
|
||||
|------|----------|------------|
|
||||
| Large diff touching many files | High | Split into three chained PRs; each under 400 changed lines. |
|
||||
| Visual regressions on pages not manually tested | Medium | Rely on typecheck/lint for TSX; do visual spot-checks on representative pages. |
|
||||
| Inline-style refactor breaks dynamic values | Medium | Only refactor static layout styles; keep dynamic values (computed positions) in JS. |
|
||||
| Token changes affect existing components unexpectedly | Medium | Add new tokens only; do not rename existing tokens without a compat layer. |
|
||||
| Mobile safe-area behavior varies across devices | Medium | Use `env(safe-area-inset-bottom, 0px)` with a sensible fallback. |
|
||||
|
||||
---
|
||||
|
||||
## Effort Estimate
|
||||
|
||||
| Pass | Focus | Est. Files | Est. Lines | Complexity |
|
||||
|------|-------|------------|------------|------------|
|
||||
| Pass 1: Foundations | tokens, missing primitives, mobile nav/FAB | 4–6 | ~400 | Medium |
|
||||
| Pass 2: Component Cleanup | inline styles, sidebars, forms, badges | 10–14 | ~600 | High |
|
||||
| Pass 3: Consolidation | modals, cards, breakpoints, docs | 8–12 | ~400 | Medium |
|
||||
| **Total** | | **22–32** | **~1400** | **High** |
|
||||
|
||||
**Review workload forecast:** Each pass should stay near or below the 400-line review budget. Pass 2 may need to be split further if it grows.
|
||||
|
||||
---
|
||||
|
||||
## Next Recommended Phase
|
||||
|
||||
**Spec** — detail the exact token additions, primitive class API, mobile-nav height calculation, and the migration path for inline styles.
|
||||
|
||||
Should I proceed to spec?
|
||||
@@ -0,0 +1,653 @@
|
||||
# OpenSpec Spec: Web UI Foundations — Spacing, Typography & Visual Rhythm
|
||||
|
||||
## Change
|
||||
`web-ui-spacing-typography-rework` — Pass 1: Foundations
|
||||
|
||||
## Parent Proposal
|
||||
`openspec/proposals/web-ui-spacing-typography-rework.md`
|
||||
|
||||
## Status
|
||||
spec
|
||||
|
||||
---
|
||||
|
||||
## 1. Scope of This Spec
|
||||
|
||||
This spec covers **Pass 1 (Foundations)** only:
|
||||
|
||||
- Expand `apps/web/src/styles/tokens.css` with missing primitives.
|
||||
- Add missing primitive component styles to `apps/web/src/styles/global.css` and `apps/web/src/styles/utilities.css`.
|
||||
- Fix mobile navigation / FAB / bottom-sheet collisions via a shared `--mobile-nav-height` token.
|
||||
- Replace critical inline font-size violations (`0.7rem`) with token values.
|
||||
- Add visible focus states to interactive controls.
|
||||
- Provide full CSS for the currently unstyled mobile detail view.
|
||||
|
||||
Out of scope for Pass 1:
|
||||
- Refactoring inline styles in editor components (Pass 2).
|
||||
- Modal/dialog unification (Pass 3).
|
||||
- Card/badge consolidation across page CSS files (Pass 3).
|
||||
- Breakpoint standardization beyond the safe-area/mobile-nav work (Pass 3).
|
||||
|
||||
---
|
||||
|
||||
## 2. Token Additions
|
||||
|
||||
Add to `apps/web/src/styles/tokens.css` **without renaming or removing existing tokens**.
|
||||
|
||||
### 2.1 Spacing
|
||||
|
||||
```css
|
||||
--space-7: 2.5rem;
|
||||
--space-9: 5rem;
|
||||
--space-12: 6rem;
|
||||
```
|
||||
|
||||
### 2.2 Border Radius
|
||||
|
||||
```css
|
||||
--radius-sm: 6px;
|
||||
--radius-md: 10px;
|
||||
--radius-lg: 14px;
|
||||
--radius-xl: 16px;
|
||||
--radius-full: 999px;
|
||||
```
|
||||
|
||||
### 2.3 Line Height
|
||||
|
||||
```css
|
||||
--line-height-tight: 1.25;
|
||||
--line-height-normal: 1.5;
|
||||
--line-height-relaxed: 1.75;
|
||||
```
|
||||
|
||||
### 2.4 Shadow / Elevation
|
||||
|
||||
```css
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
--shadow-xl: 0 12px 40px rgba(0, 0, 0, 0.18);
|
||||
```
|
||||
|
||||
### 2.5 Layout & Touch
|
||||
|
||||
```css
|
||||
--touch-target: 44px;
|
||||
--sidebar-width: 280px;
|
||||
--mobile-nav-height: calc(64px + env(safe-area-inset-bottom, 0px));
|
||||
```
|
||||
|
||||
### 2.6 Token Documentation Header
|
||||
|
||||
Add a short comment block at the top of `tokens.css` describing the naming convention and scales, so future contributors know which token to reach for.
|
||||
|
||||
---
|
||||
|
||||
## 3. Primitive Component Styles
|
||||
|
||||
Add the following classes to `apps/web/src/styles/global.css` unless noted otherwise.
|
||||
|
||||
### 3.1 Buttons
|
||||
|
||||
Unify the existing ad-hoc button usage. The codebase already references `.btn`, `.btn-primary`, `.btn-secondary`, `.btn-sm` in many places.
|
||||
|
||||
```css
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-2);
|
||||
min-height: var(--touch-target);
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 500;
|
||||
line-height: var(--line-height-tight);
|
||||
background: var(--panel);
|
||||
color: var(--ink);
|
||||
cursor: pointer;
|
||||
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
|
||||
}
|
||||
|
||||
.btn:focus-visible {
|
||||
outline: 2px solid var(--brand);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--brand);
|
||||
color: var(--primary-fg);
|
||||
border-color: var(--brand);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: var(--brand-strong);
|
||||
border-color: var(--brand-strong);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--panel);
|
||||
color: var(--ink);
|
||||
border-color: var(--border);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
min-height: calc(var(--touch-target) - 8px);
|
||||
padding: var(--space-1) var(--space-3);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
.btn:disabled,
|
||||
.btn[aria-disabled="true"] {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Link Button
|
||||
|
||||
```css
|
||||
.link-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--brand);
|
||||
font-size: inherit;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.link-button:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.link-button:focus-visible {
|
||||
outline: 2px solid var(--brand);
|
||||
outline-offset: 2px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 Form Inputs
|
||||
|
||||
Add a baseline `.form-input` class and a `.form-textarea` modifier.
|
||||
|
||||
```css
|
||||
.form-input,
|
||||
.form-textarea {
|
||||
width: 100%;
|
||||
min-height: var(--touch-target);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--panel);
|
||||
color: var(--ink);
|
||||
font-size: var(--font-size-base);
|
||||
line-height: var(--line-height-normal);
|
||||
}
|
||||
|
||||
.form-input:focus,
|
||||
.form-textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--brand);
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--brand) 20%, transparent);
|
||||
}
|
||||
|
||||
.form-input::placeholder,
|
||||
.form-textarea::placeholder {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
min-height: 6rem;
|
||||
resize: vertical;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 Checkbox Label
|
||||
|
||||
```css
|
||||
.checkbox-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
min-height: var(--touch-target);
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--ink);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.checkbox-label input[type="checkbox"] {
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
accent-color: var(--brand);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 Hint Text
|
||||
|
||||
```css
|
||||
.hint {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--muted);
|
||||
line-height: var(--line-height-normal);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.6 Error Message
|
||||
|
||||
```css
|
||||
.form-error,
|
||||
.error-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--danger-light);
|
||||
color: var(--danger);
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: var(--line-height-normal);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.7 Alert Banner
|
||||
|
||||
```css
|
||||
.alert {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border);
|
||||
background: var(--panel);
|
||||
color: var(--ink);
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: var(--line-height-normal);
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background: var(--danger-light);
|
||||
border-color: color-mix(in srgb, var(--danger) 30%, transparent);
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background: var(--warning-light);
|
||||
border-color: color-mix(in srgb, var(--warning) 30%, transparent);
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background: var(--info-light);
|
||||
border-color: color-mix(in srgb, var(--info) 30%, transparent);
|
||||
color: var(--info);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.8 Loading State
|
||||
|
||||
```css
|
||||
.loading-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-4);
|
||||
color: var(--muted);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.9 Badges
|
||||
|
||||
```css
|
||||
.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;
|
||||
line-height: var(--line-height-tight);
|
||||
background: var(--bg);
|
||||
color: var(--muted);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
background: var(--success-light);
|
||||
color: var(--success);
|
||||
border-color: color-mix(in srgb, var(--success) 30%, transparent);
|
||||
}
|
||||
|
||||
.badge-secondary {
|
||||
background: var(--info-light);
|
||||
color: var(--info);
|
||||
border-color: color-mix(in srgb, var(--info) 30%, transparent);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.10 Text Utilities
|
||||
|
||||
Add `.text-muted` as an alias for existing muted text patterns. The codebase references `.text-muted` in `mobile-detail-view.tsx`.
|
||||
|
||||
```css
|
||||
.text-muted {
|
||||
color: var(--muted);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Mobile Detail View Styles
|
||||
|
||||
Add to `apps/web/src/styles/utilities.css` (these classes are referenced only in mobile components and fit the utilities file).
|
||||
|
||||
```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);
|
||||
line-height: var(--line-height-tight);
|
||||
}
|
||||
|
||||
.mobile-detail-subtitle {
|
||||
margin: var(--space-1) 0 0;
|
||||
color: var(--muted);
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: var(--line-height-normal);
|
||||
}
|
||||
|
||||
.mobile-detail-actions {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.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: var(--radius-md);
|
||||
}
|
||||
|
||||
.mobile-detail-field-label {
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.mobile-detail-field-value {
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--ink);
|
||||
line-height: var(--line-height-normal);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.mobile-detail-code {
|
||||
overflow: auto;
|
||||
max-width: 100%;
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: var(--line-height-normal);
|
||||
}
|
||||
```
|
||||
|
||||
### 4.1 Mobile List Search
|
||||
|
||||
```css
|
||||
.mobile-list-search {
|
||||
padding: var(--space-3);
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--panel);
|
||||
}
|
||||
|
||||
.mobile-list-search-input {
|
||||
width: 100%;
|
||||
min-height: var(--touch-target);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
|
||||
.mobile-list-search-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--brand);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Mobile Nav / FAB / Bottom-Sheet Collisions
|
||||
|
||||
### 5.1 Replace Magic Numbers
|
||||
|
||||
Wherever the mobile nav height is hardcoded as `64px`, switch to `var(--mobile-nav-height)`.
|
||||
|
||||
Affected selectors (verify current line numbers before editing):
|
||||
|
||||
- `.mobile-nav` in `utilities.css`
|
||||
- `.mobile-edit-actions` in `utilities.css`
|
||||
- `.shell-content.mobile` in `utilities.css`
|
||||
- `.start-tool-fab` in `global.css`
|
||||
- `.mobile-bottom-sheet` / `.mobile-action-sheet` if present
|
||||
|
||||
### 5.2 Start Tool FAB
|
||||
|
||||
```css
|
||||
.start-tool-fab {
|
||||
position: fixed;
|
||||
bottom: calc(var(--mobile-nav-height) + var(--space-3));
|
||||
right: var(--space-4);
|
||||
z-index: 50;
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 Shell Content Padding
|
||||
|
||||
```css
|
||||
.shell-content.mobile {
|
||||
padding-bottom: calc(var(--mobile-nav-height) + var(--space-3));
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 Mobile Edit Actions
|
||||
|
||||
```css
|
||||
.mobile-edit-actions {
|
||||
position: fixed;
|
||||
bottom: var(--mobile-nav-height);
|
||||
/* existing left/right rules preserved */
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Focus States
|
||||
|
||||
Add visible focus rings to the following selectors in `global.css`/`utilities.css`.
|
||||
|
||||
```css
|
||||
.nav-item:focus-visible,
|
||||
.mobile-nav-item:focus-visible,
|
||||
.tab:focus-visible,
|
||||
.tree-entry:focus-visible,
|
||||
.btn:focus-visible,
|
||||
.form-input:focus-visible,
|
||||
.form-textarea:focus-visible,
|
||||
.link-button:focus-visible,
|
||||
.checkbox-label input:focus-visible {
|
||||
outline: 2px solid var(--brand);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
```
|
||||
|
||||
For `.nav-item-active` and `.tab.active`, use `--primary-fg` or a high-contrast outline color if `--brand` is insufficient.
|
||||
|
||||
---
|
||||
|
||||
## 7. Inline Font-Size Violations
|
||||
|
||||
Replace inline `style={{ fontSize: "0.7rem" }}` and similar hardcoded values with token-based classes.
|
||||
|
||||
Known locations (verify before editing):
|
||||
|
||||
- `ConfigProfileListSidebar.tsx` lines 82, 95
|
||||
- Any other `0.7rem`/`11px`/`10px` inline styles found during implementation
|
||||
|
||||
Use `.text-xs` or add a `.text-xs` utility mapped to `--font-size-xs` if it does not already exist. If a component truly needs smaller than `--font-size-xs`, escalate instead of adding a new smaller token.
|
||||
|
||||
---
|
||||
|
||||
## 8. SSH Key Page Styles
|
||||
|
||||
Add missing styles for the signing/verification sections in `SSHKeyList.tsx`.
|
||||
|
||||
```css
|
||||
.key-signing,
|
||||
.key-verification {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-3);
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
margin-top: var(--space-3);
|
||||
}
|
||||
|
||||
.signature-result,
|
||||
.verify-result {
|
||||
padding: var(--space-3);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
font-family: "IBM Plex Mono", monospace;
|
||||
font-size: var(--font-size-sm);
|
||||
word-break: break-all;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Profile Page Avatar Styles
|
||||
|
||||
Add fallback styles for the avatar section used in `ProfilePage.tsx`.
|
||||
|
||||
```css
|
||||
.profile-avatar-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4);
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.avatar-preview,
|
||||
.avatar-placeholder {
|
||||
width: 6rem;
|
||||
height: 6rem;
|
||||
border-radius: var(--radius-full);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--bg);
|
||||
border: 2px solid var(--border);
|
||||
}
|
||||
|
||||
.avatar-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.avatar-placeholder {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: 600;
|
||||
color: var(--muted);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Acceptance Criteria
|
||||
|
||||
- [ ] `tokens.css` contains all new tokens from §2 and a documentation header.
|
||||
- [ ] All class names listed in §3–§9 have working CSS rules.
|
||||
- [ ] No referenced class in the audited report remains undefined (verified by searching for the class names and confirming a rule exists).
|
||||
- [ ] Mobile detail view renders with proper padding, label/value hierarchy, and bounded code blocks.
|
||||
- [ ] Mobile nav, FAB, edit actions, and shell content no longer collide on devices with safe-area insets.
|
||||
- [ ] All interactive controls have visible `:focus-visible` rings.
|
||||
- [ ] Inline `0.7rem` font sizes are replaced with token-based classes.
|
||||
- [ ] `npm run typecheck` passes in `apps/web`.
|
||||
- [ ] `npm run lint` passes in `apps/web`.
|
||||
- [ ] No visual changes to desktop pages beyond improved spacing/consistency.
|
||||
|
||||
---
|
||||
|
||||
## 11. Verification Plan
|
||||
|
||||
1. Run `cd apps/web && npm run typecheck`.
|
||||
2. Run `cd apps/web && npm run lint`.
|
||||
3. Search for each undefined class from the audit and confirm it now has a rule.
|
||||
4. Open the mobile detail view in a narrow viewport and verify layout.
|
||||
5. Open the SSH keys page and verify signing/verification sections.
|
||||
6. Open the profile page and verify avatar styling.
|
||||
|
||||
---
|
||||
|
||||
## 12. Next Phase
|
||||
|
||||
After this spec is approved, create **tasks** for Pass 1 implementation, then delegate to `sdd-apply`.
|
||||
@@ -0,0 +1,108 @@
|
||||
# OpenSpec Tasks: Web UI Foundations — Spacing, Typography & Visual Rhythm
|
||||
|
||||
## Change
|
||||
`web-ui-spacing-typography-rework` — Pass 1: Foundations
|
||||
|
||||
## Parent Spec
|
||||
`openspec/specs/web-ui-spacing-typography-rework.md`
|
||||
|
||||
## Status
|
||||
tasks
|
||||
|
||||
---
|
||||
|
||||
## Implementation Tasks
|
||||
|
||||
- [ ] 1. Expand token scale in `apps/web/src/styles/tokens.css`
|
||||
- Add `--space-7`, `--space-9`, `--space-12`
|
||||
- Add `--radius-sm`, `--radius-md`, `--radius-lg`, `--radius-xl`, `--radius-full`
|
||||
- Add `--line-height-tight`, `--line-height-normal`, `--line-height-relaxed`
|
||||
- Add `--shadow-sm`, `--shadow-md`, `--shadow-lg`, `--shadow-xl`
|
||||
- Add `--touch-target`, `--sidebar-width`, `--mobile-nav-height`
|
||||
- Add a documentation header comment explaining the token naming convention
|
||||
|
||||
- [ ] 2. Add button primitives to `apps/web/src/styles/global.css`
|
||||
- `.btn`, `.btn-primary`, `.btn-secondary`, `.btn-sm`
|
||||
- Include `:focus-visible`, `:hover`, and `:disabled` states
|
||||
|
||||
- [ ] 3. Add link-button primitive to `apps/web/src/styles/global.css`
|
||||
|
||||
- [ ] 4. Add form primitives to `apps/web/src/styles/global.css`
|
||||
- `.form-input`, `.form-textarea`
|
||||
- `.checkbox-label`
|
||||
- `.hint`
|
||||
- `.form-error`, `.error-message`
|
||||
|
||||
- [ ] 5. Add alert primitives to `apps/web/src/styles/global.css`
|
||||
- `.alert`, `.alert-error`, `.alert-warning`, `.alert-info`
|
||||
|
||||
- [ ] 6. Add loading-state primitive to `apps/web/src/styles/global.css`
|
||||
|
||||
- [ ] 7. Add badge primitives to `apps/web/src/styles/global.css`
|
||||
- `.badge`, `.badge-success`, `.badge-secondary`
|
||||
|
||||
- [ ] 8. Add text utility primitives
|
||||
- `.text-muted` in `global.css`
|
||||
|
||||
- [ ] 9. Add mobile detail view styles to `apps/web/src/styles/utilities.css`
|
||||
- `.mobile-detail-view`
|
||||
- `.mobile-detail-header`
|
||||
- `.mobile-detail-header-content`
|
||||
- `.mobile-detail-title`
|
||||
- `.mobile-detail-subtitle`
|
||||
- `.mobile-detail-actions`
|
||||
- `.mobile-detail-fields`
|
||||
- `.mobile-detail-field`
|
||||
- `.mobile-detail-field-label`
|
||||
- `.mobile-detail-field-value`
|
||||
- `.mobile-detail-code`
|
||||
|
||||
- [ ] 10. Add mobile list search styles to `apps/web/src/styles/utilities.css`
|
||||
- `.mobile-list-search`
|
||||
- `.mobile-list-search-input`
|
||||
|
||||
- [ ] 11. Fix mobile nav / FAB / bottom-sheet collisions
|
||||
- Update `--mobile-nav-height` usage in `utilities.css` and `global.css`
|
||||
- `.start-tool-fab` uses `calc(var(--mobile-nav-height) + var(--space-3))`
|
||||
- `.shell-content.mobile` uses `calc(var(--mobile-nav-height) + var(--space-3))`
|
||||
- `.mobile-edit-actions` uses `var(--mobile-nav-height)`
|
||||
|
||||
- [ ] 12. Add visible focus states
|
||||
- Add `:focus-visible` rings for `.nav-item`, `.mobile-nav-item`, `.tab`, `.tree-entry`, `.btn`, `.form-input`, `.form-textarea`, `.link-button`
|
||||
|
||||
- [ ] 13. Replace critical inline font-size violations
|
||||
- Replace `fontSize: "0.7rem"` in `ConfigProfileListSidebar.tsx` with token-based class
|
||||
- Audit for other `0.7rem` / `10px` / `11px` inline styles and fix
|
||||
|
||||
- [ ] 14. Add SSH key page signing/verification styles
|
||||
- `.key-signing`, `.key-verification`, `.signature-result`, `.verify-result` in `utilities.css`
|
||||
|
||||
- [ ] 15. Add profile page avatar styles
|
||||
- `.profile-avatar-section`, `.avatar-preview`, `.avatar-image`, `.avatar-placeholder` in `utilities.css`
|
||||
|
||||
- [ ] 16. Verification
|
||||
- Run `cd apps/web && npm run typecheck`
|
||||
- Run `cd apps/web && npm run lint`
|
||||
- Search for each previously undefined class and confirm it now has a rule
|
||||
- Spot-check mobile detail view, SSH keys page, and profile page
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- All tasks above are completed.
|
||||
- `npm run typecheck` passes.
|
||||
- `npm run lint` passes.
|
||||
- No referenced class from the audit remains undefined.
|
||||
- Mobile nav/FAB/edit-actions no longer collide on devices with safe-area insets.
|
||||
- All interactive controls have visible `:focus-visible` states.
|
||||
- Inline `0.7rem` font sizes are replaced with token-based classes.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Do not rename or remove existing tokens; only add new ones.
|
||||
- Do not refactor inline styles in editor components in this pass (Pass 2).
|
||||
- Do not unify modals/dialogs in this pass (Pass 3).
|
||||
- Keep each commit focused; prefer one commit per task group.
|
||||
@@ -0,0 +1,314 @@
|
||||
# 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 30–91), `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 37–42) | 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:33–36` | Badge classes used but never defined. |
|
||||
| `.key-signing`, `.key-verification`, `.signature-result`, `.verify-result` | `components/features/ssh-keys/SSHKeyList.tsx` (lines 79–161) | 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 53–91)
|
||||
- **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`:
|
||||
```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 2448–2474) and `styles/global.css` (lines 1153–1205)
|
||||
- **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 40–45), `styles/utilities.css` (lines 2501–2526), 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 22–23): `style={{ width: "280px", minWidth: "280px" }}`
|
||||
- `components/features/tool-workshop/ToolTypeListSidebar.tsx` (lines 22–23): `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:623–628` | `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 24–31, 40–45)
|
||||
- **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 394–451 and 619–643)
|
||||
- **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 14–68) and `styles/global.css` (lines 12–77)
|
||||
- **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 320–337)
|
||||
- **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 230–261) and `styles/pages/workspaces.css` (lines 1–236)
|
||||
- **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 428–451)
|
||||
- **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 2356–2396) and `styles/global.css` (lines 1092–1131)
|
||||
- **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 42–59)
|
||||
- **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 134–180)
|
||||
- **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 ~1200–1900)
|
||||
- **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
|
||||
|
||||
4. **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`.
|
||||
|
||||
5. **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`.
|
||||
|
||||
6. **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
|
||||
|
||||
7. **Merge `.form-field` and `.form-group`** into one component class with consistent vertical rhythm.
|
||||
|
||||
8. **Deprecate `.modal-overlay`/`.modal-content`** in favor of `.dialog-overlay`/`.dialog` and migrate callers.
|
||||
|
||||
9. **Consolidate status badges/chips** into a single `.status-badge` family and reuse across sessions, instances, and workspaces.
|
||||
|
||||
10. **Consolidate card patterns** across page CSS files into `global.css` modifiers (`.card-sm`, `.card-lg`, `.card-elevated`).
|
||||
|
||||
### P3 — Polish and documentation
|
||||
|
||||
11. **Add design-system documentation comments** at the top of `tokens.css` listing the spacing/type/radius/shadow scales and naming rules.
|
||||
|
||||
12. **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` | 1–92 | Incomplete token scale; needs spacing, radius, line-height, shadow, touch-target, and nav tokens. |
|
||||
| `apps/web/src/styles/global.css` | 1–1206 | Many magic numbers, duplicate modal/dialog systems, missing primitive classes, inconsistent breakpoints. |
|
||||
| `apps/web/src/styles/utilities.css` | 1–3270 | 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` | 1–150 | Header/nav padding misalignment, mobile nav/FAB collision. |
|
||||
| `apps/web/src/components/features/mobile/mobile-detail-view.tsx` | 1–95 | Component references unstyled classes. |
|
||||
| `apps/web/src/components/features/mobile/mobile-list-view.tsx` | 1–115 | 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` | 1–150 | Hardcoded 280 px sidebar; inline styles. |
|
||||
| `apps/web/src/components/features/config-profiles/ConfigProfileListSidebar.tsx` | 1–160 | 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` | 1–180 | Signing/verification sections have no CSS. |
|
||||
| `apps/web/src/pages/ProfilePage.tsx` | 130–180 | Desktop profile card uses unstyled avatar classes. |
|
||||
| `apps/web/src/pages/WorkspacesPage.tsx` | 90–110 | Uses `.alert alert-error` and `.loading-state` without styles. |
|
||||
|
||||
---
|
||||
|
||||
*End of audit. No code changes were made; this is an analysis-only report.*
|
||||
Reference in New Issue
Block a user