# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Commands ```bash pnpm check # Merge gate — must pass before every commit (knip + format + lint + typecheck + test) pnpm knip # Unused code detection (Knip) pnpm test # Run all tests (Vitest) pnpm test:watch # Tests in watch mode pnpm typecheck # tsc --build (project references) pnpm lint # Biome lint pnpm format # Biome format (writes) pnpm --filter web dev # Vite dev server (localhost:5173) pnpm --filter web build # Production build ``` Run a single test file: `pnpm vitest run packages/domain/src/__tests__/advance-turn.test.ts` ## Architecture Strict layered architecture with ports/adapters and enforced dependency direction: ``` apps/web (React 19 + Vite) → packages/application (use cases) → packages/domain (pure logic) ``` - **Domain** — Pure functions, no I/O, no framework imports. All state transitions are deterministic. Errors returned as values (`DomainError`), never thrown. Adapters may throw only for programmer errors. - **Application** — Orchestrates domain calls via port interfaces (e.g., `EncounterStore`). No business logic here. - **Web** — React adapter. Implements ports using hooks/state. Layer boundaries are enforced by `scripts/check-layer-boundaries.mjs`, which runs as a Vitest test. Domain and application must never import from React, Vite, or upper layers. ## Conventions - **Biome 2.0** for formatting and linting (no Prettier, no ESLint). Tab indentation, 80-char lines. Imports are auto-organized alphabetically. - **TypeScript strict mode** with `verbatimModuleSyntax`. Use `.js` extensions in relative imports when required by the repo's ESM settings (e.g., `./types.js`). - **Branded types** for identity values (e.g., `CombatantId`). Prefer immutability/`readonly` where practical. - **Domain events** are plain data objects with a `type` discriminant — no classes. - **Tests** live in `packages/*/src/__tests__/*.test.ts`. Test pure functions directly; map acceptance scenarios and invariants from specs to individual `it()` blocks. - **Feature specs** live in `specs//` with spec.md, plan.md, tasks.md. The project constitution is at `.specify/memory/constitution.md`. ## Constitution (key principles) The constitution (`.specify/memory/constitution.md`) governs all feature work: 1. **Deterministic Domain Core** — Pure state transitions only; no I/O, randomness, or clocks in domain. 2. **Layered Architecture** — Domain → Application → Adapters. Never skip layers or reverse dependencies. 3. **Clarification-First** — Ask before making non-trivial assumptions. 4. **MVP Baseline** — Say "MVP baseline does not include X", never permanent bans. 5. **Every feature begins with a spec** — Spec → Plan → Tasks → Implementation. ## Active Technologies - TypeScript 5.x (strict mode, verbatimModuleSyntax) + React 19, Vite (003-remove-combatant) - In-memory React state (local-first, single-user MVP) (003-remove-combatant) - TypeScript 5.x (project), Go binary via npm (Lefthook) + `lefthook` (npm devDependency) (006-pre-commit-gate) - TypeScript 5.x (strict mode, verbatimModuleSyntax) + Knip v5 (new), Biome 2.0, Vitest, Vite 6, React 19 (007-add-knip) - TypeScript 5.x (strict mode, verbatimModuleSyntax) + React 19, Vite 6, Biome 2.0, existing domain/application packages (008-persist-encounter) - Browser localStorage (adapter layer only) (008-persist-encounter) - TypeScript 5.x (strict mode, verbatimModuleSyntax) + React 19, Vite 6, Biome 2.0, Vites (009-combatant-hp) - TypeScript 5.8 (strict mode, verbatimModuleSyntax) + React 19, Vite 6, Tailwind CSS v4, shadcn/ui, Lucide React (icons) (010-ui-baseline) - N/A (no storage changes — localStorage persistence unchanged) (010-ui-baseline) - TypeScript 5.8 (strict mode, verbatimModuleSyntax) + React 19, Vite 6, Tailwind CSS v4, shadcn/ui-style components, Lucide React (icons) (011-quick-hp-input) - N/A (no storage changes -- existing localStorage persistence handles HP via `adjustHpUseCase`) (011-quick-hp-input) - N/A (no storage changes -- existing localStorage persistence unchanged) (012-turn-navigation) - N/A (no storage changes -- purely derived state, existing localStorage persistence unchanged) (013-hp-status-indicators) - TypeScript 5.8 (strict mode, verbatimModuleSyntax) + jscpd (new dev dependency), Lefthook (existing), Biome 2.0 (existing), Knip (existing) (015-add-jscpd-gate) - N/A (no storage changes) (015-add-jscpd-gate) - Browser localStorage (existing adapter, transparent JSON serialization) (016-combatant-ac) - TypeScript 5.8 (strict mode, verbatimModuleSyntax) + React 19, Vite 6, Tailwind CSS v4, Lucide React (icons) (017-combat-conditions) - N/A (no storage changes — existing localStorage persistence unchanged) (019-combatant-row-declutter) - TypeScript 5.8 (strict mode, verbatimModuleSyntax) + React 19, Tailwind CSS v4, Lucide React (icons) (020-fix-zero-hp-opacity) - Browser localStorage (existing adapter, extended for creatureId) (021-bestiary-statblock) - TypeScript 5.8 (strict mode, verbatimModuleSyntax) + React 19, Tailwind CSS v4 + React 19, Tailwind CSS v4, Vite 6 (022-fixed-layout-bars) - N/A (no storage changes -- purely presentational) (022-fixed-layout-bars) - Browser localStorage (existing adapter, updated to handle empty encounters) (023-clear-encounter) - N/A (no storage changes — purely presentational fix) (024-fix-hp-popover-overflow) ## Recent Changes - 003-remove-combatant: Added TypeScript 5.x (strict mode, verbatimModuleSyntax) + React 19, Vite