---
phase: 09-item-note-crud-wiring
plan: 02
subsystem: ui
tags: [slint, rust, ureq, shopify, modal, crud, item-lookup]

requires:
  - phase: 09-item-note-crud-wiring
    plan: 01
    provides: PendingEditQueue, CardData extensions (package-id, active-item-id), on_card_add_item stub, lookup-modal.slint stub

provides:
  - LookupModal component with search input, results list, and Create New form
  - on_card_add_item wired to open lookup modal with target card context
  - on_lookup_search_changed wired to client.search_item_catalog with NoopClient seed data
  - on_lookup_item_selected dispatches EditCommand::AddItem with optimistic card update
  - on_lookup_create_confirmed dispatches AddItem with synchronous Shopify image fetch
  - on_lookup_close resets modal state
  - fetch_shopify_product_image helper using ureq and SHOPIFY_ACCESS_TOKEN env var
  - NoopClient seeded with 4 catalog entries for development testability

affects:
  - 12-production-data-client (real search_item_catalog implementation)

tech-stack:
  added: [ureq 2 with json feature]
  patterns:
    - Synchronous Shopify API call on UI thread (Rc not Send; background thread requires Arc refactor)
    - LookupResultEntry struct defined in lookup-modal.slint, imported by dashboard.slint
    - Modal open/close via lookup-modal-open bool property on DashboardWindow
    - NoopClient extended with seed catalog for development testability

key-files:
  created:
    - crates/app/ui/lookup-modal.slint
  modified:
    - crates/app/Cargo.toml
    - crates/app/ui/dashboard.slint
    - crates/app/src/main.rs

key-decisions:
  - "Shopify image fetch kept synchronous on UI thread because Rc<RefCell<>> is not Send; background threading requires Arc<Mutex<>> refactor (deferred to production client phase)"
  - "ureq json feature required explicitly (ureq = { version = \"2\", features = [\"json\"] }) for .into_json() method"
  - "LookupModal uses full-overlay backdrop Rectangle (not PopupWindow) since PopupWindow lacks semi-transparent backdrop dimming"
  - "Search results empty string guard: if query is empty, clear lookup-results immediately without calling search_item_catalog"

patterns-established:
  - "Modal z-order: LookupModal declared last in dashboard.slint to render above all other content"
  - "FocusScope auto-focused via init callback in modal component for immediate Escape key capture"

requirements-completed: [ITEM-01]

duration: 35min
completed: 2026-03-19
---

# Phase 9 Plan 02: Item/Note CRUD Wiring - Lookup Modal Summary

**Shipment Product Lookup modal with catalog search, Create New form, and synchronous Shopify image auto-fetch via ureq wired end-to-end to EditDispatcher**

## Performance

- **Duration:** ~35 min
- **Started:** 2026-03-19T21:32:22Z
- **Completed:** 2026-03-19T22:20:00Z
- **Tasks:** 2
- **Files modified:** 4

## Accomplishments

- LookupModal component fully built: semi-transparent backdrop, centered 480x520 panel, search input with placeholder, scrollable results list with "Create New" row, Create New form with inline validation, Shopify URL field with fetch status display
- on_card_add_item opens modal with target card name; on_lookup_close resets all modal state
- on_lookup_search_changed calls search_item_catalog, converts results to LookupResultEntry model
- on_lookup_item_selected: optimistic item_summary/image_hint update, dispatches EditCommand::AddItem, closes modal
- on_lookup_create_confirmed: synchronous Shopify image fetch via ureq, then dispatch with fetched image_hint
- NoopClient extended with 4 seed catalog entries for development testing (Wireless Earbuds, Laptop Stand, Mug Set, Phone Case)
- fetch_shopify_product_image reads SHOPIFY_ACCESS_TOKEN + SHOPIFY_SHOP_DOMAIN from env, GET /admin/api/2024-01/products/{id}.json
- All workspace tests pass (cargo test --workspace)

## Task Commits

All tasks committed together with Plan 01 in the session:

1. **Tasks 1 + 2: Lookup modal UI and callback wiring** - `3eca5b2` (feat)
2. **Warning fix: _item_id suppression** - `3942e7f` (fix)

## Files Created/Modified

- `crates/app/ui/lookup-modal.slint` - LookupResultEntry struct + LookupModal component (424 lines)
- `crates/app/Cargo.toml` - Added ureq 2 with json feature
- `crates/app/ui/dashboard.slint` - lookup-modal-open, lookup-target-card-index/name, lookup-results, shopify-fetch-in-progress, shopify-fetched-image-url properties; lookup callbacks; LookupModal instance
- `crates/app/src/main.rs` - fetch_shopify_product_image helper; on_card_add_item, on_lookup_search_changed, on_lookup_item_selected, on_lookup_create_confirmed, on_lookup_close callbacks; NoopClient seed catalog

## Decisions Made

- Shopify image fetch runs synchronously on the UI thread because `Rc<RefCell<>>` (used throughout main.rs callbacks) does not implement `Send`. `std::thread::spawn` requires `Send + 'static` closures, and `slint::invoke_from_event_loop` has the same requirement. For a low-frequency operation in a dev tool this is acceptable; upgrading to `Arc<Mutex<>>` is deferred to the production client phase.
- ureq JSON feature must be explicitly opted in: `ureq = { version = "2", features = ["json"] }`. Without it, `.into_json()` does not exist.
- LookupModal uses a full-overlay Rectangle as backdrop (not a Slint PopupWindow) because PopupWindow does not support semi-transparent dimming of the underlying content.

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 1 - Bug] Removed invalid `in property <bool> visible` redeclaration in LookupModal**
- **Found during:** Task 1 (Slint compile verification)
- **Issue:** Declaring `in property <bool> visible: false;` in a component body causes a Slint compile error "Cannot override property 'visible'" because `visible` is a built-in Slint property on all elements
- **Fix:** Removed the property declaration; parent controls visibility via `visible: root.lookup-modal-open` on the LookupModal instance in dashboard.slint
- **Files modified:** `crates/app/ui/lookup-modal.slint`
- **Verification:** `cargo build -p app` succeeds
- **Committed in:** `3eca5b2`

**2. [Rule 1 - Bug] Changed background thread Shopify fetch to synchronous**
- **Found during:** Task 2 (Rust compile verification)
- **Issue:** Plan specified `std::thread::spawn` + `slint::invoke_from_event_loop` for Shopify image fetch, but both require `Send + 'static` closures. All shared state in main.rs uses `Rc<RefCell<>>` which is not `Send`. Compile error E0277.
- **Fix:** Replaced background thread with synchronous fetch: `w.set_shopify_fetch_in_progress(true)`, then synchronous `fetch_shopify_product_image()` call, then `w.set_shopify_fetch_in_progress(false)`.
- **Files modified:** `crates/app/src/main.rs`
- **Verification:** `cargo build -p app` succeeds, all tests pass
- **Committed in:** `3eca5b2`

**3. [Rule 2 - Missing Critical] Added ureq json feature flag**
- **Found during:** Task 2 (Rust compile verification)
- **Issue:** `ureq = "2"` does not enable `.into_json()` by default; the method does not exist without the `json` feature. Compile error: method not found.
- **Fix:** Changed to `ureq = { version = "2", features = ["json"] }` in Cargo.toml
- **Files modified:** `crates/app/Cargo.toml`
- **Verification:** `cargo build -p app` succeeds
- **Committed in:** `3eca5b2`

---

**Total deviations:** 3 auto-fixed (1 Rule 1 - Bug, 1 Rule 1 - Bug, 1 Rule 2 - Missing Critical)
**Impact on plan:** All fixes necessary for correct compilation. Synchronous Shopify fetch is a pragmatic deviation; background threading is deferred to production client phase.

## Issues Encountered

- The `item_id` parameter in `on_lookup_item_selected` was unused (item is identified via card_data, not the lookup result's item_id). Fixed by prefixing with underscore (`_item_id`) to suppress the compiler warning.

## User Setup Required

To enable Shopify image auto-fetch during Create New flow, set environment variables:
- `SHOPIFY_ACCESS_TOKEN` - Shopify Admin API access token
- `SHOPIFY_SHOP_DOMAIN` - Shop domain (e.g., `mystore.myshopify.com`)

Without these env vars, image fetch silently returns None and the item is added without an image.

## Next Phase Readiness

- Phase 9 item/note CRUD wiring is complete; all card mutation callbacks are wired to EditDispatcher
- PendingEditQueue accumulates failed edits; Phase 10 or Phase 12 can implement drain-on-startup retry
- NoopClient returns empty data; Phase 12 (production-data-client) will replace with real API calls
- Shopify synchronous fetch is a known technical debt item; production client phase can revisit with Arc refactor

---
*Phase: 09-item-note-crud-wiring*
*Completed: 2026-03-19*
