# Phase 12 — UI Review

**Audited:** 2026-03-22
**Baseline:** Abstract 6-pillar standards (no UI-SPEC.md exists)
**Screenshots:** Not captured — no dev server detected (Slint native desktop app, not browser-based)

---

## Pillar Scores

| Pillar | Score | Key Finding |
|--------|-------|-------------|
| 1. Copywriting | 3/4 | Labels are specific and contextual; one missing empty-grid state |
| 2. Visuals | 3/4 | Clear hierarchy and modal pattern; settings button too small at 10px text |
| 3. Color | 2/4 | Two near-identical muted text tokens (#8a8fa8 vs #8a92a8) used interchangeably |
| 4. Typography | 2/4 | 9 distinct font sizes in use — exceeds the 4-size limit by more than double |
| 5. Spacing | 3/4 | Pixel-based layout is consistent; one non-4-multiple padding (5px) in toast |
| 6. Experience Design | 4/4 | Loading, saving, error, connection, and destructive-confirm states all covered |

**Overall: 17/24**

---

## Top 3 Priority Fixes

1. **Typography explosion (9 font sizes)** — Cognitive dissonance from font size inconsistency makes the UI feel noisy; design intent is unclear. Consolidate to 4 sizes: 11px (metadata), 12px (secondary), 13px (body/labels), 18px (headings). Retire 9px, 10px, 14px, 16px, 20px by mapping each to the nearest canonical size.

2. **Dual muted-text color tokens (#8a8fa8 / #8a92a8)** — Near-identical values 4 color channels apart produce invisible inconsistency today but diverge under future theming. Pick one token and apply it everywhere. `settings-modal.slint` uses both on the same screen (form labels at #8a8fa8, button text at #8a92a8). Fix: standardize on #8a92a8 across all files, update settings-modal.slint:83, 132, 193.

3. **Missing empty-grid state when unconfigured** — When no config exists, the app shows an empty card grid with only a small "Not configured" pill in the top-left corner. A first-time user has no on-screen guidance prompting them to open Settings. Fix: render a centered instructional message in the card-grid area when `connection-status == 0`, e.g. "No data yet — open Settings to connect your GitHub project."

---

## Detailed Findings

### Pillar 1: Copywriting (3/4)

**Passing items:**
- CTA labels are task-specific: "Save Settings" (not "Save"), "Change Token", "Confirm Clear" (not "OK"), "Refresh all". These are strong.
- Error copy is surfaced dynamically from Rust via `error-message` property — not hardcoded generic strings.
- Connection status labels are descriptive: "Not configured", "Connecting...", "Connected", "Connected (no Shopify)", "Disconnected".
- Saving in-progress state changes button label to "Saving..." — good feedback.
- Toast on settings save: "Settings saved. Connecting..." (main.rs:1039) — clear and accurate. The plan's original "Restart app to connect" toast was replaced with live reconnect, which is better UX.

**Issues:**
- `dashboard.slint:325` — "Not configured" appears only in a 12px pill at top-left. There is no heading-level empty state copy in the card grid area telling users what to do. The settings modal auto-opens on first run per Plan 05, which mitigates this somewhat, but after cancelling the modal, the dashboard is visually blank with no guidance.
- `settings-modal.slint:168` — Placeholder text "https://github.com/orgs/BigscreenVR/projects/11" is an actual project URL for a specific organization. This is a development artifact that should be genericized to "https://github.com/orgs/YourOrg/projects/N" before shipping.
- `card.slint:18` — Default `stale-label: "STALE"` is all-caps; consider "Out of date" for a friendlier tone (minor).

### Pillar 2: Visuals (3/4)

**Passing items:**
- Full-overlay backdrop pattern (`#00000080`) is consistent between settings-modal and lookup-modal — clear visual modal hierarchy.
- Settings modal uses a centered 480×440px card on `#242838` that lifts off the `#1a1e2a` background — good layering.
- Backdrop click dismisses the modal — interaction matches visual affordance.
- Save button uses the primary accent (#4a7cff) with a lighter hover state (#3a6aee) — clear affordance.
- Destructive "Clear" button transitions to red (`#e05050` / `#3a1a1a`) on hover and confirms with "Confirm Clear" — proper destructive action pattern.
- `mouse-cursor: pointer` applied to all interactive TouchAreas in Phase 12 files.

**Issues:**
- `dashboard.slint:296–299` — Settings pill button is 52×18px with 10px font. At this scale, the text "Settings" is harder to read than the connection status text at 12px next to it. The button is functionally important (first-run entry point) but visually subordinate to the status indicator. Consider 11px or 12px font to match the surrounding label text.
- `settings-modal.slint:53–57` — Modal panel is fixed at 480×440px with no overflow handling. If `error-message` is long or multi-line, it occupies y=300 in a fixed-height container, potentially overlapping the button row at y=390. The `wrap: word-wrap` on the error text helps but the 22px height allocation (line 329) truncates multi-line errors.
- No empty state visual in the card grid for the unconfigured case — the grid area shows as a plain `#12151f` rectangle with no illustration or instructional copy.

### Pillar 3: Color (2/4)

**Passing items:**
- Primary accent #4a7cff is used purposefully: primary CTA buttons, active borders, filter chips, tab indicators. Not decorative overuse.
- Warning (#f0a030), success (#4caf50), error (#e05050 / #ef5350) semantic colors are applied correctly to their respective states.
- Background layering is coherent: #1a1e2a (window) → #12151f (card grid) → #242838 (cards/modals) → #1a1e2a (input fields inside modals).

**Issues:**
- Two muted-text tokens are used interchangeably. `#8a8fa8` appears in settings-modal.slint:83, 132, 193 (field labels). `#8a92a8` appears in settings-modal.slint:277, 305, 362 (button text) and dashboard.slint:399. These are visually identical but are different hex values. On the same modal panel, field labels use one token and button text use the other. There is no design reason for this split. Files: `settings-modal.slint:83,132,193` vs `settings-modal.slint:277,362`.
- Two near-duplicate border tokens: `#3a3f55` (settings-modal inputs/buttons) vs `#3a4060` (card.slint, lookup-modal, recipient-picker). These represent the same semantic role (inactive border) but differ by 11 color units in the blue channel. Should be unified to one token.
- `#e0e4f0` (settings modal title, settings-modal.slint:74) vs `#e0e4ef` (input text color throughout). Off by 1 in the blue channel — unintentional drift.

**Color inventory (hardcoded hex values):** 28+ distinct color values across the UI. This is above the threshold for a design-system-less codebase and warrants a token extraction pass.

### Pillar 4: Typography (2/4)

**Font size distribution (across all .slint files):**

| Size | Usage count | Role |
|------|-------------|------|
| 12px | 34 | Secondary text, labels |
| 13px | 31 | Body / button labels |
| 11px | 24 | Metadata, small caps |
| 16px | 8 | Mid-hierarchy text |
| 14px | 7 | Input text |
| 18px | 4 | Modal headings |
| 10px | 4 | Helper text |
| 9px  | 1 | (card.slint) — near-illegible |
| 20px | 1 | (one-off) |

Nine distinct sizes exceeds the 4-size standard by more than double. The dominant cluster is 11/12/13px which is healthy, but 9px, 10px, 14px, 16px, and 20px are one-off or low-frequency uses that create visual inconsistency.

**Font weight distribution:**
- `font-weight: 700` — 9 uses (headings, emphasis)
- `font-weight: 600` — 7 uses (sub-headings)
- Implicit default (400) — everywhere else

Two explicit weights is acceptable. The 600/700 split is reasonable for heading hierarchy.

**Phase 12 specific:**
- `settings-modal.slint:73` — Title "Settings" at 18px/700: correct.
- `settings-modal.slint:82` — Field labels at 12px: correct.
- `settings-modal.slint:182` — Helper text at 10px: should be 11px to avoid being below minimum readable size.
- `settings-modal.slint:197` — Security note at 10px: same concern.

### Pillar 5: Spacing (3/4)

**Passing items:**
- Settings modal uses a consistent 24px side margin throughout (x: 24px for all content, width: parent.width - 48px for all fields/buttons).
- Vertical rhythm in settings modal: fields at 60px / 126px / 218px give approximately 60px field-group spacing, consistent.
- Card grid uses 12px gutters throughout (padding + gaps).
- Spacing values of 8px, 12px, 16px, 24px represent a coherent base-4 scale.

**Issues:**
- `dashboard.slint:641–642` — Toast horizontal padding uses `padding-top: 5px; padding-bottom: 5px`. This 5px value is not on the 4-based scale (4/8/12/16/24). Should be 4px or 6px. Minor, but breaks the scale.
- `settings-modal.slint:54–56` — The modal card is fixed at `height: 440px` with absolute `y:` positioning for each element. If future content changes require more vertical space, elements will overflow or require manual coordinate recalculation. Not a problem now, but rigid.
- The `y: (parent.height - 440px) / 2` centering calculation in settings-modal.slint:54 assumes a fixed panel height — works correctly for the current content.

**Spacing values outside base-4 scale:** 5px (toast padding), 22px (error text height at settings-modal.slint:329). These are isolated.

### Pillar 6: Experience Design (4/4)

Phase 12 is the production wiring phase, and state coverage is comprehensive.

**Loading/progress states:**
- `saving-in-progress` property on SettingsModal disables save button and changes label to "Saving..." during async save.
- `connection-status` property with 5 states (0–4) gives granular visibility into app connectivity.
- `pending-edit-count` indicator shows sync backlog.

**Error states:**
- `error-message` displayed inline in settings modal on validation failure or save error (settings-modal.slint:327–335).
- `show-error` / `refresh-error` on RecipientCard for per-card sync errors.
- Connection status 3 (Disconnected) visible in header.
- Input border highlights focus state via `has-focus` conditional (`#4a7cff` border on focus).

**Empty states:**
- Empty card grid on first run / unconfigured (gap identified in Copywriting pillar but the grid itself renders cleanly).
- Recipient picker has explicit empty state rendering (recipient-picker.slint:193).
- `is-unassigned` card state with "Pick Recipient" CTA for Shopify cards without a matched recipient.

**Destructive action confirmation:**
- "Clear" token button requires two clicks: first click enters `clear-confirming` state (red border, "Confirm Clear" label), second click executes. Correct pattern.
- Note save/cancel on cards uses explicit "Save" and "Cancel" buttons.

**First-run experience:**
- Settings modal auto-opens when `load_config()` returns None (main.rs per Plan 05 summary).
- Live reconnect after settings save — no restart required.

**Keyboard accessibility:**
- Escape key handled in FocusScope within the modal.
- Escape within TextInput elements also triggers cancel.
- Tab strip uses F5 for refresh-all.

---

## Files Audited

**Phase 12 new/modified UI files:**
- `crates/app/ui/settings-modal.slint` (created in Plan 05)
- `crates/app/ui/dashboard.slint` (modified in Plans 04 and 05)
- `crates/app/src/main.rs` (modified in Plans 04 and 05 — wiring only, no direct UI strings)

**Pre-existing UI files reviewed for context:**
- `crates/app/ui/card.slint`
- `crates/app/ui/lookup-modal.slint`
- `crates/app/ui/recipient-picker.slint`
- `crates/app/ui/tab-strip.slint`
- `crates/app/ui/chip-bar.slint`
- `crates/app/ui/search-bar.slint`
- `crates/app/ui/option-grid.slint`
