···55Uses `jacquard::oauth` with `LoopbackConfig` to authenticate:
66771. User enters handle or DID
88-2. Resolve authorization server via `jacquard_oauth::resolver`
88+2. Resolve authorization server via `jacquard::oauth::resolver`
993. Build `AtprotoClientMetadata` with app identity
10104. `OAuthClient` initiates PAR + DPoP flow
11115. Loopback server captures redirect on `127.0.0.1:<port>`
···1717## Multi-Account
18181919- SQLite table `accounts`: `did TEXT PK, handle TEXT, pds_url TEXT, active INTEGER`
2020-- Encrypted token storage via `jacquard_oauth::authstore` trait with a persistent implementation backed by SQLite + OS keychain (Tauri's `tauri-plugin-keychain` or raw `security-framework`)
2020+- Encrypted token storage via `jacquard::oauth::authstore` trait with a persistent implementation backed by SQLite + OS keychain (Tauri's `tauri-plugin-keychain` or raw `security-framework`)
2121- Account switcher in sidebar — click to swap active session
2222- Each account gets its own `OAuthSession` instance
2323- Active account DID stored in app state; Tauri events notify frontend on switch
+11-9
docs/tasks/01-backend-setup.md
···55## Steps
6677- [x] Add Cargo dependencies: `jacquard`, `rusqlite` (bundled), `sqlite-vec`, `fastembed`, `tokio`
88-- [x] Add Tauri plugins: `tauri-plugin-deep-link`, `tauri-plugin-notification`, `tauri-plugin-updater`, `tauri-plugin-log`
88+- [x] Add Tauri plugins: `tauri-plugin-deep-link`, `tauri-plugin-notification`, `tauri-plugin-log`
99- [x] Add frontend deps: `solid-motionone` (animation), install via npm
1010-- [ ] Create `src-tauri/src/db.rs` — initialize SQLite, run migrations, load `sqlite-vec` extension
1111-- [ ] Create migration system: `accounts`, `posts`, `posts_fts`, `posts_vec` tables
1010+- [x] Create `src-tauri/src/db.rs` — initialize SQLite, run migrations, load `sqlite-vec` extension
1111+- [x] Create migration system: `accounts`, `posts`, `posts_fts`, `posts_vec` tables
1212 - Embedded files via `include_str!` for SQL schema
1313-- [ ] Create `src-tauri/src/state.rs` — `AppState` struct holding DB pool, active session, account list
1414-- [ ] Register `AppState` as Tauri managed state
1515-- [ ] Create Tauri command scaffold with error handling pattern using `thiserror` crate
1616-- [ ] Set up dark/light theme: CSS custom properties, OS preference detection via `prefers-color-scheme`
1313+- [x] Create `src-tauri/src/state.rs` — `AppState` struct holding DB pool, active session, account list
1414+- [x] Register `AppState` as Tauri managed state
1515+- [x] Create Tauri command scaffold with error handling pattern using `thiserror` crate
1616+- [x] Set up dark/light theme: CSS custom properties, OS preference detection via `prefers-color-scheme`
1717 - Follow the design spec
1818-- [ ] Create global error toast component using `Presence` for enter/exit animations
1919-- [ ] Verify build compiles on macOS with `pnpm tauri dev`
1818+- [x] Create global error toast component using `Presence` for enter/exit animations
1919+- [x] Verify build compiles on macOS with `pnpm tauri dev`
2020+2121+- Note: `tauri-plugin-updater` will come after the first release
···11-// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
22-#[tauri::command]
33-fn greet(name: &str) -> String {
44- format!("Hello, {}! You've been greeted from Rust!", name)
55-}
11+mod commands;
22+mod db;
33+mod error;
44+mod state;
55+66+use commands::{get_app_bootstrap, list_accounts, set_active_account};
77+use db::initialize_database;
88+use state::AppState;
99+use tauri::Manager;
610711#[cfg_attr(mobile, tauri::mobile_entry_point)]
812pub fn run() {
913 tauri::Builder::default()
1010- .plugin(tauri_plugin_updater::Builder::new().build())
1414+ .setup(|app| {
1515+ let db_pool =
1616+ initialize_database(app.handle()).expect("database initialization should succeed during startup");
1717+ let app_state =
1818+ AppState::bootstrap(db_pool).expect("application state should be bootstrapped from database");
1919+2020+ app.manage(app_state);
2121+ Ok(())
2222+ })
1123 .plugin(tauri_plugin_notification::init())
1224 .plugin(
1325 tauri_plugin_log::Builder::new()
···1628 )
1729 .plugin(tauri_plugin_deep_link::init())
1830 .plugin(tauri_plugin_opener::init())
1919- .invoke_handler(tauri::generate_handler![greet])
3131+ .invoke_handler(tauri::generate_handler![
3232+ get_app_bootstrap,
3333+ list_accounts,
3434+ set_active_account
3535+ ])
2036 .run(tauri::generate_context!())
2137 .expect("error while running tauri application");
2238}
+45
src-tauri/src/migrations/001_initial.sql
···11+CREATE TABLE IF NOT EXISTS accounts (
22+ did TEXT PRIMARY KEY,
33+ handle TEXT,
44+ pds_url TEXT,
55+ active INTEGER NOT NULL DEFAULT 0 CHECK(active IN (0, 1))
66+);
77+88+CREATE TABLE IF NOT EXISTS posts (
99+ uri TEXT PRIMARY KEY,
1010+ cid TEXT NOT NULL,
1111+ author_did TEXT NOT NULL,
1212+ author_handle TEXT,
1313+ text TEXT,
1414+ created_at TEXT,
1515+ indexed_at TEXT DEFAULT CURRENT_TIMESTAMP,
1616+ json_record TEXT,
1717+ source TEXT NOT NULL
1818+);
1919+2020+CREATE VIRTUAL TABLE IF NOT EXISTS posts_fts USING fts5(
2121+ text,
2222+ uri UNINDEXED,
2323+ content=posts,
2424+ content_rowid=rowid
2525+);
2626+2727+CREATE VIRTUAL TABLE IF NOT EXISTS posts_vec USING vec0(
2828+ uri TEXT PRIMARY KEY,
2929+ embedding float[768]
3030+);
3131+3232+CREATE TRIGGER IF NOT EXISTS posts_ai AFTER INSERT ON posts BEGIN
3333+ INSERT INTO posts_fts(rowid, text, uri) VALUES (new.rowid, new.text, new.uri);
3434+END;
3535+3636+CREATE TRIGGER IF NOT EXISTS posts_ad AFTER DELETE ON posts BEGIN
3737+ INSERT INTO posts_fts(posts_fts, rowid, text, uri)
3838+ VALUES('delete', old.rowid, old.text, old.uri);
3939+END;
4040+4141+CREATE TRIGGER IF NOT EXISTS posts_au AFTER UPDATE ON posts BEGIN
4242+ INSERT INTO posts_fts(posts_fts, rowid, text, uri)
4343+ VALUES('delete', old.rowid, old.text, old.uri);
4444+ INSERT INTO posts_fts(rowid, text, uri) VALUES (new.rowid, new.text, new.uri);
4545+END;