---
gsd_state_version: 1.0
milestone: v1.1
milestone_name: — Data Architecture & Product Catalog
status: Ready to plan
stopped_at: Phase 20.5 context gathered
last_updated: "2026-04-16T10:31:16.519Z"
last_activity: 2026-04-16
progress:
  total_phases: 18
  completed_phases: 13
  total_plans: 66
  completed_plans: 65
  percent: 98
---

# Project State

## Project Reference

See: .planning/PROJECT.md (updated 2026-03-22)

**Core value:** From dashboard load, the user can find any recipient and confirm shipment state in under 10 seconds.
**Current focus:** Phase 20.5 — Modal State Architectural Audit

## Current Position

Phase: 21
Plan: Not started

## Performance Metrics

**Velocity (v1.0 baseline):**

- Total plans completed: 83
- Average duration: ~24 min
- Total execution time: ~17 hours

## Accumulated Context

### Decisions

Recent decisions affecting v1.1 work:

- [Phase 13]: DATA-FLOW.md is the single source of truth for data architecture; all agents must read before touching data
- [Phase 13]: github_profile_url is PROHIBITED (RULE-01); Cards exist only from Shopify orders (RULE-04)
- [Phase 13]: GH Issues (ww-card, ww-product) are cloud of record (RULE-05); SQLite is single read source once implemented (RULE-03)
- [Phase 12.1]: keyring v3 with windows-native feature used for real WCM FFI
- [Phase 12.1.1]: Shopify-first pipeline: orders_by_tag('wit-what') as primary source; GH recipients as lookup table
- [Phase 14-ui-polish]: tokens.slint uses export global pattern (Colors and Typography) as Slint singleton
- [Phase 15]: Recipient struct is now identity-only — github_profile_url removed (RULE-01), shipment_status/tracking_state removed (RULE-02); shipment state lives on Package and RecipientCardSnapshot
- [Phase 15]: Customer matching is now discord-only; URL-based Shopify matching removed along with github_profile_url from RecipientIdentity
- [Phase 15-02]: NoteEntry in core crate (date+content); item_summary/latest_note/first_item_image_hint replaced with product_names: Vec<String> and notes: Vec<NoteEntry> across all structs
- [Phase 15-02]: item_squares model is now canonical source of item names in CardData; pipe-separated item_summary cache eliminated from Slint
- [Phase 15]: rusqlite 0.32 bundled + refinery 0.8 for SQLite with embedded SQL migrations
- [Phase 15]: SqliteStore uses single Arc<Mutex<Connection>> with WAL mode — no connection pool needed for 2-thread pattern
- [Phase 15]: LiveClient.fetch_card_snapshots() reads from SQLite (RULE-03); run_sync_cycle() writes matched cards to SQLite (PERSIST-01)
- [Phase 16]: purpose_color stored as derived hex string in SQLite recipients table for persistence; batch recipient join in fetch_card_snapshots avoids N+1 queries
- [Phase 16-03]: Rx edit state (editing-rx-od/os) on RecipientCard parent (not PopupWindow) to survive re-init; GH write-back uses free function with Arc<SqliteStore>+Arc<AppConfig> extracted before Rc-wrap
- [Phase 16-03]: copy_to_clipboard from dashboard::external used for Copy Rx; GH write-back fire-and-forget on background thread, failures logged to stderr
- [Phase 16.1]: Discord token entry gated by discord-token-changing flag (not NOT configured) to prevent TextInput showing on initial open before Add Token is clicked
- [Phase 16.1]: New CardData Slint fields (discord-username, avatar-image, last-activity-label, last-activity-products, recipient-id) default to empty in view_model_to_card_data(); full wiring in Plan 04
- [Phase 16.1]: Two-tier avatar storage: GitHub orphan branch as durable store, local disk as performance cache
- [Phase 16.1]: contact_secondary drops email param — email is popover-only as of Phase 16.1
- [Phase 16.1-03]: Pencil glyph \u{270F} renders in Slint — no ASCII fallback needed
- [Phase 16.1-03]: Inline edit affordance wraps in Rectangle (not HorizontalLayout) for safe parent.width reference in conditional elements
- [Phase 16.1-03]: Save/Discard has-changes and reset extended to include discord-username-draft alongside Rx and purpose drafts
- [Phase 16.1-04]: compute_last_activity maps "In Transit" -> "Shipped", "Return In Transit" -> "Returning"; "In Transit" as display label is FORBIDDEN
- [Phase 16.1-04]: on_settings_save_clicked preserves existing discord_bot_secret_ref/guild_id when saving GitHub/Shopify settings
- [Phase 16.1-04]: sync_avatars_from_branch uses read_all_recipients() not list_recipients(); runs on background thread at startup when discord configured
- [Phase 16.2-bugsweeper]: BugsweeperBackend as trait object (Arc<dyn BugsweeperBackend>) — app supplies impl, bugsweeper owns HTTP infra; avoids bugsweeper needing DashboardWindow type
- [Phase 16.2-bugsweeper]: query_ui<T> bridge: FnOnce() -> T + Send + 'static lets callers capture Weak<DashboardWindow> — bugsweeper stays UI-type-agnostic; tiny_http chosen for zero async overhead in debug-only crate
- [Phase 16.2-bugsweeper]: query_raw validates SQL at SqliteStore level with SELECT-only check and mutation keyword rejection
- [Phase 16.2-bugsweeper]: get_app_config exposes has_shopify_token and has_discord_token booleans; never exposes actual token values
- [Phase 16.2-bugsweeper]: ItemSquareJson mirrors full ItemSquareData (6 fields: item_id, display_name, initials, bg_color_index, has_image, image_url) not the 3-field condensed version in plan
- [Phase 16.2-bugsweeper]: router PUT /api/ui/property/{name} accepts both bare JSON value and {value: X} wrapper for curl ergonomics; POST /api/ui/callback parses {name, args} body
- [Phase 17-01]: GhIssuesClient reuses find_gh() from gh_cli_client; uses gh api --jq .id for numeric database_id (not GraphQL node_id)
- [Phase 17-01]: No unit_refs or parent_ref in body JSON or SQLite — GitHub Subissues REST API handles parent/child natively
- [Phase 17-01]: V004 migration adds github_issue_number INTEGER to products and serial_instances; all products hardcoded serializable
- [Phase 17]: Product initials placeholder uses ? — Slint lacks string substring/toUpperCase methods
- [Phase 17]: ProductDetailPanel overlays right side of product grid (shrinks grid width); AddProductForm uses centered modal with backdrop dismiss
- [Phase 17-02]: sync_products is non-fatal: errors logged but do not fail the overall sync cycle
- [Phase 17-02]: build_product_option_grid matches products to cards by name (case-insensitive) during transition period
- [Phase 17]: ProductRef uses serde JSON in TEXT column for atomic card upserts; product_names kept as display fallback
- [Phase 17-04]: Send-safe ProductTileSendData intermediate for cross-thread Slint data transfer (Image not Send)
- [Phase 17-04]: Product archive callback is logged no-op (full archive behavior deferred)
- [Phase 17-04]: rfd 0.15 for native Windows file picker; uuid v1 with v4 for product_id generation
- [Phase 17-06]: AddProductForm dimensions fixed at 480x520 to match LookupModal, not inheriting from parent
- [Phase 17]: product_detail_visible cleared on breadcrumb-back only; sidecar condition is !show-option-grid && product-detail-visible in filtered card view
- [Phase 17]: Tab persistence uses refresh_product_grid injection at restore_mode_state call sites — avoids signature mismatch between Rc<RefCell<>> and &[CardData] slice
- [Phase 17]: sync_products Shopify image: CDN URL stored directly as image_url (no branch re-upload) for simplicity; GH Issue body updated via edit_issue_body with known fields
- [Phase 17]: Stale product cleanup: only products with github_issue_number are eligible for deletion; locally-created products preserved
- [Phase 17-08]: creating-unit and creation-in-progress are separate flags: creating-unit controls input visibility, creation-in-progress disables + button during API call
- [Phase 17-08]: breadcrumb-back deselects unit first before going to OptionGrid; serial filtering uses item_square display_name contains check on current model
- [Phase 18]: github_issue_number stored as INTEGER (i64), not TEXT, consistent with products/serial_instances pattern; set_card_issue_number separate from upsert_card to prevent overwrite on sync
- [Phase 18]: epoch_secs_to_iso8601 implemented without chrono using Hinnant Gregorian algorithm — no new dependency for ISO 8601 timestamps in pending_edits.created_at
- [Phase 18]: GhIssuesClient wrapped in Arc once at LiveClient construction; all threads clone the Arc (not the client) for thread-safe sharing in save_note and PendingEditFlusher
- [Phase 18]: detect_card_changes snapshots old cards before the upsert loop in run_sync_cycle; sync_card_issues runs after detect_card_changes (Step 5c) to avoid double-detecting newly-created issues
- [Phase 18]: PendingEditFlusher sleeps 60s before first flush to avoid racing sync_card_issues creating issues on startup
- [Phase 18]: last_attempted_at stored as epoch seconds string for direct arithmetic in backoff comparison — TEXT column compatible
- [Phase 18]: PendingEditQueue JSON-file backed offline queue fully removed; SQLite pending_edits table is the sole queue
- [Phase 20.2-02]: read_units_by_card already exists in sqlite.rs — no new method needed for cascade (A3 resolved)
- [Phase 20.2-02]: cascade placed at Step 4c outside gh_issues_client block — cascade does not require GH Issues client to be present
- [Phase 20.2-sync-integrity-and-shipment-state-fixes]: D-10 image pipeline PASSING: 3/4 products have images; Testorama expected null (no Shopify URL)
- [Phase 20.2-sync-integrity-and-shipment-state-fixes]: Migration files must be LF on Windows: refinery checksums are line-ending-sensitive; CRLF causes DivergentVersion panic
- [Phase 20.1.1]: author: None for local-write NoteEntry sites; Some(gh_handle) wired by Plan 04 GH comment read-back
- [Phase 20.1.1]: read_notes ORDER BY id DESC (newest-first) applied to all three notes read paths
- [Phase 20.1.1]: upsert_notes_for_card diff-keyed on (card_id, note_date, content) — preserves synced_at per SQLITE_TIPS.md
- [Phase 20.1.1]: .gitattributes *.sql eol=lf added to prevent Windows autocrlf CRLF injection causing refinery DivergentVersion
- [Phase 20.1.1]: parse_comments_json extracted as pub(crate) associated fn on GhIssuesClient for subprocess-free unit testing
- [Phase 20.1.1]: list_issue_comments passes i64 issue_number via args[] (no shell interpolation) — T-2012-01 mitigated
- [Phase 20.1.1]: Colors.text-on-accent absent from tokens.slint; recipient-detail.slint uses #ffffff and Colors.avatar-text as substitutions (documented in file comments)
- [Phase 20.1.1]: RecipientDetailPanel Esc handler: cancels active inline-edit first, fires close-clicked only when no edit is open
- [Phase 20.1.1]: format_relative_time weeks bucket is < 30d; 30d boundary falls to months — test uses 28d
- [Phase 20.1.1]: enrich_view_with_notes placed in main.rs alongside enrich_view_with_unit_state, same pattern, same call sites
- [Phase 20.1.1]: NoteDisplayData struct placed in card.slint (not dashboard.slint) to avoid recursive import cycle
- [Phase 20.1.1]: composer-input.focus() cannot cross if-conditional branch boundaries in Slint; removed from collapsed branch
- [Phase 20.1.1]: note-draft property reused for composer (already on RecipientCard for Row 5 editor); Plan 06 removes Row 5
- [Phase 20.1.1]: fetch_notes_for_card placed in DashboardDataClient trait (not impl LiveClient) so Rc<dyn DashboardDataClient> callers need no downcast
- [Phase 20.1.1]: on_complete closure captures Arc<SqliteStore>+Weak<Window> only (not Rc<RefCell<>>) — patches Slint model in-place from SQLite
- [Phase 20.1.1]: note-draft kept (not deleted) — Plan 04 reused the single existing declaration for the composer; removing the Row 5 block leaves the declaration intact
- [Phase 20.1.1]: Row 4 item squares grown to 80px row height / 44x44 inner square after Row 5 removal freed 24px vertical budget
- [Phase 20.1.1]: summary-popup deleted entirely; sidebar owns all recipient editing state (D-16)
- [Phase 20.1.1]: populate_recipient_sidebar writes only in data props, never in-out drafts (T-2018-04)
- [Phase 20.1.1]: ByRecipient tile-click opens sidebar without navigating to filtered card view (D-19/Pitfall 8)
- [Phase 20.1.1]: card-name-navigate is a no-op stub — Plan 09 implements cross-tab navigation
- [Phase 20.1.1]: on_card_name_navigate: set_mode_by_index(2) + select_tile before invoke_tab_clicked(2) ensures runtime state is consistent with tab restore; reuses populate_recipient_sidebar from Plan 08
- [Phase 20.1.1.1]: card-hover-zone as first child: passive TouchArea for whole-card hover detection without stealing clicks from later siblings
- [Phase 20.1.1.1]: VerticalLayout inside clipped Rectangle (not Flickable) for content-driven notes rows — avoids SLINT_TIPS bottom-align bug
- [Phase 20.1.1.1]: D-08: close-clicked() callback retained (not deleted) — FocusScope Esc handler still calls it for two-level Esc dismissal via dashboard.slint forwarding chain
- [Phase 20.1.1.1]: D-09: Purpose pill text uses #ffffff (Colors.text-on-accent absent from tokens.slint); edit mode uses 180px fixed width for stable TextInput layout
- [Phase 20.1.1.1]: D-10: Discord/Shopify email/Shopify customer reordered above Rx OD/OS/Copy Rx by pure cut-and-paste; internals of all blocks unchanged
- [Phase 20.1.1.1]: Sidebar mount guard scoped to show-recipient-grid only, not show-option-grid — mirrors product sidebar pattern
- [Phase 20.1.1.1]: D-07: stamp GH handle on optimistic NoteEntry at save_note time via gh_user_login field resolved at LiveClient init
- [Phase 20.1.1.1]: D-02 restore block in on_tab_clicked omits tile rebuild because restore_mode_state already calls apply_filters which rebuilds recipient tiles
- [Phase 20.1.1.1]: on_card_post_note now uses targeted set_row_data notes patch instead of apply_filters to preserve open PopupWindow state (D-05)
- [Phase 20.1.1.1]: Bugsweeper bridge synced: CardDataJson fields removed (moved to sidebar in 20.1.1); sidebar-save-* callbacks take (rid, value) string pair
- [Phase 20.1.1.1.1]: ModeViewState::FilteredCards is the correct variant (plan had FilteredCardView)
- [Phase 20.1.1.1.1]: D-01 purpose-options wiring was already complete from Plan 02 — no new code needed in Plan 04
- [Phase 20.1.1.1.1]: D-13: item_display_label falls back to 'N items' count string when product_names empty but product_refs yield count > 0
- [Phase 20.1.1.1.1]: D-14: sync thread liveness confirmed via eprintln! at loop top — shopify_token presence and cycle result logged to stderr
- [Phase 20.1.1.1.1]: D-12: sync_return_states already fully instrumented; resolves once D-14 sync connectivity confirmed in UAT
- [Phase 20.4]: M-1 parallel-sites checklist template stored at .planning/templates/ (project-local); M-10 gate as quality_gate checklist items in planner prompt; additional_planner_rules block in plan-phase.md as extension point for future M-* rules
- [Phase 20.4]: M-2 and M-6 hooks use permissionDecision:allow — soft reminders only, never blocking
- [Phase 20.4]: .gitignore updated to expose .claude/hooks/** and settings.json for contributor sharing while keeping worktrees/ ignored
- [Phase 20.4]: RULE-09 added to DATA-FLOW.md (next available number after RULE-08); SYNC-MERGE.md uses SYNC-NN local numbering distinct from DATA-FLOW RULE-NN
- [Phase 20.4]: CALLBACK_PIPELINE.md includes three invariants (INV-1/INV-2/INV-3) with full callsite inventory as of 2026-04-16
- [Phase 20.4]: MODAL-STATE.md is a TODO placeholder — actual audit content deferred to Phase 20.5; Phase 20.5 inserted in ROADMAP.md with depends_on: Phase 20.4; CLAUDE.md modal state machine rule added citing 71% fragility statistic
- [Phase 20.4]: bugsweeper_smoke step scoped to phases touching *.rs or *.slint under crates/app/ — doc-only phases record N/A and skip
- [Phase 20.4]: Missing BUGSWEEPER Coverage section for runtime-touching phases is a gaps_found condition in verify-phase.md determine_status

### Roadmap Evolution

- Phase 16.1 inserted after Phase 16: Discord username inline editing in summary popover with GH Project write-back, Discord user profile image fetching, and avatar caching (URGENT)
- Phase 16.2 inserted after Phase 16: BUGSWEEPER live debugging framework for agent-executed Slint frontend testing and UAT (URGENT)
- Phase 20.1 inserted after Phase 20: UI Polish and Bug Fixes (URGENT)
- Phase 20.2 inserted after Phase 20: Sync Integrity and Shipment State Fixes (URGENT)
- Phase 20.3 inserted after Phase 20.2: Write-Context Enforcement — Link SQLite Writes to Pending Edit Enqueues (URGENT)
- Phase 20.1.1 inserted after Phase 20.1: Change (i) button to scrollable ww-note history with submission textbox; remove notes from card faces; enlarge product squares/text (URGENT)
- Phase 20.4 inserted after Phase 20: Implement all RETRO-AGENT-FAILURE-PATTERNS mitigations (M-1 through M-10) — agent failure pattern hardening (URGENT)

### Pending Todos

30 pending todos:

- Add start return feature for Shopify orders -> Phase 19
- Add Lists feature with viewing mode and card management -> Phase 21
- Avatar enlargement, card subtext repositioning, and Recipients view avatar rings
- Discord avatar via GET /users endpoint with Shopify-tag-stored user IDs
- New Card button with recipient search and Shopify draft order creation
- Remove Missing note warning and use (add note) placeholder
- Unassigned card view mode with conditional amber tab
- Unify Shopify and Discord token section styling in settings modal
- Auto-promote recipient GH Project rows from drafts to ww-recipient issues
- Product unit ownership audit trail via GH issue timeline comments
- Centralized modular search modal with shared UX patterns
- Default card shipping state to 'No items added' when empty
- Editable Shopify product URL in product view sidebar
- Hide card (+) button until card is hovered

### Blockers/Concerns

- Phase 15 (SQLite Foundation): Connection management strategy decision needed before coding — single Arc<Mutex<Connection>> + WAL mode vs. r2d2-sqlite pool. Deadlock risk is concrete (see research PITFALLS.md).
- Phase 17 (GH Issues Client): OQ-01 (serial instance storage: ww-product body block vs. structured comments) and OQ-03 (GH Issue body encoding format) must be resolved before implementation. Body schema locks in Phases 18 and 19.
- v1.0 phases 9/12.1.1/14 still have open plans — complete before or parallel to Phase 15 planning.

### Quick Tasks Completed

| # | Description | Date | Commit | Directory |
|---|-------------|------|--------|-----------|
| 260325-t3v | BUGSWEEPER flexibility update — dynamic property/callback registry, PRAGMA support, Phase 17 coverage | 2026-03-26 | e8ce27a | [260325-t3v-bugsweeper-flexibility-update-dynamic-pr](./quick/260325-t3v-bugsweeper-flexibility-update-dynamic-pr/) |
| Phase 17 P06 | 4 | 1 tasks | 1 files |
| Phase 17 P07 | 30 | 2 tasks | 5 files |
| Phase 17 P09 | 15 | 2 tasks | 3 files |
| Phase 17 P08 | 35 | 2 tasks | 3 files |
| 260326-0lz | BUGSWEEPER Phase 17 feature gaps: writable lookup-modal, product/unit model endpoints, callback registry refresh | 2026-03-26 | bc22b72 | [260326-0lz-bugsweeper-phase-17-feature-gaps-writabl](./quick/260326-0lz-bugsweeper-phase-17-feature-gaps-writabl/) |
| Phase 18 P01 | 10 | 2 tasks | 5 files |
| Phase 18 P02 | 4 | 3 tasks | 1 files |
| Phase 18-gh-issues-write-back-notes-and-card-cloud-storage P03 | 12 | 2 tasks | 5 files |
| Phase 19.1 P07 | 2 | 1 tasks | 0 files |
| 260410-t6r | G-10 fix and BUGSWEEPER GUI interaction endpoints | 2026-04-11 | 7e199cb | [260410-t6r-g-10-fix-and-bugsweeper-gui-interaction-](./quick/260410-t6r-g-10-fix-and-bugsweeper-gui-interaction-/) |
| Phase 20.2 P02 | 15 | 2 tasks | 2 files |
| Phase 20.2-sync-integrity-and-shipment-state-fixes P04 | 25 | 2 tasks | 1 files |
| Phase 20.1.1 P01 | 11 | 4 tasks | 10 files |
| Phase 20.1.1 P02 | 3 | 2 tasks | 1 files |
| Phase 20.1.1 P07 | 15 | 1 tasks | 1 files |
| Phase 20.1.1 P03 | 27 | 2 tasks | 7 files |
| Phase 20.1.1 P04 | 6 | 2 tasks | 3 files |
| Phase 20.1.1 P05 | 20 | 2 tasks | 4 files |
| Phase 20.1.1 P06 | 25 | 3 tasks | 10 files |
| Phase 20.1.1 P08 | 90 | 4 tasks | 4 files |
| Phase 20.1.1 P09 | 10 | 1 tasks | 1 files |
| Phase 20.1.1.1 P01 | 18 | 2 tasks | 1 files |
| Phase 20.1.1.1 P02 | 3 | 3 tasks | 1 files |
| Phase 20.1.1.1 P03 | 2 | 1 tasks | 1 files |
| Phase 20.1.1.1 P04 | 15 | 2 tasks | 2 files |
| Phase 20.1.1.1 P05 | 25 | 3 tasks | 2 files |
| Phase 20.1.1.1.1 P04 | 12 | 2 tasks | 2 files |
| Phase 20.1.1.1.1 P05 | 12 | 2 tasks | 2 files |
| 260416-10s | Fix return status derivation: preserve return-related shipment statuses across sync cycles | 2026-04-16 | bc3f0bc | [260416-10s-fix-return-status-underway](./quick/260416-10s-fix-return-status-underway/) |
| Phase 20.4 P01 | 8 | 2 tasks | 4 files |
| Phase 20.4 P02 | 7 | 2 tasks | 4 files |
| Phase 20.4 P03 | 6 | 2 tasks | 5 files |
| Phase 20.4 P04 | 2 | 2 tasks | 3 files |
| Phase 20.4 P05 | 5 | 2 tasks | 2 files |
| 260416-3zv | Per-state card status pill colors | 2026-04-16 | 51159ac | [260416-3zv-card-pill-colors-per-state](./quick/260416-3zv-card-pill-colors-per-state/) |

## Session Continuity

Last activity: 2026-04-16
Stopped at: Phase 20.5 context gathered
Resume file: .planning/phases/20.5-modal-state-architectural-audit-inserted/20.5-CONTEXT.md
