Developer-facing map of project structure, architecture, and key files.
vividkit-app/
├── src/ # React frontend (118 TS/TSX files)
│ ├── components/ # 65 UI components organized by module
│ │ ├── brainstorm/ # AI ideation: terminal, insights, reports
│ │ ├── cook/ # Task execution: terminal, progress, preview
│ │ ├── dashboard/ # Homepage: stats cards, quick actions
│ │ ├── decks/ # Project organization: cards, lists
│ │ ├── generate-plan/ # Plan wizard with phase indicator
│ │ ├── layout/ # Sidebar, header, app shell
│ │ ├── new-project/ # Project creation form
│ │ ├── onboarding/ # 4-step wizard (welcome, git, AI, setup)
│ │ ├── plans/ # Plan review: checklist, preview, cook
│ │ ├── settings/ # App config: 4 tabs + CCS test console
│ │ ├── tasks/ # Task board: list + kanban views
│ │ ├── ui/ # 15 shadcn/ui primitives
│ │ └── worktrees/ # Worktree management: list, merge
│ ├── hooks/ # Custom hooks (extract logic >10 lines)
│ ├── lib/ # Utilities
│ │ ├── tauri.ts # Tauri IPC wrappers (spawn_ccs, stop_ccs, etc.)
│ │ └── utils.ts # Format, parse, helpers
│ ├── locales/ # i18n JSON (vi default, en secondary) split by domain
│ ├── pages/ # 14 route pages (lazy-loaded)
│ ├── stores/ # 8 Zustand stores (project, task, deck, etc.)
│ ├── types/ # TypeScript interfaces (project, task, plan, etc.)
│ ├── App.tsx # Root component, routing
│ ├── router.tsx # React Router v6 config
│ └── main.tsx # Entry point
│
├── src-tauri/src/ # Rust backend (11 files, ~306 LOC)
│ ├── commands/ # Tauri command handlers
│ │ ├── ai.rs # spawn_ccs, stop_ccs, CCS process management
│ │ ├── fs.rs # File system operations (stubs)
│ │ ├── git.rs # Git operations (stubs, git2 integration pending)
│ │ ├── worktree.rs # Git worktree lifecycle (stubs)
│ │ └── mod.rs # Command module exports
│ ├── models/ # Serde structs for IPC & DB
│ │ ├── config.rs # AppConfig (provider, theme)
│ │ ├── project.rs # Project model
│ │ ├── task.rs # Task model
│ │ └── mod.rs # Model exports
│ ├── lib.rs # Plugin registration, state management
│ └── main.rs # Tauri app entry point
│
├── docs/ # Developer documentation
│ ├── project-overview-pdr.md # Product requirements & vision
│ ├── codebase-summary.md # This file
│ ├── system-architecture.md # Deep architecture docs
│ ├── code-standards.md # Coding conventions
│ ├── design-system.md # UI design tokens, typography, components
│ ├── development-roadmap.md # 9-phase MVP timeline
│ └── project-changelog.md # Change history
│
├── plans/ # Implementation plans with phase files
│ ├── 260222-1340-vividkit-project-setup/
│ ├── 260222-2244-screen-implementation/
│ └── reports/ # Scout, researcher, tester reports
│
├── public/ # Static assets
├── Cargo.toml # Rust dependencies
├── package.json # Node dependencies
├── tsconfig.json # TypeScript config
├── vite.config.ts # Vite build config
├── tauri.conf.json # Tauri app metadata
├── CLAUDE.md # Critical rules for all developers
├── README.md # Project overview
└── repomix-output.xml # Codebase compaction (generated)
React Component (UI only)
↓
Custom Hook (logic, state subscriptions)
↓
Zustand Store (state, actions)
↓
invoke() wrapper (src/lib/tauri.ts)
↓
Rust Command Handler
↓
PTY / Filesystem / Git
Each module (brainstorm, cook, tasks, etc.) contains:
- UI components (
*.tsx— presentational only) - No async logic in components
- Props-driven rendering
- Side effects in custom hooks
Max 200 lines per file — split if exceeded.
// src/hooks/useXxx.ts
export function useMyFeature() {
const store = useMyStore()
const [state, setState] = useState()
useEffect(() => {
invoke('command_name', args).then(...)
}, [deps])
return { state, actions }
}// src/stores/my-store.ts
interface MyStore {
items: Item[]
selectedId: string | null
setSelected: (id: string) => void
}
export const useMyStore = create<MyStore>((set) => ({
items: [],
selectedId: null,
setSelected: (id) => set({ selectedId: id }),
}))One store per domain; actions defined inside create().
Typed wrappers around invoke():
export async function spawnCcs(args: SpawnCcsArgs): Promise<SpawnCcsResult> {
return invoke<SpawnCcsResult>('spawn_ccs', args)
}
export async function stopCcs(runId: string): Promise<StopCcsResult> {
return invoke<StopCcsResult>('stop_ccs', { run_id: runId })
}Every wrapper has:
- Typed arguments
- Typed return
- Error handling (try/catch at call site)
Each command is a Tauri #[tauri::command] function:
#[tauri::command]
pub async fn spawn_ccs(
window: tauri::Window,
run_id: String,
profile: String,
command: Option<String>,
cwd: Option<String>,
) -> Result<SpawnCcsResult, String> {
// Implementation
}Return pattern: Always Result<T, String> — no exceptions.
Error handling: Use .map_err(|e| e.to_string()) with ? operator.
CcsProcessRegistry — manages active PTY processes:
pub struct CcsProcessRegistry {
runs: Mutex<HashMap<String, CommandChild>>,
}spawn_ccs flow:
- Generate
run_id - Spawn PTY child process
- Store child in registry
- Return
{ run_id, pid }to frontend - Stream output events with run_id envelope
- On terminal: remove from registry, emit final event
stop_ccs flow:
- Lookup
run_idin registry - Remove entry first (idempotent)
- Kill process
- Return
{ run_id, stopped: bool }
Events sent via emit_to with run-scoped payload:
window.emit_to(
window.label(),
"ccs_run_event",
CcsRunEvent {
run_id: run_id.clone(),
kind: "stdout".to_string(),
chunk: Some(output),
code: None,
message: None,
}
)Frontend filters by activeRunId to avoid cross-run mixing.
Serde structs for IPC serialization:
Project— id, name, gitPath, ccsConnectedTask— id, title, status, priority, typeCcsAccount— provider, email, status- All have
#[derive(Serialize, Deserialize)]
User clicks "Run" in CCS Test Console (Settings)
↓
handleRunCcs() invokes spawn_ccs({ profile, command, cwd })
↓
Rust: spawn_ccs generates run_id, spawns PTY, returns immediately
↓
Frontend: stores activeRunId, registers event listener
↓
Rust: streams ccs_run_event { run_id, kind: "stdout", chunk }
↓
Frontend: filters by activeRunId, writes chunk to xterm.js terminal
↓
Loop until: ccs_run_event { run_id, kind: "terminated"|"error", code }
↓
User clicks "Stop" (optional)
↓
Frontend: invokes stop_ccs(activeRunId)
↓
Rust: kills process, removes from registry, emits "terminated"
↓
Frontend: transitions to "done" state, terminal stays alive for review
| File | Purpose | Lines |
|---|---|---|
src/App.tsx |
Root component, routing | ~50 |
src/router.tsx |
React Router config, 14 routes | ~100 |
src/lib/tauri.ts |
Typed IPC wrappers | ~80 |
src/stores/project-store.ts |
Project state & actions | ~50 |
src/components/settings/ccs-test-console.tsx |
CCS runner + terminal (dev tool) | ~200 |
src/components/cook/cook-terminal.tsx |
Task execution terminal | ~150 |
src-tauri/src/commands/ai.rs |
spawn_ccs, stop_ccs, process mgmt | ~174 |
src-tauri/src/lib.rs |
State registration, command dispatch | ~26 |
src-tauri/Cargo.toml |
Rust deps (tauri, git2, tokio, etc.) | ~40 |
package.json |
Node deps (React, Tailwind, Zustand, etc.) | ~50 |
- ✅ Tauri v2 setup + React 18 skeleton
- ✅ Routing (13 routes, 14 pages)
- ✅ AppLayout (sidebar, header, collapsible)
- ✅ All 65 UI components (mostly shells)
- ✅ Zustand stores (8 domains)
- ✅ IPC layer + typed wrappers
- ✅ spawn_ccs + stop_ccs with process registry
- ✅ CCS Test Console (run, stream, stop)
- ✅ xterm.js terminal integration
- ✅ Event envelope (run-scoped)
- 🔄 Brainstorm terminal streaming
- 🔄 Cook terminal with task context
- 🔄 Task Kanban board functionality
- ⬜ git.rs — git2 integration (init, status, commit, branch)
- ⬜ fs.rs — file operations
- ⬜ worktree.rs — git worktree (create, list, merge, cleanup)
- ⬜ SQLite schema + migrations
- ✅ i18n: vi default + en secondary, domain-split JSON, react-i18next wired
| Category | Count | Total LOC |
|---|---|---|
| React components | 65 | ~5,500 |
| Pages | 14 | ~600 |
| Hooks | — | ~200 |
| Stores | 8 | ~400 |
| Types | 8 | ~300 |
| Rust commands | 4 | ~241 |
| Rust models | 4 | ~65 |
| Total | ~118 TS + 11 Rust | ~7,000+ |
Always use ? with .map_err(|e| e.to_string()).
Never concatenate with / or \. Always use PathBuf and std::path::Path.
use std::path::PathBuf;
let path = home_dir.join("projects").join("my-project");- macOS: wrap with
script -q /dev/nullfor TTY mode - Windows: use
taskkill /PIDfor process termination - Linux: native process spawning
Always call terminal.dispose() on component unmount.
useEffect(() => {
return () => {
if (terminalRef.current) {
terminalRef.current.dispose()
}
}
}, [])Never assume events are for active run. Always check:
const handleCcsEvent = (event: CcsRunEvent) => {
if (event.run_id !== activeRunId) return // Ignore stale
// Process event
}No hardcoded strings in JSX. Use:
const { t } = useTranslation()
return <div>{t('key.name')}</div>Custom hooks subscribe to stores, not useState.
- React 18, React Router v6
- TypeScript, Tailwind v4, shadcn/ui
- Zustand (state)
- xterm.js (terminal emulator)
- dnd-kit (drag & drop)
- react-markdown, axios
- @tauri-apps/api (IPC)
- tauri v2 (desktop framework)
- tauri-plugin-shell, dialog, fs, opener
- tokio v1 (async runtime)
- git2 v0.20 (git operations)
- rusqlite v0.32 (SQLite)
- serde / serde_json (serialization)
- Read
docs/code-standards.mdfor conventions - Read
CLAUDE.mdfor critical rules (cross-platform, i18n, patterns) - Reference
system-architecture.mdfor deep dives - Follow component → hook → store → IPC pattern
- Keep files under 200 lines; split by concern
- Test cross-platform before committing
Terminal output missing?
→ Check run_id filter in event handler; listener may be registered after first event.
CCS process won't stop?
→ Verify stop_ccs is being called with correct run_id from spawn response.
Path issues on Windows?
→ Use PathBuf, never string concatenation.
Component size bloated?
→ Extract logic into custom hooks in src/hooks/.
i18n key undefined?
→ Add key to both en.json and vi.json in src/locales/.