docs(ui): add UI normalization design spec and implementation plan
This commit is contained in:
96
docs/superpowers/specs/2026-05-30-ui-normalization-design.md
Normal file
96
docs/superpowers/specs/2026-05-30-ui-normalization-design.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# UI Normalization & Single Source of Truth — Design
|
||||
|
||||
Date: 2026-05-30
|
||||
Status: Approved
|
||||
|
||||
## Goal
|
||||
|
||||
Make working on the ClaudeDo UI simpler by establishing the design tokens as the single source of truth for **every** visual value, eliminating duplicated styles, and providing reusable helpers for the patterns that are currently copy-pasted across views. Accept minor visual shifts where current values don't match the token scale — consistency is the priority over pixel-preservation.
|
||||
|
||||
## Scope decisions (locked)
|
||||
|
||||
- **Lane C (full normalization)** — global defaults + shared helpers + tokenize every hardcoded font/spacing/radius/color.
|
||||
- **Normalization strategy: B (snap to existing scale).** Stray values round to the nearest existing token; off-palette colors fold into the closest design brush. The token vocabulary stays small; the UI shifts slightly in places and is verified by human eyeball.
|
||||
- Badge colors collapse to palette (option A): blue is dropped.
|
||||
|
||||
## 1. Global defaults — `src/ClaudeDo.App/App.axaml`
|
||||
|
||||
Add application-level default styles so unstyled controls inherit the intended look instead of falling back to FluentTheme's Segoe UI:
|
||||
|
||||
- Default `FontFamily` = `{DynamicResource SansFont}` (Inter Tight) for text-bearing controls (`TextBlock`, `TextBox`, `Button`, `ComboBox`, `CheckBox`, `NumericUpDown`, `TabItem`).
|
||||
- Default `FontSize` baseline = `{StaticResource FontSizeBody}` (13) where a control has no more specific style.
|
||||
- Controls that need mono (`MonoFont`) continue to opt in explicitly via their class/style.
|
||||
|
||||
This single change fixes the Settings modal font and every other bare-Segoe-UI label across the app.
|
||||
|
||||
## 2. Tokens = source of truth — `src/ClaudeDo.Ui/Design/Tokens.axaml`
|
||||
|
||||
### Fonts — snap to the existing scale
|
||||
Existing tokens: Eyebrow=10, Mono=11, Micro=11, Body=13, TaskTitle=14, H3=18, H2=24, H1=32.
|
||||
- `9 → 10` (FontSizeEyebrow)
|
||||
- `12 → 13` (FontSizeBody)
|
||||
- `16 → 18` (FontSizeH3)
|
||||
- Every `FontSize="N"` literal across all views/styles becomes a `{StaticResource FontSize*}` reference. No new size tokens are added.
|
||||
|
||||
### Spacing / radius — snap to the existing scale
|
||||
- Modal body padding `16` / `20 → 18` (SpaceXl); the vertical component `12` stays `SpaceMd`.
|
||||
- Corner radius `4 → 6` (ButtonCornerRadius).
|
||||
- Text inputs (TextBox) standardize on `InputCornerRadius` (8); the `6` currently on DetailsIslandView TextBoxes moves to 8.
|
||||
|
||||
### Colors — fold off-palette into the palette
|
||||
Add semantic brushes where a recurring role genuinely needs one, but reuse existing palette brushes wherever possible:
|
||||
|
||||
- **Connection-status dots** (MainWindow): green `#4CAF50` → `StatusRunningBrush`; amber `#FFA726` → `StatusReviewBrush`; red `#EF5350` → `StatusErrorBrush`. Also applies to the `#EF5350` literals in WorktreesOverviewModal.
|
||||
- **Planning/draft badges** (IslandStyles `DraftBadgeBrush`/`PlanningBadgeBrush`/`PlannedBadgeBrush`): re-point to palette — draft → `TextMuteBrush`, planning → `PeatBrush`, planned → `SageBrush`. Blue dropped.
|
||||
- **Named-color literals:** `OrangeRed` / `Orange` → `BloodBrush`; `White` → `TextBrush` (or `DeepBrush` where it sits on an accent fill, e.g. primary button text).
|
||||
- **Terminal background** `#FF080C0B` (terminal + task-live-tail) → `VoidBrush` (`#FF0A0E0C`).
|
||||
- **Status alpha-tints:** the repeated `#1F<hue>` fills and `#4C<hue>` borders used by chips and agent-strips become named brushes defined once in Tokens (e.g. `RunningTintBrush` / `RunningTintBorderBrush`, and the same for review/error/queued), then referenced from IslandStyles. The `#26<hue>` worktree-badge tints and `#147C9166` agent-strip tints fold into the same named tint family (snap the alpha to one value per family).
|
||||
- **Island hairline overlay** `#0DFFFFFF` → a named `HairlineOverlayBrush` token.
|
||||
|
||||
## 3. Shared helpers
|
||||
|
||||
### `src/ClaudeDo.Ui/Design/IslandStyles.axaml`
|
||||
Promote the styles currently copy-pasted into modals into the shared stylesheet, then delete the per-modal copies:
|
||||
- `Button.primary` — standardize on **one** definition: `AccentDimBrush` background + `AccentBrush` border + `TextBrush` foreground (matching the existing `Button.btn.primary` variant). Resolves the AccentBrush-vs-AccentDimBrush divergence.
|
||||
- `Button.danger` — `BloodBrush` background + `TextBrush` foreground.
|
||||
- `TextBlock.field-label` — FontSize Micro (11), `TextDimBrush`, bottom margin 4.
|
||||
- `TextBlock.section-label` already exists in IslandStyles; remove the duplicate local copies.
|
||||
|
||||
### New control: `ModalShell` (`src/ClaudeDo.Ui/Views/Controls/ModalShell.axaml`)
|
||||
A reusable `TemplatedControl` / `UserControl` providing the chrome every modal re-implements:
|
||||
- Title bar: mono uppercase title (FontSize Mono, LetterSpacing 1.4), draggable region, ✕ close button (`icon-btn`).
|
||||
- Outer border (SurfaceBrush bg, LineBrush border, ModalCornerRadius).
|
||||
- Content slot for the body.
|
||||
- Optional footer slot for action buttons (right-aligned).
|
||||
- Exposes: `Title` (string), `Body` content, `Footer` content, and a `CloseCommand`.
|
||||
|
||||
The 8 modal windows (Settings, ListSettings, Merge, About, UnfinishedPlanning, RepoImport, Diff, PlanningDiff, ConflictResolution) migrate to wrap their content in `ModalShell` instead of re-declaring titlebar/border/footer grids. Window-level concerns (Width/Height, KeyBindings, WindowDecorations) stay on the `Window`; only the inner chrome is replaced.
|
||||
|
||||
## 4. Bug fixes (folded into the migration)
|
||||
|
||||
- `TaskRowView.axaml` schedule flyout: `BorderBrush="{DynamicResource BorderBrush}"` → `{DynamicResource LineBrush}` (the `BorderBrush` key does not exist in Tokens; current runtime resource-not-found).
|
||||
- `DiffModalView.axaml`, `PlanningDiffView.axaml`, `ConflictResolutionView.axaml`: convert all `{StaticResource <token>}` references to `{DynamicResource <token>}` to match the rest of the app and survive theme changes. (Style-internal `Setter` references that must stay `StaticResource` for Avalonia reasons are left as-is; only token lookups in element attributes are converted.)
|
||||
|
||||
## 5. Verification
|
||||
|
||||
- `dotnet build` per project (`.slnx` requires .NET 9 — build individual csproj):
|
||||
- `src/ClaudeDo.App/ClaudeDo.App.csproj` (pulls in Ui + Data)
|
||||
- `src/ClaudeDo.Worker/ClaudeDo.Worker.csproj`
|
||||
- A clean build confirms XAML compiles and all resource keys resolve (compiled bindings + StaticResource keys are validated at build time).
|
||||
- Human visual pass: launch the app and walk each view/modal against a per-view checklist (provided with the plan), since lane B intentionally shifts some values. The eyeball is the regression check.
|
||||
|
||||
## Sequencing
|
||||
|
||||
1. Tokens.axaml: add new named brushes (tints, status, hairline), re-point badge brushes. (No behavior change yet.)
|
||||
2. App.axaml: global font/size defaults.
|
||||
3. IslandStyles.axaml: promote shared styles (primary/danger/field-label), replace internal hardcoded values with token refs.
|
||||
4. Per-view migration: replace every hardcoded FontSize/spacing/radius/color with token refs; snap stray values.
|
||||
5. ModalShell control + migrate the 8 modals.
|
||||
6. Bug fixes (BorderBrush key, Static→Dynamic in the three views).
|
||||
7. Build all projects; produce visual-check checklist.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- No layout/structure redesign — only values and shared chrome.
|
||||
- No new features.
|
||||
- No changes to ViewModels or behavior (ModalShell migration is markup-only; existing `CancelCommand` etc. bind through unchanged).
|
||||
Reference in New Issue
Block a user