# Phase 07 — UI Review

**Audited:** 2026-03-22
**Baseline:** Abstract 6-pillar standards (no UI-SPEC.md for this phase)
**Screenshots:** Not captured (no dev server detected on ports 3000, 5173, 8080)

---

## Pillar Scores

| Pillar | Score | Key Finding |
|--------|-------|-------------|
| 1. Copywriting | 3/4 | Toast copy is good; hidden-archived message gated on search text only |
| 2. Visuals | 3/4 | Opacity dimming and menu controls are well-placed; info icon lacks label context |
| 3. Color | 3/4 | Consistent dark palette; archive amber (#f0a030) overloaded across warning + action |
| 4. Typography | 3/4 | 5 distinct font sizes (10–18px); two weights; 10px Settings label is borderline small |
| 5. Spacing | 2/4 | Absolute pixel positioning throughout; non-standard 5px padding in toast layout |
| 6. Experience Design | 3/4 | Disabled states, hover reveals, undo toast all present; no loading skeleton for archive filter |

**Overall: 17/24**

---

## Top 3 Priority Fixes

1. **Hidden archived count message gated on search text** — Users browsing without a search query never see "N archived results hidden" even when archived cards exist; they have no way to know cards are hidden — Remove `&& root.search-text != ""` from the condition on `dashboard.slint:549` so the message appears any time `hidden-archived-count > 0 && !current-show-archived`

2. **Absolute pixel layout with no spacing system** — All child positions in `card.slint` are hardcoded pixel coordinates (x: 14px, y: 10px, y: 40px, y: 76px, etc.) rather than layout containers; any card height change requires a cascade of manual coordinate fixes — Migrate card body rows to a `VerticalLayout` with a consistent `spacing` value (e.g. 8px or 12px) so coordinates derive from layout rather than being listed individually

3. **Archive amber color (#f0a030) used for both warnings and archive actions** — The "Archive" and "Archive Now" menu labels use `#f0a030` (dashboard.slint:670 via card.slint:670), which is the same color as the `missing-label` warning (card.slint:423) and the `stale` badge (card.slint:441); this conflates destructive-ish archive actions with data-integrity warnings — Give archive actions a distinct color (e.g. #c0a060 or #d4956a) so the amber warning semantic remains exclusive to missing/stale states

---

## Detailed Findings

### Pillar 1: Copywriting (3/4)

**Strengths:**
- Archive action labels ("Archive", "Archive Now", "Unarchive") are context-sensitive and clear; the three-state menu label logic at `card.slint:667` is correct
- Toast messages are specific: `"Archived {name}. Undo?"` (main.rs:1338) includes the recipient name rather than a generic "Done"
- "N archived results hidden" message at `dashboard.slint:562` is appropriately subtle and action-oriented
- Connection status strings ("Not configured", "Connecting...", "Connected", "Disconnected") at `dashboard.slint:325–329` are unambiguous

**Issues:**
- **dashboard.slint:549** — The "N archived results hidden" message has an extra condition `&& root.search-text != ""`. This means users who are browsing the full card list without typing anything will never see the message, even if 5 cards are hidden. The message was intended to surface when archived cards exist, regardless of search state.
- `"Save"` and `"Cancel"` labels (card.slint:375, card.slint:401) are generic but appropriate in the note editing context; no improvement needed.
- `"Retry"` label (card.slint:23) is a property default — contextually fine.

### Pillar 2: Visuals (3/4)

**Strengths:**
- Opacity dimming (`archive-state > 0 ? 0.5 : 1.0` at card.slint:81) provides immediate visual distinction between active and archived/TBA cards without removing them from the layout
- Archive actions correctly live in the three-dots popup menu (`card-menu` at card.slint:473); this keeps the card surface clean and avoids accidental triggers
- Hover feedback on all interactive menu items (`has-hover ? #3a4060 : transparent`) is consistent across the popup
- Toast width correctly widens from 260px to 300px when Undo button is present (`dashboard.slint:619–621`), preventing text truncation
- Connection status indicator (colored dot + text) at top-left provides ambient system state without occupying primary attention

**Issues:**
- The info icon glyph `"\u{24D8}"` (card.slint:145) that triggers the summary popup is positioned at x: 48px + 8px, y: 10px — overlapping the recipient name area. It has no tooltip or label. Users unfamiliar with this circled-i glyph may not discover the summary panel.
- The three-dots button (`\u{2026}` at card.slint:456) has no tooltip. Icon-only controls should have an `accessible-description` or tooltip text when their function is non-obvious. Slint supports `accessible-description` property.
- The "Show archived" chip at index 6 is structurally identical to status filter chips. There is no visual separator or grouping to distinguish it from the status filters, which could confuse users who think toggling it filters by an "archived" status rather than changing visibility.

### Pillar 3: Color (3/4)

**Palette analysis:**
- Background hierarchy: `#1a1e2a` (window) → `#12151f` (card grid) → `#242838` (card) → `#2d3348` (popup/overlay) — well-structured 4-level depth
- Text hierarchy: `#e0e4ef` (primary) → `#c0c8da` (secondary) → `#8a92a8` (tertiary) → `#6b7590` (muted) — consistent 4-stop scale
- Accent blue: `#4a7cff` used for CTAs (Refresh All button, Show button, Close link) — appropriately limited
- Status/interactive blue: `#7ea8ff` used for status pill text, menu item actions — distinct from CTA blue

**Issues:**
- **Amber (#f0a030) overloading**: Used for three distinct semantic roles across the codebase — (1) warning/missing state (`card.slint:129, 423, 499`), (2) stale badge (`card.slint:441`), (3) archive action labels ("Archive", "Archive Now" at `card.slint:670`). The archive action is not a warning; conflating them makes amber ambiguous.
- **Hardcoded colors throughout**: All colors are inline hex values. There is no color token system. If the accent blue `#4a7cff` needs to change, it requires a multi-file search-and-replace. This is a maintainability concern rather than a current visual defect.
- `#555555` for disabled refresh text (`card.slint:645`) deviates from the muted text color family (`#6b7590`, `#8a92a8`). Minor inconsistency.

### Pillar 4: Typography (3/4)

**Font size distribution (card.slint + dashboard.slint combined):**
| Size | Usage |
|------|-------|
| 10px | Settings button label (dashboard.slint:298) |
| 11px | Avatar initial, badges, sub-labels (card.slint:107, 406, 441, 706) |
| 12px | Body text, status date, note, menu items, toast (most usage) |
| 13px | Recipient name, summary section headers, refresh button (card.slint:121, 733) |
| 16px | Three-dots glyph, back arrow glyph (card.slint:462, dashboard.slint:398) |
| 18px | Add item "+" button (card.slint:311) |

6 distinct sizes in active use, which is one over the abstract standard maximum of 4-5 for a dense data UI.

**Font weight distribution:**
| Weight | Usage |
|--------|-------|
| 700 | Avatar initial, item initials (card.slint:108, 235) |
| 600 | Summary panel section headers (card.slint:733–745) |

Two weights is appropriate and within standard.

**Issues:**
- **10px Settings label** (`dashboard.slint:298`) — Below recommended minimum for legibility, especially on high-DPI displays with software scaling. The label reads "Settings" at 10px on a 52px-wide pill; 11px would improve legibility without layout impact.
- **6 font sizes** exceeds the 4-size guideline for a focused utility app. The 18px "+" button (add item) and 16px glyph sizes are justified by their tap-target requirements but could potentially align to a shared "icon size" token to reduce the perceived scale count.

### Pillar 5: Spacing (2/4)

**Pattern analysis:**

All card child elements use absolute `x`/`y` pixel coordinates (card.slint:85–780). Key coordinates:
- Header row: y: 10px
- Status pill: y: 40px
- Item label: y: 76px
- Item squares: y: 96px
- Note area: y: 148px / y: 144px
- Missing label: y: 170px

The gaps between rows are inconsistent: 10→40 (30px gap), 40→76 (36px gap), 76→96 (20px gap), 96→148 (52px gap). These are not derived from a spacing scale.

**Inconsistent padding in toast layout:**
- `padding-top: 5px` / `padding-bottom: 5px` at `dashboard.slint:641–642` is a non-standard value. The rest of the UI uses multiples of 4px (4, 8, 12, 16, 28). 5px appears to be a visual fudge rather than a deliberate scale value.
- `padding-left: 16px` / `padding-right: 8px` in the same block (dashboard.slint:639–640) creates asymmetric horizontal padding, which shifts the toast text slightly left of center. Given the text uses `horizontal-stretch: 1`, this is mostly invisible, but the 8px right padding is insufficient to keep the Undo button away from the edge.

**What works:**
- The card grid spacing is consistent: 12px gaps on all sides (dashboard.slint:470–471)
- Popup menu items are uniformly 28px height with 4px internal padding (card.slint:486)
- Summary popup uses consistent `padding: 16px` / `spacing: 12px` (card.slint:729–730)

### Pillar 6: Experience Design (3/4)

**State coverage:**
- **Loading/disabled**: Refresh button disables during in-flight refresh (`refresh-disabled` prop), Refresh All dims to `#3a5299` — both correct
- **Error states**: `show-error` + `retry-label` on card (card.slint:693), connection status dot (dashboard.slint:312), `refresh-error` visual indicator on three-dots button (card.slint:453) — well-covered
- **Empty states**: No explicit empty card grid state. When `cards.length == 0`, the flickable area is empty with no message. This is a pre-existing gap, not introduced in phase 7.
- **Destructive action confirmation**: Archive is NOT destructive enough to require confirmation (it's reversible via Undo), so the lack of confirm dialog is correct per the context decisions.
- **Undo pattern**: `toast-has-undo` flag, 5-second timer, `toast-undo` callback are all correctly wired (dashboard.slint:638–677, main.rs:1338)

**Archive-specific UX gaps:**
- **Hidden count message gated on search** (see Copywriting pillar): When showing the full list with no filter, a user cannot see "3 archived results hidden" — they have no affordance to discover and show archived cards except through the ChipBar chip.
- **No loading state for archive filter pipeline**: When `filter_cards_by_archive` runs on a large card set during a filter toggle, the UI updates synchronously. This is currently fine at seed data scale, but there is no skeleton/pending state if the operation ever becomes async.
- **Unarchive has no undo**: `on_card_unarchive` at main.rs:1366 shows `"Unarchived {name}"` as a simple informational toast (`has_undo: false`). This is intentional per the context doc, but worth noting: a misclick on Unarchive on an archived card is not reversible via toast.

---

## Files Audited

- `crates/app/ui/card.slint` — RecipientCard component with archive-state, opacity binding, popup menu actions
- `crates/app/ui/dashboard.slint` — DashboardWindow with hidden-archived-count, toast-has-undo, toggle-show-archived, archive callbacks, "N archived results hidden" message
- `crates/app/src/main.rs` — Archive filter pipeline, chip routing (idx 6), card-archive/unarchive/archive-now callbacks, show_archive_toast helper, toast-undo, toggle-show-archived
- `crates/app/src/dashboard/archive.rs` — (audited via SUMMARY.md; direct read not required for UI pillars)
- `.planning/phases/07-archive-lifecycle-controls/07-01-SUMMARY.md`
- `.planning/phases/07-archive-lifecycle-controls/07-02-SUMMARY.md`
- `.planning/phases/07-archive-lifecycle-controls/07-01-PLAN.md`
- `.planning/phases/07-archive-lifecycle-controls/07-02-PLAN.md`
- `.planning/phases/07-archive-lifecycle-controls/07-CONTEXT.md`
