# Phase 19.1: LookupModal Dual-Mode — Parent Product Adds and Serial Unit Picks - Context

**Gathered:** 2026-04-07
**Status:** Ready for planning

<domain>
## Phase Boundary

Unify the product lookup modal so it serves as a single entry point for both adding parent products to cards and picking specific serial units. Replace the flat product list in LookupModal with a hierarchical product-to-unit tree. Remove the now-redundant ProductAddToCardPicker component.

</domain>

<decisions>
## Implementation Decisions

### Unification Strategy
- **D-01:** Replace LookupModal's flat `LookupResultEntry` list with a hierarchical tree showing products with collapsible serial units underneath (same structure as current ProductAddToCardPicker).
- **D-02:** ProductAddToCardPicker (`product-picker.slint`) becomes redundant and is removed. All its functionality (Available/Assigned sections, reassignment prompts, state badges, unit creation) moves into LookupModal.
- **D-03:** LookupModal keeps its existing modal shell (backdrop, search input, close button, Esc handling). Only the results area is replaced with the hierarchical tree.

### Interaction Model
- **D-04:** Clicking a parent product row expands/collapses its unit list. It does NOT add the product to the card.
- **D-05:** Each parent product row has a right-aligned [+] button that adds the parent product to the card.
- **D-06:** Clicking a unit row does nothing (display only).
- **D-07:** Each unit row has a right-aligned [+] button that adds that specific unit to the card.
- **D-08:** Products default to collapsed state (consistent with Phase 19 D-22).

### Search Behavior
- **D-09:** Fuzzy search matches both product names and serial numbers simultaneously.
- **D-10:** Products whose name matches the search query are shown. Products with matching units are also shown (forced visible even if the parent name doesn't match).
- **D-11:** Parents with matching units display a match count pill (e.g., "2 matches"). Parents that match only by name show no pill.
- **D-12:** Matching parents are NOT auto-expanded. The user manually expands to see matching units.
- **D-13:** When a parent is expanded, only units matching the current search query are shown (not all units). When search is cleared, all units are shown on expand.
- **D-14:** Non-matching products (no name match AND no unit matches) are hidden entirely.

### Create New Unit Flow
- **D-15:** When a product is expanded, a "Create New Unit" button appears at the bottom of the unit list (below Available and Assigned sections).
- **D-16:** Clicking "Create New Unit" inserts an inline textbox above the button for serial number entry. The textbox is immediately focused.
- **D-17:** Enter key or clicking the neighboring "OK" button creates the new serial unit inline under that product.
- **D-18:** The button text is "Create New Unit" (not "+") to avoid confusion with the [+] add-to-card buttons on product/unit rows.

### Create New Product Flow
- **D-19:** "Create New Product" row remains at the bottom of the entire product list (below all products), preserving the existing page-switch to the create form with name + Shopify URL inputs.

### Reassignment Handling
- **D-20:** When a unit's [+] button is clicked and the unit is already assigned to another card, show the inline reassignment prompt (carried forward from Phase 19 D-17). Prompt: "{serial_id} is assigned to {name}. Move it to this card?"
- **D-21:** Available/Assigned section split with state badges on each unit row (carried forward from Phase 19 D-21).

### Claude's Discretion
- Migration strategy for Rust callback wiring (adapting from two component callback sets to one)
- Exact highlight style for search-matched units
- Match count pill styling and positioning
- Whether to keep `PickerProductData`/`PickerUnitData` structs as-is or merge with `LookupResultEntry`

### UI Design Contract
- Run `/gsd:ui-phase 19.1` before or during planning to generate a UI-SPEC.md design contract for this phase.

</decisions>

<canonical_refs>
## Canonical References

**Downstream agents MUST read these before planning or implementing.**

### Data Architecture
- `.planning/DATA-FLOW.md` -- Authoritative data entity reference, sync directions, agent rules

### Prior Phase Context
- `.planning/phases/19-serial-instance-tracking-and-start-return/19-CONTEXT.md` -- Serial lifecycle state machine, product picker design (D-20 through D-22), single-assignment enforcement (D-16/D-17), Available/Assigned sections (D-21)
- `.planning/phases/17-gh-issues-client-and-product-catalog/17-CONTEXT.md` -- Product entity model, product picker origin

### Seed
- `.planning/seeds/SEED-003-lookup-modal-nested-units.md` -- Original idea: nested serial units in lookup modal + SN fuzzy search

### Code Tips
- `code_tips/` -- SQLite gotchas, Slint quirks (must read before modifying code)

### Existing UI Components (to be modified/removed)
- `crates/app/ui/lookup-modal.slint` -- Current LookupModal: flat product list, search, Create New form (to be reworked)
- `crates/app/ui/product-picker.slint` -- Current ProductAddToCardPicker: hierarchical tree with units, reassignment (to be removed after merging into LookupModal)
- `crates/app/ui/dashboard.slint` -- LookupResultEntry struct, modal visibility bindings

### Existing Data Layer
- `crates/service/src/db/sqlite.rs` -- read_all_products(), read_units_by_product(), ProductUnitRow
- `crates/app/src/dashboard/discovery.rs` -- Fuzzy search infrastructure

</canonical_refs>

<code_context>
## Existing Code Insights

### Reusable Assets
- `PickerProductData` / `PickerUnitData` structs in `product-picker.slint`: hierarchical product-unit model with state, selectable, sections -- can be migrated into LookupModal
- `LookupModal` shell: backdrop, centered panel, search input with focus management, Esc handling, Create New form page -- all reusable
- Reassignment prompt UI in ProductAddToCardPicker: inline warning with "Move it" / "Cancel" buttons
- `expanded-product-id` single-expand pattern (Pitfall 4 from Phase 19): only one product expanded at a time

### Established Patterns
- Modal shell pattern: backdrop TouchArea + centered Rectangle + FocusScope for Esc (used by LookupModal, AddProductForm, StateTransitionModal)
- Collapsible tree: `expanded-product-id` string comparison for conditional unit rendering
- `toggle-product-expanded` callback for expand/collapse state management
- `search-focus-trigger` counter pattern for reliable TextInput focus on modal open

### Integration Points
- `on_lookup_search_changed` in main.rs: currently queries `read_all_products()` only -- must add serial unit search
- `item-selected` callback: currently fires (item_id, display_name, image_hint) for parent products -- needs unit-selected path
- `on-show-product-picker` callback in main.rs: populates picker data from SQLite -- this logic moves to LookupModal population
- Product picker removal: all dashboard.slint bindings referencing ProductAddToCardPicker must be redirected to LookupModal

</code_context>

<specifics>
## Specific Ideas

- Product row click is purely expand/collapse -- the [+] button is the action for adding to card
- Unit row click does nothing -- the [+] button is the action for adding the unit
- Match count pill on parents (not auto-expand) keeps the user in control of what they see
- "Create New Unit" as text button (not "+") to disambiguate from add-to-card [+] buttons
- Inline SN textbox with auto-focus when creating a new unit -- minimal friction
- Parent forced visible in search results when its units match, even if parent name itself doesn't match

</specifics>

<deferred>
## Deferred Ideas

- **Centralized modular search modal with shared UX patterns** (todo) -- broader unification of all search/lookup modals across the app. Phase 19.1 could be a foundation for this but the full vision is larger scope.
- **Product unit ownership audit trail** (todo) -- GH Issue timeline comments for assignment events
- **Serial unit search box seeds new unit SN field** (todo) -- when search text matches no existing unit, pre-fill the create-unit SN input with the search text. Could be folded into D-16 implementation as a natural enhancement.

### Reviewed Todos (not folded)
- "Fix product sidecar stays visible when switching away from product tab" -- UI bug, separate concern
- "Serial unit search box seeds new unit SN field" -- related but user did not fold into scope
- "Centralized modular search modal with shared UX patterns" -- broader initiative, not this phase
- "Product unit ownership audit trail via GH issue timeline comments" -- integrations concern, separate
- "Add Lists feature with viewing mode and card management" -- Phase 21 scope
- "Unassigned card view mode with conditional amber tab" -- separate feature
- "Chip toggle on-off breaks Product Shipped option grid" -- UI bug, separate

</deferred>

---

*Phase: 19.1-lookupmodal-dual-mode-parent-product-adds-and-serial-unit-picks*
*Context gathered: 2026-04-07*
