Files
initiative/CLAUDE.md
Lukas 2c643cc98b
All checks were successful
CI / check (push) Successful in 2m13s
CI / build-image (push) Has been skipped
Introduce adapter injection and migrate test suite
Replace direct adapter/persistence imports with context-based injection
(AdapterContext + useAdapters) so tests use in-memory implementations
instead of vi.mock. Migrate component tests from context mocking to
AllProviders with real hooks. Extract export/import logic from ActionBar
into useEncounterExportImport hook. Add bestiary-cache and
bestiary-index-adapter test suites. Raise adapter coverage thresholds
(68→80 lines, 56→62 branches).

77 test files, 891 tests, all passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:55:45 +02:00

8.2 KiB

CLAUDE.md

Initiative is a browser-based combat encounter tracker for tabletop RPGs (D&D 5.5e, Pathfinder 2e). It runs entirely client-side — no backend, no accounts — with localStorage and IndexedDB for persistence.

Commands

pnpm check              # Merge gate — must pass before every commit (audit + knip + biome + oxlint + typecheck + test/coverage + jscpd + jsinspect)
pnpm oxlint             # Type-aware linting (oxlint — complements Biome)
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 check:props        # Component prop count enforcement (max 8)
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 (EncounterStore, BestiarySourceCache). No business logic here.
  • Web — React adapter. Implements ports using hooks/state. All UI components and user interaction live here.

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.

Data & Storage

  • localStorage — encounter persistence (adapter layer, JSON serialization)
  • IndexedDB — bestiary source cache (apps/web/src/adapters/bestiary-cache.ts, via idb wrapper)
  • data/bestiary/index.json — pre-built search index for creature lookup, generated by scripts/generate-bestiary-index.mjs

Project Structure

apps/web/                  React app — components, hooks, adapters
packages/domain/src/       Pure state transitions, types, validation
packages/application/src/  Use cases, port interfaces
data/bestiary/             Bestiary search index
scripts/                   Build tooling (layer checks, index generation)
specs/NNN-feature-name/    Feature specs (spec.md, plan.md, tasks.md)
.specify/                  Speckit config (templates, scripts, constitution)
docs/agents/               RPI skill artifacts (research reports, plans)
.claude/skills/            Agent skills (rpi-research, rpi-plan, rpi-implement)

Tech Stack

  • TypeScript 5.8 (strict mode, verbatimModuleSyntax)
  • React 19, Vite 6, Tailwind CSS v4
  • Lucide React (icons)
  • idb (IndexedDB wrapper for bestiary cache)
  • Biome 2.4 (formatting + linting), oxlint (type-aware linting), Knip (unused code), jscpd (copy-paste detection), jsinspect-plus (structural duplication)
  • Vitest (testing, v8 coverage), Lefthook (pre-commit hooks)

Conventions

  • Biome 2.4 for formatting and linting (no Prettier, no ESLint). Tab indentation, 80-char lines. Imports are auto-organized alphabetically.
  • oxlint for type-aware linting that Biome can't do. Configured in .oxlintrc.json.
  • TypeScript strict mode with verbatimModuleSyntax. Use .js extensions in relative imports.
  • 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 __tests__/ directories adjacent to source. See the Testing section below for conventions on mocking, assertions, and per-layer approach.
  • Quality gates are enforced at pre-commit via Lefthook (parallel jobs). No gate may exist only as a CI step or manual process.

For component prop rules, export format compatibility, and ADRs, see docs/conventions.md.

Testing

Philosophy

Test user-visible behavior, not implementation details. A good test answers "does this feature work?" not "does this internal function get called?"

Adapter Injection

Adapters (storage, cache, browser APIs) are provided via AdapterContext. Production wires real implementations; tests wire in-memory implementations. This means:

  • No vi.mock() for adapter or persistence modules
  • Tests control adapter behavior by configuring the in-memory implementation
  • Type changes in adapter interfaces are caught at compile time

Per-Layer Approach

Layer How to test
Domain (packages/domain) Pure unit tests, no mocks, test invariants and acceptance scenarios
Application (packages/application) Mock port interfaces only, use real domain logic
Hooks (context-wrapped) Test via renderHook with AllProviders wrapping in-memory adapters
Hooks (component-specific) Test through the component that uses them
Components Render with AllProviders, use in-memory adapters, use userEvent for interactions

Test Data

Use factory functions from apps/web/src/__tests__/factories/ to construct domain objects. Each factory provides sensible defaults overridden via Partial<T>:

import { buildEncounter } from "../../__tests__/factories/build-encounter.js";
import { buildCombatant } from "../../__tests__/factories/build-combatant.js";

const encounter = buildEncounter({
  combatants: [buildCombatant({ name: "Goblin" })],
  activeIndex: 0,
  roundNumber: 1,
});

Add new factory files as needed (one per domain type). Don't inline test data construction — use factories so type changes are caught at compile time.

Anti-Patterns

  • vi.mock() for adapters: Use in-memory adapter implementations via AdapterContext instead
  • Mocking contexts: Use AllProviders and drive state through real hooks instead of vi.mock("../../contexts/..."). Exception: context mocks are acceptable when the component under test requires specific state machine states that cannot be reached through adapter configuration alone — document the reason in a comment at the top of the test file.
  • Stubbing child components: Render real children; stub only if the child has heavy I/O that can't be mocked at the adapter level
  • Asserting mock call counts: Prefer asserting what the user sees (screen.getByText(...)) over expect(mockFn).toHaveBeenCalledWith(...)
  • Testing internal state: Don't assert result.current.suggestionIndex === 0; assert the first suggestion is highlighted
  • Assertion-free tests: Every it() block must contain at least one expect(). Tests that render without asserting inflate coverage without catching bugs.

Self-Review Checklist

Before finishing a change, consider:

  • Is this the simplest approach that solves the current problem?
  • Is there duplication that hurts readability? (But don't abstract prematurely.)
  • Are errors handled correctly and communicated sensibly to the user?
  • Does the UI follow modern patterns and feel intuitive to interact with?

Speckit Workflow

Specs are living documents in specs/NNN-feature-name/ that describe features, not individual changes. Use /speckit.* and RPI skills (rpi-research, rpi-plan, rpi-implement) to manage them — skill descriptions have full usage details.

Scope Workflow
Bug fix / CSS tweak Just fix it, commit
Small change to existing feature /integrate-issue → implement → commit
Larger addition to existing feature /integrate-issuerpi-researchrpi-planrpi-implement
New feature /speckit.specify/speckit.clarify/speckit.plan/speckit.tasks/speckit.implement

Research scope: Always scan for existing patterns similar to what the feature needs. Identify extraction and consolidation opportunities before implementation, not during.

Constitution

Project principles governing all feature work are in .specify/memory/constitution.md. Key rules: deterministic domain core, strict layer boundaries, clarification before assumptions.