| title | Dispatch — Build Roadmap v2 |
|---|---|
| type | feat |
| status | active |
| date | 2026-03-19 |
| version | v2 |
| origin | ROADMAP-REVIEW.md + ROADMAP.md + PRD.md + research/* |
- The roadmap now starts with an explicit architecture lock. v1 jumped straight into scaffold work while key ownership questions were still unresolved; that would have caused rework in PTY, filesystem, and settings.
- PTY lifecycle is now single-owner:
create_terminal_session()creates and persists sessions in Rust, andGET /ws/terminal/:session_idonly attaches to an existing session. Reconnects attach; they do not spawn. - Direct CLI dispatch no longer uses shell-string templates. Agent definitions are now structured as
program,args[],env, andcwd, with typed placeholder resolution and zero shell parsing. - Canonical storage ownership is fixed. Rust owns SQLite via
rusqlite, Rust owns filesystem access and watching, and Rust owns secrets via OS keychain access. The frontend only uses typed Tauri commands and keeps non-authoritative UI state locally. agent_sessionsmoves into the early foundation schema, not late polish. Task/session linkage and automatic task state transitions are part of the core flow, not an afterthought.- Save points are moved out of branch history and into
refs/dispatch/*. Pre-run save points are always created, even when the repo is clean. - Verification is now part of every phase: Rust unit/integration tests, React component tests, and smoke tests are all assigned where the risk actually appears.
- Heavy tabs now use lazy-mount-once, not blanket mount-everything. Agents, Files, History, and Chat keep state after first open; lighter surfaces do not stay alive unnecessarily.
- Browser is removed from the v1 critical path. If implemented after release, it is limited to localhost iframe preview only.
- PRD drift is closed. Kanban markdown export and auto agent-pick are added to v1. Voice input is explicitly deferred to post-v1 because it introduces audio permission and transcription backend decisions that are not required to ship the core command center.
| Domain | Owner | Implementation |
|---|---|---|
| Projects, tasks, agent sessions, chat cache, non-secret settings | Rust backend | rusqlite = 0.39.0 + SQL migrations in src-tauri/migrations/ |
| Secrets | Rust backend | keyring = 3.6.3; if keychain write is unavailable, Dispatch falls back to inherited env vars and does not persist the secret elsewhere |
| Filesystem read/search/watch | Rust backend | notify = 8.2.0, ignore = 0.4.25, grep-searcher = 0.1.16, grep-regex = 0.1.14 |
| PTY lifecycle and transport | Rust backend | portable-pty = 0.9.0 + axum = 0.8.8 websocket attach server |
| OpenClaw connectivity | Rust backend | tokio-tungstenite = 0.28.0 + reqwest = 0.12.28 |
| Frontend state | React frontend | Zustand for UI state only; no direct DB or filesystem access |
- No
tauri-plugin-sql, notauri-plugin-store, and notauri-plugin-fsin v1. - No shell-string interpolation for agent dispatch.
- No implicit git repo initialization. History v1 works only for existing git repositories.
- No branch-history pollution for save points. Use
refs/dispatch/*. - No arbitrary filesystem access from the frontend. Every path must resolve under a registered project root.
- No background process without cleanup ownership. PTYs, watchers, and OpenClaw sockets must be shut down or reconciled on restart.
Dispatch v1 ships:
- Multi-project desktop shell
- Project CRUD and persistent data foundation
- Embedded terminals and direct CLI dispatch
- Kanban with task/session linkage and markdown export
- Files tab with safe Rust-owned browsing/search/watch
- History v1 with Dispatch ref save points and restore
- Thin OpenClaw integration
- Full text chat and orchestrated review loop
- Settings, notifications, shortcuts, packaging
Dispatch post-v1:
- Voice input / Whisper capture
- Browser preview tab
- Branch-safe "revert as new branch commit" workflow
- PR creation/review automation
| Package | Version |
|---|---|
@tauri-apps/cli |
2.10.1 |
@tauri-apps/api |
2.10.1 |
react |
19.2.4 |
react-dom |
19.2.4 |
typescript |
5.9.3 |
vite |
8.0.12 |
@vitejs/plugin-react |
6.0.1 |
tailwindcss |
4.2.2 |
@tailwindcss/vite |
4.2.2 |
zustand |
5.0.12 |
immer |
11.1.4 |
react-resizable-panels |
4.7.3 |
@dnd-kit/core |
6.3.1 |
@dnd-kit/sortable |
10.0.0 |
@tanstack/react-virtual |
3.13.23 |
@xterm/xterm |
6.0.0 |
@xterm/addon-fit |
0.11.0 |
@xterm/addon-search |
0.16.0 |
@xterm/addon-web-links |
0.12.0 |
@xterm/addon-webgl |
0.19.0 |
react-markdown |
10.1.0 |
remark-gfm |
4.0.1 |
rehype-highlight |
7.0.2 |
rehype-slug |
6.0.0 |
highlight.js |
11.11.1 |
react-hotkeys-hook |
5.2.4 |
cmdk |
1.1.1 |
lucide-react |
0.577.0 |
@tauri-apps/plugin-dialog |
2.6.0 |
@tauri-apps/plugin-notification |
2.3.3 |
@tauri-apps/plugin-opener |
2.5.3 |
@tauri-apps/plugin-global-shortcut |
2.3.1 |
vitest |
4.1.0 |
@testing-library/react |
16.3.2 |
@testing-library/user-event |
14.6.1 |
@playwright/test |
1.58.2 |
| Crate | Version |
|---|---|
tauri |
2.10.3 |
tokio |
1.50.0 |
axum |
0.8.8 |
rusqlite |
0.39.0 |
portable-pty |
0.9.0 |
git2 |
0.20.4 |
notify |
8.2.0 |
ignore |
0.4.25 |
grep-searcher |
0.1.16 |
grep-regex |
0.1.14 |
grep-matcher |
0.1.8 |
tracing |
0.1.44 |
tracing-subscriber |
0.3.23 |
tracing-appender |
0.2.4 |
tokio-tungstenite |
0.28.0 |
reqwest |
0.12.28 |
keyring |
3.6.3 |
tauri-plugin-dialog |
2.6.0 |
tauri-plugin-opener |
2.5.3 |
tauri-plugin-notification |
2.3.3 |
tauri-plugin-global-shortcut |
2.3.1 |
repo-root/
├── docs/
│ ├── adr/
│ ├── checklists/
│ └── test-strategy.md
├── scripts/
│ └── smoke/
├── src/
│ ├── app/
│ ├── features/
│ │ ├── agents/
│ │ ├── chat/
│ │ ├── files/
│ │ ├── history/
│ │ ├── projects/
│ │ ├── settings/
│ │ └── tasks/
│ ├── shared/
│ └── styles/
├── src-tauri/
│ ├── capabilities/
│ ├── migrations/
│ ├── src/
│ │ ├── commands/
│ │ ├── db/
│ │ ├── models/
│ │ ├── services/
│ │ ├── app_state.rs
│ │ ├── error.rs
│ │ ├── logging.rs
│ │ ├── lib.rs
│ │ └── main.rs
│ └── tests/
└── package.json
| Phase | Name | Outcome | Est. Effort |
|---|---|---|---|
0A |
Architecture Lock | Runtime boundaries, schema, verification, and scope frozen | 0.5-1 day |
0B |
Scaffold + Shell | Running desktop shell with lazy tab host and test harness | 1-1.5 days |
1 |
Projects + Persistence Foundation | Project CRUD, schema, settings, secrets, logging | 1.5-2 days |
2 |
Terminal Core | Persistent PTY sessions with attach-only websocket transport | 2-3 days |
3 |
Direct Dispatch | Structured agent registry and safe CLI dispatch | 1-2 days |
4 |
Tasks | Kanban, markdown export, dispatch bindings | 1.5-2 days |
5 |
Files | Safe Rust-owned file browser, search, and watch | 1.5-2 days |
6 |
History v1 | Dispatch ref save points, diff, restore | 2-3 days |
7 |
OpenClaw Thin Integration | Connection, spawn/list/kill, orchestrated sessions in Agents | 1-2 days |
8 |
Full Chat + Review Loop | Chat tab, streaming, review automation | 2-3 days |
9 |
System Integration + Polish | Settings, shortcuts, notifications, cleanup, perf | 1.5-2 days |
10 |
Packaging + Release | CI, Linux builds, metrics, release artifacts | 1-1.5 days |
11 |
Browser Experimental | Post-v1 localhost iframe preview only | post-v1 |
Goal: Freeze the build contract before code: ownership, schema, session model, cleanup rules, and verification strategy.
Files
docs/adr/0001-runtime-boundaries.mddocs/adr/0002-terminal-lifecycle.mddocs/adr/0003-data-model.mddocs/adr/0004-history-save-points.mddocs/checklists/v1-scope.mddocs/test-strategy.md
Dependencies in play
tauri 2.10.3react 19.2.4rusqlite 0.39.0portable-pty 0.9.0git2 0.20.4notify 8.2.0
Commands
mkdir -p repo-root/docs/adr repo-root/docs/checklists repo-root/scripts/smoke
node -v && rustc -V && cargo -VImplementation
- Write the ownership ADR that states Rust owns SQLite, secrets, filesystem, PTYs, and OpenClaw connectivity.
- Write the terminal lifecycle ADR that states:
create_terminal_session()is the only PTY creation entry point.dispatch_agent()internally calls that same creation path.GET /ws/terminal/:session_idonly attaches to an existing session.- websocket disconnect does not kill the PTY.
- Write the data model ADR with the initial schema:
projectstasksagent_sessionssettings
- Lock task state semantics:
workflow_state:draft | planning | in_progress | review | donelast_run_state:idle | running | succeeded | failed | canceled | abandoned
- Lock secret precedence:
- app keychain secret if set
- inherited environment variable if keychain value is absent
- missing/blank if neither exists
- Lock v1 deferrals:
- no voice input
- no Browser tab in release scope
- no branch-history revert workflow
- Write the test strategy doc with the exact lanes:
- Rust unit tests in
src-tauri/src/** - Rust integration/smoke tests in
src-tauri/tests/** - React component tests in
src/**/__tests__/** - shell smoke scripts in
scripts/smoke/**
- Rust unit tests in
Verification
- Review the ADR set against
PRD.mdandROADMAP-REVIEW.mdand confirm every contradiction is resolved in writing. - Add a checklist section to
docs/checklists/v1-scope.mdfor:- PTY single creation point
- structured dispatch model
- one DB owner
- one filesystem owner
agent_sessionsin early schema- verification in every phase
Done when
- The four ADRs exist and are internally consistent.
- The task state model, session lifecycle, and save-point namespace are frozen.
- v1 vs post-v1 scope is explicit enough that implementation can start without re-deciding fundamentals.
Goal: Create the Tauri app, test harness, dark shell, and lazy tab host.
Files
package.jsonvite.config.tstsconfig.jsonsrc/main.tsxsrc/app/App.tsxsrc/app/TabHost.tsxsrc/app/providers.tsxsrc/shared/components/TopBar.tsxsrc/shared/components/TabBar.tsxsrc/shared/components/ErrorBoundary.tsxsrc/features/projects/ProjectsPlaceholder.tsxsrc/features/agents/AgentsPlaceholder.tsxsrc/features/tasks/TasksPlaceholder.tsxsrc/features/files/FilesPlaceholder.tsxsrc/features/history/HistoryPlaceholder.tsxsrc/features/chat/ChatPlaceholder.tsxsrc/store/index.tssrc/store/uiSlice.tssrc/styles/globals.csssrc-tauri/src/main.rssrc-tauri/src/lib.rssrc-tauri/src/logging.rssrc-tauri/src/error.rssrc-tauri/capabilities/default.jsonsrc/app/__tests__/TabHost.test.tsxsrc-tauri/tests/app_boot_smoke.rsscripts/smoke/phase-0b-shell.sh
Dependencies introduced
- Frontend:
@tauri-apps/api 2.10.1react 19.2.4react-dom 19.2.4vite 8.0.1@vitejs/plugin-react 6.0.1tailwindcss 4.2.2@tailwindcss/vite 4.2.2zustand 5.0.12immer 11.1.4react-resizable-panels 4.7.3lucide-react 0.577.0vitest 4.1.0@testing-library/react 16.3.2@testing-library/user-event 14.6.1
- Rust:
tauri 2.10.3tokio 1.50.0tracing 0.1.44tracing-subscriber 0.3.23tracing-appender 0.2.4
Commands
npm create tauri-app@latest dispatch
cd repo root
npm install tailwindcss@4.2.2 @tailwindcss/vite@4.2.2 zustand@5.0.12 immer@11.1.4 react-resizable-panels@4.7.3 lucide-react@0.577.0
npm install -D vitest@4.1.0 @testing-library/react@16.3.2 @testing-library/user-event@14.6.1
npm run tauri devImplementation
- Scaffold the app at
repo root. - Replace the default app with the Dispatch shell and six top-level tabs.
- Implement lazy-mount-once tab behavior:
- persistent after first open: Agents, Files, History, Chat
- normal mount/unmount: Tasks, Settings overlays
- Add
logging.rsearly:- write app logs to
AppLog/dispatch.log - rotate file appender
- install panic hook that writes
panic-<timestamp>.log
- write app logs to
- Add
ErrorBoundaryaround each tab surface. - Add a health command in
lib.rsso smoke tests can boot the app state without feature code.
Verification
- Rust:
cargo test --manifest-path src-tauri/Cargo.toml app_boot_smoke
- Component:
npm run test -- src/app/__tests__/TabHost.test.tsx
- Smoke:
bash scripts/smoke/phase-0b-shell.sh
Done when
npm run tauri devopens a native Dispatch window.- The top bar and tab shell render in the intended dark theme.
- Heavy tabs do not mount until opened once.
dispatch.logis written under the Tauri app log directory on first boot.
Goal: Establish the canonical SQLite schema, project CRUD, safe path authority, and secret handling.
Files
src-tauri/migrations/001_init.sqlsrc-tauri/src/db/mod.rssrc-tauri/src/db/migrate.rssrc-tauri/src/models/project.rssrc-tauri/src/models/task.rssrc-tauri/src/models/agent_session.rssrc-tauri/src/models/setting.rssrc-tauri/src/services/project_registry.rssrc-tauri/src/services/path_guard.rssrc-tauri/src/services/secrets.rssrc-tauri/src/commands/projects.rssrc-tauri/src/commands/settings.rssrc/shared/lib/tauri.tssrc/features/projects/ProjectSwitcher.tsxsrc/features/projects/AddProjectDialog.tsxsrc/features/projects/__tests__/ProjectSwitcher.test.tsxsrc-tauri/tests/projects_db_tests.rssrc-tauri/tests/path_guard_tests.rsscripts/smoke/phase-1-projects.sh
Dependencies introduced
- Frontend:
@tauri-apps/plugin-dialog 2.6.0
- Rust:
rusqlite 0.39.0keyring 3.6.3tauri-plugin-dialog 2.6.0
Commands
cd repo root
npm install @tauri-apps/plugin-dialog@2.6.0
cargo test --manifest-path src-tauri/Cargo.toml projects_db_testsImplementation
- Create
001_init.sqlwith:projectstasksagent_sessionssettings
- Required columns:
tasks:workflow_state,last_run_state,last_session_id,assigned_agent_mode,markdown_export_pathagent_sessions:project_id,task_id,source,session_kind,status,program,args_json,env_keys_json,cwd,transport,exit_code,started_at,ended_at
- Implement
path_guard.rs:- canonicalize project roots
- reject symlink/path traversal escapes
- expose
assert_project_relative(project_id, relative_path)
- Implement
secrets.rs:set_secret(key, value)get_secret_status(key)returningkeychain | env | missingclear_secret(key)- never return the stored secret to the frontend after initial set
- Add project CRUD commands.
- Persist non-secret settings in SQLite, not JSON store.
- Update
ProjectSwitcher.tsxto load real projects and add/remove them via typed Tauri commands.
Verification
- Rust:
cargo test --manifest-path src-tauri/Cargo.toml projects_db_testscargo test --manifest-path src-tauri/Cargo.toml path_guard_tests
- Component:
npm run test -- src/features/projects/__tests__/ProjectSwitcher.test.tsx
- Smoke:
bash scripts/smoke/phase-1-projects.sh
Done when
- Projects can be added, listed, removed, and reloaded after restart.
- The SQLite DB is created only by Rust and stored in the Tauri app data directory.
agent_sessionsexists before any terminal code lands.- Path traversal outside a registered project root is rejected in tests.
- Secret status can be shown in Settings without storing secrets in SQLite or JS.
Goal: Ship durable PTY sessions with correct attach/reconnect semantics and cleanup.
Files
src-tauri/src/services/pty_manager.rssrc-tauri/src/services/terminal_ws.rssrc-tauri/src/services/session_supervisor.rssrc-tauri/src/commands/terminal.rssrc/features/agents/AgentsTab.tsxsrc/features/agents/TerminalPanel.tsxsrc/features/agents/SessionSidebar.tsxsrc/features/agents/store/agentsSlice.tssrc/features/agents/__tests__/TerminalPanel.test.tsxsrc-tauri/tests/pty_manager_tests.rssrc-tauri/tests/terminal_ws_attach_tests.rsscripts/smoke/phase-2-terminal-core.sh
Dependencies introduced
- Frontend:
@xterm/xterm 6.0.0@xterm/addon-fit 0.11.0@xterm/addon-search 0.16.0@xterm/addon-web-links 0.12.0@xterm/addon-webgl 0.19.0
- Rust:
axum 0.8.8portable-pty 0.9.0
Commands
cd repo root
npm install @xterm/xterm@6.0.0 @xterm/addon-fit@0.11.0 @xterm/addon-search@0.16.0 @xterm/addon-web-links@0.12.0 @xterm/addon-webgl@0.19.0
cargo test --manifest-path src-tauri/Cargo.toml pty_manager_testsImplementation
- Implement
create_terminal_session(project_id, shell, cwd)inpty_manager.rsas the single PTY creation point. - Persist every PTY-backed session immediately in
agent_sessions. - Implement
GET /ws/terminal/:session_idinterminal_ws.rsto:- load existing session
- reject missing/finished sessions
- attach websocket I/O only
- never spawn a process
- Support:
- binary PTY output frames
- typed resize message
- reconnect attach
- multi-session sidebar
- Add cleanup rules in
session_supervisor.rs:- on kill: SIGTERM, then SIGKILL after timeout
- on app close: drain all sessions
- on startup: mark stale running sessions as
abandoned
- Write session logs under
AppLog/sessions/<session_id>.log. - Wire terminal readiness so the frontend asks for the websocket port only after Axum bind completes.
Verification
- Rust:
cargo test --manifest-path src-tauri/Cargo.toml pty_manager_testscargo test --manifest-path src-tauri/Cargo.toml terminal_ws_attach_tests
- Component:
npm run test -- src/features/agents/__tests__/TerminalPanel.test.tsx
- Smoke:
bash scripts/smoke/phase-2-terminal-core.sh
Done when
- A terminal session is created exactly once, stored in SQLite, and can be reattached after websocket disconnect.
- Resizing sends explicit PTY resize events and updates the child process correctly.
- Multiple terminal sessions can remain alive while switching tabs.
- Session cleanup on kill and app shutdown is covered by tests.
Goal: Add safe structured agent dispatch, task/session linkage, and direct CLI execution without shell parsing.
Files
src-tauri/migrations/002_agent_profiles.sqlsrc-tauri/src/models/agent_profile.rssrc-tauri/src/services/agent_registry.rssrc-tauri/src/services/dispatch.rssrc-tauri/src/commands/dispatch.rssrc/features/agents/DispatchModal.tsxsrc/features/agents/AgentSessionToolbar.tsxsrc/features/settings/AgentRegistryPane.tsxsrc/features/agents/__tests__/DispatchModal.test.tsxsrc-tauri/tests/dispatch_validation_tests.rssrc-tauri/tests/task_transition_tests.rsscripts/smoke/phase-3-direct-dispatch.sh
Dependencies introduced
- Frontend:
react 19.2.4zustand 5.0.12@xterm/xterm 6.0.0
- Rust:
rusqlite 0.39.0portable-pty 0.9.0axum 0.8.8
Commands
cd repo root
cargo test --manifest-path src-tauri/Cargo.toml dispatch_validation_tests
cargo test --manifest-path src-tauri/Cargo.toml task_transition_testsImplementation
- Add
agent_profilespersistence in SQLite, seeded with Codex, Claude Code, and Gemini defaults. - Replace command templates with a structured model:
struct AgentProfile {
id: String,
name: String,
program: String,
args: Vec<AgentArg>,
env: HashMap<String, AgentEnvValue>,
cwd: AgentCwd,
}
enum AgentArg {
Literal(String),
Prompt,
ProjectPath,
TaskTitle,
TaskBody,
}- Resolve the profile into
program,Vec<String>,env, andcwdin Rust and feed that directly into PTY spawn. No shell, no quoting pass, nosh -c. - Add optional
task_idtodispatch_agent(). - On dispatch from a task:
- set
workflow_state = in_progress - set
last_run_state = running - store
last_session_id
- set
- On direct session completion:
- exit
0->workflow_state = review,last_run_state = succeeded - non-zero -> keep
workflow_state = in_progress, setlast_run_state = failed - killed/canceled -> set
last_run_state = canceled
- exit
- Add sidebar badges for
session_kindandstatus. - Add
Copy Outputand full-screen terminal actions. - Add "Auto" as the first agent option:
- standalone mode resolves to last-used local agent for the project
- OpenClaw-aware auto-pick is activated in Phase 7
Verification
- Rust:
cargo test --manifest-path src-tauri/Cargo.toml dispatch_validation_testscargo test --manifest-path src-tauri/Cargo.toml task_transition_tests
- Component:
npm run test -- src/features/agents/__tests__/DispatchModal.test.tsx
- Smoke:
bash scripts/smoke/phase-3-direct-dispatch.sh
Done when
- Direct dispatch passes prompts with quotes/newlines safely because each argument is a real argv entry.
- Task-linked dispatch updates task workflow and run state automatically.
- Custom agents can be added without introducing shell injection risk.
- Session completion and failure are visible in both the Agents tab and the linked task.
Goal: Build the Kanban board, task editing, and markdown export with direct dispatch wiring.
Files
src-tauri/src/commands/tasks.rssrc-tauri/src/services/task_export.rssrc/features/tasks/TasksTab.tsxsrc/features/tasks/KanbanBoard.tsxsrc/features/tasks/KanbanColumn.tsxsrc/features/tasks/KanbanCard.tsxsrc/features/tasks/TaskDetailDrawer.tsxsrc/features/tasks/store/tasksSlice.tssrc/features/tasks/__tests__/KanbanBoard.test.tsxsrc-tauri/tests/task_export_tests.rsscripts/smoke/phase-4-tasks.sh
Dependencies introduced
@dnd-kit/core 6.3.1@dnd-kit/sortable 10.0.0@tanstack/react-virtual 3.13.23react-markdown 10.1.0remark-gfm 4.0.1rehype-highlight 7.0.2rehype-slug 6.0.0highlight.js 11.11.1
Commands
cd repo root
npm install @dnd-kit/core@6.3.1 @dnd-kit/sortable@10.0.0 @tanstack/react-virtual@3.13.23 react-markdown@10.1.0 remark-gfm@4.0.1 rehype-highlight@7.0.2 rehype-slug@6.0.0 highlight.js@11.11.1
npm run test -- src/features/tasks/__tests__/KanbanBoard.test.tsxImplementation
- Implement 5 workflow columns: Draft, Planning, In Progress, Review, Done.
- Surface
last_run_stateon each card as a badge. - Build task create/edit/delete and drag/drop.
- Reuse
DispatchModal.tsxfrom Phase 3 for "Send to Agent". - Add markdown export in
task_export.rs:- export path:
<project-root>/dispatch/tasks/<task-id>-<slug>.md - include frontmatter:
id,project_id,workflow_state,priority,last_run_state,last_session_id - update export on create, edit, status change, and session transition
- export path:
- Add import-friendly markdown body containing description, subtasks, and review notes.
- Keep the task board scoped to
activeProjectId.
Verification
- Rust:
cargo test --manifest-path src-tauri/Cargo.toml task_export_tests
- Component:
npm run test -- src/features/tasks/__tests__/KanbanBoard.test.tsx
- Smoke:
bash scripts/smoke/phase-4-tasks.sh
Done when
- Tasks can be created, edited, reordered, and dispatched.
- Markdown exports are written under
dispatch/tasks/inside each project root. - Task cards reflect real session state without manual refresh.
- Dispatching from a task produces both a session row and a linked markdown export update.
Goal: Ship a safe, Rust-owned Files tab with tree navigation, preview, search, and file watching.
Files
src-tauri/src/services/project_fs.rssrc-tauri/src/services/file_watch.rssrc-tauri/src/commands/files.rssrc/features/files/FilesTab.tsxsrc/features/files/FileTree.tsxsrc/features/files/FilePreview.tsxsrc/features/files/store/filesSlice.tssrc/features/files/__tests__/FilePreview.test.tsxsrc-tauri/tests/project_fs_tests.rssrc-tauri/tests/file_watch_tests.rsscripts/smoke/phase-5-files.sh
Dependencies introduced
- Frontend:
@tauri-apps/plugin-opener 2.5.3
- Rust:
notify 8.2.0ignore 0.4.25grep-searcher 0.1.16grep-regex 0.1.14grep-matcher 0.1.8tauri-plugin-opener 2.5.3
Commands
cd repo root
npm install @tauri-apps/plugin-opener@2.5.3
cargo test --manifest-path src-tauri/Cargo.toml project_fs_tests
cargo test --manifest-path src-tauri/Cargo.toml file_watch_testsImplementation
- All file commands accept
project_idplus project-relative paths. No absolute path entry from the frontend. - Implement:
list_project_tree(project_id, root_relative_path)read_project_file(project_id, relative_path)search_project_paths(project_id, query)search_project_content(project_id, query)open_in_editor(project_id, relative_path)
- Use
ignorefor.gitignorefiltering and traversal. - Use
notifywatchers managed by Rust:- start on first Files tab open
- stop on project switch or app shutdown
- debounce change events and emit typed frontend refresh events
- Keep Files tab lazy-mounted-once so preview state persists, but do not watch projects that have never opened the Files tab.
Verification
- Rust:
cargo test --manifest-path src-tauri/Cargo.toml project_fs_testscargo test --manifest-path src-tauri/Cargo.toml file_watch_tests
- Component:
npm run test -- src/features/files/__tests__/FilePreview.test.tsx
- Smoke:
bash scripts/smoke/phase-5-files.sh
Done when
- The file tree is always derived from Rust-owned project scope.
.gitignoreentries are hidden.- Full-text search works without shelling out to system
rg. - File watching stops and starts cleanly during project switches.
Goal: Build a safe snapshot history on refs/dispatch/* for existing git repos only.
Files
src-tauri/migrations/003_save_points.sqlsrc-tauri/src/services/history/mod.rssrc-tauri/src/services/history/save_points.rssrc-tauri/src/services/history/diff.rssrc-tauri/src/services/history/restore.rssrc-tauri/src/commands/history.rssrc/features/history/HistoryTab.tsxsrc/features/history/SavePointList.tsxsrc/features/history/DiffViewer.tsxsrc/features/history/__tests__/HistoryTab.test.tsxsrc-tauri/tests/save_point_tests.rssrc-tauri/tests/history_restore_tests.rsscripts/smoke/phase-6-history.sh
Dependencies introduced
git2 0.20.4
Commands
cd repo root
cargo test --manifest-path src-tauri/Cargo.toml save_point_tests
cargo test --manifest-path src-tauri/Cargo.toml history_restore_tests
npm run test -- src/features/history/__tests__/HistoryTab.test.tsxImplementation
- Add
save_pointsmetadata table with:project_idrun_idref_namecommit_oidbase_head_oidstagecreated_at
- History v1 only activates for an existing git repository discovered under the project root.
- Create save points under:
refs/dispatch/runs/<run_id>/prerefs/dispatch/runs/<run_id>/postrefs/dispatch/manual/<timestamp>refs/dispatch/latest
- Always create the pre-run save point, even if the repo tree matches the last snapshot.
- Use a synthetic Dispatch signature, never the user's git identity.
- The v1 restore action restores the workspace snapshot from a Dispatch ref. It does not create a visible branch commit automatically.
- Single-file restore is in scope.
- Hook save-point creation into direct dispatch and orchestrated dispatch lifecycle.
Verification
- Rust:
cargo test --manifest-path src-tauri/Cargo.toml save_point_testscargo test --manifest-path src-tauri/Cargo.toml history_restore_tests
- Component:
npm run test -- src/features/history/__tests__/HistoryTab.test.tsx
- Smoke:
bash scripts/smoke/phase-6-history.sh
Done when
- Save points live under
refs/dispatch/*, not on the active branch. - A clean repo still gets a pre-run anchor.
- The UI can list, diff, and restore Dispatch save points for existing repos.
- History v1 never initializes git automatically in a non-git project.
Goal: Connect Dispatch to OpenClaw early enough to support orchestrated sessions without blocking standalone mode.
Files
src-tauri/src/services/openclaw/client.rssrc-tauri/src/services/openclaw/protocol.rssrc-tauri/src/services/openclaw/session_bridge.rssrc-tauri/src/commands/openclaw.rssrc/features/agents/OrchestratedSessionView.tsxsrc/features/agents/__tests__/OpenClawStatus.test.tsxsrc-tauri/tests/openclaw_client_tests.rsscripts/smoke/phase-7-openclaw.sh
Dependencies introduced
- Rust:
tokio-tungstenite 0.28.0reqwest 0.12.28
Commands
cd repo root
cargo test --manifest-path src-tauri/Cargo.toml openclaw_client_tests
bash scripts/smoke/phase-7-openclaw.shImplementation
- Implement connection state management:
disconnectedconnectingconnectedreconnecting
- Add Rust commands for:
- connect
- disconnect
- status
- sessions list
- sessions spawn
- sessions send
- sessions kill
- Show orchestrated sessions in the Agents sidebar with a distinct badge.
- Activate the "Dispatch via OpenClaw" button when connected.
- Upgrade the "Auto" agent mode:
- connected -> OpenClaw picks the agent
- disconnected -> fallback to last-used direct CLI agent
- Wire task state transitions for orchestrated sessions using the same rules as direct dispatch.
Verification
- Rust:
cargo test --manifest-path src-tauri/Cargo.toml openclaw_client_tests
- Component:
npm run test -- src/features/agents/__tests__/OpenClawStatus.test.tsx
- Smoke:
bash scripts/smoke/phase-7-openclaw.sh
Done when
- Dispatch can connect to a local OpenClaw gateway and list/spawn/kill sessions.
- Orchestrated sessions appear in the same Agents sidebar as PTY sessions.
- Auto-pick behaves correctly in connected and standalone modes.
- Session completion/failure continues to drive task transitions automatically.
Goal: Ship the primary chat surface and the task review loop that turns completed sessions into reviewable work.
Files
src-tauri/migrations/004_chat_cache.sqlsrc-tauri/src/services/openclaw/chat.rssrc-tauri/src/services/review_router.rssrc/features/chat/ChatTab.tsxsrc/features/chat/MessageList.tsxsrc/features/chat/ChatInput.tsxsrc/features/chat/store/chatSlice.tssrc/features/chat/__tests__/ChatTab.test.tsxsrc-tauri/tests/chat_stream_tests.rssrc-tauri/tests/review_router_tests.rsscripts/smoke/phase-8-chat-review.sh
Dependencies introduced
- Frontend:
react 19.2.4react-markdown 10.1.0remark-gfm 4.0.1rehype-highlight 7.0.2
- Rust:
tokio-tungstenite 0.28.0reqwest 0.12.28rusqlite 0.39.0
Commands
cd repo root
cargo test --manifest-path src-tauri/Cargo.toml chat_stream_tests
cargo test --manifest-path src-tauri/Cargo.toml review_router_tests
npm run test -- src/features/chat/__tests__/ChatTab.test.tsxImplementation
- Build the Chat tab with:
- streaming message list
- markdown rendering
- syntax-highlighted code blocks
- model selector
- project context badge
- Cache chat history in SQLite via Rust commands.
- Add the review router:
- task reaches
reviewafter successful session completion - if automated review is enabled, Dispatch sends the review request through OpenClaw
- OpenClaw response routes:
- pass ->
workflow_state = done - fail ->
workflow_state = in_progress, review note populated
- pass ->
- task reaches
- Keep Agents tab stream view for orchestrated sessions.
- Explicitly defer voice input:
- no microphone button in v1 release
- add backlog note in code and docs, not a half-implemented UI
Verification
- Rust:
cargo test --manifest-path src-tauri/Cargo.toml chat_stream_testscargo test --manifest-path src-tauri/Cargo.toml review_router_tests
- Component:
npm run test -- src/features/chat/__tests__/ChatTab.test.tsx
- Smoke:
bash scripts/smoke/phase-8-chat-review.sh
Done when
- Chat streams reliably from OpenClaw and survives reconnects.
- A successful session can move a task from
reviewtodonevia the review router. - A failed review can move a task back to
in_progresswith feedback stored. - Voice input is explicitly documented as post-v1, not silently omitted.
Goal: Finish the product surface: settings, notifications, shortcuts, cleanup visibility, and performance hardening.
Files
src/features/settings/SettingsDialog.tsxsrc/features/settings/ConnectionSettings.tsxsrc/features/settings/SecretsPane.tsxsrc/features/settings/ProjectsPane.tsxsrc/features/settings/AgentRegistryPane.tsxsrc/shared/components/CommandPalette.tsxsrc/shared/hooks/useAppHotkeys.tssrc-tauri/src/services/tray.rssrc-tauri/src/commands/window.rssrc-tauri/src/commands/notifications.rssrc/features/settings/__tests__/SettingsDialog.test.tsxsrc-tauri/tests/settings_secret_tests.rsscripts/smoke/phase-9-system.sh
Dependencies introduced
- Frontend:
cmdk 1.1.1react-hotkeys-hook 5.2.4@tauri-apps/plugin-notification 2.3.3@tauri-apps/plugin-global-shortcut 2.3.1
- Rust:
tauri-plugin-notification 2.3.3tauri-plugin-global-shortcut 2.3.1
Commands
cd repo root
npm install cmdk@1.1.1 react-hotkeys-hook@5.2.4 @tauri-apps/plugin-notification@2.3.3 @tauri-apps/plugin-global-shortcut@2.3.1
cargo test --manifest-path src-tauri/Cargo.toml settings_secret_tests
npm run test -- src/features/settings/__tests__/SettingsDialog.test.tsxImplementation
- Build the Settings dialog with sections for:
- OpenClaw connection
- projects
- secret status and set/clear flows
- agent registry
- terminal preferences
- notifications
- about/build info
- Add desktop notifications for:
- task completed
- task failed
- review complete
- Add system tray behavior:
- closing the window hides to tray instead of destroying the app
- tray tooltip shows running session count
- tray menu exposes Show, New Terminal, and Quit
- Add global shortcut to focus Dispatch.
- Add command palette actions for:
- create task
- new terminal
- dispatch selected task
- create manual save point
- open settings
- Add performance hardening:
- watchers only active when needed
- session logs discoverable from the Settings/About pane
- error boundaries on all major surfaces
Verification
- Rust:
cargo test --manifest-path src-tauri/Cargo.toml settings_secret_tests
- Component:
npm run test -- src/features/settings/__tests__/SettingsDialog.test.tsx
- Smoke:
bash scripts/smoke/phase-9-system.sh
Done when
- Users can configure OpenClaw, projects, agent profiles, and secret storage without raw file edits.
- Notifications and shortcuts work without destabilizing the app.
- Cleanup state is visible enough that stale sessions and log paths are debuggable.
Goal: Produce a releasable Linux build with CI, metrics, and documented install steps.
Files
src-tauri/tauri.conf.json.github/workflows/release.ymlREADME.mdscripts/smoke/phase-10-release.shsrc-tauri/tests/release_smoke.rs
Dependencies introduced
- Frontend:
@tauri-apps/cli 2.10.1
- Rust:
tauri 2.10.3
Commands
cd repo root
npm run tauri build
cargo test --manifest-path src-tauri/Cargo.toml release_smoke
bash scripts/smoke/phase-10-release.shImplementation
- Package Linux targets:
.AppImage.deb
- Build CI on Ubuntu 20.04 for glibc compatibility.
- Add release checklist for:
- startup time
- idle memory
- terminal spawn latency
- save-point creation latency
- Document build prerequisites and installation steps in
README.md. - Set release version to
0.1.0.
Verification
- Rust:
cargo test --manifest-path src-tauri/Cargo.toml release_smoke
- Smoke:
bash scripts/smoke/phase-10-release.sh
- Release metrics:
- cold start under 2 seconds on target Linux box
- direct dispatch spawn under 3 seconds
Done when
- CI produces
.AppImageand.debartifacts. - A clean Linux test box can install and launch the app.
- Release docs and smoke checklist are complete enough for repeatable packaging.
Goal: If Browser returns after release, keep it deliberately narrow: localhost iframe preview only.
Files
src/features/browser/BrowserTab.tsxsrc/features/browser/AddressBar.tsxsrc/features/browser/store/browserSlice.tssrc/features/browser/__tests__/BrowserTab.test.tsxscripts/smoke/phase-11-browser.sh
Dependencies introduced
- Frontend:
react 19.2.4vite 8.0.1
Commands
cd repo root
npm run test -- src/features/browser/__tests__/BrowserTab.test.tsx
bash scripts/smoke/phase-11-browser.shImplementation
- Limit preview targets to
http://localhost:*andhttp://127.0.0.1:*. - Use iframe preview only.
- No arbitrary external browsing.
- No separate multi-webview manager.
- No DevTools embedding.
- Keep the feature behind an experimental flag until it proves stable.
Verification
- Component:
npm run test -- src/features/browser/__tests__/BrowserTab.test.tsx
- Smoke:
bash scripts/smoke/phase-11-browser.sh
Done when
- Browser is explicitly post-v1.
- Any future implementation is constrained to safe localhost preview scope.
agent_sessionsexists before PTY or OpenClaw work starts.- PTY creation happens in one place only.
- WebSocket routes attach only.
- Direct CLI dispatch uses structured argv, env, and cwd.
- Rust is the only DB owner.
- Rust is the only filesystem owner.
- Secrets never land in SQLite or JSON settings.
- Save points live in
refs/dispatch/*. - Every phase has Rust tests, component tests, or a smoke script assigned.
- Browser and voice input are explicitly out of the v1 critical path.