---
phase: 15
slug: sqlite-foundation-deprecated-field-cleanup-and-ui-polish-gap-closure
status: draft
shadcn_initialized: false
preset: none
created: 2026-03-22
---

# Phase 15 — UI Design Contract

> Visual and interaction contract for Phase 15: SQLite Foundation, Deprecated Field Cleanup, and UI Polish Gap Closure.
> Generated by gsd-ui-researcher, verified by gsd-ui-checker.

---

## Design System

| Property | Value |
|----------|-------|
| Tool | none — Slint UI framework (not React/Next.js/Vite; shadcn gate not applicable) |
| Preset | not applicable |
| Component library | Slint built-in widgets + custom components |
| Icon library | Unicode glyphs rendered in Slint Text elements |
| Font | System default (Slint runtime resolves platform font) |

**Token source:** `crates/app/ui/tokens.slint` — `Colors` and `Typography` exported globals.
All color and size values below are sourced directly from this file (verified by read on 2026-03-22).

---

## Spacing Scale

Declared values (multiples of 4 only). Source: 14-CONTEXT.md decision + card.slint established patterns.

| Token | Value | Usage |
|-------|-------|-------|
| xs | 4px | Toast top/bottom padding, icon inner gap |
| sm | 8px | Card body VerticalLayout spacing between rows, inline element gaps |
| md | 16px | Card internal section padding, modal content padding |
| lg | 24px | Modal section breaks |
| xl | 32px | Layout-level gaps |
| 2xl | 48px | Major section spacing |
| 3xl | 64px | Page-level spacing (not used in this phase) |

Exceptions:
- Toast left/right padding: 12px (established in Phase 14 — not a multiple of 4 but locked by Phase 14 decision)
- Card border-radius: 10px (existing, keep as-is)
- Stale badge border-radius: 9px (pill shape, existing, keep as-is)

---

## Typography

Source: `crates/app/ui/tokens.slint` — `Typography` global (verified by read).
All four sizes are canonical. No other sizes permitted in any `.slint` file in this phase.

| Role | Size | Weight | Line Height |
|------|------|--------|-------------|
| xs — metadata, secondary labels, stale badge | 11px (`Typography.size-xs`) | regular (400) | 1.2 (single-line labels, no wrapping) |
| sm — body text, data values, note preview | 12px (`Typography.size-sm`) | regular (400) | 1.4 |
| md — primary labels, recipient name, status pill | 13px (`Typography.size-md`) | semibold (600) for names, regular (400) for status | 1.4 |
| lg — modal titles, section headings | 18px (`Typography.size-lg`) | semibold (600) | 1.2 |

**Slint constraint note:** Slint does not expose `font-weight` as a numeric value in all contexts. Use `font-weight: 700` for semibold approximation where supported; use `font-italic: false` as default. Bold is expressed via `font-weight: 700` on Text elements that support it.

---

## Color

Source: `crates/app/ui/tokens.slint` — `Colors` global (verified by read).

| Role | Token | Hex Value | Usage |
|------|-------|-----------|-------|
| Dominant (60%) | `Colors.background` | `#1a1e2a` | Window background, behind card grid |
| Secondary (30%) | `Colors.surface` | `#242838` | Card backgrounds, panel surfaces |
| Secondary elevated | `Colors.surface-elevated` | `#2a3050` | Popup surfaces, dropdown menus |
| Secondary popup | `Colors.surface-popup` | `#2d3348` | Modal overlay backgrounds |
| Accent (10%) | `Colors.accent` | `#4a7cff` | Focus borders on search/picker inputs, active tab indicator, informational toast background (`Colors.toast-info-bg: #4a7cffe0`) |
| Accent hover | `Colors.accent-hover` | `#3a6aee` | Accent element hover state |
| Warning semantic | `Colors.warning` | `#f0a030` | Stale badge text, missing-label text, warning toast background (`Colors.toast-warning-bg: #f0a030cc`) |
| Archive action | `Colors.archive-action` | `#c0a060` | Archive/unarchive label text only |
| Error semantic | `Colors.error` | `#e05050` | Retry button text, error indicator |
| Success semantic | `Colors.success` | `#4caf50` | Connected status indicator |
| Text primary | `Colors.text-primary` | `#e0e4f0` | Recipient name, modal headings |
| Text secondary | `Colors.text-secondary` | `#c0c8da` | Status pill text, label text |
| Text muted | `Colors.text-muted` | `#8a92a8` | Note preview, secondary metadata |
| Text dim | `Colors.text-dim` | `#6b7590` | "i" info icon, placeholder text |
| Border default | `Colors.border-default` | `#3a4060` | Card borders on hover, input borders default |
| Border muted | `Colors.border-muted` | `#4a5578` | Separator lines, inactive borders |
| Avatar bg | `Colors.avatar-bg` | `#2a3560` | Recipient initial avatar background |
| Avatar text | `Colors.avatar-text` | `#7ea8ff` | Recipient initial avatar letter |

**Accent reserved for (exhaustive list):**
- Search bar input focus border
- Recipient picker search input focus border
- Active tab indicator strip
- Informational (non-warning) toast background
- Active chip/filter selection highlight

**Toast conditional rule (POLISH-01):**
- `toast-is-warning = true` → background uses `Colors.toast-warning-bg` (`#f0a030cc`, amber)
- `toast-is-warning = false` → background uses `Colors.toast-info-bg` (`#4a7cffe0`, blue)
- This is already wired in `main.rs` at 8+ call sites. Verification only; no new wiring expected.

**Hardcoded hex prohibition:** No new hardcoded hex literals in any `.slint` file. All colors must reference `Colors.*` tokens. Phase 15 inherits this rule from Phase 14.

---

## Component Inventory

These are the Slint components affected by Phase 15 work. No new components are introduced.

### Modified: `card.slint` — RecipientCard

**POLISH-01 gap closure — VerticalLayout migration:**
- Card body interior uses `VerticalLayout` with `spacing: 8px` (source: 14-CONTEXT.md decision)
- All absolute `y:` coordinates in card body rows are removed
- Card height becomes content-driven (not fixed at 196px)
- Cards with more item squares will be taller — acceptable by design decision

**CLEAN-03 / CLEAN-04 — Property changes:**
- Remove `in property <string> item-summary: ""` — replaced by item squares data (`item-squares`, `item-display-label`) which already exists on the component
- Remove `in property <string> image-hint: ""` — DATA-FLOW.md marks `first_item_image_hint` as "Retired"; image squares are the replacement
- `note-preview` property remains but source becomes `Vec<NoteEntry>` in Rust; Slint side shows the most recent entry's content (Phase 15 behavior — full history list is Phase 18)

**No new visual properties in this phase.**

### Modified: `dashboard.slint` — CardData struct

**CLEAN-03 / CLEAN-04 — Struct field changes:**
- Remove `item-summary: string` from `CardData` struct
- Remove `image-hint: string` from `CardData` struct
- These fields are removed from all Rust-to-Slint mapping call sites in `main.rs`

**Toast conditional already wired:** `toast-is-warning: bool` property exists and is wired in `main.rs`. No UI change needed.

---

## Interaction States

### Empty Dashboard (First Launch with Empty SQLite)

When SQLite is freshly created and initial sync is in progress, the card grid is empty.

| State | Visual Treatment |
|-------|-----------------|
| Unconfigured (connection-status = 0) | Centered text: "Open Settings to connect your GitHub project" — color: `Colors.text-muted`, size: 13px |
| Configured, syncing (connection-status = 1) | Existing connection indicator shows "Connecting..." — no separate empty state copy needed; existing sync indicator covers this |
| Configured, sync error | Centered text: "No orders found. Check your Shopify connection in Settings." — color: `Colors.text-muted`, size: 13px |

**Source:** 14-CONTEXT.md decisions (already implemented in Phase 14 for dashboard empty state).
Verification required: confirm these empty states survived intact through Phase 14 implementation.

### Card VerticalLayout

After migration, card rows stack top-to-bottom with 8px spacing:
1. Recipient name row (38px touch area, absolute overlay — keep as-is, it is not in VerticalLayout)
2. Status pill + date row
3. Item display label row
4. Item squares row (variable height based on item count)
5. Note preview row

The name-area TouchArea and icon overlays (info icon, dots menu) remain as absolute overlays over the name row — they are not part of the VerticalLayout body. This is the existing pattern.

### Toast Colors

| toast-is-warning | Background |
|-----------------|------------|
| false | `Colors.toast-info-bg` = `#4a7cffe0` |
| true | `Colors.toast-warning-bg` = `#f0a030cc` |

---

## Copywriting Contract

| Element | Copy |
|---------|------|
| Primary CTA (none in this phase) | This phase has no new user-facing CTAs. All work is backend SQLite + cleanup. |
| Sync indicator (initial load) | "Syncing data..." — displayed via existing connection indicator while SQLite populates on first launch |
| Empty state — unconfigured | "Open Settings to connect your GitHub project" |
| Empty state — no orders found | "No orders found. Check your Shopify connection in Settings." |
| Empty state — search zero results | "No recipients match your search" |
| Error state — sync failure | Existing toast mechanism: "{error description}" with `toast-is-warning: true` (amber background) |
| Destructive actions | None in this phase — deprecated field removal is code-only, not user-facing |

**Copywriting constraint:** No new user-visible strings are introduced in this phase beyond the sync/empty states listed above. All copy is inherited from Phase 14 decisions.

---

## Registry Safety

| Registry | Blocks Used | Safety Gate |
|----------|-------------|-------------|
| shadcn official | none | not applicable — Slint project, not React |
| Third-party | none | not applicable |

This project uses the Slint UI framework (Rust desktop app). No npm/shadcn registry is involved. Registry vetting gate is not applicable.

---

## Phase-Specific Constraints

### Slint Framework Notes (from project memory)

These constraints apply to all Slint work in this phase:

- Unicode `\u{22EE}` (vertical ellipsis) does NOT render in Slint — use ASCII alternatives (e.g., `"..."`)
- No per-corner border-radius on Rectangle — `border-radius` applies to all corners uniformly
- Full-card TouchArea declared last blocks all child interactions (z-order = declaration order) — keep TouchAreas ordered correctly
- TextInput captures arrow keys even in single-line mode — do not rely on parent FocusScope for Up/Down when TextInput is focused
- `CardData.archive_state` must be synced between archive store and all_cards_ref after every mutation
- Archive callbacks must use `w.get_cards().row_data(idx)` (filtered Slint model) not `all_cards_ref[idx]` for correct index mapping

### Deprecated Field Removal — UI Impact

| Field Removed | UI Files Affected | Required Change |
|---------------|-------------------|-----------------|
| `item-summary: string` on CardData | `dashboard.slint` | Remove field from `CardData` struct |
| `item-summary` on RecipientCard | `card.slint` | Remove `in property <string> item-summary` |
| `image-hint: string` on CardData | `dashboard.slint` | Remove field from `CardData` struct |
| `image-hint` on RecipientCard | `card.slint` | Remove `in property <string> image-hint` |

**All item display now goes through `item-squares: [ItemSquareData]` and `item-display-label: string`** — these properties already exist on both `RecipientCard` and `CardData`.

### No New Visual Components

Phase 15 introduces zero new Slint components. All UI work is:
1. Removing deprecated property declarations
2. Completing the card VerticalLayout migration (if not already done — audit first)
3. Verifying toast-is-warning conditional is active

---

## Checker Sign-Off

- [ ] Dimension 1 Copywriting: PASS
- [ ] Dimension 2 Visuals: PASS
- [ ] Dimension 3 Color: PASS
- [ ] Dimension 4 Typography: PASS
- [ ] Dimension 5 Spacing: PASS
- [ ] Dimension 6 Registry Safety: PASS

**Approval:** pending
