Files
initiative/CLAUDE.md
Lukas 86768842ff
All checks were successful
CI / check (push) Successful in 1m18s
CI / build-image (push) Has been skipped
Refactor App.tsx from god component to context-based architecture
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 15:33:33 +01:00

8.0 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Commands

pnpm check              # Merge gate — must pass before every commit (audit + knip + biome + oxlint + typecheck + test/coverage + jscpd)
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, routing, 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)
  • 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 (unnecessary type assertions, deprecated APIs, replaceAll preference, String.raw). Configured in .oxlintrc.json.
  • 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/NNN-feature-name/ with spec.md (and optionally plan.md, tasks.md for new work). Specs describe features, not individual changes. The project constitution is at .specify/memory/constitution.md.
  • Component props — max 8 explicitly declared props per component interface (enforced by scripts/check-component-props.mjs). Use React context for shared state; reserve props for per-instance config (data items, layout variants, refs).
  • Quality gates are enforced at pre-commit via Lefthook's pnpm check — the project's single earliest enforcement point. No gate may exist only as a CI step or manual process.

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

Speckit (/speckit.* skills) manages the spec-driven development pipeline. Specs are living documents that describe features, not individual changes.

Issue-driven workflow

  • /write-issue — create a well-structured Gitea issue via interactive interview
  • /integrate-issue <number> — fetch an issue, route it to the right spec, and update the spec with the new/changed requirements. Then implement directly.
  • /sync-issue <number> — push acceptance criteria from the spec back to the Gitea issue

RPI skills (Research → Plan → Implement)

  • rpi-research — deep codebase research producing a written report in docs/agents/research/
  • rpi-plan — interactive phased implementation plan in docs/agents/plans/
  • rpi-implement — execute a plan file phase by phase with automated + manual verification

Choosing the right workflow by scope

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

Speckit manages what to build (specs as living documents). RPI manages how to build it (research, planning, execution). The full speckit pipeline is for new features. For changes to existing features, update the spec via /integrate-issue, then use RPI skills if the change is non-trivial.

Current feature specs

  • specs/001-combatant-management/ — CRUD, persistence, clear, batch add, confirm buttons
  • specs/002-turn-tracking/ — rounds, turn order, advance/retreat, top bar
  • specs/003-combatant-state/ — HP, AC, conditions, concentration, initiative
  • specs/004-bestiary/ — search index, stat blocks, source management, panel UX
  • specs/005-player-characters/ — persistent player character templates (CRUD), search & add to encounters, color/icon visual distinction, PlayerCharacterStore port

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. Spec-driven features — Features are described in living specs; evolve existing specs via /integrate-issue, create new ones via /speckit.specify. Bug fixes and tooling changes do not require specs.

Active Technologies

  • TypeScript 5.8 (strict mode, verbatimModuleSyntax) + React 19, Vite 6, Tailwind CSS v4, Lucide Reac (005-player-characters)
  • localStorage (new key "initiative:player-characters") (005-player-characters)

Recent Changes

  • 005-player-characters: Added TypeScript 5.8 (strict mode, verbatimModuleSyntax) + React 19, Vite 6, Tailwind CSS v4, Lucide Reac