---
status: resolved
trigger: "cards-disappear-on-tab-switch"
created: 2026-04-06T00:00:00Z
updated: 2026-04-06T12:30:00Z
---

## Current Focus
<!-- OVERWRITE on each update - reflects NOW -->

hypothesis: CONFIRMED — settings-save on_sync callback missing invoke_sync_cards_updated left all_cards_ref permanently empty.
test: fix applied (2 lines added), build passes, regression test with BUGSWEEPER shows all tabs work correctly with 6 cards.
expecting: user confirms cards persist across tab switches after initial setup
next_action: await human verification

## Symptoms
<!-- Written during gathering, then IMMUTABLE -->

expected: Cards should persist across tab switches. Switching sort/filter tabs should re-sort or re-filter the same cards, not lose them.
actual: 6 cards visible on initial load under "Status Updated" tab. After clicking "Ship Date" tab, all cards vanish. They do not reappear when switching back.
errors: No terminal — the app was launched directly (not from a terminal), so no console output is visible. No visible error in the UI.
reproduction: Launch app → configure settings for FIRST TIME → see 6 cards → click "Ship Date" tab → cards gone
started: Discovered just now on the latest build.

## Eliminated
<!-- APPEND only - prevents re-investigating -->

- hypothesis: Slint UI visibility condition based on active-mode-index hides cards
  evidence: dashboard.slint card-flickable is visible: !root.show-option-grid, no mode-index condition. card.slint uses active-mode-index only for display text, not visibility.
  timestamp: 2026-04-06

- hypothesis: sort_cards_by_mode for ByShipDate removes cards
  evidence: sort only reorders, never removes. status_date_inline = Some("") for empty dates still compares fine.
  timestamp: 2026-04-06

- hypothesis: restore_mode_state for ByShipDate sets show_option_grid=true hiding cards
  evidence: uses_option_grid = false for ByShipDate, the OptionGrid mutation block is skipped. apply_filters falls to _ branch which sets show_option_grid=false.
  timestamp: 2026-04-06

- hypothesis: RefCell borrow conflict causes panic
  evidence: all borrows in on_tab_clicked are sequential temporaries. No nested borrows of same Rc<RefCell>.
  timestamp: 2026-04-06

- hypothesis: SQLite read_all_cards fails due to threading issue
  evidence: SqliteStore uses Mutex<Connection>, all accesses lock/unlock atomically. No deadlock scenario visible.
  timestamp: 2026-04-06

- hypothesis: filter_cards_by_status with empty set filters all cards
  evidence: empty set → returns all cards unchanged (early return).
  timestamp: 2026-04-06

- hypothesis: on_sync_cards_updated reads 0 during a timing window
  evidence: BUGSWEEPER tests show correct behavior; invoke_from_event_loop means sync callback runs after event loop starts, all callbacks wired; update_cards_in_place then invoke_sync_cards_updated are sequential on event loop.
  timestamp: 2026-04-06

- hypothesis: apply_filters filtered_cards lookup fails due to recipient_name+status_pill mismatch
  evidence: filter_models built from all_cards, lookups use same key derivation. BUGSWEEPER test with 6 Delivered cards shows 6/6 filtered correctly.
  timestamp: 2026-04-06

## Evidence
<!-- APPEND only - facts discovered -->

- timestamp: 2026-04-06
  checked: tab-strip.slint, dashboard.slint, tab-clicked callback wiring
  found: tab-clicked(int) fires from Slint → on_tab_clicked in Rust. on_tab_clicked calls set_mode_by_index(index) then restore_mode_state which calls apply_filters.
  implication: tab switch triggers apply_filters with current all_cards_ref

- timestamp: 2026-04-06
  checked: apply_filters function (main.rs:712)
  found: builds filter_models from all_cards, filters by search/status/purpose/archive, sorts, then maps back to CardData via recipient_name+status_pill lookup. Falls to _ branch for ByShipDate (not option grid mode).
  implication: if all_cards_ref is non-empty and no filters active, returns all non-archived cards

- timestamp: 2026-04-06
  checked: on_sync_cards_updated callback (main.rs:2110)
  found: reads w.get_cards() (DISPLAYED model, not all_cards) to rebuild all_cards_ref. If displayed model has 0 cards, all_cards_ref becomes empty.
  implication: this is the mechanism — but WHEN does it show 0 to read?

- timestamp: 2026-04-06
  checked: update_cards_in_place and on_sync_complete callback
  found: on_sync_complete calls update_cards_in_place THEN invoke_sync_cards_updated. update_cards_in_place sets w.cards to new synced cards. on_sync_cards_updated then reads w.cards (now synced data).
  implication: for the MAIN sync path, this works correctly.

- timestamp: 2026-04-06
  checked: BUGSWEEPER live testing — tab switches, concurrent refresh
  found: with existing SQLite cache and existing config, all tab switches work correctly. all_cards_ref stays populated.
  implication: bug only occurs in INITIAL SETUP scenario (no existing config/cache).

- timestamp: 2026-04-06
  checked: on_settings_save_clicked callback (main.rs:2321) and on_settings_shopify_clear_clicked (main.rs:2220)
  found: CRITICAL — both callbacks create a new on_sync callback that calls update_cards_in_place but does NOT call invoke_sync_cards_updated. The all_cards_ref is never updated after settings-save sync. When sync completes, w.cards has 6 synced cards (user sees them), but all_cards_ref = [] (empty from startup).
  implication: ROOT CAUSE FOUND. Any tab switch after initial setup calls apply_filters with all_cards_ref=[] → 0 cards shown → permanent empty state.

- timestamp: 2026-04-06
  checked: fix applied — added w2.invoke_sync_cards_updated() after update_cards_in_place in both settings-save and clear-shopify-token on_sync callbacks
  found: cargo build passes. BUGSWEEPER regression test: all 4 tabs switch correctly with 6 cards each, apply_filters logs show all_cards=6 throughout.
  implication: fix is correct and doesn't break existing functionality.

## Resolution
<!-- OVERWRITE as understanding evolves -->

root_cause: on_settings_save_clicked and on_settings_shopify_clear_clicked create new LiveClient instances with on_sync callbacks that call update_cards_in_place(w, new_cards) but never call w.invoke_sync_cards_updated(). This means all_cards_ref is never updated after these sync paths complete. When the first sync after settings-save finishes, w.cards gets populated (user sees cards) but all_cards_ref stays empty. The first tab switch then calls apply_filters with all_cards_ref=[] producing 0 filtered cards, which are set to w.cards. State is now permanently empty — all subsequent tab switches also show 0 cards.
fix: Added w2.invoke_sync_cards_updated() after update_cards_in_place in both the settings-save on_sync callback (main.rs ~line 2337) and the clear-shopify-token on_sync callback (main.rs ~line 2235). These two lines bring both secondary sync paths to parity with the primary sync path which already had this call.
verification: cargo build clean. BUGSWEEPER test: all 4 tabs switch correctly, 6 cards shown throughout. apply_filters logs confirm all_cards=6 on every tab switch.
files_changed: [crates/app/src/main.rs]
