# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Build & Run Commands ```sh pnpm install # install deps pnpm dev # start all apps in dev mode (API :4000, Web :3000) pnpm build # prod build all packages pnpm lint # ESLint across all packages pnpm typecheck # strict TypeScript checks pnpm test # run all Vitest tests pnpm format # Prettier format pnpm clean # remove dist + .turbo + node_modules ``` Single package: ```sh pnpm --filter @yetanother/web dev pnpm --filter @yetanother/api dev pnpm --filter @yetanother/api exec prisma studio pnpm --filter @yetanother/web test:watch pnpm --filter @yetanother/web test:e2e # Playwright pnpm --filter @yetanother/web test -- -t "test name" ``` Database: ```sh pnpm --filter @yetanother/db db:migrate # create migration pnpm --filter @yetanother/db db:deploy # apply migrations (prod) pnpm --filter @yetanother/db db:seed # seed dev data pnpm --filter @yetanother/db exec prisma generate # regen Prisma client ``` Desktop (Tauri v2, requires Rust): ```sh cd apps/desktop && pnpm tauri dev cd apps/desktop && pnpm tauri build ``` Infra: ```sh docker compose up -d postgres redis meilisearch docker compose up --build # full prod build ``` ## Tech Stack - **Monorepo**: Turborepo + pnpm workspaces (pnpm 11+) - **Frontend**: React 19 + TypeScript strict, Vite 6, Tailwind CSS 4, shadcn/ui-style (Radix primitives) - **State**: Zustand (global stores), TanStack React Query (server), Jotai (local UI), react-router-dom v7 - **Editor**: TipTap / ProseMirror (block-based with tables, task lists, code blocks, slash menu) - **Backend**: Fastify 5, Prisma ORM + PostgreSQL 16 (pgvector), Redis (ioredis), Zod validation - **Real-time**: Yjs (CRDT) + WebSocket (fastify-websocket), y-prosemirror, y-indexeddb - **Search**: Meilisearch (full-text) + pgvector (semantic embeddings) - **API**: REST (under `/api/v1/`) + GraphQL (Mercurius at `/graphql`) - **Desktop**: Tauri v2 (Rust) - **Testing**: Vitest + React Testing Library + jsdom, Playwright (E2E) - **i18n**: i18next + react-i18next - **Auth**: JWT (fastify-jwt) with refresh tokens, bcryptjs ## Architecture ### Monorepo layout ``` apps/ api/ — Fastify server (REST + GraphQL + WebSocket) web/ — React SPA (Vite, Tailwind, TipTap editor) desktop/ — Tauri wrapper around web app mobile/ — React Native (not started) packages/ db/ — Prisma schema + client, seed data types/ — Zod schemas + TypeScript types (shared frontend/backend) ui/ — Radix-based component library (button, dialog, tabs, toast, tooltip) hooks/ — Shared React hooks (useDebounce, useLocalStorage, useMediaQuery) utils/ — Shared utilities (cn, constants) tooling/ eslint/ — Shared ESLint configs (base, node, react) typescript/ — Shared TS configs (base, node, react) ``` ### Data model (Node-based) Everything is a **Node** — tasks, events, notes, projects, goals share a unified schema. Nodes have polymorphic types, parent/child hierarchies, typed links (references, blocks, relates_to, etc.), tags, and version-based optimistic concurrency. Embeddings store pgvector vectors for semantic search. ### API patterns - Routes registered in `apps/api/src/main.ts` under `/api/v1` prefix - Auth via JWT with refresh token rotation (cookie + bearer) - Route handlers: `app.get('/nodes', async (request) => { ... })` with `app.prisma` decorator - Zod body validation inline in route files - Background: Meilisearch indexing + activity logging fire-and-forget (.catch(() => {})) - Error handling: `AppError` class + `handleError` helper (Zod errors → 400, AppError → statusCode, else → 500) ### Frontend patterns - Zustand stores for UI state (stores/notes.ts, stores/tasks.ts, stores/calendar.ts) - API client in `lib/api.ts` with auto-refresh on 401 - TipTap editor with custom extensions, toolbar, slash menu - React.lazy route splitting for notes/tasks/calendar pages - Calendar: drag-and-drop events, multi-view (day/week/month), event dialog - Tasks: drag-and-drop reorder (dnd-kit), views (list/board/timeline), NL date parsing (chrono-node) ### Key constraints - All objects belong to a workspace (multi-tenant via workspaceId) - Soft delete via `status: 'deleted'` on nodes - Optimistic concurrency on node updates via `version` field - Pre-commit: Husky runs lint-staged (ESLint + Prettier) - Commit style: conventional commits (`feat:`, `fix:`, `refactor:`, etc.)