6.8 KiB
CLAUDE.md
Commands
source ~/.nvm/nvm.sh && npm start # Dev with hot-reload
source ~/.nvm/nvm.sh && npm run make # Build distributable packages
source ~/.nvm/nvm.sh && npm run package # Package without installers
source ~/.nvm/nvm.sh && npm run lint # ESLint (.ts/.tsx)
source ~/.nvm/nvm.sh && npx drizzle-kit generate # Generate migration from schema
source ~/.nvm/nvm.sh && npx drizzle-kit push # Push schema directly (dev only)
No test suite currently.
Architecture
Adiuva is a local-first Electron desktop app. The three processes communicate via a custom tRPC v11 ↔ IPC bridge (the public electron-trpc package is incompatible with tRPC v11).
Renderer (React 19) ──ipcLink──► Preload (contextBridge) ──IPC──► Main (tRPC router + SQLite)
Main Process (src/main/)
Owns the database and all business logic.
| File | Purpose |
|---|---|
index.ts |
Window creation, app lifecycle |
ipc.ts |
Bridges ipcMain to tRPC procedures |
router/index.ts |
All tRPC sub-routers merged into appRouter |
db/index.ts |
Drizzle + better-sqlite3, WAL mode, singleton getDb() |
db/schema.ts |
Table definitions: clients, projects, tasks, checkpoints, notes, taskComments |
db/vectordb.ts |
LanceDB vector store for note embeddings |
store.ts |
electron-store for persistent UI settings |
Preload (src/preload/trpc.ts)
Exposes window.electronTRPC with sendMessage() / onMessage().
Renderer (src/renderer/)
React 19 — never accesses Node APIs directly. All data through trpc.*.useQuery() / trpc.*.useMutation().
| File | Purpose |
|---|---|
lib/ipcLink.ts |
Custom TRPCLink routing through window.electronTRPC |
lib/trpc.ts |
createTRPCReact<AppRouter>() typed client |
index.tsx |
QueryClient + tRPC + Router providers |
Routing
File-based via TanStack Router (tsr.config.json at root). Route tree auto-generated at routeTree.gen.ts.
Routes: __root.tsx (AppShell layout), index, tasks, timeline, projects, notes.$noteId
tRPC Routers
health, settings, clients, projects, tasks, checkpoints, notes, taskComments, ai
Database
Schema in src/main/db/schema.ts, migrations in src/main/db/migrations/. DB created in Electron's userData as adiuva.db. On startup, initDb() runs non-destructive migrations.
To add a table/column: edit schema.ts → drizzle-kit generate → drizzle-kit push (dev) or commit the migration.
Adding a Feature (end-to-end)
- Schema —
src/main/db/schema.ts - Router — Add sub-router in
src/main/router/index.ts, merge intoappRouter - Types — Flow automatically via
AppRouterexport - UI — Components in
src/renderer/components/<feature>/, data viatrpc.*.useQuery()
AI Subsystem (src/main/ai/)
LangGraph-based agentic system with pluggable LLM providers.
Orchestrator (orchestrator.ts)
Classifies user intent → routes to a specialist agent:
| Agent | Scope | Tools |
|---|---|---|
| Project | Project-scoped Q&A | read_project_notes, add_task, get_summary, suggest_checkpoints, suggest_tasks |
| Knowledge | Cross-project search | vector_search_all |
| General | Workspace-wide | add_task |
All providers use LangChain bindTools() + ToolMessage loop (max 5 iterations).
Also exports dailyBrief() for AI-generated daily summaries (ai.dailyBrief tRPC mutation).
Streaming
sendStreamChunk(sender, token, done) over IPC 'ai:stream'. Renderer subscribes via window.electronAI.onStreamChunk() in AIChatPanel.tsx. <tool_call> blocks are filtered before display.
Providers (llm.ts)
| Provider | Model | Notes |
|---|---|---|
| OpenAI | gpt-4o-mini |
Via LangChain |
| Anthropic | claude-sonnet-4-20250514 |
Via LangChain |
| Copilot | ChatCopilot wrapper |
copilot.ts / chat-copilot.ts |
All use temperature: 0.3, streaming enabled. Provider management in provider.ts.
Token Storage (token.ts)
Three-tier fallback (keytar service name: 'adiuva'):
- keytar (OS keychain) — preferred;
keytarFailedflag skips after first failure - electron-store +
safeStorage— encrypted at rest - Plain electron-store — WSL fallback
Vector Embeddings (db/vectordb.ts)
LanceDB in {userData}/vectors/. Schema: { id, projectId, content, vector } (1536-dim, text-embedding-3-small via embeddings.ts). Embedding priority: Copilot CLI token → OpenAI token.
upsertNoteEmbedding()on note create/update (fire-and-forget)migrateNotesIfNeeded()backfills on first startupsearchNotes(query, limit=5)used by Knowledge agent
AI Approval Pattern
Tasks and checkpoints have isAiSuggested + isApproved columns. AI suggestions appear pending user approval (dashed borders in UI).
Config Notes
- Vite configs use
.mts(not.ts) — avoids ESM/CJS conflicts with electron-forge @/*path alias →src/renderer/*(TypeScript + Vite + shadcn/ui)- shadcn/ui: new-york style, neutral base color
- Icons: lucide-react only — do not introduce other icon libraries
- Tailwind 4 — CSS variable theming in
globals.css, notailwind.config.js - Notes editor: Milkdown (
@milkdown/crepe) atsrc/renderer/components/notes/MilkdownEditor.tsx
Design Context
Target User
Freelancers and solo professionals managing client work (projects, tasks, notes, timelines). Single workspace, no enterprise overhead. AI as force multiplier.
Brand
Calm, intelligent, warm. Thoughtful companion, not flashy tool. Confident and understated, never loud or gamified.
Palette
| Canvas | Primary | Secondary | Borders | |
|---|---|---|---|---|
| Light | Pinkish-white #f4edf3 |
Golden yellow #fbc881 |
Slate blue-gray #8a8ea9 |
Dusty lavender #c8c3cd |
| Dark | Near-black #0c0c0c |
Pure white | — | Dark gray #323232 |
Typography
Geist sans-serif, weights 400/500/600. Tight tracking (-1px) on headings. Body text-sm, metadata text-xs.
Visual Language
- 10px border-radius,
rounded-2xlfor chat elements - Glassmorphism on AI inputs (
backdrop-blur-xl, transparency) - Spring animations (stiffness 400, damping 30), scale-and-fade transitions
- No gamification (badges, streaks, confetti). Mature and professional
Design Principles
- Clarity over cleverness — Clear hierarchy, generous whitespace, comfortable density
- AI as quiet partner — Deeply integrated but never intrusive. Dashed borders for pending AI items, Sparkles icon as AI marker
- Warmth in restraint — Warm palette feels approachable without being playful. Dark mode trades warmth for focus
- Motion with purpose — Animations reinforce spatial relationships, never decorative
- Confidence through consistency — CSS variable tokens, shadcn/ui primitives, Geist font. Predictable, keyboard-first