# 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` fills and `#4C` 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` 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 }` references to `{DynamicResource }` 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).