# Stack Research

**Domain:** Rust/Slint Windows desktop app — v1.1 additions only (SQLite persistence, GH Issues API write, product/serial entities, offline edit queue, Lists UI)
**Researched:** 2026-03-22
**Confidence:** HIGH (all library versions verified against docs.rs / crates.io at research time)

---

## Scope

The existing stack (Rust, Slint, tokio, serde/serde_json, ureq v2, keyring, fuzzy-matcher, toml, uuid) is validated and NOT re-researched. This document covers only the **additions and version changes** required by v1.1 features.

---

## New / Changed Dependencies

### Core Additions

| Technology | Version | Crate Feature(s) | Purpose | Why Recommended |
|------------|---------|-----------------|---------|-----------------|
| `rusqlite` | `0.39` | `bundled`, `chrono`, `uuid` | SQLite local mirror cache — the single read source per RULE-03 | `bundled` compiles SQLite 3.51.3 from C source via the `cc` crate, eliminating the Windows system SQLite requirement (no MSVC link headache). Direct, low-overhead; no ORM needed for this schema size. |
| `rusqlite_migration` | `2.5` | — | Schema migration on startup | Tracks schema version in SQLite's built-in `user_version` integer — no migration table, no CLI, no external files. Define SQL strings in Rust; call `Migrations::new(v).to_latest(&conn)` at service init. Released 2026-03-21. |
| `chrono` | `0.4` | `serde` | `DateTime<Utc>` for timestamps in domain types and SQLite columns | `NoteEntry.date`, `assigned_at`, `last_synced_at` all need ISO-8601 round-trip through SQLite TEXT columns and GH Issue body JSON. The `serde` feature covers both paths. Standard ecosystem choice with 392M downloads. |
| `ureq` | `3.3` | `json` | HTTP client — **version upgrade from v2** | The existing codebase uses ureq v2, which is no longer maintained. v3 is a clean semver break: stricter TLS defaults (rustls by default), improved error types, same blocking API design. GH Issues REST calls (POST/PATCH) are simple JSON operations that ureq handles natively. |

### Feature Additions to Existing Crates

| Library | Addition | Purpose |
|---------|---------|---------|
| `rusqlite` `chrono` feature | Automatic `DateTime<Utc>` ↔ TEXT binding | Eliminates manual `to_rfc3339()` in every query row mapper |
| `rusqlite` `uuid` feature | Automatic `Uuid` ↔ TEXT binding | `product_id` and future UUID PKs bind without `.to_string()` |

### What Does NOT Need to Change

| Existing Dep | Why Unchanged |
|--------------|---------------|
| `tokio` | Already present. Offline queue uses `tokio::sync::mpsc` (channel to background flush task) — no new tokio primitives needed. |
| `serde` / `serde_json` | GH Issue body structured fields (`product_refs`, `notes`, serial instances) are JSON strings. Same serde pipeline as Shopify responses. |
| `keyring` | GitHub token already stored. Shopify token added in Phase 12.1. No change. |
| `uuid` (in `service`) | Already present. May need to move to `wit_core` if `product_id` generation lives there; no new crate needed. |
| `slint` | No new Slint primitives needed. Lists UI feature uses existing `VecModel` and callback patterns. |

---

## Supporting Libraries

| Library | Version | Purpose | When to Use |
|---------|---------|---------|-------------|
| `dirs` (existing in `app`) | `5` | Resolve `data_local_dir()` for SQLite DB file path | Already present; DB path = `dirs::data_local_dir() + "witwhat/witwhat.db"` |

---

## Cargo.toml Changes by Crate

### `crates/core/Cargo.toml`

```toml
[dependencies]
chrono = { version = "0.4", features = ["serde"] }
serde = { version = "1", features = ["derive"] }
```

`wit_core` is where domain structs live (`NoteEntry`, `ProductRef`, `SerialInstance`, `SerialInstanceState`). Chrono and serde belong here so all crates share the same type definitions without re-importing.

### `crates/service/Cargo.toml`

```toml
[dependencies]
wit_core    = { path = "../core" }
integrations = { path = "../integrations" }
uuid        = { version = "1", features = ["v4"] }
keyring     = { version = "3", features = ["windows-native"] }
rusqlite    = { version = "0.39", features = ["bundled", "chrono", "uuid"] }
rusqlite_migration = "2.5"
```

`service` owns sync logic, the Repository, and the new SQLite layer. Migrations run at startup inside service initialization.

### `crates/integrations/Cargo.toml`

```toml
[dependencies]
wit_core   = { path = "../core" }
serde      = { version = "1", features = ["derive"] }
serde_json = "1"
ureq       = { version = "3", features = ["json"] }
```

Upgrade ureq from `"2"` to `"3"`. The GH Issues REST client (create/update `ww-card`, `ww-product`, add comments) lives here alongside the existing Shopify and GH GraphQL clients. ureq v3 error handling (`ureq::Error` variants) changed from v2 — audit existing match arms during upgrade.

### `crates/app/Cargo.toml`

```toml
# Remove direct ureq dependency — app should route HTTP through integrations crate
# Keep everything else unchanged
```

`app` currently duplicates the ureq dependency. With GH Issues writes going through `integrations`, the `app` crate's direct ureq usage should be removed if no longer needed after the upgrade audit.

---

## Development Tools

| Tool | Purpose | Notes |
|------|---------|-------|
| `cargo` | Build / test | No change |
| MSVC C compiler | Required by `rusqlite` `bundled` feature to compile SQLite from C source | Present on Windows dev machine; no extra install needed |

---

## Architecture Notes Per Feature

### SQLite Full-Mirror Cache (RULE-03)

- DB file: `dirs::data_local_dir()` + `"witwhat/witwhat.db"`.
- Schema tables: `recipients`, `cards`, `products`, `serial_instances`, `pending_edits`.
- `rusqlite::Connection` wrapped in `Arc<std::sync::Mutex<Connection>>` — shared between background sync thread and main thread. Use `std::sync::Mutex` (not `tokio::sync::Mutex`) because rusqlite operations are synchronous blocking calls.
- All sync writes go to SQLite first; Slint model hydrates from SQLite reads only.
- Migrations: ordered `Vec<M>` of SQL `&str` defined at compile time. `Migrations::new(v).to_latest(&conn)` on startup.

### GH Issues API — Create / Update (`ww-card`, `ww-product`)

- Endpoints (ureq v3 + `Authorization: Bearer {token}`):
  - Create issue: `POST https://api.github.com/repos/BigscreenVR/beyond-outgoing/issues`
  - Update issue: `PATCH https://api.github.com/repos/BigscreenVR/beyond-outgoing/issues/{number}`
  - Add note comment: `POST https://api.github.com/repos/BigscreenVR/beyond-outgoing/issues/{number}/comments`
- Token: same GitHub token already stored in keyring.
- Issue body format: fenced JSON block inside markdown (` ```json\n{...}\n``` `) — machine-parseable by the sync pipeline, human-readable in GitHub UI. Exact schema is TBD per DATA-FLOW.md.
- No SDK dependency (octocrab/octorust) — ureq + serde_json is sufficient for a handful of REST endpoints.

### Offline Edit Queue

- `pending_edits` SQLite table: `(id INTEGER PK, operation_type TEXT, payload_json TEXT, created_at TEXT, retry_count INTEGER)`.
- On edit: `INSERT INTO pending_edits` immediately; apply optimistically to local SQLite state.
- On each sync cycle: drain `pending_edits ORDER BY created_at`; `DELETE` on success; increment `retry_count` on failure (cap at 5, surface error to UI).
- No external job queue library. This is an append-only table with a sequential drain loop inside the existing `tokio::spawn` sync task.

### Serial Instance State Machine

- Pure Rust enum in `wit_core`:
  ```rust
  pub enum SerialInstanceState {
      Created, Assigned, InTransit, Delivered,
      ReturnInTransit, Returned, Available,
  }
  ```
- Stored as TEXT in SQLite. Serialize via `Display`/`FromStr` or serde string variant.
- Transition guard: `fn transition(state: SerialInstanceState, event: SerialEvent) -> Result<SerialInstanceState, TransitionError>` in `service`. No state machine crate needed — the graph is linear with 7 states and deterministic transitions.

### Product Catalog and Lists UI

- No new crates. `Product`, `ProductRef` domain types in `wit_core`.
- Lists UI uses `slint::VecModel<ListData>` — identical pattern to existing `VecModel<CardData>`.

---

## Alternatives Considered

| Recommended | Alternative | When to Use Alternative |
|-------------|-------------|-------------------------|
| `rusqlite` direct | `diesel` | If schema is large and complex enough to benefit from type-safe query building; overkill here |
| `rusqlite` direct | `sqlx` | If the entire app were async-first; sqlx is a poor fit for the blocking sync design |
| `rusqlite_migration` | `refinery` | If multi-database support is needed; refinery supports postgres/mysql/sqlite but adds more complexity |
| ureq v3 (blocking) | `reqwest` (async) | If the sync pipeline were redesigned as async throughout; reqwest + tokio adds architectural complexity |
| `pending_edits` table | `effectum` / `aide-de-camp-sqlite` | If job queue needs retries, priorities, worker pools, or is shared across processes |
| `chrono` | `time` crate | For new projects; `time` is gaining adoption but `chrono` integrates with rusqlite's feature flag directly |

---

## What NOT to Add

| Avoid | Why | Use Instead |
|-------|-----|-------------|
| `octocrab` / `octorust` | Hundreds of transitive dependencies for 3 REST endpoints | ureq v3 + serde_json |
| `diesel` | Requires schema codegen and diesel-cli at build time | rusqlite direct |
| `sqlx` | Async-first; queries checked at compile time against live DB — incompatible with blocking sync design | rusqlite direct |
| `effectum` or any job queue crate | Offline queue is a simple drain table, not a distributed scheduler | `pending_edits` SQLite table |
| ureq v2 (current) | No longer maintained; v3 is the supported branch | Upgrade to ureq v3 |
| `reqwest` | Async HTTP; forces `.await` throughout existing blocking sync thread | ureq v3 |

---

## Version Compatibility

| Package | Compatible With | Notes |
|---------|-----------------|-------|
| `rusqlite 0.39` | `rusqlite_migration 2.5` | rusqlite_migration 2.x targets rusqlite 0.3x series; verified compatible |
| `rusqlite 0.39` `bundled` | Windows MSVC toolchain | Compiles SQLite 3.51.3 from C via `cc` crate; MSVC present on dev machine |
| `ureq 3.x` | `serde_json 1.x` | ureq v3 `json` feature uses serde_json directly; no workspace version conflict |
| `chrono 0.4` | `rusqlite 0.39` `chrono` feature | rusqlite's `chrono` feature bridges `DateTime<Utc>` to SQLite TEXT natively |
| `chrono 0.4` | `serde 1.x` | chrono `serde` feature provides ISO-8601 default serialization and `ts_seconds` module |

---

## Sources

- [rusqlite docs.rs](https://docs.rs/rusqlite/latest/rusqlite/) — version 0.39.0 confirmed; `bundled` compiles SQLite 3.51.3 from source
- [rusqlite GitHub — bundled feature](https://github.com/rusqlite/rusqlite) — Windows build guidance confirmed
- [rusqlite_migration docs.rs](https://docs.rs/crate/rusqlite_migration/latest) — version 2.5.0 confirmed (released 2026-03-21)
- [ureq docs.rs](https://docs.rs/crate/ureq/latest) — version 3.3.0 confirmed; v3 API changes (error types, TLS defaults) noted
- [chrono crates.io](https://crates.io/crates/chrono) — version 0.4.42 confirmed; `serde` feature verified
- [GitHub REST API — Issues](https://docs.github.com/en/rest/reference/issues) — create/update/comment endpoints confirmed; no SDK required
- [Tokio channels](https://tokio.rs/tokio/tutorial/channels) — `mpsc` channel pattern for background flush task (HIGH confidence — official Tokio documentation)
- [effectum GitHub](https://github.com/dimfeld/effectum) — evaluated and rejected as overkill for a drain-table pattern

---

*Stack research for: WITwhat v1.1 new crate additions (SQLite, GH Issues write, offline queue)*
*Researched: 2026-03-22*
