---
phase: 20.1.1.1.1-phase-20-1-1-round-2-gap-closure-note-dedup-sidebar-nav-role
verified: 2026-04-15T12:00:00Z
status: human_needed
score: 12/14 must-haves verified
overrides_applied: 0
human_verification:
  - test: "D-12 — Return status shows 'Return Underway' for cards with tracking numbers"
    expected: "Cards that previously showed 'Return Created' now show 'Return Underway' when they have tracking numbers assigned to their returns"
    why_human: "sync_return_states logic is correctly wired and instrumented, but 'Return Created' -> 'Return Underway' transition only fires when a sync cycle completes against live Shopify data. Cannot verify programmatically without running the app against live data."
  - test: "D-14 — Missing card appears and 'Connecting...' status resolves"
    expected: "All cards appear in the dashboard; connection status changes from 'Connecting...' to 'Connected' (or 'Connected (no Shopify)') within 30 seconds of app launch. [sync_thread] Starting sync cycle 1 appears in stderr."
    why_human: "Diagnostic eprintln! lines have been added to the sync thread loop. Whether the Shopify sync actually completes depends on live API token validity and network connectivity — cannot be verified without running the app."
  - test: "D-01/D-02 — Purpose pill dropdown renders correctly in sidebar header"
    expected: "Purpose pill appears right of avatar, just below recipient name, with 2px hollow outline border (no background fill). Clicking opens a dropdown with the GH Project role options. Selecting a role closes the dropdown and updates the purpose."
    why_human: "PopupWindow positioning and hollow border style require visual confirmation in the running UI. The Slint code exists and compiles, but final positioning relative to the header row must be verified visually."
  - test: "D-08/D-09 — Product add button no longer flickers and aligns with product squares"
    expected: "Moving cursor back and forth over the + button does not cause it to flicker. The + button circle is vertically centered at the same y position as product image squares."
    why_human: "Hover interaction behavior (flicker-free) requires live UI testing. Visual alignment cannot be confirmed from code alone."
  - test: "D-03 — Tile-click navigates to filtered cards alongside sidebar"
    expected: "Clicking a recipient tile shows filtered card view for that recipient WITH the sidebar visible alongside the cards. Previously the sidebar appeared on the tile grid without navigating."
    why_human: "Navigation flow requires live UI interaction to confirm the view transition fires correctly and both filtered cards and sidebar are visible together."
---

# Phase 20.1.1.1.1 Verification Report

**Phase Goal:** Close 14 defects discovered in round 2 UAT (D-01 through D-14). Issues span UI (pill styling, sidebar navigation, button flicker), data (note dedup, stale Rx, wrong statuses), and sync (missing card, Shopify connectivity).
**Verified:** 2026-04-15T12:00:00Z
**Status:** human_needed
**Re-verification:** No — initial verification

## Goal Achievement

### Observable Truths

| # | Truth | Status | Evidence |
|---|-------|--------|----------|
| 1 | Purpose pill opens PopupWindow dropdown with role options (D-01) | VERIFIED | `recipient-detail.slint` line 139: `purpose-dropdown := PopupWindow`; pill-touch clicked handler calls `purpose-dropdown.show()`; role-touch clicked fires `root.save-purpose(role)` |
| 2 | Purpose pill is right of avatar / below name with 2px hollow border (D-02) | VERIFIED | `recipient-detail.slint` lines 115-117: `background: transparent; border-width: 2px; border-color: root.purpose-color`; pill is inside header VerticalLayout as second child after name Text |
| 3 | Tile-click navigates to filtered card view AND shows sidebar (D-03) | VERIFIED | `main.rs` lines 3409-3415: `ms.view_state = ModeViewState::FilteredCards` set before `apply_filters`, then `set_recipient_detail_visible(true)` |
| 4 | Tab-restore preserves filtered card view and sidebar (D-04) | VERIFIED | `main.rs` tab-clicked handler (line 3371+): calls `restore_mode_state` (preserves view_state) then re-shows sidebar via `set_recipient_detail_visible(true)` when `selected_tile` is present |
| 5 | Esc does NOT close the sidebar when no inline edit is active (D-05) | VERIFIED | `recipient-detail.slint` line 65: FocusScope Esc else branch returns `return reject` (not `return accept`); no `root.close-clicked()` call in that branch |
| 6 | Breadcrumb-back dismisses sidebar and returns to tile grid (D-06) | VERIFIED | `main.rs` lines 3601-3605: `set_recipient_detail_visible(false)` plus all four editing flags set to false, placed after `apply_filters` in `on_breadcrumb_back` |
| 7 | Notes deduplicated on (card_id, author, content) not timestamp (D-07) | VERIFIED | `sqlite.rs` line 622: `COALESCE(author,'') = COALESCE(?2,'') AND content = ?3`; line 613: D-07 comment; UPDATE branch line 634 fills GH-authoritative timestamp and NULL author |
| 8 | Product add (+) button does not flicker on hover (D-08) | VERIFIED (code) | `card.slint` line 501: `btn-zone := TouchArea` with no clicked handler; line 511: `if card-hover-zone.has-hover \|\| btn-zone.has-hover` — passive zone eliminates feedback loop. Requires human UAT for live confirmation. |
| 9 | Product add (+) button is vertically aligned with product image squares (D-09) | VERIFIED (code) | `card.slint` line 521: `y: 14px` on inner button circle; line 407: `y: 14px` on inner image Rectangle |
| 10 | Sidebar shows fresh Rx values after sync cycle (D-10) | VERIFIED | `main.rs` lines 2671-2678: `get_recipient_detail_visible()` check in sync callback; if true, calls `populate_recipient_sidebar` using `sync_store_for_cb` and `slug_for_cb` |
| 11 | Product image squares and SN labels scaled to fill 80px row (D-11) | VERIFIED | `card.slint` line 408: `width: 52px` / `height: 52px`; line 407: `y: 14px`; line 398: `x: sq-index * 58px`; line 449: SN label `y: 68px`, `width: 58px`; `sq-touch width: 52px` at line 468 |
| 12 | Cards with tracking numbers show 'Return Underway' not 'Return Created' (D-12) | NEEDS HUMAN | `live_client.rs` line 886-895: `sync_return_states` correctly transitions `Return Created -> Return Underway` when `reverse_fulfillment_tracking` returns tracking. Logic is wired; requires live sync to confirm data path |
| 13 | Cards with populated item_squares show product count label (D-13) | VERIFIED | `main.rs` line 649-655: D-13 comment; fallback derives `"1 item"` / `"N items"` when `product_names` is empty but `product_refs` count > 0 |
| 14 | Shopify sync completes and connection status updates from 'Connecting...' (D-14) | NEEDS HUMAN | `live_client.rs` lines 149-170: `sync_cycle_count` counter + `eprintln!("[sync_thread]")` diagnostics added. Actual sync completion requires live Shopify API token and network |

**Score:** 12/14 truths verified (2 require human UAT)

### Required Artifacts

| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `crates/app/ui/card.slint` | Flicker-free hover, aligned button, scaled squares | VERIFIED | btn-zone passive TouchArea; 52x52 squares; 58px stride; y:14px alignment |
| `code_tips/SLINT_TIPS.md` | Hover-flicker fix documentation | VERIFIED | "Hover-conditional element flicker" section added with problem/root-cause/solution/code example |
| `crates/app/ui/recipient-detail.slint` | PopupWindow dropdown, hollow pill, Esc fix | VERIFIED | purpose-dropdown PopupWindow; transparent background + 2px border; return reject in Esc else |
| `crates/service/src/db/sqlite.rs` | Corrected upsert_notes_for_card with author+content dedup | VERIFIED | COALESCE dedup key, UPDATE branch, D-07 comment |
| `crates/app/ui/dashboard.slint` | Sidebar mount guard, card-flickable width, purpose-options | VERIFIED | No show-recipient-grid guard; width condition includes recipient-detail-visible; detail-recipient-purpose-options property + binding |
| `crates/app/src/main.rs` | Tile-click nav, breadcrumb-back, purpose-options, post-sync refresh | VERIFIED | ModeViewState::FilteredCards in on_tile_clicked; set_recipient_detail_visible(false) in on_breadcrumb_back; set_detail_recipient_purpose_options at startup; D-10 sidebar refresh in sync callback |
| `crates/app/src/live_client.rs` | Diagnostic logging for sync thread | VERIFIED | sync_cycle_count + eprintln! at loop top, after result, and on error |

### Key Link Verification

| From | To | Via | Status | Details |
|------|----|-----|--------|---------|
| `purpose-pill-touch clicked` | `purpose-dropdown.show()` | editing-purpose flag | VERIFIED | `recipient-detail.slint` line 124 |
| `role-touch clicked` | `root.save-purpose(role)` | dropdown item selection | VERIFIED | `recipient-detail.slint` line 168 |
| `on_tile_clicked (ByRecipient)` | `apply_filters + set_recipient_detail_visible(true)` | ModeViewState::FilteredCards before apply_filters | VERIFIED | `main.rs` lines 3409-3415 |
| `on_breadcrumb_back` | `set_recipient_detail_visible(false)` | sidebar dismiss + editing state reset | VERIFIED | `main.rs` line 3601 |
| `on_sync callback` | `populate_recipient_sidebar` | get_recipient_detail_visible check | VERIFIED | `main.rs` lines 2671-2676 |
| `upsert_notes_for_card` | `notes table` | COALESCE(author,'') = COALESCE(?2,'') AND content = ?3 | VERIFIED | `sqlite.rs` line 622 |
| `card.slint btn-zone TouchArea` | `card-hover-zone.has-hover \|\| btn-zone.has-hover` | OR condition for button visibility | VERIFIED | `card.slint` lines 501-511 |
| `build_item_squares_from_vm` | `item_display_label` | product_names fallback when empty | VERIFIED | `main.rs` lines 649-655 |
| `run_sync_cycle` | `[sync_thread] eprintln!` | sync_cycle_count loop | VERIFIED | `live_client.rs` lines 149-170 |

### Data-Flow Trace (Level 4)

| Artifact | Data Variable | Source | Produces Real Data | Status |
|----------|---------------|--------|--------------------|--------|
| `recipient-detail.slint` purpose-options | `purpose-options: [string]` | `main.rs` startup fetches GH Project Purpose field options via `set_detail_recipient_purpose_options` | Yes — GH API fetch populates real options; empty fallback if GH unavailable | FLOWING |
| `main.rs build_item_squares_from_vm` | `item_display_label` | `product_refs` count from SQLite projection | Yes — falls back to "N items" count from product_refs when product_names empty | FLOWING |
| `live_client.rs sync_return_states` | card status update | `reverse_fulfillment_tracking` Shopify API call | Conditionally — requires live Shopify API; diagnostic logs confirm path | NEEDS RUNTIME |

### Behavioral Spot-Checks

| Behavior | Command | Result | Status |
|----------|---------|--------|--------|
| cargo check -p app | `cargo check -p app` | `Finished dev profile [1 warning]` | PASS |
| sqlite.rs note dedup key uses COALESCE(author) | `grep "COALESCE.*author" crates/service/src/db/sqlite.rs` | 2 matches in upsert_notes_for_card | PASS |
| btn-zone passive TouchArea in card.slint | `grep "btn-zone" crates/app/ui/card.slint` | 4 matches (declaration + OR condition + stride uses) | PASS |
| Hover flicker OR condition | `grep "has-hover || btn-zone.has-hover" crates/app/ui/card.slint` | 1 match | PASS |
| 58px stride in card.slint | `grep "58px" crates/app/ui/card.slint` | 9 matches | PASS |
| sync thread diagnostics | `grep "sync_thread" crates/app/src/live_client.rs` | 3 eprintln! matches | PASS |
| D-10 post-sync sidebar refresh | `grep "get_recipient_detail_visible" crates/app/src/main.rs` | match in sync callback | PASS |
| Purpose popup in recipient-detail.slint | `grep "purpose-dropdown := PopupWindow" crates/app/ui/recipient-detail.slint` | 1 match | PASS |
| workspace tests | `cargo test --workspace` | 1 pre-existing failure (`dashboard_layout_grid_baseline_defines_three_column_shell`) | WARN (pre-existing) |

### Requirements Coverage

The phase uses defect IDs (D-01..D-14) sourced from the 2026-04-15 UAT CONTEXT.md, not from REQUIREMENTS.md standard IDs. REQUIREMENTS.md does not contain D-series IDs — they are phase-internal defect trackers. No orphaned REQUIREMENTS.md IDs map to this phase.

| Defect | Plan | Description | Status |
|--------|------|-------------|--------|
| D-01 | 02 | Purpose pill dropdown (PopupWindow) | VERIFIED |
| D-02 | 02 | Pill position + hollow border | VERIFIED |
| D-03 | 04 | Tile-click navigates to filtered cards + sidebar | VERIFIED |
| D-04 | 04 | Tab-restore preserves sidebar | VERIFIED (side-effect of D-03) |
| D-05 | 02 | Esc does not close sidebar | VERIFIED |
| D-06 | 04 | Breadcrumb-back dismisses sidebar | VERIFIED |
| D-07 | 03 | Note dedup by author+content | VERIFIED |
| D-08 | 01 | Product add button hover flicker | VERIFIED (code) + human UAT |
| D-09 | 01 | Product add button vertical alignment | VERIFIED |
| D-10 | 04 | Stale Rx after sync | VERIFIED |
| D-11 | 01 | Product icon + SN label scaling | VERIFIED |
| D-12 | 05 | Return status "Return Underway" | NEEDS HUMAN |
| D-13 | 05 | item_display_label shows count | VERIFIED |
| D-14 | 05 | Missing card + Shopify sync | NEEDS HUMAN |

### Anti-Patterns Found

| File | Line | Pattern | Severity | Impact |
|------|------|---------|----------|--------|
| `crates/app/tests/dashboard_layout_tests.rs` | 9 | Test asserts `"/ 3"` literal but dashboard.slint uses `/ card-cols` variable | ⚠️ Warning | Pre-existing failure from earlier phase (confirmed via git history: present before commit df26eb9). Not introduced by this phase. 1 of 2 workspace tests fails. |

### Human Verification Required

#### 1. D-12 — Return Status: "Return Underway" for tracked returns

**Test:** Build and run the app (`cargo run`). Open the dashboard. Find a card that shows "Return Created" and has a Shopify return with a tracking number. Trigger a sync (F5 or wait for the 5-minute auto-poll).
**Expected:** After sync completes, the card status updates from "Return Created" to "Return Underway".
**Why human:** `sync_return_states` correctly calls `reverse_fulfillment_tracking` and transitions the status — but the transition only fires against live Shopify data. Cannot be verified without a running app with live Shopify credentials.

#### 2. D-14 — Shopify Sync Connectivity + Missing Card

**Test:** Build and run: `cargo run`. Watch stderr for `[sync_thread] Starting sync cycle 1`. After 30 seconds, check connection status in the app.
**Expected:** Stderr shows `[sync_thread] Starting sync cycle 1 (shopify_token=true)` followed by `[sync_thread] Sync cycle 1 result: ok=true`. Connection status changes from "Connecting..." to "Connected" or "Connected (no Shopify)". Previously-missing card appears after first sync completes.
**Why human:** Diagnostic instrumentation is in place but actual connectivity depends on live Shopify API token validity and network. Cannot verify without runtime.

#### 3. D-01/D-02 — Purpose Pill Visual Position and Dropdown

**Test:** Run the app. Open any recipient's sidebar. Observe the purpose pill position and click it.
**Expected:** Pill appears to the right of the avatar, just below the recipient name, with a 2px hollow outline (no filled background). Clicking opens a dropdown with GH Project role options. Selecting a role saves and closes the dropdown.
**Why human:** PopupWindow relative positioning and transparent-border styling require visual confirmation. The Slint code compiles and contains the correct structure.

#### 4. D-08 — Product Add Button Hover Flicker

**Test:** Run the app. Navigate to any card with products. Slowly move the cursor from the card body onto the + button and back, repeatedly.
**Expected:** The + button does not disappear and reappear (flicker) as the cursor moves between the card hover zone and the button.
**Why human:** Hover interaction behavior cannot be confirmed from static code analysis.

### Gaps Summary

No blocking gaps found. All 14 defects have code implementations in place. Two defects (D-12, D-14) require live Shopify connectivity to confirm end-to-end behavior — both have diagnostics added that will make any runtime failures immediately visible in stderr.

**Pre-existing test failure:** `dashboard_layout_grid_baseline_defines_three_column_shell` asserts `"/ 3"` as a literal substring of dashboard.slint. This literal was replaced with `/ card-cols` in a prior phase before 20.1.1.1.1. The SUMMARY for Plan 05 explicitly noted this as pre-existing. The test should be updated to assert `"/ card-cols"` but this is not a regression introduced by this phase.

---

_Verified: 2026-04-15T12:00:00Z_
_Verifier: Claude (gsd-verifier)_
