8.4 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.
Vector Embeddings (db/vectordb.ts)
Provider factory (llm.ts): gpt-4o-mini (OpenAI), claude-sonnet-4-20250514 (Anthropic), or ChatCopilot wrapper — all with temperature: 0.3 and streaming enabled.
Token storage (token.ts) — two-tier fallback:
- electron-store +
safeStorage— encrypted at rest (preferred) - Plain electron-store — last resort (e.g. WSL with no keyring)
AI approval pattern: Tasks and checkpoints have isAiSuggested (bool) and isApproved (bool) columns. AI-suggested items appear in the UI pending user approval before being treated as real records.
Vector Embeddings (src/main/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
Users
Freelancers and solo professionals managing client work (projects, tasks, notes, timelines). Single workspace, no enterprise overhead. AI as force multiplier. They open the app mid-workday — often stressed — so the interface must feel immediately grounding and in control.
Brand Personality
Calm. Intelligent. Warm. A thoughtful companion, not a flashy tool. Confident and understated — never loud, gamified, or corporate. Fully original aesthetic (no external design system references; this look is intentional and owned).
Emotional Goal
When a user opens Adiuva, the first impression should communicate "everything is under control" — calm clarity over urgency. The design should lower cognitive load, not raise it.
Aesthetic Direction
- Light mode: pinkish-white canvas
#f4edf3, golden yellow primary#fbc881, slate blue-gray secondary#8a8ea9, dusty lavender borders#c8c3cd - Dark mode: near-black
#0c0c0c, pure white primary, dark gray#323232surfaces - Geist sans-serif, weights 400/500/600. Tight tracking (
-1px) on headings. Bodytext-sm, metadatatext-xs - 10px border-radius (
rounded-lg),rounded-2xlfor chat/AI elements - Glassmorphism on AI inputs (
backdrop-blur-xl, transparency, gradient border via padding-box/border-box technique) - Spring animations (stiffness 400, damping 30), scale-and-fade transitions
- No gamification (badges, streaks, confetti). Mature and professional
- Dashed borders + Sparkles icon = AI-pending state marker
Accessibility
Best-effort — not formally audited. Maintain reasonable contrast and keyboard operability without targeting a specific WCAG level.
Current Design Focus
Polish and refinement. The overall direction is solid; the priority is elevating specific areas that feel rough or inconsistent — tighter spacing, more intentional hierarchy, better empty/loading states, and smoother motion.
Design Principles
- Clarity over cleverness — Clear hierarchy, generous whitespace, comfortable density. Never sacrifice legibility for style.
- AI as quiet partner — Deeply integrated but never intrusive. Dashed borders for pending AI items, Sparkles icon as the sole AI marker. Surface AI capabilities without making them the hero.
- Warmth in restraint — The warm palette feels approachable without being playful. Dark mode trades warmth for focus. Neither mode should feel cold or aggressive.
- Motion with purpose — Spring animations reinforce spatial relationships and acknowledge state changes. Never purely decorative. Respect reduced-motion preferences where possible.
- Polish over features — Every surface should feel considered. Prefer refining what exists over introducing new complexity. The right amount of visual weight is the minimum needed.