FlowTask
A local-first desktop task manager built with Tauri 2 + React + Rust. Every artifact below was generated through the OpenLogos methodology using Claude Code.
.claude-plugin with skills/ commands/ hooks/ for methodology-driven development via conversational AI coding. Requirements — Who needs this and why?
Source: logos/resources/prd/1-product-requirements/01-requirements.md
User Persona & Pain Points
Xiao Li, 28, Freelancer — juggles multiple projects, with tasks scattered across sticky notes, phone memos, and notebooks. Frequently misses deadlines because there's no single place to track everything.
Scenario Overview
| ID | Scenario | Trigger | Pain | Priority |
|---|---|---|---|---|
| S01 | New user registers and starts using | First app launch | P01, P03 | P0 |
| S02 | Login and manage today's tasks | Registered user opens app | P01, P02 | P0 |
| S03 | Organize tasks by category | User wants to classify tasks | P02 | P1 |
| S04 | Change account password | User wants to change password | P03 | P1 |
Acceptance Criteria — S01: Register
Product Design — What will users see and do?
Source: logos/resources/prd/2-product-design/1-feature-specs/01-feature-specs.md
Information Architecture
FlowTask
├── Auth Module
│ ├── Register Page (first launch)
│ └── Login Page (returning user)
└── Main App (after login)
├── Task View (default)
│ ├── Category Sidebar
│ ├── Task List
│ └── Create/Edit Task Panel
└── Account Settings
└── Change Password Form UI Prototype — Register & Main View
Source: logos/resources/prd/2-product-design/2-page-design/
Interactive HTML prototypes generated during Product Design phase — every page is designed before any code is written.
S01 Interaction Spec — Register Flow
- App launches, detects no local account → display Register page
- Register page contains: username input, password input, confirm password input, register button, "Already have an account? Log in" link
- Real-time validation: username 2-20 chars, password ≥8 chars, passwords match
- Submit success → create local account (password encrypted) → auto-login → redirect to Task View
- Task View first entry shows empty state: "No tasks yet, click + to create your first task"
| Field | Type | Required | Validation |
|---|---|---|---|
| Username | Text input | Yes | 2-20 characters |
| Password | Password input | Yes | ≥ 8 characters |
| Confirm Password | Password input | Yes | Must match password |
Implementation — From architecture to verified code
Architecture Overview
Source: logos/resources/prd/3-technical-plan/1-architecture/01-architecture-overview.md
graph TB
subgraph FE["Frontend (React 18 + TypeScript)"]
Pages["React Components
UI Pages"]
Store["Zustand Store
Global State"]
IPC["Tauri IPC
Frontend-Backend Bridge"]
end
subgraph BE["Backend (Rust Commands)"]
Commands["auth · task · category
Auth / Task CRUD / Category CRUD / bcrypt"]
end
subgraph DB["Data Layer"]
SQLite[("SQLite
Local File Storage")]
end
Pages --> Store
Store --> IPC
IPC --> Commands
Commands --> SQLite Business logic (forms, state, routing) in TypeScript; Rust handles only DB reads/writes and bcrypt encryption — minimizing Rust code while staying frontend-developer-friendly.
Scenario → Sequence Diagram
Source: logos/resources/prd/3-technical-plan/2-scenario-implementation/S01-register.md
S01: User Registration — Sequence Diagram
sequenceDiagram
participant U as User
participant FE as React Frontend
participant BE as Tauri Commands (Rust)
participant DB as SQLite
U->>FE: Step 1: Open app
FE->>BE: Step 2: invoke('check_has_user')
BE->>DB: Step 3: SELECT COUNT(*) FROM users
DB-->>BE: Step 4: return count = 0
BE-->>FE: Step 5: return { has_user: false }
FE-->>U: Step 6: Render Register page
U->>FE: Step 7: Fill username, password, confirm & click Register
FE->>FE: Step 8: Validate — name 2-20 chars, pwd ≥8, match
Note over FE: Fail → EX-8.1 / EX-8.2 / EX-8.3
FE->>BE: Step 9: invoke('register', { username, password })
BE->>DB: Step 10: SELECT id FROM users WHERE username = ?
DB-->>BE: Step 11: return empty (username available)
Note over BE: Taken → EX-10.1
BE->>BE: Step 12: bcrypt::hash(password, cost=12)
BE->>DB: Step 13: INSERT INTO users (username, password)
DB-->>BE: Step 14: return new user { id, username, created_at }
Note over BE: Write fail → EX-13.1
BE-->>FE: Step 15: return { user_id, username }
FE->>FE: Step 16: Write Zustand Store { currentUser }
FE-->>U: Step 17: Redirect to Task View (empty state) Steps
- User opens FlowTask desktop app.
- React FE calls
invoke('check_has_user')to ask Rust if any account exists locally. - Tauri Rust queries SQLite:
SELECT COUNT(*) FROM users. - SQLite returns
count = 0— no local account. - Rust returns
has_user: falseto FE. - React FE renders Register page (if account exists, show Login instead).
- User fills username, password, confirm password, clicks "Register".
- React FE validates: username 2-20 chars, password ≥8, passwords match. On failure → show error, skip backend call. → EX-8.1 / EX-8.2 / EX-8.3
- React FE calls
invoke('register')passing raw password to Rust (IPC, no network). - Rust checks username uniqueness:
SELECT id FROM users WHERE username = ?. - SQLite returns empty — username available. → EX-10.1 (if taken)
- Rust hashes password with bcrypt (cost=12). Raw password never stored.
- Rust writes user:
INSERT INTO users. → EX-13.1 (if write fails) - SQLite returns new user record (id, username, created_at).
- Rust returns session to FE.
- React FE writes to Zustand Store, establishing login session (no token needed for desktop app).
- React FE redirects to Task View with empty-state guidance.
Exception Cases
USERNAME_EXISTSDB_WRITE_ERRORAPI Specs + DB Schema
API Specification — Tauri IPC Commands
Source: logos/resources/api/tasks.yaml
Database Schema — SQLite Tables
Source: logos/resources/database/schema.yaml
Test Case Design (before code!)
Source: logos/resources/test/S01-test-cases.md
S01 Test Cases — Unit Tests (excerpt)
| ID | Description | Input | Expected |
|---|---|---|---|
UT-S01-01 | Username exactly 2 chars (lower boundary) | "ab" | Pass |
UT-S01-03 | Username 1 char (below boundary) | "a" | Error: 2-20 chars required |
UT-S01-06 | Password exactly 8 chars (lower boundary) | "Pass1234" | Pass |
UT-S01-07 | Password 7 chars (below boundary) | "Pass123" | Error: ≥ 8 chars required |
UT-S01-11 | Duplicate username → DB UNIQUE constraint | "xiaoli" (exists) | USERNAME_EXISTS |
UT-S01-14 | Password stored as bcrypt hash | "Pass1234" | Starts with $2b$ |
S01 Test Cases — Scenario Tests (excerpt)
| ID | Description | Covers |
|---|---|---|
ST-S01-01 | Full register flow: launch → register → task view | Steps 1-17 |
ST-S01-02 | Empty username → frontend intercept | EX-8.1 |
ST-S01-05 | Duplicate username → backend error | EX-10.1 |
Code Generation — Design-driven implementation via Claude Code
Source: src-tauri/src/lib.rs · src/pages/ · src-tauri/tests/orchestration.rs
Every Tauri command is annotated with the scenario step it implements. Claude Code reads the sequence diagram and API spec, then generates backend + frontend + tests in closed-loop batches.
Backend — Rust Tauri Command → maps to S01 Steps 9-15
// ── S01 Step 9-15: register ───────────────────────
#[tauri::command]
async fn register(
params: RegisterParams,
pool: tauri::State<'_, SqlitePool>,
) -> CmdResult<UserSession> {
let row = sqlx::query("SELECT COUNT(*) as count FROM users WHERE username = ?")
.bind(¶ms.username).fetch_one(pool.inner()).await
.map_err(|_| CommandError::new("DB_ERROR", "Query failed"))?;
if row.get::<i64, _>("count") > 0 {
return Err(CommandError::new("USERNAME_EXISTS", "Username taken"));
}
let pw_hash = hash(¶ms.password, DEFAULT_COST)
.map_err(|_| CommandError::new("HASH_ERROR", "Hash failed"))?;
let result = sqlx::query("INSERT INTO users (username, password) VALUES (?, ?)")
.bind(¶ms.username).bind(&pw_hash)
.execute(pool.inner()).await
.map_err(|_| CommandError::new("DB_WRITE_ERROR", "Write failed"))?;
Ok(UserSession {
user_id: result.last_insert_rowid(),
username: params.username,
tasks: vec![], categories: vec![],
})
}Orchestration Test → verifies S01 end-to-end
#[tokio::test]
async fn ot_s01_01_register_success() {
let pool = setup_db().await;
let session = cmd_register("xiaoli".into(), "Pass1234".into(), &pool)
.await.expect("register should succeed");
assert_eq!(session.username, "xiaoli");
assert!(session.user_id > 0);
assert!(session.tasks.is_empty());
}
#[tokio::test]
async fn ot_s01_02_register_username_exists() {
let pool = setup_db().await;
cmd_register("xiaoli".into(), "Pass1234".into(), &pool).await.unwrap();
let err = cmd_register("xiaoli".into(), "Other123".into(), &pool)
.await.expect_err("duplicate should fail");
assert_eq!(err.code, "USERNAME_EXISTS");
}openlogos verify — Automated Acceptance
Source: logos/resources/verify/acceptance-report.md
AC Traceability (excerpt)
S01-AC-001 Register returns UserSession with user_id → UT-S01-01 PASSS01-AC-002 Duplicate username returns USERNAME_EXISTS → UT-S01-02 PASSS02-AC-005 Create task returns complete TaskItem → UT-S02-05 PASSS03-AC-003 Delete category sets task.category_id to null → UT-S03-03 PASSS04-AC-001 Change password returns success=true → UT-S04-01 PASSChange Tracking — Every iteration is impact-aware
Traditional AI coding ("vibe coding") fixes the code but forgets the design doc, the API spec, and the test cases. Over time, documentation drifts from reality and becomes useless. OpenLogos solves this with a structured delta workflow — every change, no matter how small, must evaluate its ripple effect across the entire artifact chain.
openlogos mergeChange Propagation Rules
The change type determines the minimum update scope. A seemingly small feature request can cascade through the entire chain:
Real Example — Code-level Fix
Source: logos/changes/archive/fix-register-redirect/proposal.md
fix-register-redirect
Problem: After registration, the app didn't redirect to Task View. User was stuck on the Register page.
Root cause: useEffect condition required view === "loading", but after register, view was "register" — redirect never fired.
Impact Analysis
App.tsx — extend redirect condition to cover register & login viewsWant to try it yourself?
Clone the repo and run FlowTask locally, or start your own project with OpenLogos.