This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Doughnut is a Personal Knowledge Management (PKM) tool combining zettelkasten-style note capture with spaced-repetition learning features and knowledge sharing capabilities.
This project uses Nix for environment management. With direnv configured, the environment auto-loads when entering the directory.
pnpm sutThis starts all services with auto-reload:
- Backend (Spring Boot with Gradle continuous build)
- Frontend (Vite + local proxy; browser http://localhost:5173 — see
docs/gcp/prod_env.mdLocal E2E / dev) - Mountebank (mock external services)
DO NOT restart services after code changes - they auto-reload.
Prefix commands with the nix wrapper:
CURSOR_DEV=true nix develop -c <command>Cursor Cloud VM has no Nix — run commands without that prefix. See .cursor/rules/cloud-agent-setup.mdc.
| Task | Command |
|---|---|
| Start all services | pnpm sut |
| Run backend tests | pnpm backend:verify |
| Run backend tests (no migration) | pnpm backend:test_only |
| Run frontend tests | pnpm frontend:test |
| Run single frontend test | pnpm -C frontend test tests/path/to/TestFile.spec.ts |
| Run E2E test (single feature) | CURSOR_DEV=true nix develop -c pnpm cypress run --spec e2e_test/features/path/to.feature |
| Open Cypress IDE | pnpm cy:open |
| Format all code | pnpm format:all |
| Lint all code | pnpm lint:all |
| Format shared API test fixtures only (Biome) | pnpm test-fixtures:format |
| Lint shared API test fixtures only (Biome) | pnpm test-fixtures:lint |
| Regenerate TypeScript from OpenAPI | pnpm generateTypeScript |
| Connect to local DB | mysql -S $MYSQL_HOME/mysql.sock -u doughnut -p (password: doughnut) |
Unless you are already inside nix develop, wrap pnpm / Gradle invocations like the E2E row: CURSOR_DEV=true nix develop -c <command>. (Cloud VM: no wrapper — see above.)
- Backend: Spring Boot 3.x, Java 25, MySQL, Redis, JPA/Hibernate, Flyway migrations
- Frontend: Vue 3, TypeScript, Vite, Pinia, Tailwind CSS, DaisyUI
- Testing: JUnit 5 (backend), Vitest (frontend), Cypress + Cucumber (E2E)
- External Services: OpenAI API, Wikidata API
doughnut/
├── backend/ # Spring Boot backend
│ └── src/main/java/com/odde/doughnut/
│ ├── controllers/ # REST endpoints + DTOs
│ ├── services/ # Business logic, AI integration
│ ├── entities/ # JPA entities + repositories
│ └── configs/ # Spring configurations
├── frontend/ # Vue 3 frontend
│ └── src/
│ ├── pages/ # Page components
│ ├── components/ # Reusable components
│ ├── composables/ # Vue 3 composables
│ ├── store/ # Pinia state management
│ └── (imports from packages/generated/doughnut-backend-api/)
├── packages/
│ ├── generated/
│ │ └── doughnut-backend-api/ # Auto-generated API client
│ └── doughnut-test-fixtures/ # Shared `makeMe` API-shaped builders; **public import:** `doughnut-test-fixtures/makeMe` only
├── e2e_test/ # Cucumber E2E tests
│ ├── features/ # Gherkin feature files
│ ├── step_definitions/ # Step implementations
│ └── start/ # Page objects
└── mcp-server/ # MCP server for Claude integration
- Frontend API client is auto-generated from OpenAPI spec
- After changing backend controller signatures, run
pnpm generateTypeScript - Frontend production build outputs to
frontend/dist/; CLI bundle iscli/dist/doughnut-cli.bundle.mjs(pnpm cli:bundle). Prod serves/doughnut-cli-latest/doughnutfrom GCS; local E2E/dev serves that path frome2e_test/e2e-prod-topology-proxy.mjsreadingcli/dist(not Spring on 9081). The CLI bundle aliases optional Ink DevTools importreact-devtools-coreto a stub undercli/src/shims/; see Build output in.cursor/rules/cli.mdc. - Dev server proxies
/api/,/attachments/,/logout/,/testability/to backend
- Test through controllers when possible
- Use
makeMefactory pattern for test data (e.g.,makeMe.aNote().creatorAndOwner(user).please()) - Tests use real database with
@Transactional - Always use proper imports, never inline fully qualified class names
- Use
<script setup lang="ts">for components - Import API services from
@generated/doughnut-backend-api/sdk.gen - Use
apiCallWithLoading()for user-initiated actions with loading/error handling - DaisyUI classes use
daisy-prefix - Avoid
getByRolequeries in tests (performance) - usegetByText,getByLabelText, etc. - Use
mockSdkService()helper for mocking API calls in tests - API-shaped test and Storybook data:
makeMe— importdoughnut-test-fixtures/makeMe(the package’s only supported export; builders undersrc/are internal).@tests/*still maps tofrontend/tests/for helpers.
- Keep step definitions lightweight, delegate to page objects
- Use tags like
@usingMockedOpenAiService,@mockBrowserTimefor test configuration - Backend logs at
backend/logs/doughnut-e2e.log
- Location:
backend/src/main/resources/db/migration/ - Naming:
V{version}__{description}.sql(e.g.,V300000176__description.sql) - Never modify committed migrations; create new ones
- Optional recall-load delay for manual testing or stable Vitest coverage: set
DOUGHNUT_CLI_SLOW_RECALL_LOAD_MSto a positive number of milliseconds (capped at 60000). Applies only when the TTY passes an abortablerecallNextload (/recalland recall session continuations that use the same path). Default is off. Seecli/src/recall.tsand.cursor/rules/cli.mdc.
- Informal plans for active work:
ongoing/<short-name>.md - How to phase work, E2E vs unit tests, TDD workflow, deploy gate, and interim behavior:
.cursor/rules/planning.mdc
- High cohesion — Minimize duplication; keep related code together (see also
.cursor/rules/general.mdc). - Keep it simple — Avoid defensive programming; use the minimum code that fits the task.
- No historical documentation — Code and comments reflect the current state only; temporary notes may live in
ongoing/. - Test observable behavior, not internal structure — Assert what users or callers can see (responses, UI, terminal output, exit codes) by driving high-level entry points (controllers, mounted UI,
run/runInteractive, etc.). Those tests often do not import the code under change directly; they still exercise it through the real path. Avoid tests that mainly mirror the codebase (low-level functions + internal-only parameters or adapter shapes): they refactor badly and can miss real wiring. Prefer fewer tests at observable surfaces over a 1:1 map from implementation files to tests; keep assertions for one user-visible behavior grouped in one place when practical. Testing strategy and layer roles:.cursor/rules/planning.mdc. Stack habits:backend-development.mdc,frontend.mdc,cli.mdc,e2e_test.mdc.