# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
**Read [`CONSTRAINTS.md`](./CONSTRAINTS.md) before starting work.** It's the enforced quality/security bar (types, lint, format, IPC/vault boundary, secrets/vuln scanning status, speed budget). Never weaken a rule in it to make a change pass — fix the change, or edit CONSTRAINTS.md in its own commit with a stated reason.
## What This Project Is
**Notvex** is a cross-platform Electron desktop app for secure, fully-local encrypted notes. All data lives in a single SQLCipher-encrypted SQLite database — no account, no server, no network. Core crypto stack: Argon2id (KDF), XChaCha20-Poly1305 (note encryption), SQLCipher (AES-256 database), BIP39 recovery key, optional key-file second factor.
## Commands
```bash
pnpm dev # Start Electron + Vite dev server
pnpm build # Production build (all processes)
pnpm build:win # Windows installer
pnpm build:mac # macOS installer
pnpm build:linux # Linux installer
pnpm typecheck # Type-check main + preload + renderer
pnpm lint # ESLint (zero warnings allowed)
pnpm lint:fix # ESLint with auto-fix
pnpm format # Prettier format (TS, CSS, JSON)
pnpm format:check # Check formatting without writing
pnpm validate # typecheck + lint + format:check
pnpm depcruise # IPC/vault boundary check (.dependency-cruiser.cjs)
pnpm test # Vitest, all tests (test:watch for watch mode)
pnpm test: # One folder under tests/: main, vault, db, ipc, app, preload, renderer
pnpm test:coverage # Vitest with coverage
pnpm doctor # react-doctor scan (doctor:staged for staged files only)
pnpm check:fast # typecheck + lint — edit loop
pnpm check:task # validate + depcruise + lint:commits + test — run before calling work done
pnpm check:full # check:task + coverage — review/CI
```
`pnpm check:task` is the before-done gate. Gate tiers, speed budget and coverage floor are in [`CONSTRAINTS.md`](./CONSTRAINTS.md).
## Architecture
The project uses electron-vite with three separate build targets. Directories are described by responsibility — read the tree for their current contents.
**`src/main/`** — Electron main process (Node.js backend)
- `index.ts` — Entry point: app lifecycle (single-instance lock, `.nvx` file association), delegates window creation to `window.ts`
- `window.ts` — Creates the `BrowserWindow` (`contextIsolation`/`nodeIntegration` config lives here), applies `setContentProtection`, triggers vault lock on suspend/lock-screen/blur/minimize
- `ipc-handlers.ts` — Defines all IPC handlers; the only communication bridge to the renderer
- `vault/` — All cryptographic operations and vault orchestration (KDF, memory locking, BIP39 recovery, key container, backups)
- `db/` — SQLite/SQLCipher queries and schema migrations
- Remaining top-level modules are single-purpose main-process services (prefs, updater, file opening, clipboard, renderer/URL guards)
**`src/preload/index.ts`** — Exposes `window.notvex` API surface to the renderer via `contextBridge`. Any new IPC channel must be declared here.
**`src/renderer/src/`** — React 19 frontend
- `views/` — Top-level screens (setup, unlock, main app, post-recovery reset)
- `components/` — Feature components, grouped into subfolders by feature; `components/ui/` holds shadcn/ui primitives (see UI Rules)
- `hooks/` — Custom React hooks
- `store/` — Zustand stores (vault state, UI state, preferences)
- `lib/ipc.ts` — Typed IPC client (renderer side); the renderer's only route to main
- `lib/` — Other renderer utilities, including the CodeMirror 6 markdown editor (`lib/editor/`)
**`src/shared/types.ts`** — TypeScript types shared across all three processes.
**`tests/`** — Vitest tests, one folder per area mirroring `src/`: `tests/main/{vault,db,ipc,app}/`, `tests/preload/` and `tests/eslint-rules/` run in node, `tests/renderer/` in jsdom. One file per topic, not per module. Never colocate tests in `src/`. Placement and typecheck rules: [`CONSTRAINTS.md` § Test file location](./CONSTRAINTS.md#test-file-location).
### IPC Pattern
All renderer↔main communication goes through IPC:
1. Define handler in `src/main/ipc-handlers.ts`
2. Expose channel in `src/preload/index.ts` via `contextBridge`
3. Call from renderer via `src/renderer/src/lib/ipc.ts`
### Vault Files on Disk
- `vault.nvx` — SQLCipher-encrypted SQLite database (notes + tags) with metadata, recovery-key-encrypted master key, salt, settings
- `userData/vault-backups//` — copies of a vault taken before a header/schema migration; the newest 3 per vault are kept (`vault/backups.ts`)
---
## Code Style
- **Prettier:** no semicolons, single quotes, 2-space indent, 100-char line width, `prettier-plugin-tailwindcss` for class ordering
- **Path aliases:** `@/*` → `src/renderer/src/*` in the renderer; `@main/*` → `src/main/*` in the main process; `@shared/*` -> `src/shared/*`
- **Tailwind CSS 4** — config lives in CSS (`src/renderer/src/assets/main.css`), not `tailwind.config.js`
- **No `any` in TypeScript** — use `unknown` with narrowing or proper types
- **File naming:** React components in PascalCase (`NoteEditor.tsx`), everything else in kebab-case (`ipc-handlers.ts`, `vault.store.ts`)
- **Commits:** Conventional Commits format (`feat:`, `fix:`, `chore:`, etc.), lowercase subject, max 72 chars
- **Comments explain *why*, never history.** A comment states a constraint the code can't
express, in the present tense, for a reader who has never seen the issue tracker.
- Never in a comment: issue/PR numbers (`#36`), task/phase numbers, "found by"/"flagged by
", "verified by hand", line references (`vault.ts:657`), pointers to `tasks/*.md`.
That history goes in the **commit body or PR description** — don't copy the PR text into code.
- One canonical explanation per concept (e.g. `withVaultLock` reentrancy lives in its
docstring); elsewhere a one-line pointer at most. Never paste the same block twice.
- If the code already says it, don't comment it.
- ✅ `// rekey succeeded but the checkpoint threw: newRawKey must still be zeroed`
- ❌ `// Failure point the issue's literal suggested diff did NOT cover (#24)…`
- Enforced by `pnpm lint` (`local/no-history-comments`); narrative and duplication aren't
machine-checkable, so they're on you.
---
## UI Rules — read before writing any component or style
### 1. Never use raw HTML elements — always check for an existing component first
Before writing any `