feat(web/ui): consolidate dialogs, cards, and breakpoints (Pass 3)
- Unify modal/dialog system; .modal-* are now aliases of .dialog-* - Migrate WorkspacesPage, start-tool-modal, merge-dialog, workspace-tools-panel to .dialog-* - Add .card-sm/.card-md/.card-lg/.card-elevated/.card-borderless modifiers - Apply card utilities across workspaces, projects, ssh-keys, workspace-detail, git-history - Remove duplicated card-like background/border/padding from page CSS - Remove 860px breakpoint; standardize on 767px/768px mobile split - Add docs/development/ui-review-checklist.md - Archive web-ui-spacing-typography-rework OpenSpec change
This commit is contained in:
+127
@@ -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?
|
||||
+457
@@ -0,0 +1,457 @@
|
||||
# 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
|
||||
|
||||
```tsx
|
||||
<div style={{ width: "var(--sidebar-width)", minWidth: "var(--sidebar-width)" }}>
|
||||
```
|
||||
|
||||
### Option B: Utility class
|
||||
|
||||
```css
|
||||
.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:
|
||||
|
||||
```css
|
||||
.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:
|
||||
```css
|
||||
.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
|
||||
|
||||
```css
|
||||
.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`:
|
||||
|
||||
```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
|
||||
|
||||
```css
|
||||
.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
|
||||
|
||||
```css
|
||||
.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
|
||||
|
||||
```css
|
||||
.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
|
||||
|
||||
```css
|
||||
.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
|
||||
|
||||
```css
|
||||
.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`.
|
||||
+375
@@ -0,0 +1,375 @@
|
||||
# OpenSpec Spec: Web UI Consolidation & Polish — Modals, Cards, Breakpoints
|
||||
|
||||
## Change
|
||||
`web-ui-spacing-typography-rework` — Pass 3: Consolidation & Polish
|
||||
|
||||
## Parent Proposal
|
||||
`openspec/proposals/web-ui-spacing-typography-rework.md`
|
||||
|
||||
## Parent Specs
|
||||
- `openspec/specs/web-ui-spacing-typography-rework.md`
|
||||
- `openspec/specs/web-ui-spacing-typography-rework-pass2.md`
|
||||
|
||||
## Status
|
||||
spec
|
||||
|
||||
---
|
||||
|
||||
## 1. Scope of This Spec
|
||||
|
||||
This spec covers **Pass 3 (Consolidation & Polish)** only:
|
||||
|
||||
- Unify the two modal/dialog systems (`.modal-*` and `.dialog-*`).
|
||||
- Consolidate duplicated card patterns across page CSS files into `global.css` modifiers.
|
||||
- Standardize responsive breakpoints to the existing token scale (`--bp-sm`, `--bp-md`, `--bp-lg`, `--bp-xl`).
|
||||
- Add design-system documentation to `tokens.css`.
|
||||
- Add a lightweight code-review checklist to prevent new inline `style={{...}}` layout declarations and undefined class names.
|
||||
|
||||
Out of scope for Pass 3:
|
||||
- Token additions (Pass 1, done).
|
||||
- Inline-style refactor in editor components (Pass 2, done).
|
||||
- Behavior changes or new features.
|
||||
|
||||
---
|
||||
|
||||
## 2. Modal/Dialog Unification
|
||||
|
||||
### Current state
|
||||
|
||||
Two separate modal implementations exist:
|
||||
|
||||
1. `.dialog-overlay` + `.dialog` / `.dialog-lg` (rounded, centered, responsive)
|
||||
2. `.modal-overlay` + `.modal-content` (older, square, fixed `min-width: 400px`)
|
||||
|
||||
Callers mix both:
|
||||
- `.modal-overlay` / `.modal-content`: `WorkspacesPage`, `SessionsPage`, `workspace-tools-panel`, `merge-dialog`, `start-tool-modal`, `start-tool-fab`
|
||||
- `.dialog-overlay` / `.dialog`: `instance-list`, `commit-dialog`, `ProjectDialog`, `repository-create-dialog`
|
||||
|
||||
### Target state
|
||||
|
||||
Deprecate `.modal-*` and make it a backward-compatible alias to `.dialog-*`.
|
||||
|
||||
```css
|
||||
.modal-overlay {
|
||||
/* alias */
|
||||
composes: dialog-overlay; /* plain CSS fallback: duplicate the rule */
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
/* alias */
|
||||
composes: dialog; /* plain CSS fallback: duplicate the rule */
|
||||
}
|
||||
```
|
||||
|
||||
Because plain CSS does not support `composes`, the worker must:
|
||||
|
||||
1. Keep `.dialog-overlay` and `.dialog` as the canonical implementation.
|
||||
2. Make `.modal-overlay` and `.modal-content` duplicate or wrap the same rules.
|
||||
3. Migrate callers incrementally:
|
||||
- For components being touched anyway in this pass, replace `.modal-overlay` → `.dialog-overlay` and `.modal-content` → `.dialog`.
|
||||
- For components not being touched, leave the old class names; the aliases keep them working.
|
||||
|
||||
### Canonical dialog styles
|
||||
|
||||
Ensure `.dialog-overlay` and `.dialog` already in `global.css` cover:
|
||||
|
||||
```css
|
||||
.dialog-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-4);
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.dialog {
|
||||
width: 100%;
|
||||
max-width: 32rem;
|
||||
max-height: calc(100vh - var(--space-8));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-xl);
|
||||
}
|
||||
|
||||
.dialog-lg {
|
||||
max-width: 48rem;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.dialog-header h2,
|
||||
.dialog-header h3 {
|
||||
margin: 0;
|
||||
font-size: var(--font-size-lg);
|
||||
line-height: var(--line-height-tight);
|
||||
}
|
||||
|
||||
.dialog-body {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
.dialog-footer,
|
||||
.dialog-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4);
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.dialog-close {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--touch-target);
|
||||
height: var(--touch-target);
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--muted);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dialog-close:hover {
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
}
|
||||
```
|
||||
|
||||
### Mobile dialog behavior
|
||||
|
||||
On viewports narrower than `--bp-md`:
|
||||
|
||||
```css
|
||||
@media (max-width: 767px) {
|
||||
.dialog-overlay {
|
||||
align-items: flex-end;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
max-width: 100%;
|
||||
max-height: calc(100vh - var(--space-6));
|
||||
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: this breakpoint will be standardized to `--bp-md` in §4.
|
||||
|
||||
---
|
||||
|
||||
## 3. Card Consolidation
|
||||
|
||||
### Current state
|
||||
|
||||
Each page CSS file redeclares card-like containers:
|
||||
|
||||
- `.repo-block`, `.workspace-chip`, `.instance-card`, `.settings-section`, `.project-card`, `.session-card`, `.config-profile-card`, etc.
|
||||
- Common pattern: `background: var(--panel); border: 1px solid var(--border); border-radius: 10/12/14px;`.
|
||||
|
||||
### Target state
|
||||
|
||||
Consolidate into `global.css` card modifiers:
|
||||
|
||||
```css
|
||||
.card {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.card-sm {
|
||||
padding: var(--space-3);
|
||||
}
|
||||
|
||||
.card-md {
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
.card-lg {
|
||||
padding: var(--space-5);
|
||||
}
|
||||
|
||||
.card-elevated {
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.card-borderless {
|
||||
border-color: transparent;
|
||||
}
|
||||
```
|
||||
|
||||
Then update page CSS files to:
|
||||
|
||||
1. Use `.card` / `.card-md` / `.card-lg` where the structure is generic.
|
||||
2. Keep only page-specific layout (e.g. `.history-container` two-column grid, `.sessions-grid` column config).
|
||||
3. Remove duplicate background/border/border-radius/padding declarations.
|
||||
|
||||
### Migration order
|
||||
|
||||
Start with the page files that have the most duplication:
|
||||
|
||||
1. `styles/pages/workspaces.css`
|
||||
2. `styles/pages/sessions.css`
|
||||
3. `styles/pages/ssh-keys.css`
|
||||
4. `styles/pages/workspace-detail.css`
|
||||
5. `styles/pages/projects.css` (if it has card-like patterns)
|
||||
6. `styles/pages/git-history.css`
|
||||
|
||||
Leave `styles/pages/sessions.css` option-menu styles untouched; they are component-specific, not card patterns.
|
||||
|
||||
---
|
||||
|
||||
## 4. Breakpoint Standardization
|
||||
|
||||
### Current state
|
||||
|
||||
Breakpoints are inconsistent:
|
||||
|
||||
- `767px` (max-width)
|
||||
- `768px` (min-width)
|
||||
- `860px` (max-width, one-off in `.shell-body`)
|
||||
- `1024px` (min-width)
|
||||
|
||||
The token scale already defines:
|
||||
|
||||
```css
|
||||
--bp-sm: 480px;
|
||||
--bp-md: 768px;
|
||||
--bp-lg: 1024px;
|
||||
--bp-xl: 1280px;
|
||||
```
|
||||
|
||||
### Target state
|
||||
|
||||
1. Remove the `860px` one-off breakpoint.
|
||||
2. Standardize all media queries to use `--bp-md` (`768px`) for mobile/desktop split.
|
||||
3. Prefer mobile-first `min-width` queries for new/refactored rules.
|
||||
4. For existing `max-width: 767px` rules, convert to `max-width: 767px` (equivalent to `--bp-md - 1px`) or refactor to mobile-first `min-width: 768px` where the logic is clearer.
|
||||
|
||||
Because CSS custom properties cannot be used directly in `@media` queries, document the pixel values in a comment and use the literal values `480px`, `768px`, `1024px`, `1280px`.
|
||||
|
||||
### Specific changes
|
||||
|
||||
- `global.css` line 554: remove `@media (max-width: 860px)` and make `.shell-body` collapse at `767px` only.
|
||||
- `global.css` line 779: change `max-width: 768px` to `max-width: 767px` for consistency.
|
||||
- Page CSS files: update any `767px`/`768px` queries to be consistent with the chosen direction.
|
||||
|
||||
### Recommended mobile-first pattern
|
||||
|
||||
For refactored rules, prefer:
|
||||
|
||||
```css
|
||||
.my-component {
|
||||
/* mobile first */
|
||||
padding: var(--space-3);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.my-component {
|
||||
padding: var(--space-4);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For existing rules that are desktop-first, keep `max-width: 767px` with a comment linking to `--bp-md`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Token Documentation
|
||||
|
||||
Add a documentation block at the top of `apps/web/src/styles/tokens.css`:
|
||||
|
||||
```css
|
||||
/*
|
||||
* Headquarter Design Tokens
|
||||
*
|
||||
* Naming convention:
|
||||
* --space-* : 4px-based spacing scale (1 = 4px, 2 = 8px, ...)
|
||||
* --font-size-*: fluid type scale from xs to 2xl
|
||||
* --radius-* : border radius (sm/md/lg/xl/full)
|
||||
* --shadow-* : elevation shadows (sm/md/lg/xl)
|
||||
* --line-height-*: text line heights
|
||||
* --bp-* : responsive breakpoints (use literal px in @media)
|
||||
*
|
||||
* Prefer these tokens over hardcoded px/rem values in all new CSS.
|
||||
*/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Code Review Checklist
|
||||
|
||||
Add a lightweight markdown file at `docs/development/ui-review-checklist.md`:
|
||||
|
||||
```markdown
|
||||
# UI Change Review Checklist
|
||||
|
||||
When changing styles or components in `apps/web/src`:
|
||||
|
||||
- [ ] New layout/spacing uses `--space-*` tokens, not hardcoded px/rem.
|
||||
- [ ] New type sizes use `--font-size-*` tokens.
|
||||
- [ ] New radii use `--radius-*` tokens.
|
||||
- [ ] New shadows use `--shadow-*` tokens.
|
||||
- [ ] No new inline `style={{...}}` blocks for static layout (computed values are OK).
|
||||
- [ ] Interactive controls meet `min-height: var(--touch-target)`.
|
||||
- [ ] `:focus-visible` states are visible and use `--brand`.
|
||||
- [ ] Mobile layouts are tested down to 375px wide.
|
||||
- [ ] New class names are defined in a stylesheet before being referenced.
|
||||
- [ ] Breakpoints use the literal values from `tokens.css` (`480px`, `768px`, `1024px`, `1280px`).
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Acceptance Criteria
|
||||
|
||||
- [ ] `.modal-overlay` and `.modal-content` are aliases/wrappers of `.dialog-overlay` and `.dialog`.
|
||||
- [ ] At least two `.modal-*` callers are migrated to `.dialog-*`.
|
||||
- [ ] Dialog mobile behavior (slide-up sheet) is implemented.
|
||||
- [ ] `.card`, `.card-sm`, `.card-md`, `.card-lg`, `.card-elevated` exist in `global.css`.
|
||||
- [ ] At least two page CSS files have duplicated card rules removed.
|
||||
- [ ] The `860px` breakpoint is removed.
|
||||
- [ ] All media queries use `480px`, `768px`, `1024px`, or `1280px` consistently.
|
||||
- [ ] `tokens.css` has a documentation header.
|
||||
- [ ] `docs/development/ui-review-checklist.md` exists.
|
||||
- [ ] `npm run typecheck` passes in `apps/web`.
|
||||
- [ ] `npm run lint` passes in `apps/web`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Verification Plan
|
||||
|
||||
1. Run `cd apps/web && npm run typecheck`.
|
||||
2. Run `cd apps/web && npm run lint`.
|
||||
3. Search for `.modal-overlay` and `.modal-content` in `apps/web/src`; confirm they are aliases or have fewer direct callers.
|
||||
4. Search for `860px`; confirm zero matches.
|
||||
5. Search for card-like duplicated rules in page CSS; confirm reductions.
|
||||
6. Open a dialog on desktop and mobile viewport and verify behavior.
|
||||
7. Confirm `tokens.css` has documentation header.
|
||||
8. Confirm `docs/development/ui-review-checklist.md` exists.
|
||||
|
||||
---
|
||||
|
||||
## 9. Next Phase
|
||||
|
||||
After this spec is approved, create **tasks** for Pass 3 implementation, then delegate to `sdd-apply`. After Pass 3 is complete, archive the OpenSpec change.
|
||||
+653
@@ -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`.
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
# OpenSpec Tasks: Web UI Component Cleanup — Inline Styles, Forms, Badges
|
||||
|
||||
## Change
|
||||
`web-ui-spacing-typography-rework` — Pass 2: Component Cleanup
|
||||
|
||||
## Parent Spec
|
||||
`openspec/specs/web-ui-spacing-typography-rework-pass2.md`
|
||||
|
||||
## Status
|
||||
tasks
|
||||
|
||||
---
|
||||
|
||||
## Implementation Tasks
|
||||
|
||||
- [x] 1. Add utility classes to `apps/web/src/styles/utilities.css`
|
||||
- Layout: `.row`, `.row-sm`, `.row-md`, `.row-lg`, `.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`, `.w-full`, `.min-w-0`, `.max-w-prose`, `.max-w-form`
|
||||
- Spacing: `.m-0`, `.mt-1` through `.mt-6`, `.mb-0` through `.mb-6`, `.ml-auto`, `.mr-auto`, `.p-1` through `.p-5`, `.px-2` through `.px-4`, `.py-1` through `.py-4`
|
||||
- Typography: `.text-xs`, `.text-sm`, `.text-base`, `.text-lg`, `.text-xl`, `.font-mono`, `.font-medium`, `.font-semibold`, `.text-center`, `.text-left`, `.text-right`
|
||||
- Visual: `.cursor-pointer`, `.overflow-auto`, `.overflow-hidden`, `.rounded-sm`, `.rounded-md`, `.rounded-lg`, `.rounded-xl`, `.rounded-full`
|
||||
- Cards: `.card-sm`, `.card-md`, `.card-lg`
|
||||
|
||||
- [x] 2. Add form utilities to `apps/web/src/styles/global.css`
|
||||
- `.form-section`
|
||||
- `.form-row`
|
||||
- `.form-help`
|
||||
- `.text-error`
|
||||
- Unify `.form-group` and `.form-field` into one rule block
|
||||
|
||||
- [x] 3. Add button aliases in `apps/web/src/styles/global.css`
|
||||
- `.primary-button` → alias to `.btn.btn-primary`
|
||||
- `.secondary-button` / `.button-secondary` → alias to `.btn.btn-secondary`
|
||||
- `.ghost-button` → explicit style or alias to `.btn.btn-secondary`
|
||||
- `.btn-icon` modifier
|
||||
|
||||
- [x] 4. Add status badge family to `apps/web/src/styles/global.css`
|
||||
- `.status-badge`
|
||||
- `.status-badge--running`, `.status-badge--success`
|
||||
- `.status-badge--error`, `.status-badge--failed`
|
||||
- `.status-badge--warning`
|
||||
- `.status-badge--info`, `.status-badge--pending`, `.status-badge--idle`
|
||||
- Keep `.instance-chip` as deprecated alias if widely used
|
||||
|
||||
- [x] 5. Refactor `apps/web/src/components/features/tool/manifest-editor.tsx`
|
||||
- Replace static inline styles with utility classes
|
||||
- Keep dynamic values inline (e.g. drag-over background, preview content)
|
||||
- Target ≥50% reduction in inline style blocks
|
||||
|
||||
- [x] 6. Refactor `apps/web/src/components/features/config-profiles/ConfigProfileEditorPanel.tsx`
|
||||
- Replace static inline styles with utility classes
|
||||
- Use `.alert-success` for save success banner
|
||||
- Use `.drag-item` / `.drag-item-active` for include list items
|
||||
- Use `.dialog-actions` for sticky footer
|
||||
|
||||
- [x] 7. Refactor `apps/web/src/components/features/tool-workshop/ToolTypeEditorPanel.tsx`
|
||||
- Replace static inline styles with utility classes
|
||||
- Use shared empty-state and form header patterns
|
||||
|
||||
- [x] 8. Refactor `apps/web/src/components/features/tool-workshop/ToolTypeListSidebar.tsx`
|
||||
- Replace `280px` with `var(--sidebar-width)`
|
||||
- Replace list-item inline spacing with utility classes
|
||||
|
||||
- [x] 9. Refactor `apps/web/src/components/features/config-profiles/ConfigProfileListSidebar.tsx`
|
||||
- Replace `280px` with `var(--sidebar-width)`
|
||||
- Replace remaining inline badge styles with `.badge`/`.badge-secondary`
|
||||
- Replace inline delete button with `.btn-icon` or `.ghost-button.small`
|
||||
|
||||
- [x] 10. Refactor `apps/web/src/components/features/git/git-mount-editor.tsx`
|
||||
- Replace static inline styles with utility classes
|
||||
- Use `.stack`, `.row`, `.card-*`, `.gap-*`, `.p-*`, `.mb-*`
|
||||
|
||||
- [x] 11. Verification
|
||||
- Run `cd apps/web && npm run typecheck`
|
||||
- Run `cd apps/web && npm run lint`
|
||||
- Count inline style blocks in target components; confirm ≥50% reduction overall
|
||||
- Search for `280px` in `apps/web/src`; confirm zero matches
|
||||
- Spot-check Tool Workshop and Config Profiles pages
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- All tasks above are completed.
|
||||
- `npm run typecheck` passes.
|
||||
- `npm run lint` passes.
|
||||
- Inline style blocks in the six target components reduced by at least 50% overall.
|
||||
- No hardcoded `280px` sidebar widths remain in source.
|
||||
- `.status-badge` family is defined and used by at least one caller.
|
||||
- `.form-group` and `.form-field` share a single CSS rule block.
|
||||
- Button aliases work visually.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Do not rename or remove existing classes without aliases; backward compatibility matters.
|
||||
- Keep dynamic inline styles (computed values) in JS.
|
||||
- Do not unify modals or cards across page CSS files in this pass (Pass 3).
|
||||
- Prefer one commit per major component or utility group.
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
# OpenSpec Tasks: Web UI Consolidation & Polish — Modals, Cards, Breakpoints
|
||||
|
||||
## Change
|
||||
`web-ui-spacing-typography-rework` — Pass 3: Consolidation & Polish
|
||||
|
||||
## Parent Spec
|
||||
`openspec/specs/web-ui-spacing-typography-rework-pass3.md`
|
||||
|
||||
## Status
|
||||
tasks
|
||||
|
||||
---
|
||||
|
||||
## Implementation Tasks
|
||||
|
||||
- [x] 1. Unify modal/dialog system in `apps/web/src/styles/global.css`
|
||||
- Ensure `.dialog-overlay`, `.dialog`, `.dialog-lg`, `.dialog-header`, `.dialog-body`, `.dialog-footer`, `.dialog-actions`, `.dialog-close` are canonical
|
||||
- Add `.modal-overlay` and `.modal-content` as aliases/wrappers
|
||||
- Add mobile dialog slide-up behavior at `767px`
|
||||
|
||||
- [x] 2. Migrate at least two `.modal-*` callers to `.dialog-*`
|
||||
- Candidates: `components/features/tool/start-tool-modal.tsx`, `components/features/workspace/workspace-tools-panel.tsx`, `components/features/git/merge-dialog.tsx`, `pages/WorkspacesPage.tsx`, `pages/SessionsPage.tsx`, `components/features/tool/start-tool-fab.tsx`
|
||||
|
||||
- [x] 3. Consolidate card patterns in `apps/web/src/styles/global.css`
|
||||
- `.card`, `.card-sm`, `.card-md`, `.card-lg`, `.card-elevated`, `.card-borderless`
|
||||
|
||||
- [x] 4. Remove duplicated card rules from page CSS files
|
||||
- `styles/pages/workspaces.css`
|
||||
- `styles/pages/sessions.css`
|
||||
- `styles/pages/ssh-keys.css`
|
||||
- `styles/pages/workspace-detail.css`
|
||||
- `styles/pages/projects.css`
|
||||
- `styles/pages/git-history.css`
|
||||
|
||||
- [x] 5. Standardize breakpoints
|
||||
- Remove `860px` breakpoint from `global.css`
|
||||
- Make `global.css` line 779 use `767px` instead of `768px`
|
||||
- Ensure all `@media` queries use `480px`, `767px`, `768px`, `1024px`, or `1280px`
|
||||
- Prefer mobile-first `min-width: 768px` for refactored rules
|
||||
|
||||
- [x] 6. Add token documentation header to `apps/web/src/styles/tokens.css`
|
||||
|
||||
- [x] 7. Create `docs/development/ui-review-checklist.md`
|
||||
|
||||
- [x] 8. Verification
|
||||
- Run `cd apps/web && npm run typecheck`
|
||||
- Run `cd apps/web && npm run lint`
|
||||
- Search for `860px` in `apps/web/src`; confirm zero matches
|
||||
- Search for `.modal-overlay` / `.modal-content`; confirm they are aliases or have fewer direct callers
|
||||
- Open a dialog on desktop and mobile viewport and verify behavior
|
||||
- Confirm `tokens.css` documentation header exists
|
||||
- Confirm `docs/development/ui-review-checklist.md` exists
|
||||
|
||||
- [x] 9. Update OpenSpec artifacts
|
||||
- Mark tasks in `openspec/tasks/web-ui-spacing-typography-rework-pass3.md` complete
|
||||
- Add final report note
|
||||
|
||||
- [x] 10. Archive OpenSpec change
|
||||
- Move `openspec/proposals/`, `openspec/specs/`, `openspec/tasks/` files for this change to `openspec/changes/archive/web-ui-spacing-typography-rework/` (or current archive structure)
|
||||
- Update `progress.md` if applicable
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- All tasks above are completed.
|
||||
- `npm run typecheck` passes.
|
||||
- `npm run lint` passes.
|
||||
- `.modal-overlay` and `.modal-content` are aliases/wrappers of `.dialog-*`.
|
||||
- At least two `.modal-*` callers migrated to `.dialog-*`.
|
||||
- `.card` family exists and is used by at least two page CSS files.
|
||||
- `860px` breakpoint removed.
|
||||
- `tokens.css` has documentation header.
|
||||
- `docs/development/ui-review-checklist.md` exists.
|
||||
- OpenSpec change archived.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Keep aliases for backward compatibility; do not delete `.modal-*` classes if other callers remain.
|
||||
- Do not change behavior of dialogs; only visual unification.
|
||||
- Do not remove page-specific layout grids; only remove duplicated card-like background/border/padding.
|
||||
- Prefer one commit per major area (modals, cards, breakpoints, docs).
|
||||
+108
@@ -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
|
||||
|
||||
- [x] 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
|
||||
|
||||
- [x] 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
|
||||
|
||||
- [x] 3. Add link-button primitive to `apps/web/src/styles/global.css`
|
||||
|
||||
- [x] 4. Add form primitives to `apps/web/src/styles/global.css`
|
||||
- `.form-input`, `.form-textarea`
|
||||
- `.checkbox-label`
|
||||
- `.hint`
|
||||
- `.form-error`, `.error-message`
|
||||
|
||||
- [x] 5. Add alert primitives to `apps/web/src/styles/global.css`
|
||||
- `.alert`, `.alert-error`, `.alert-warning`, `.alert-info`
|
||||
|
||||
- [x] 6. Add loading-state primitive to `apps/web/src/styles/global.css`
|
||||
|
||||
- [x] 7. Add badge primitives to `apps/web/src/styles/global.css`
|
||||
- `.badge`, `.badge-success`, `.badge-secondary`
|
||||
|
||||
- [x] 8. Add text utility primitives
|
||||
- `.text-muted` in `global.css`
|
||||
|
||||
- [x] 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`
|
||||
|
||||
- [x] 10. Add mobile list search styles to `apps/web/src/styles/utilities.css`
|
||||
- `.mobile-list-search`
|
||||
- `.mobile-list-search-input`
|
||||
|
||||
- [x] 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)`
|
||||
|
||||
- [x] 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`
|
||||
|
||||
- [x] 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
|
||||
|
||||
- [x] 14. Add SSH key page signing/verification styles
|
||||
- `.key-signing`, `.key-verification`, `.signature-result`, `.verify-result` in `utilities.css`
|
||||
|
||||
- [x] 15. Add profile page avatar styles
|
||||
- `.profile-avatar-section`, `.avatar-preview`, `.avatar-image`, `.avatar-placeholder` in `utilities.css`
|
||||
|
||||
- [x] 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.
|
||||
Reference in New Issue
Block a user