docs(openspec): add web ui spacing/typography proposal, spec, tasks and audit

This commit is contained in:
Developer
2026-06-16 12:24:10 +00:00
parent 454a7845bb
commit da3f8da3ac
4 changed files with 1202 additions and 0 deletions
@@ -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 | 46 | ~400 | Medium |
| Pass 2: Component Cleanup | inline styles, sidebars, forms, badges | 1014 | ~600 | High |
| Pass 3: Consolidation | modals, cards, breakpoints, docs | 812 | ~400 | Medium |
| **Total** | | **2232** | **~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.