---
status: resolved
trigger: "Serial units' state not syncing properly to the card state. All serial units on existing cards are in 'Delivered' state, even though 3 of them should be 'Return Underway'"
created: 2026-04-16
updated: 2026-04-16
resolved: 2026-04-16
---

# Debug Session: unit-state-card-sync

## Symptoms

- **Expected:** Every unit on a card should reflect the card's current pill state (e.g., if card pill says "Return Underway", its units should also be "Return Underway")
- **Actual:** All serial units show as "Delivered" even on cards whose pill reads "Return Underway"
- **Error messages:** None observed
- **Timeline:** Noticed after recent bugfixes. Return states are relatively new feature. The entire sync flow from unit state to card state needs review.
- **Reproduction:** Open app, look at cards with "Return Underway" pill — their units still show "Delivered". Clicking Refresh does not fix it.

## Current Focus

- hypothesis: CONFIRMED - sync_return_states() receives cards with empty product_refs
- test: Will read cards from SQLite before calling sync_return_states
- expecting: Unit states will transition correctly
- next_action: apply fix
- reasoning_checkpoint: Root cause identified

## Evidence

### E-01: product_refs initialized as empty during sync
`crates/app/src/live_client.rs:1170-1194` — matched cards are created with `product_refs: vec![]`
`crates/app/src/live_client.rs:1202-1226` — unassigned cards created with `product_refs: vec![]`

### E-02: preservation logic only updates CardRow, not snapshot
`crates/app/src/live_client.rs:1242-1263` — preservation logic merges existing product_refs from database INTO the CardRow that gets written to SQLite, but does NOT update the RecipientCardSnapshot that was created earlier with empty product_refs.

### E-03: sync_return_states receives stale snapshots
`crates/app/src/live_client.rs:1383-1384` — sync_return_states is called with the original `matched` and `unassigned` vectors, which still have empty product_refs from their creation.

### E-04: empty product_refs means no units found
`crates/app/src/live_client.rs:942-946` — units_for_card is built by iterating `card.product_refs`, extracting serial_ids, and reading units from SQLite. Since product_refs is empty, units_for_card is always empty, so unit state updates never execute.

## Eliminated

- ✓ determine_transition logic — correct
- ✓ update_unit_state persistence — would work if called
- ✓ serial_id field structure — ProductRef has optional serial_id field

## Resolution

- root_cause: sync_return_states() received card snapshots with empty product_refs vectors. The preservation logic that merges existing product_refs from SQLite (lines 1242-1263) updates the CardRow that gets written to the database, but does NOT update the in-memory RecipientCardSnapshot that gets passed to sync_return_states(). Since product_refs was empty, the iteration over units (line 942) found nothing, so unit state transitions were never executed.

- fix: Read cards back from SQLite after upserts and before calling sync_return_states(). The fresh snapshots built from SQLite CardRows have product_refs properly populated with serial_ids from locally-assigned units. This allows sync_return_states to find the units and execute state transitions.

- verification: Build and test return state sync with assigned serial units. Confirm unit states transition from "Delivered" -> "Return Created" -> "Return Underway" -> "Returned" based on Shopify return tracking data.

- files_changed:
  - crates/app/src/live_client.rs:1379-1409 — Read cards from SQLite before sync_return_states calls
