diff --git a/docs/superpowers/plans/2026-04-13-orchai-phase1-foundation.md b/docs/superpowers/plans/2026-04-13-orchai-phase1-foundation.md
new file mode 100644
index 0000000..4bbe356
--- /dev/null
+++ b/docs/superpowers/plans/2026-04-13-orchai-phase1-foundation.md
@@ -0,0 +1,1660 @@
+# Orchai Phase 1: Foundation Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Get a working Tauri 2 desktop app with SQLite storage and full Project CRUD (create from local path or clone URL, list, edit, delete) with a React UI.
+
+**Architecture:** Tauri 2 (Rust backend) with React + TypeScript frontend. SQLite database via rusqlite for persistence. Tauri IPC commands expose backend operations to the frontend. React Router for navigation with a sidebar-based layout.
+
+**Tech Stack:** Rust, Tauri 2, React 18, TypeScript, Vite, Tailwind CSS, SQLite (rusqlite), react-router-dom v6
+
+---
+
+## Phasing Strategy
+
+This is Plan 1 of 4:
+- **Plan 1 (this):** Foundation -- Tauri scaffold, SQLite, Project Manager
+- **Plan 2:** Tuleap Integration -- credentials, API client, poller, filter engine, tracker config
+- **Plan 3:** Agent Pipeline -- orchestrator, worktree manager, ticket processing, results UI
+- **Plan 4:** Notifications + Polish -- notifier, system notifications, dashboard
+
+---
+
+## File Structure
+
+```
+orchai/
+├── docs/ # existing
+├── src-tauri/
+│ ├── Cargo.toml # modify: add rusqlite, uuid, chrono, serde
+│ ├── build.rs # from scaffold
+│ ├── tauri.conf.json # modify: app name, identifier, permissions
+│ ├── capabilities/
+│ │ └── default.json # modify: add dialog permissions
+│ ├── migrations/
+│ │ └── 001_init.sql # create: full schema (all tables)
+│ └── src/
+│ ├── main.rs # from scaffold (unchanged)
+│ ├── lib.rs # modify: setup app state, register commands
+│ ├── db.rs # create: SQLite init + migration runner
+│ ├── error.rs # create: shared error type
+│ ├── models/
+│ │ ├── mod.rs # create: re-exports
+│ │ └── project.rs # create: Project struct + CRUD
+│ └── commands/
+│ ├── mod.rs # create: re-exports
+│ └── project.rs # create: Tauri commands for project CRUD
+├── src/
+│ ├── main.tsx # from scaffold (unchanged)
+│ ├── App.tsx # modify: router setup
+│ ├── App.css # delete (replaced by Tailwind)
+│ ├── index.css # modify: Tailwind directives
+│ ├── lib/
+│ │ ├── types.ts # create: TypeScript types matching Rust models
+│ │ └── api.ts # create: typed invoke wrappers
+│ ├── components/
+│ │ ├── layout/
+│ │ │ ├── AppLayout.tsx # create: sidebar + main content area
+│ │ │ └── Sidebar.tsx # create: project list + add button
+│ │ └── projects/
+│ │ ├── ProjectList.tsx # create: empty state / project cards
+│ │ ├── ProjectForm.tsx # create: create/edit form with folder picker
+│ │ └── ProjectDashboard.tsx # create: project overview (placeholder)
+├── index.html # from scaffold
+├── package.json # modify: add dependencies
+├── vite.config.ts # from scaffold
+├── tsconfig.json # from scaffold
+├── tsconfig.node.json # from scaffold
+├── tailwind.config.js # create
+├── postcss.config.js # create
+└── .gitignore # from scaffold
+```
+
+---
+
+### Task 1: Scaffold Tauri 2 + React + TypeScript
+
+**Files:**
+- Create: entire project scaffold via CLI
+- Preserve: `docs/`, `.git/`
+
+- [ ] **Step 1: Save existing repo contents**
+
+```bash
+cd /home/leclere/Projets
+cp -r orchai/docs /tmp/orchai-docs-backup
+cp -r orchai/.git /tmp/orchai-git-backup
+```
+
+- [ ] **Step 2: Scaffold Tauri 2 project**
+
+```bash
+cd /home/leclere/Projets
+rm -rf orchai
+npm create tauri-app@latest orchai
+```
+
+When prompted, select:
+- Project name: `orchai`
+- Identifier: `com.orchai.app`
+- Frontend language: `TypeScript / JavaScript`
+- Package manager: `npm`
+- UI template: `React`
+- UI flavor: `TypeScript`
+
+- [ ] **Step 3: Restore repo history and docs**
+
+```bash
+cd /home/leclere/Projets/orchai
+rm -rf .git
+cp -r /tmp/orchai-git-backup .git
+cp -r /tmp/orchai-docs-backup docs
+rm -rf /tmp/orchai-docs-backup /tmp/orchai-git-backup
+```
+
+- [ ] **Step 4: Install dependencies and verify**
+
+```bash
+cd /home/leclere/Projets/orchai
+npm install
+cd src-tauri && cargo build
+cd ..
+```
+
+Expected: build succeeds with no errors.
+
+- [ ] **Step 5: Verify dev server starts**
+
+```bash
+npm run tauri dev
+```
+
+Expected: Tauri window opens with the default React starter page. Close it after verifying.
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add -A
+git commit -m "scaffold: Tauri 2 + React + TypeScript via create-tauri-app"
+```
+
+---
+
+### Task 2: Configure Tailwind CSS + project metadata
+
+**Files:**
+- Create: `tailwind.config.js`, `postcss.config.js`
+- Modify: `src/index.css`, `package.json`, `src-tauri/tauri.conf.json`
+- Delete: `src/App.css`
+
+- [ ] **Step 1: Install Tailwind**
+
+```bash
+cd /home/leclere/Projets/orchai
+npm install -D tailwindcss @tailwindcss/vite
+```
+
+- [ ] **Step 2: Add Tailwind to Vite config**
+
+Replace the contents of `vite.config.ts`:
+
+```typescript
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+import tailwindcss from "@tailwindcss/vite";
+
+const host = process.env.TAURI_DEV_HOST;
+
+export default defineConfig(async () => ({
+ plugins: [react(), tailwindcss()],
+ clearScreen: false,
+ server: {
+ port: 1420,
+ strictPort: true,
+ host: host || false,
+ hmr: host
+ ? {
+ protocol: "ws",
+ host,
+ port: 1421,
+ }
+ : undefined,
+ watch: {
+ ignored: ["**/src-tauri/**"],
+ },
+ },
+}));
+```
+
+- [ ] **Step 3: Replace index.css with Tailwind directives**
+
+Replace the contents of `src/index.css`:
+
+```css
+@import "tailwindcss";
+```
+
+- [ ] **Step 4: Delete App.css and clean up App.tsx**
+
+Delete `src/App.css`.
+
+Replace `src/App.tsx`:
+
+```tsx
+function App() {
+ return (
+
+
Orchai
+
+ );
+}
+
+export default App;
+```
+
+- [ ] **Step 5: Update Tauri config**
+
+In `src-tauri/tauri.conf.json`, update the `app` section:
+
+```json
+{
+ "$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-config-schema/schema.json",
+ "productName": "Orchai",
+ "version": "0.1.0",
+ "identifier": "com.orchai.app",
+ "build": {
+ "frontendDist": "../dist",
+ "devUrl": "http://localhost:1420",
+ "beforeDevCommand": "npm run dev",
+ "beforeBuildCommand": "npm run build"
+ },
+ "app": {
+ "title": "Orchai",
+ "windows": [
+ {
+ "title": "Orchai",
+ "width": 1200,
+ "height": 800
+ }
+ ],
+ "security": {
+ "csp": null
+ }
+ }
+}
+```
+
+- [ ] **Step 6: Verify Tailwind works**
+
+```bash
+npm run tauri dev
+```
+
+Expected: window opens, "Orchai" heading rendered with Tailwind styling (bold, large). Close after verifying.
+
+- [ ] **Step 7: Commit**
+
+```bash
+git add -A
+git commit -m "configure: Tailwind CSS + app metadata"
+```
+
+---
+
+### Task 3: SQLite database + migration system
+
+**Files:**
+- Modify: `src-tauri/Cargo.toml`
+- Create: `src-tauri/migrations/001_init.sql`
+- Create: `src-tauri/src/db.rs`
+- Create: `src-tauri/src/error.rs`
+- Modify: `src-tauri/src/lib.rs`
+
+- [ ] **Step 1: Write the failing test for db initialization**
+
+Add dependencies to `src-tauri/Cargo.toml` under `[dependencies]`:
+
+```toml
+rusqlite = { version = "0.31", features = ["bundled"] }
+uuid = { version = "1", features = ["v4", "serde"] }
+chrono = { version = "0.4", features = ["serde"] }
+```
+
+Create `src-tauri/src/db.rs`:
+
+```rust
+use rusqlite::{Connection, Result};
+use std::path::Path;
+
+pub fn init(db_path: &Path) -> Result {
+ todo!()
+}
+
+pub fn init_in_memory() -> Result {
+ todo!()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_init_in_memory_creates_tables() {
+ let conn = init_in_memory().expect("should initialize");
+
+ // Verify all 6 tables exist
+ let tables: Vec = conn
+ .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name")
+ .unwrap()
+ .query_map([], |row| row.get(0))
+ .unwrap()
+ .collect::, _>>()
+ .unwrap();
+
+ assert_eq!(
+ tables,
+ vec![
+ "notifications",
+ "processed_tickets",
+ "projects",
+ "tuleap_credentials",
+ "watched_trackers",
+ "worktrees",
+ ]
+ );
+ }
+
+ #[test]
+ fn test_init_in_memory_enables_foreign_keys() {
+ let conn = init_in_memory().expect("should initialize");
+ let fk_enabled: i32 = conn
+ .query_row("PRAGMA foreign_keys", [], |row| row.get(0))
+ .unwrap();
+ assert_eq!(fk_enabled, 1);
+ }
+
+ #[test]
+ fn test_init_in_memory_sets_wal_mode() {
+ let conn = init_in_memory().expect("should initialize");
+ let mode: String = conn
+ .query_row("PRAGMA journal_mode", [], |row| row.get(0))
+ .unwrap();
+ assert_eq!(mode, "wal");
+ }
+}
+```
+
+- [ ] **Step 2: Run tests to verify they fail**
+
+```bash
+cd /home/leclere/Projets/orchai/src-tauri
+cargo test db::tests
+```
+
+Expected: 3 failures with `not yet implemented`.
+
+- [ ] **Step 3: Create migration SQL**
+
+Create `src-tauri/migrations/001_init.sql`:
+
+```sql
+CREATE TABLE IF NOT EXISTS projects (
+ id TEXT PRIMARY KEY,
+ name TEXT NOT NULL,
+ path TEXT NOT NULL,
+ cloned_from TEXT,
+ base_branch TEXT NOT NULL DEFAULT 'main',
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
+);
+
+CREATE TABLE IF NOT EXISTS tuleap_credentials (
+ id TEXT PRIMARY KEY,
+ tuleap_url TEXT NOT NULL,
+ username TEXT NOT NULL,
+ password_encrypted TEXT NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS watched_trackers (
+ id TEXT PRIMARY KEY,
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
+ tracker_id INTEGER NOT NULL,
+ tracker_label TEXT NOT NULL,
+ polling_interval INTEGER NOT NULL DEFAULT 10,
+ agent_config_json TEXT NOT NULL,
+ filters_json TEXT NOT NULL,
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
+);
+
+CREATE TABLE IF NOT EXISTS processed_tickets (
+ id TEXT PRIMARY KEY,
+ tracker_id TEXT NOT NULL REFERENCES watched_trackers(id) ON DELETE CASCADE,
+ artifact_id INTEGER NOT NULL,
+ artifact_title TEXT NOT NULL,
+ artifact_data TEXT NOT NULL,
+ status TEXT NOT NULL DEFAULT 'Pending',
+ analyst_report TEXT,
+ developer_report TEXT,
+ detected_at TEXT NOT NULL DEFAULT (datetime('now')),
+ processed_at TEXT
+);
+
+CREATE TABLE IF NOT EXISTS worktrees (
+ id TEXT PRIMARY KEY,
+ ticket_id TEXT NOT NULL REFERENCES processed_tickets(id),
+ path TEXT NOT NULL,
+ branch_name TEXT NOT NULL,
+ status TEXT NOT NULL DEFAULT 'Active',
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
+ merged_at TEXT,
+ merged_into TEXT
+);
+
+CREATE TABLE IF NOT EXISTS notifications (
+ id TEXT PRIMARY KEY,
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
+ ticket_id TEXT REFERENCES processed_tickets(id),
+ type TEXT NOT NULL,
+ title TEXT NOT NULL,
+ message TEXT NOT NULL,
+ read INTEGER NOT NULL DEFAULT 0,
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
+);
+```
+
+- [ ] **Step 4: Implement db::init and db::init_in_memory**
+
+Replace the `todo!()` implementations in `src-tauri/src/db.rs`:
+
+```rust
+use rusqlite::{Connection, Result};
+use std::path::Path;
+
+const MIGRATION_001: &str = include_str!("../migrations/001_init.sql");
+
+pub fn init(db_path: &Path) -> Result {
+ let conn = Connection::open(db_path)?;
+ configure(&conn)?;
+ migrate(&conn)?;
+ Ok(conn)
+}
+
+pub fn init_in_memory() -> Result {
+ let conn = Connection::open_in_memory()?;
+ configure(&conn)?;
+ migrate(&conn)?;
+ Ok(conn)
+}
+
+fn configure(conn: &Connection) -> Result<()> {
+ conn.pragma_update(None, "journal_mode", "wal")?;
+ conn.pragma_update(None, "foreign_keys", "ON")?;
+ Ok(())
+}
+
+fn migrate(conn: &Connection) -> Result<()> {
+ let version: i32 = conn.pragma_query_value(None, "user_version", |row| row.get(0))?;
+
+ if version < 1 {
+ conn.execute_batch(MIGRATION_001)?;
+ conn.pragma_update(None, "user_version", 1)?;
+ }
+
+ Ok(())
+}
+```
+
+- [ ] **Step 5: Run tests to verify they pass**
+
+```bash
+cd /home/leclere/Projets/orchai/src-tauri
+cargo test db::tests
+```
+
+Expected: 3 tests pass. Note: `test_init_in_memory_sets_wal_mode` will return `"memory"` for in-memory DBs. Update the test:
+
+Replace the WAL test assertion:
+
+```rust
+ #[test]
+ fn test_init_in_memory_sets_wal_mode() {
+ // WAL is set but in-memory DBs report "memory" — verify no error on configure
+ let conn = init_in_memory().expect("should initialize");
+ let mode: String = conn
+ .query_row("PRAGMA journal_mode", [], |row| row.get(0))
+ .unwrap();
+ // In-memory databases report "memory" instead of "wal"
+ assert_eq!(mode, "memory");
+ }
+```
+
+Re-run tests. Expected: 3 pass.
+
+- [ ] **Step 6: Create error type**
+
+Create `src-tauri/src/error.rs`:
+
+```rust
+use serde::Serialize;
+
+#[derive(Debug, Serialize)]
+pub struct AppError {
+ pub message: String,
+}
+
+impl From for AppError {
+ fn from(e: rusqlite::Error) -> Self {
+ AppError {
+ message: e.to_string(),
+ }
+ }
+}
+
+impl From for AppError {
+ fn from(e: std::io::Error) -> Self {
+ AppError {
+ message: e.to_string(),
+ }
+ }
+}
+
+impl From for AppError {
+ fn from(s: String) -> Self {
+ AppError { message: s }
+ }
+}
+
+impl std::fmt::Display for AppError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.message)
+ }
+}
+```
+
+- [ ] **Step 7: Wire up db module in lib.rs**
+
+Replace `src-tauri/src/lib.rs`:
+
+```rust
+mod db;
+mod error;
+
+use std::sync::Mutex;
+
+pub struct AppState {
+ pub db: Mutex,
+}
+
+#[cfg_attr(mobile, tauri::mobile_entry_point)]
+pub fn run() {
+ tauri::Builder::default()
+ .setup(|app| {
+ let db_dir = app.path().app_data_dir()?;
+ std::fs::create_dir_all(&db_dir)?;
+ let db_path = db_dir.join("orchai.db");
+ let conn = db::init(&db_path).expect("Failed to initialize database");
+ app.manage(AppState {
+ db: Mutex::new(conn),
+ });
+ Ok(())
+ })
+ .run(tauri::generate_context!())
+ .expect("error while running tauri application");
+}
+```
+
+- [ ] **Step 8: Verify it compiles and runs**
+
+```bash
+cd /home/leclere/Projets/orchai/src-tauri
+cargo build
+```
+
+Expected: compiles with no errors.
+
+- [ ] **Step 9: Commit**
+
+```bash
+git add -A
+git commit -m "feat: SQLite database with migration system and full schema"
+```
+
+---
+
+### Task 4: Project model + CRUD repository + tests
+
+**Files:**
+- Create: `src-tauri/src/models/mod.rs`
+- Create: `src-tauri/src/models/project.rs`
+- Modify: `src-tauri/src/lib.rs` (add `mod models`)
+
+- [ ] **Step 1: Write failing tests for Project CRUD**
+
+Create `src-tauri/src/models/mod.rs`:
+
+```rust
+pub mod project;
+```
+
+Create `src-tauri/src/models/project.rs`:
+
+```rust
+use rusqlite::{params, Connection, Result};
+use serde::{Deserialize, Serialize};
+use uuid::Uuid;
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Project {
+ pub id: String,
+ pub name: String,
+ pub path: String,
+ pub cloned_from: Option,
+ pub base_branch: String,
+ pub created_at: String,
+}
+
+impl Project {
+ pub fn insert(
+ conn: &Connection,
+ name: &str,
+ path: &str,
+ cloned_from: Option<&str>,
+ base_branch: &str,
+ ) -> Result {
+ todo!()
+ }
+
+ pub fn list(conn: &Connection) -> Result> {
+ todo!()
+ }
+
+ pub fn get_by_id(conn: &Connection, id: &str) -> Result {
+ todo!()
+ }
+
+ pub fn update(conn: &Connection, id: &str, name: &str, base_branch: &str) -> Result<()> {
+ todo!()
+ }
+
+ pub fn delete(conn: &Connection, id: &str) -> Result<()> {
+ todo!()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::db;
+
+ fn setup() -> Connection {
+ db::init_in_memory().expect("db init should succeed")
+ }
+
+ #[test]
+ fn test_insert_project_local_path() {
+ let conn = setup();
+ let project = Project::insert(&conn, "My Project", "/home/user/code/myproject", None, "main")
+ .expect("insert should succeed");
+
+ assert_eq!(project.name, "My Project");
+ assert_eq!(project.path, "/home/user/code/myproject");
+ assert!(project.cloned_from.is_none());
+ assert_eq!(project.base_branch, "main");
+ assert!(!project.id.is_empty());
+ assert!(!project.created_at.is_empty());
+ }
+
+ #[test]
+ fn test_insert_project_cloned() {
+ let conn = setup();
+ let project = Project::insert(
+ &conn,
+ "Cloned Project",
+ "/home/user/code/cloned",
+ Some("https://github.com/org/repo.git"),
+ "stable",
+ )
+ .expect("insert should succeed");
+
+ assert_eq!(project.cloned_from.as_deref(), Some("https://github.com/org/repo.git"));
+ assert_eq!(project.base_branch, "stable");
+ }
+
+ #[test]
+ fn test_list_projects_empty() {
+ let conn = setup();
+ let projects = Project::list(&conn).expect("list should succeed");
+ assert!(projects.is_empty());
+ }
+
+ #[test]
+ fn test_list_projects_returns_all() {
+ let conn = setup();
+ Project::insert(&conn, "A", "/path/a", None, "main").unwrap();
+ Project::insert(&conn, "B", "/path/b", None, "main").unwrap();
+
+ let projects = Project::list(&conn).expect("list should succeed");
+ assert_eq!(projects.len(), 2);
+ }
+
+ #[test]
+ fn test_get_by_id() {
+ let conn = setup();
+ let created = Project::insert(&conn, "Test", "/path/test", None, "main").unwrap();
+ let found = Project::get_by_id(&conn, &created.id).expect("get should succeed");
+
+ assert_eq!(found.id, created.id);
+ assert_eq!(found.name, "Test");
+ }
+
+ #[test]
+ fn test_get_by_id_not_found() {
+ let conn = setup();
+ let result = Project::get_by_id(&conn, "nonexistent");
+ assert!(result.is_err());
+ }
+
+ #[test]
+ fn test_update_project() {
+ let conn = setup();
+ let created = Project::insert(&conn, "Old Name", "/path", None, "main").unwrap();
+
+ Project::update(&conn, &created.id, "New Name", "develop").expect("update should succeed");
+
+ let updated = Project::get_by_id(&conn, &created.id).unwrap();
+ assert_eq!(updated.name, "New Name");
+ assert_eq!(updated.base_branch, "develop");
+ }
+
+ #[test]
+ fn test_delete_project() {
+ let conn = setup();
+ let created = Project::insert(&conn, "ToDelete", "/path", None, "main").unwrap();
+
+ Project::delete(&conn, &created.id).expect("delete should succeed");
+
+ let result = Project::get_by_id(&conn, &created.id);
+ assert!(result.is_err());
+ }
+}
+```
+
+Add `mod models;` to `src-tauri/src/lib.rs` (after `mod error;`).
+
+- [ ] **Step 2: Run tests to verify they fail**
+
+```bash
+cd /home/leclere/Projets/orchai/src-tauri
+cargo test models::project::tests
+```
+
+Expected: 8 failures with `not yet implemented`.
+
+- [ ] **Step 3: Implement Project CRUD**
+
+Replace the `todo!()` implementations in `src-tauri/src/models/project.rs`:
+
+```rust
+ pub fn insert(
+ conn: &Connection,
+ name: &str,
+ path: &str,
+ cloned_from: Option<&str>,
+ base_branch: &str,
+ ) -> Result {
+ let id = Uuid::new_v4().to_string();
+ let now = chrono::Utc::now().to_rfc3339();
+
+ conn.execute(
+ "INSERT INTO projects (id, name, path, cloned_from, base_branch, created_at) VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
+ params![id, name, path, cloned_from, base_branch, now],
+ )?;
+
+ Ok(Project {
+ id,
+ name: name.to_string(),
+ path: path.to_string(),
+ cloned_from: cloned_from.map(String::from),
+ base_branch: base_branch.to_string(),
+ created_at: now,
+ })
+ }
+
+ pub fn list(conn: &Connection) -> Result> {
+ let mut stmt = conn.prepare(
+ "SELECT id, name, path, cloned_from, base_branch, created_at FROM projects ORDER BY created_at DESC",
+ )?;
+ let rows = stmt.query_map([], |row| {
+ Ok(Project {
+ id: row.get(0)?,
+ name: row.get(1)?,
+ path: row.get(2)?,
+ cloned_from: row.get(3)?,
+ base_branch: row.get(4)?,
+ created_at: row.get(5)?,
+ })
+ })?;
+ rows.collect()
+ }
+
+ pub fn get_by_id(conn: &Connection, id: &str) -> Result {
+ conn.query_row(
+ "SELECT id, name, path, cloned_from, base_branch, created_at FROM projects WHERE id = ?1",
+ params![id],
+ |row| {
+ Ok(Project {
+ id: row.get(0)?,
+ name: row.get(1)?,
+ path: row.get(2)?,
+ cloned_from: row.get(3)?,
+ base_branch: row.get(4)?,
+ created_at: row.get(5)?,
+ })
+ },
+ )
+ }
+
+ pub fn update(conn: &Connection, id: &str, name: &str, base_branch: &str) -> Result<()> {
+ conn.execute(
+ "UPDATE projects SET name = ?1, base_branch = ?2 WHERE id = ?3",
+ params![name, base_branch, id],
+ )?;
+ Ok(())
+ }
+
+ pub fn delete(conn: &Connection, id: &str) -> Result<()> {
+ conn.execute("DELETE FROM projects WHERE id = ?1", params![id])?;
+ Ok(())
+ }
+```
+
+Add the missing imports at the top of the file:
+
+```rust
+use chrono;
+```
+
+- [ ] **Step 4: Run tests to verify they pass**
+
+```bash
+cd /home/leclere/Projets/orchai/src-tauri
+cargo test models::project::tests
+```
+
+Expected: 8 tests pass.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add -A
+git commit -m "feat: Project model with CRUD operations and tests"
+```
+
+---
+
+### Task 5: Project Tauri commands
+
+**Files:**
+- Create: `src-tauri/src/commands/mod.rs`
+- Create: `src-tauri/src/commands/project.rs`
+- Modify: `src-tauri/src/lib.rs`
+- Modify: `src-tauri/Cargo.toml` (add tauri-plugin-dialog)
+
+- [ ] **Step 1: Add dialog plugin dependency**
+
+Add to `src-tauri/Cargo.toml` under `[dependencies]`:
+
+```toml
+tauri-plugin-dialog = "2"
+```
+
+Add to the `capabilities/default.json` permissions array:
+
+```json
+"dialog:default"
+```
+
+- [ ] **Step 2: Create commands module**
+
+Create `src-tauri/src/commands/mod.rs`:
+
+```rust
+pub mod project;
+```
+
+Create `src-tauri/src/commands/project.rs`:
+
+```rust
+use crate::error::AppError;
+use crate::models::project::Project;
+use crate::AppState;
+use std::process::Command;
+use tauri::State;
+
+#[tauri::command]
+pub fn create_project(
+ state: State<'_, AppState>,
+ name: String,
+ path_or_url: String,
+ base_branch: String,
+) -> Result {
+ let is_url = path_or_url.starts_with("http://")
+ || path_or_url.starts_with("https://")
+ || path_or_url.starts_with("git@");
+
+ let (local_path, cloned_from) = if is_url {
+ let home = dirs::home_dir().ok_or_else(|| AppError::from("Cannot determine home directory".to_string()))?;
+ let clone_dir = home.join("orchai-repos").join(&name);
+ std::fs::create_dir_all(&clone_dir)?;
+
+ let output = Command::new("git")
+ .args(["clone", &path_or_url, clone_dir.to_str().unwrap()])
+ .output()?;
+
+ if !output.status.success() {
+ let stderr = String::from_utf8_lossy(&output.stderr);
+ return Err(AppError::from(format!("git clone failed: {}", stderr)));
+ }
+
+ (clone_dir.to_string_lossy().to_string(), Some(path_or_url))
+ } else {
+ let path = std::path::Path::new(&path_or_url);
+ if !path.exists() {
+ return Err(AppError::from(format!("Path does not exist: {}", path_or_url)));
+ }
+ if !path.join(".git").exists() {
+ return Err(AppError::from(format!("Not a git repository: {}", path_or_url)));
+ }
+ (path_or_url, None)
+ };
+
+ let db = state.db.lock().unwrap();
+ let project = Project::insert(&db, &name, &local_path, cloned_from.as_deref(), &base_branch)?;
+ Ok(project)
+}
+
+#[tauri::command]
+pub fn list_projects(state: State<'_, AppState>) -> Result, AppError> {
+ let db = state.db.lock().unwrap();
+ let projects = Project::list(&db)?;
+ Ok(projects)
+}
+
+#[tauri::command]
+pub fn get_project(state: State<'_, AppState>, id: String) -> Result {
+ let db = state.db.lock().unwrap();
+ let project = Project::get_by_id(&db, &id)?;
+ Ok(project)
+}
+
+#[tauri::command]
+pub fn update_project(
+ state: State<'_, AppState>,
+ id: String,
+ name: String,
+ base_branch: String,
+) -> Result<(), AppError> {
+ let db = state.db.lock().unwrap();
+ Project::update(&db, &id, &name, &base_branch)?;
+ Ok(())
+}
+
+#[tauri::command]
+pub fn delete_project(state: State<'_, AppState>, id: String) -> Result<(), AppError> {
+ let db = state.db.lock().unwrap();
+ Project::delete(&db, &id)?;
+ Ok(())
+}
+```
+
+- [ ] **Step 3: Add `dirs` dependency**
+
+Add to `src-tauri/Cargo.toml` under `[dependencies]`:
+
+```toml
+dirs = "5"
+```
+
+- [ ] **Step 4: Wire up commands in lib.rs**
+
+Replace `src-tauri/src/lib.rs`:
+
+```rust
+mod commands;
+mod db;
+mod error;
+mod models;
+
+use std::sync::Mutex;
+
+pub struct AppState {
+ pub db: Mutex,
+}
+
+#[cfg_attr(mobile, tauri::mobile_entry_point)]
+pub fn run() {
+ tauri::Builder::default()
+ .plugin(tauri_plugin_dialog::init())
+ .setup(|app| {
+ let db_dir = app.path().app_data_dir()?;
+ std::fs::create_dir_all(&db_dir)?;
+ let db_path = db_dir.join("orchai.db");
+ let conn = db::init(&db_path).expect("Failed to initialize database");
+ app.manage(AppState {
+ db: Mutex::new(conn),
+ });
+ Ok(())
+ })
+ .invoke_handler(tauri::generate_handler![
+ commands::project::create_project,
+ commands::project::list_projects,
+ commands::project::get_project,
+ commands::project::update_project,
+ commands::project::delete_project,
+ ])
+ .run(tauri::generate_context!())
+ .expect("error while running tauri application");
+}
+```
+
+- [ ] **Step 5: Verify it compiles**
+
+```bash
+cd /home/leclere/Projets/orchai/src-tauri
+cargo build
+```
+
+Expected: compiles with no errors.
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add -A
+git commit -m "feat: Tauri commands for project CRUD with git clone support"
+```
+
+---
+
+### Task 6: TypeScript types + Tauri API wrappers
+
+**Files:**
+- Create: `src/lib/types.ts`
+- Create: `src/lib/api.ts`
+
+- [ ] **Step 1: Install frontend dependencies**
+
+```bash
+cd /home/leclere/Projets/orchai
+npm install react-router-dom
+npm install @tauri-apps/plugin-dialog
+```
+
+- [ ] **Step 2: Create TypeScript types**
+
+Create `src/lib/types.ts`:
+
+```typescript
+export interface Project {
+ id: string;
+ name: string;
+ path: string;
+ cloned_from: string | null;
+ base_branch: string;
+ created_at: string;
+}
+```
+
+- [ ] **Step 3: Create API wrapper**
+
+Create `src/lib/api.ts`:
+
+```typescript
+import { invoke } from "@tauri-apps/api/core";
+import type { Project } from "./types";
+
+export async function createProject(
+ name: string,
+ pathOrUrl: string,
+ baseBranch: string
+): Promise {
+ return invoke("create_project", {
+ name,
+ pathOrUrl,
+ baseBranch,
+ });
+}
+
+export async function listProjects(): Promise {
+ return invoke("list_projects");
+}
+
+export async function getProject(id: string): Promise {
+ return invoke("get_project", { id });
+}
+
+export async function updateProject(
+ id: string,
+ name: string,
+ baseBranch: string
+): Promise {
+ return invoke("update_project", { id, name, baseBranch });
+}
+
+export async function deleteProject(id: string): Promise {
+ return invoke("delete_project", { id });
+}
+```
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add -A
+git commit -m "feat: TypeScript types and Tauri API wrappers for project CRUD"
+```
+
+---
+
+### Task 7: React app shell with router + layout
+
+**Files:**
+- Modify: `src/App.tsx`
+- Create: `src/components/layout/AppLayout.tsx`
+- Create: `src/components/layout/Sidebar.tsx`
+
+- [ ] **Step 1: Create Sidebar component**
+
+Create directory structure:
+
+```bash
+mkdir -p src/components/layout src/components/projects
+```
+
+Create `src/components/layout/Sidebar.tsx`:
+
+```tsx
+import { useEffect, useState } from "react";
+import { Link, useParams } from "react-router-dom";
+import { listProjects } from "../../lib/api";
+import type { Project } from "../../lib/types";
+
+export default function Sidebar() {
+ const [projects, setProjects] = useState([]);
+ const { projectId } = useParams();
+
+ useEffect(() => {
+ listProjects().then(setProjects);
+ }, []);
+
+ // Expose a refresh function via custom event
+ useEffect(() => {
+ const handler = () => {
+ listProjects().then(setProjects);
+ };
+ window.addEventListener("orchai:refresh-projects", handler);
+ return () => window.removeEventListener("orchai:refresh-projects", handler);
+ }, []);
+
+ return (
+
+
+
Orchai
+
+
+
+
+
+ Projects
+
+
+ +
+
+
+
+ {projects.map((project) => (
+
+ {project.name}
+
+ ))}
+
+ {projects.length === 0 && (
+ No projects yet
+ )}
+
+
+ );
+}
+```
+
+- [ ] **Step 2: Create AppLayout component**
+
+Create `src/components/layout/AppLayout.tsx`:
+
+```tsx
+import { Outlet } from "react-router-dom";
+import Sidebar from "./Sidebar";
+
+export default function AppLayout() {
+ return (
+
+
+
+
+
+
+ );
+}
+```
+
+- [ ] **Step 3: Set up router in App.tsx**
+
+Replace `src/App.tsx`:
+
+```tsx
+import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
+import AppLayout from "./components/layout/AppLayout";
+
+function EmptyState() {
+ return (
+
+
Select a project or create a new one
+
+ );
+}
+
+function App() {
+ return (
+
+
+ }>
+ } />
+ Create project (coming next)} />
+ Project dashboard (coming next)} />
+ Edit project (coming next)} />
+ } />
+
+
+
+ );
+}
+
+export default App;
+```
+
+- [ ] **Step 4: Verify the shell renders**
+
+```bash
+npm run tauri dev
+```
+
+Expected: window opens with dark sidebar on the left showing "Orchai" header, "Projects" section with "No projects yet" message, and a "+" button. Main area shows "Select a project or create a new one". Close after verifying.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add -A
+git commit -m "feat: React app shell with router, sidebar layout"
+```
+
+---
+
+### Task 8: Project list + create/edit form
+
+**Files:**
+- Create: `src/components/projects/ProjectForm.tsx`
+- Create: `src/components/projects/ProjectDashboard.tsx`
+- Modify: `src/App.tsx`
+
+- [ ] **Step 1: Create ProjectForm component**
+
+Create `src/components/projects/ProjectForm.tsx`:
+
+```tsx
+import { useState, useEffect } from "react";
+import { useNavigate, useParams } from "react-router-dom";
+import { open } from "@tauri-apps/plugin-dialog";
+import { createProject, getProject, updateProject } from "../../lib/api";
+
+export default function ProjectForm() {
+ const navigate = useNavigate();
+ const { projectId } = useParams();
+ const isEditing = Boolean(projectId);
+
+ const [name, setName] = useState("");
+ const [pathOrUrl, setPathOrUrl] = useState("");
+ const [baseBranch, setBaseBranch] = useState("main");
+ const [mode, setMode] = useState<"local" | "clone">("local");
+ const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ if (projectId) {
+ getProject(projectId).then((project) => {
+ setName(project.name);
+ setPathOrUrl(project.path);
+ setBaseBranch(project.base_branch);
+ if (project.cloned_from) {
+ setMode("clone");
+ }
+ });
+ }
+ }, [projectId]);
+
+ async function handleBrowse() {
+ const selected = await open({ directory: true, multiple: false });
+ if (selected) {
+ setPathOrUrl(selected as string);
+ }
+ }
+
+ async function handleSubmit(e: React.FormEvent) {
+ e.preventDefault();
+ setError(null);
+ setLoading(true);
+
+ try {
+ if (isEditing && projectId) {
+ await updateProject(projectId, name, baseBranch);
+ } else {
+ await createProject(name, pathOrUrl, baseBranch);
+ }
+ window.dispatchEvent(new Event("orchai:refresh-projects"));
+ navigate("/");
+ } catch (err: unknown) {
+ const message = err instanceof Error ? err.message : String(err);
+ setError(message);
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ return (
+
+
+ {isEditing ? "Edit project" : "New project"}
+
+
+
+
+ );
+}
+```
+
+- [ ] **Step 2: Create ProjectDashboard placeholder**
+
+Create `src/components/projects/ProjectDashboard.tsx`:
+
+```tsx
+import { useEffect, useState } from "react";
+import { useParams, Link, useNavigate } from "react-router-dom";
+import { getProject, deleteProject } from "../../lib/api";
+import type { Project } from "../../lib/types";
+
+export default function ProjectDashboard() {
+ const { projectId } = useParams();
+ const navigate = useNavigate();
+ const [project, setProject] = useState(null);
+
+ useEffect(() => {
+ if (projectId) {
+ getProject(projectId).then(setProject);
+ }
+ }, [projectId]);
+
+ async function handleDelete() {
+ if (!projectId) return;
+ if (!window.confirm(`Delete project "${project?.name}"?`)) return;
+
+ await deleteProject(projectId);
+ window.dispatchEvent(new Event("orchai:refresh-projects"));
+ navigate("/");
+ }
+
+ if (!project) {
+ return Loading...
;
+ }
+
+ return (
+
+
+
{project.name}
+
+
+ Edit
+
+
+ Delete
+
+
+
+
+
+
+ Path:
+ {project.path}
+
+ {project.cloned_from && (
+
+ Cloned from:
+ {project.cloned_from}
+
+ )}
+
+ Base branch:
+ {project.base_branch}
+
+
+ Created:
+ {new Date(project.created_at).toLocaleDateString()}
+
+
+
+
+ Tracker surveillance and ticket processing will be available in the next update.
+
+
+ );
+}
+```
+
+- [ ] **Step 3: Wire up routes in App.tsx**
+
+Replace `src/App.tsx`:
+
+```tsx
+import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
+import AppLayout from "./components/layout/AppLayout";
+import ProjectForm from "./components/projects/ProjectForm";
+import ProjectDashboard from "./components/projects/ProjectDashboard";
+
+function EmptyState() {
+ return (
+
+
Select a project or create a new one
+
+ );
+}
+
+function App() {
+ return (
+
+
+ }>
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+ );
+}
+
+export default App;
+```
+
+- [ ] **Step 4: Verify the full flow in the browser**
+
+```bash
+npm run tauri dev
+```
+
+Test the following:
+1. Click "+" in the sidebar to navigate to the create form
+2. Fill in a name, select "Local folder", browse to an existing git repo, set base branch
+3. Click "Create" -- project appears in sidebar
+4. Click the project in sidebar -- dashboard shows project details
+5. Click "Edit" -- form pre-fills with project data
+6. Click "Delete" -- project removed from sidebar
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add -A
+git commit -m "feat: project create/edit/delete UI with folder picker and git clone"
+```
+
+---
+
+### Task 9: Final verification + cleanup
+
+**Files:**
+- Verify all tests pass
+- Clean up any scaffold files not needed
+
+- [ ] **Step 1: Run all Rust tests**
+
+```bash
+cd /home/leclere/Projets/orchai/src-tauri
+cargo test
+```
+
+Expected: all tests pass (8 model tests + 3 db tests = 11 tests).
+
+- [ ] **Step 2: Run Rust clippy**
+
+```bash
+cd /home/leclere/Projets/orchai/src-tauri
+cargo clippy -- -D warnings
+```
+
+Expected: no warnings. If there are warnings, fix them.
+
+- [ ] **Step 3: Verify frontend builds**
+
+```bash
+cd /home/leclere/Projets/orchai
+npm run build
+```
+
+Expected: Vite build succeeds.
+
+- [ ] **Step 4: Clean up scaffold files**
+
+Remove any remaining scaffold assets that are not needed:
+- `src/assets/react.svg` (if still present)
+- Any other default scaffold content
+
+- [ ] **Step 5: Final integration test**
+
+```bash
+npm run tauri dev
+```
+
+Test the complete flow one more time:
+1. Create a project pointing to a local git repo
+2. Verify it appears in sidebar
+3. View project dashboard
+4. Edit project name and base branch
+5. Delete the project
+6. Verify sidebar is empty again
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add -A
+git commit -m "cleanup: remove scaffold assets, verify all tests pass"
+```