---
status: resolved
trigger: "Products don't seem to sync on first run of the app. Cards appear, but products do not."
created: 2026-04-06T00:00:00Z
updated: 2026-04-06T00:02:00Z
---

## Current Focus
<!-- OVERWRITE on each update - reflects NOW -->

hypothesis: CONFIRMED AND FIXED
test: Build succeeded; logic traced through all paths
expecting: On first run, after settings save, product grid populates after sync completes
next_action: Human verification on a real first-run scenario

## Symptoms
<!-- Written during gathering, then IMMUTABLE -->

expected: After first-run setup and initial sync, both cards and products should be populated. Products are sourced from GH Issues labeled ww-product in the BigscreenVR/beyond-outgoing repo.
actual: On first run, 6 cards appear but no products are visible. User suspects products aren't syncing at all on first run.
errors: No visible errors (app launched without terminal).
reproduction: First run of the app after setup — cards sync but products don't appear.
started: Discovered just now on latest build. May have always been broken on first run.

## Eliminated
<!-- APPEND only - prevents re-investigating -->

- hypothesis: Product sync is missing from run_sync_cycle on first-run path
  evidence: run_sync_cycle (live_client.rs:849-873) always calls sync_products via gh_issues_client when available; the sync cycle itself is correct
  timestamp: 2026-04-06T00:01:00Z

## Evidence
<!-- APPEND only - facts discovered -->

- timestamp: 2026-04-06T00:01:00Z
  checked: main.rs:1941 — rx_write_handle initialization
  found: rx_write_handle starts as None; set to Some(...) only when app_config is Some (line 1991)
  implication: On first run (no config), rx_write_handle stays None

- timestamp: 2026-04-06T00:01:00Z
  checked: main.rs:2109 — on_sync_cards_updated closure capture
  found: let rx_handle_sync = rx_write_handle.clone() captures value at wire time before on_settings_save_clicked runs
  implication: rx_handle_sync is permanently None in on_sync_cards_updated for first-run flow

- timestamp: 2026-04-06T00:01:00Z
  checked: main.rs:2143 — refresh_product_grid call in on_sync_cards_updated
  found: refresh_product_grid(&w, &rx_handle_sync, ...) passes None rx_handle; refresh_product_grid returns early on None (line 1878)
  implication: Products are never loaded into the product grid after first-run sync

- timestamp: 2026-04-06T00:01:00Z
  checked: main.rs:2340 — on_settings_save_clicked creates new LiveClient
  found: let _live = LiveClient::new(new_config, on_sync) — creates client but never updates rx_write_handle
  implication: The on_settings_save_clicked sync callback (which calls invoke_sync_cards_updated) triggers on_sync_cards_updated with the still-None rx_handle_sync

## Resolution
<!-- OVERWRITE as understanding evolves -->

root_cause: On first run, rx_write_handle is None (no config). on_sync_cards_updated captures rx_write_handle by value (clone of None) at wire time on line 2109. When on_settings_save_clicked later creates a new LiveClient and triggers sync, on_sync_cards_updated fires with the stale None rx_handle_sync, causing refresh_product_grid to return early (line 1878: `let Some(store) = store else { return }`). Products synced to SQLite by the background sync thread are never reflected in the UI.
fix: Introduced product_grid_store: Rc<RefCell<Option<Arc<SqliteStore>>>> shared between on_sync_cards_updated (which calls refresh_product_grid using pg_store.borrow().as_ref()) and on_settings_save_clicked (which writes *pg_store_settings.borrow_mut() = Some(_live.store()) after creating the LiveClient). Also refactored refresh_product_grid to accept Option<&Arc<SqliteStore>> directly (dropping unused AppConfig param) and updated all 6 callers.
verification: cargo build succeeds cleanly
files_changed: [crates/app/src/main.rs]
