# Phase 05 — UI Review

**Audited:** 2026-03-22
**Baseline:** Abstract 6-pillar standards (no UI-SPEC.md)
**Screenshots:** Not captured (native Slint desktop app — no web dev server)

---

## Pillar Scores

| Pillar | Score | Key Finding |
|--------|-------|-------------|
| 1. Copywriting | 3/4 | CTAs are specific and contextual; tab icons are single letters (S/D/R/P) with no screen-readable label on the icon itself |
| 2. Visuals | 2/4 | Tab icons are single ASCII letters instead of Unicode glyphs — unclear and non-intuitive at a glance |
| 3. Color | 3/4 | Coherent dark palette with consistent accent (#4a7cff); toast background changed from dark to accent-blue, conflicting with original spec |
| 4. Typography | 3/4 | 7 distinct sizes in use (9px–20px) — slightly above the recommended ceiling of 4, but sizes serve clear purposes |
| 5. Spacing | 3/4 | Absolute-positioned layout is intentional for a fixed-size desktop app; no arbitrary rem/px overrides; minor: tab spacing is hand-calculated (y: 4/48/92/136) with no unifying constant |
| 6. Experience Design | 3/4 | Toast, error, retry, pending-edit, and connection-status states all covered; empty card grid has no visual state |

**Overall: 17/24**

---

## Top 3 Priority Fixes

1. **Tab icons are single capital letters (S, D, R, P)** — Users have no visual affordance for what each tab does without hovering; tooltips only appear on hover, so keyboard users and first-time users are left guessing — Replace with Unicode icon glyphs as originally planned: `\u{1F552}` (clock) for Status Updated, `\u{1F4E6}` (package/calendar) for Ship Date, `\u{1F464}` (person) for Recipient, `\u{1F69A}` (truck/shipped) for Product Shipped, or any distinct single glyphs per tab mode.

2. **No empty state for the card grid** — When the search or filter returns zero cards, the card grid region shows an empty dark rectangle with no message — Add a conditional `if root.cards.length == 0` block inside the `card-flickable` that renders a centered Text element: e.g., `"No recipients match your search"` or `"No cards in this mode"` with secondary muted text explaining the active mode.

3. **Toast background uses accent blue (#4a7cffe0) instead of the designed dark background (#1d2430e0)** — The toast competes visually with interactive buttons (refresh-all button is the same #4a7cff), creating color ambiguity — the toast is a notification, not an action, and should use the original dark semi-transparent background to stay subordinate to the UI; change dashboard.slint line 624: `background: #4a7cffe0` to `background: #1d2430e0`.

---

## Detailed Findings

### Pillar 1: Copywriting (3/4)

The copy is generally purposeful and specific throughout all five Slint files.

**Strengths:**
- Toast messages are specific: `"Filters cleared"` and `"Back to Status Updated"` (mod.rs `toast_message_for_esc`) — exactly what the user decisions called for.
- Card action menu labels are action-oriented: `"Open Discord DM"`, `"Copy Email"`, `"Shopify Profile"`, `"View Order"`, `"Pick Recipient"`.
- Error states use specific labels: `"Refresh failed - retry"` (card.slint:642) vs generic `"Retry"`.
- Settings buttons use descriptive labels: `"Save Settings"` (settings-modal.slint:385) and `"Saving..."` during in-progress state.
- Search result count is well-worded: `"1 result"` / `"N results"` (search-bar.slint:65).

**Issues:**
- `"Save"` and `"Cancel"` labels on the note edit inline buttons (card.slint:375, card.slint:401) are generic. `"Save"` is acceptable in a micro-edit context, but `"Cancel"` could be `"Discard"` to better communicate that unsaved edits are lost.
- `"Close"` on the summary popup close button (card.slint:758) is generic. Acceptable but a label like `"Done"` would better match the panel-summary context.
- Summary popup section header `"Last Received"` (card.slint:741) is truncated compared to the nearby `"Last Received Items"` convention — inconsistent with `"Last Shipment Date"` and `"Last Status Update"` which are complete phrases.

### Pillar 2: Visuals (2/4)

**Critical issue — Tab icons are single capital letters:**
The plan specified Unicode emoji glyphs (clock, calendar, person, package). The implementation uses `"S"`, `"D"`, `"R"`, `"P"` as single bold capital letters (tab-strip.slint:31, 65, 99, 133). These are:
- Ambiguous — `"D"` for "Ship Date" and `"P"` for "Product Shipped" require contextual knowledge.
- Not scannable — four identical-weight bold capitals in a vertical strip offer no visual differentiation beyond position.
- Tooltip-dependent — the hover tooltips (`"Status Updated"`, `"Ship Date"`, `"Recipient"`, `"Product Shipped"`) are the only descriptive signal, and they require a mouse hover action.

**Strengths:**
- Active tab state is clearly indicated via two simultaneous signals: the blue left accent bar (3px, #4a7cff) and background highlight (#252a3a), which is solid feedback design.
- Tab z-order is correctly managed — tooltips declared after tab rects to paint above the card grid (tab-strip.slint:147–148 comment, dashboard.slint:605).
- Visual hierarchy on cards is well-structured: recipient name + avatar row → status pill → item squares → note area, using size and color gradation.
- Connection status dot (gray/yellow/green/red) provides at-a-glance system state at top-left.
- The breadcrumb back arrow `\u{2190}` and info icon `\u{24D8}` use Unicode correctly — consistent with project's established pattern.

**Minor issues:**
- The three-dots menu button (card.slint:456) uses `\u{2026}` (horizontal ellipsis). The MEMORY.md notes that `\u{22EE}` (vertical ellipsis) does NOT render in Slint — the horizontal ellipsis used here is correct, but it may feel misaligned for a vertical-overflow menu idiom.
- The `"+"` add-item button (card.slint:307) has no label or tooltip — acceptable at 36x36px but borderline for discoverability.

### Pillar 3: Color (3/4)

The palette is coherent and internally consistent across all files. The dark theme (backgrounds: #1a1e2a, #12151f, #242838) is fully applied as designed in Plan 03.

**Color distribution:**
- Background layer: `#1a1e2a` (window), `#12151f` (card region), `#242838` (cards), `#2d3348` (popups/menus)
- Accent: `#4a7cff` (accent bar, primary buttons, links, focus ring, tooltip border) — used approximately 15–20 times across files
- Secondary accent: `#7ea8ff` (softer blue for avatar initials, menu links)
- Status colors: `#f0a030` (amber, stale/missing/warning), `#4caf50` (green, connected/add), `#ef5350` (red, error/remove)
- Text hierarchy: `#e0e4ef` (primary), `#c0c8da` (secondary), `#8a92a8` (muted), `#6b7590` (very muted)

**Issues:**
- Toast background uses `#4a7cffe0` (accent blue with alpha) at dashboard.slint:624. The original plan spec and Plan 02 SUMMARY both specify `#1d2430e0` (dark semi-transparent). Using the accent color for a notification creates visual parity with the primary action button (Refresh All, which is also `#4a7cff`), muddying the 60/30/10 separation.
- The connection status circle at `connection-status == 4` maps to yellow (`#f0c040`) with label `"Connected (no Shopify)"` (dashboard.slint:321-322) — yellow conventionally signals caution/warning, which is reasonable, but the label says "Connected" which implies success (green). A subtle inconsistency.
- `#3a2a00` used for stale badge background (card.slint:435) is a one-off dark amber not found elsewhere — effectively an orphaned token.

### Pillar 4: Typography (3/4)

**Font sizes in use across all Slint files:**
`9px`, `10px`, `11px`, `12px`, `13px`, `14px`, `16px`, `18px`, `20px`

That is 9 distinct values across the whole UI system. Within the Phase 05 core files (dashboard.slint, tab-strip.slint, card.slint, search-bar.slint):
- `10px` — Settings button label (dashboard.slint:298)
- `11px` — tooltips, sub-labels, stale badge, misc secondary
- `12px` — status date, note preview, secondary text (most common body size)
- `13px` — recipient name, item summary, primary body text
- `16px` — tab icons S/D/R/P (bold), three-dots icon, back arrow
- `18px` — add button "+" icon

Core content range is 11–16px with 18px for icon-only elements, which is reasonable for a dense information dashboard.

**Font weights in use:**
- `600` — summary popup section headings, settings title, picker modal title
- `700` — avatar initials, item square initials, tab icon letters, option grid initials

Two weights in use (600 and 700) plus implicit default (400/normal). This is clean and within the 2-weight recommendation.

**Issues:**
- 9 discrete size values is above the "4 sizes" ideal, though the inclusion of modal-specific files (settings, lookup, recipient-picker) accounts for the spread. Strictly within the phase 05 discovery-navigation files the range is 10–18px (6 values), still above ideal but purposeful.
- The tab icon letters use `font-size: 16px` with `font-weight: 700` — making text-as-icon disproportionately heavy. If replaced with Unicode glyphs, this weight would need to be revisited as glyphs render differently under bold.

### Pillar 5: Spacing (3/4)

The layout uses Slint's absolute positioning (x/y in px) rather than a Tailwind-style spacing scale. This is appropriate for a native desktop UI with fixed 1200×760 window dimensions. There are no arbitrary rem values.

**Consistent patterns observed:**
- Card internal padding: 14px from left edge consistently (card.slint: lines 99, 119, 155, 183, 196, etc.)
- Card grid gap: 12px between cards and as margin (dashboard.slint:463, 471)
- Tab strip tabs: 40px height with 8px gaps (y: 4, 48, 92, 136 — stride of 44px)
- Popup padding: 16px (card.slint:729), 4px for menu (card.slint:486)
- Modal padding: consistent 16px/24px patterns

**Issues:**
- Tab y-positions (4, 48, 92, 136) imply a stride of 44px (4px start, 40px height, 4px gap = 48px stride, but 4→48 is 44px, 48→92 is 44px, 92→136 is 44px). The 4px/44px rhythm is fine but is not defined as a named constant — it's hard-coded repetition across 4 blocks. If a tab height changes, all positions require manual update.
- The card grid right margin is 72px (60px tab + 12px gap) but the card flickable `viewport-height` uses `196px + 12px` row height — the 196px card height is implicit (not defined as a reusable constant), so it appears in two separate calculations.

### Pillar 6: Experience Design (3/4)

**States covered:**
- Loading/pending: `pending-edit-count` indicator in title bar (dashboard.slint:273–278, amber text "N edits pending"). Refresh-all button gets disabled state during refresh (`refresh-all-disabled`, dashboard.slint:343).
- Error states: `show-error` + `refresh-error` on cards with distinct `"Refresh failed - retry"` label and amber three-dots button background (card.slint:453, 642, 693). Settings modal has `error-message` display (settings-modal.slint:327–333). Lookup modal has inline validation error for required name field (lookup-modal.slint:332).
- Connection status: 4-state indicator (unconfigured, connecting, connected, disconnected) always visible at top-left (dashboard.slint:312–333).
- Toast feedback: discovery Esc actions, archive undo actions — toast-visible with auto-hide timer and optional Undo button.
- Archive states: 3-state card opacity dimming (active/TBA/archived) with distinct menu actions.
- Disabled states: refresh button disabled during in-progress, menu item `color: #555555` when refresh-disabled.

**Issues:**
- **No empty state for the card grid** — When `root.cards.length == 0` (e.g., no search results, mode with no data), the `card-flickable` renders an empty `#12151f` rectangle. There is no "no results" message, no suggestion, no call to action. The recipient-picker has an empty state (`recipient-picker.slint:194`) but the main grid does not.
- **No global loading skeleton** — During initial data load (before any cards arrive from the sync), the card grid is empty with no visual loading indicator. A "Loading..." text or spinner would prevent the blank-screen flash on startup.
- **Keyboard navigation note:** The `global-keys FocusScope` (dashboard.slint:216) is set to always capture Up/Down even when search bar has focus (`init => { self.focus(); }`). This means Up/Down mode-switch fires even while typing in the search bar. The original plan called for Up/Down suppression when text-input-focused. The `text-input-focused` property exists but is only used to gate character-key forwarding (line 247), not the Up/Down/Esc handlers. This is a behavioral gap that affects search usability — pressing Up while typing a name would cycle the discovery mode.

---

## Files Audited

- `crates/app/ui/tab-strip.slint` — TabStrip component (229 lines)
- `crates/app/ui/dashboard.slint` — DashboardWindow main layout (764 lines)
- `crates/app/ui/card.slint` — RecipientCard component (782 lines)
- `crates/app/ui/search-bar.slint` — SearchBar component (73 lines)
- `crates/app/ui/chip-bar.slint` — ChipBar (scanned for font/color patterns)
- `crates/app/ui/option-grid.slint` — RecipientGrid/ProductGrid (scanned)
- `crates/app/ui/lookup-modal.slint` — LookupModal (scanned for copy/state)
- `crates/app/ui/settings-modal.slint` — SettingsModal (scanned for copy/state)
- `crates/app/ui/recipient-picker.slint` — RecipientPickerModal (scanned)
- `crates/app/src/main.rs` — Slint runtime wiring (header scanned)
- `.planning/phases/05-discovery-navigation-framework/05-01-SUMMARY.md`
- `.planning/phases/05-discovery-navigation-framework/05-02-SUMMARY.md`
- `.planning/phases/05-discovery-navigation-framework/05-03-SUMMARY.md`
- `.planning/phases/05-discovery-navigation-framework/05-CONTEXT.md`
