From 7dd4abb12a41fe36b4aeb7de55716953b40f19b3 Mon Sep 17 00:00:00 2001 From: Lukas Date: Tue, 3 Mar 2026 12:54:29 +0100 Subject: [PATCH] =?UTF-8?q?T001=E2=80=93T006:=20Phase=201=20setup=20(works?= =?UTF-8?q?pace,=20Biome,=20TS,=20Vitest,=20layer=20boundary=20enforcement?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 13 + .nvmrc | 1 + .specify/memory/constitution.md | 165 +- apps/web/index.html | 12 + apps/web/package.json | 23 + apps/web/src/App.tsx | 3 + apps/web/src/main.tsx | 12 + apps/web/tsconfig.json | 14 + apps/web/vite.config.ts | 6 + biome.json | 33 + package.json | 19 + packages/application/package.json | 11 + packages/application/src/index.ts | 1 + packages/application/tsconfig.json | 10 + packages/domain/package.json | 8 + .../src/__tests__/layer-boundaries.test.ts | 16 + packages/domain/src/index.ts | 1 + packages/domain/tsconfig.json | 10 + pnpm-lock.yaml | 1550 +++++++++++++++++ pnpm-workspace.yaml | 3 + scripts/check-layer-boundaries.mjs | 109 ++ specs/001-advance-turn/plan.md | 349 ++++ specs/001-advance-turn/spec.md | 160 ++ specs/001-advance-turn/tasks.md | 128 ++ tsconfig.base.json | 17 + tsconfig.json | 8 + vitest.config.ts | 8 + 27 files changed, 2655 insertions(+), 35 deletions(-) create mode 100644 .gitignore create mode 100644 .nvmrc create mode 100644 apps/web/index.html create mode 100644 apps/web/package.json create mode 100644 apps/web/src/App.tsx create mode 100644 apps/web/src/main.tsx create mode 100644 apps/web/tsconfig.json create mode 100644 apps/web/vite.config.ts create mode 100644 biome.json create mode 100644 package.json create mode 100644 packages/application/package.json create mode 100644 packages/application/src/index.ts create mode 100644 packages/application/tsconfig.json create mode 100644 packages/domain/package.json create mode 100644 packages/domain/src/__tests__/layer-boundaries.test.ts create mode 100644 packages/domain/src/index.ts create mode 100644 packages/domain/tsconfig.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 scripts/check-layer-boundaries.mjs create mode 100644 specs/001-advance-turn/plan.md create mode 100644 specs/001-advance-turn/spec.md create mode 100644 specs/001-advance-turn/tasks.md create mode 100644 tsconfig.base.json create mode 100644 tsconfig.json create mode 100644 vitest.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af12857 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +node_modules/ +dist/ +build/ +*.log +.env* +.DS_Store +Thumbs.db +*.tmp +*.swp +.vscode/ +.idea/ +coverage/ +*.tsbuildinfo diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..2bd5a0a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md index a4670ff..c064026 100644 --- a/.specify/memory/constitution.md +++ b/.specify/memory/constitution.md @@ -1,50 +1,145 @@ -# [PROJECT_NAME] Constitution - + +# Encounter Console Constitution ## Core Principles -### [PRINCIPLE_1_NAME] - -[PRINCIPLE_1_DESCRIPTION] - +### I. Deterministic Domain Core -### [PRINCIPLE_2_NAME] - -[PRINCIPLE_2_DESCRIPTION] - +All domain logic MUST be implemented as pure state transitions. +Given the same input state and action, the output state MUST be +identical across runs. Side effects (I/O, randomness, clocks) MUST +be injected at the boundary, never sourced inside the domain layer. -### [PRINCIPLE_3_NAME] - -[PRINCIPLE_3_DESCRIPTION] - +- State transitions MUST be expressed as pure functions. +- Random values (e.g., dice rolls) MUST be passed in as resolved + inputs, not generated within the domain. +- Domain functions MUST NOT depend on wall-clock time, network, or + filesystem. -### [PRINCIPLE_4_NAME] - -[PRINCIPLE_4_DESCRIPTION] - +### II. Layered Architecture -### [PRINCIPLE_5_NAME] - -[PRINCIPLE_5_DESCRIPTION] - +The codebase MUST be organized into four layers with strict +dependency direction: -## [SECTION_2_NAME] - +1. **Domain** — pure types, state transitions, validation rules. + No imports from other layers. +2. **Application** — orchestrates domain operations, manages + workflows, coordinates use cases. Application defines port + interfaces that Adapters implement. May import Domain only. +3. **Adapters** — I/O, persistence, UI rendering, external APIs. + May import Application and Domain. +4. **Agent** — AI-assisted features (suggestions, analysis). + May import Application and Domain as read-only consumers. -[SECTION_2_CONTENT] - +A module in an inner layer MUST NOT import from an outer layer. -## [SECTION_3_NAME] - +### III. Agent Boundary -[SECTION_3_CONTENT] - +The agent layer MAY read domain events and current state. The agent +MAY produce suggestions, annotations, or recommendations. The agent +MUST NOT mutate domain state directly. All agent-originated changes +MUST flow through the Application layer as explicit user-confirmed +commands. + +- Agent output MUST be clearly labeled as suggestions. +- No silent or automatic application of agent recommendations. + +### IV. Clarification-First + +Before making any non-trivial assumption during specification, +planning, or implementation, the agent MUST surface a clarification +question to the user. "Non-trivial" means any decision that would +alter observable behavior, data model shape, or public API surface. +The agent MUST also ask when multiple valid interpretations exist, +when a choice would affect architectural layering, or when scope +would expand beyond the current spec. The agent MUST NOT silently +choose among valid alternatives. + +### V. Escalation Gates + +Any feature, requirement, or scope change not present in the current +spec MUST be rejected at implementation time until the spec is +explicitly updated. The workflow is: + +1. Identify the out-of-scope item. +2. Document it as a proposal. +3. Update the spec via `/speckit.specify` or `/speckit.clarify`. +4. Only then proceed with implementation. + +### VI. MVP Baseline Language + +Constraints in this constitution and in specs MUST use MVP baseline +language ("MVP baseline does not include X") rather than permanent +architectural bans ("X is forbidden"). This preserves the option to +add capabilities in future iterations without constitutional +amendment. The current MVP baseline is local-first and single-user; +this is a starting scope, not a permanent restriction. + +### VII. No Gameplay Rules in Constitution + +This constitution MUST NOT contain concrete gameplay mechanics, +rule-system specifics, or encounter resolution logic. Such details +belong in feature specs. The constitution governs process, +architecture, and quality — not product behavior. + +## Scope Constraints + +- The Encounter Console's primary focus is initiative tracking and + encounter state management. Adjacent capabilities (e.g., richer + game-engine features) are not in the MVP baseline but may be + added via spec updates in future iterations. +- Technology choices, UI framework, and storage mechanism are + spec-level decisions, not constitutional mandates. +- Testing strategy (unit, integration, contract) is determined per + feature spec; the constitution requires only that domain logic + remains testable via pure-function assertions. + +## Development Workflow + +- No change may be merged unless all automated checks (tests and + static analysis as defined by the project) pass. +- Every feature begins with a spec (`/speckit.specify`). +- Implementation follows the plan → tasks → implement pipeline. +- Domain logic MUST be testable without mocks for external systems. +- Long-running or multi-step state transitions SHOULD be verifiable + through reproducible event logs or snapshot-style tests. +- Commits SHOULD be atomic and map to individual tasks where + practical. +- Layer boundary compliance MUST be verified by automated import + rules or architectural tests. Agent-assisted or manual review MAY + supplement but not replace automated checks. ## Governance - -[GOVERNANCE_RULES] - +This constitution is the highest-authority document for the +Encounter Console project. All specs, plans, and implementations +MUST comply with its principles. -**Version**: [CONSTITUTION_VERSION] | **Ratified**: [RATIFICATION_DATE] | **Last Amended**: [LAST_AMENDED_DATE] - +**Amendment procedure**: +1. Propose change with rationale. +2. Update constitution via `/speckit.constitution`. +3. Verify downstream template compatibility (Sync Impact Report). +4. A human maintainer MUST review and commit the update before + ratification takes effect. +5. Commit with version bump. + +**Versioning policy**: MAJOR.MINOR.PATCH per semantic versioning. +- MAJOR: principle removal or backward-incompatible redefinition. +- MINOR: new principle or materially expanded guidance. +- PATCH: clarification, typo, or non-semantic refinement. + +**Compliance review**: Every spec and plan MUST include a +Constitution Check section validating adherence to all principles. + +**Version**: 1.0.3 | **Ratified**: 2026-03-03 | **Last Amended**: 2026-03-03 diff --git a/apps/web/index.html b/apps/web/index.html new file mode 100644 index 0000000..90a2dce --- /dev/null +++ b/apps/web/index.html @@ -0,0 +1,12 @@ + + + + + + Initiative Tracker + + +
+ + + diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..a1bd7d9 --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,23 @@ +{ + "name": "web", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc --build && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@initiative/application": "workspace:*", + "@initiative/domain": "workspace:*", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.3.0", + "vite": "^6.2.0" + } +} diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx new file mode 100644 index 0000000..4781b57 --- /dev/null +++ b/apps/web/src/App.tsx @@ -0,0 +1,3 @@ +export function App() { + return
Initiative Tracker
; +} diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx new file mode 100644 index 0000000..1aa0ba5 --- /dev/null +++ b/apps/web/src/main.tsx @@ -0,0 +1,12 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { App } from "./App"; + +const root = document.getElementById("root"); +if (root) { + createRoot(root).render( + + + , + ); +} diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..370eb9c --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "rootDir": "src", + "outDir": "dist", + "jsx": "react-jsx" + }, + "include": ["src"], + "references": [ + { "path": "../../packages/domain" }, + { "path": "../../packages/application" } + ] +} diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts new file mode 100644 index 0000000..58bd0a9 --- /dev/null +++ b/apps/web/vite.config.ts @@ -0,0 +1,6 @@ +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [react()], +}); diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..f5a69dd --- /dev/null +++ b/biome.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.0.0/schema.json", + "files": { + "includes": [ + "**", + "!**/dist/**", + "!.claude/**", + "!.specify/**", + "!specs/**" + ] + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": { + "level": "on" + } + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "tab", + "lineWidth": 80 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..15facff --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "private": true, + "packageManager": "pnpm@10.6.0", + "devDependencies": { + "@biomejs/biome": "2.0.0", + "typescript": "^5.8.0", + "vitest": "^3.0.0" + }, + "scripts": { + "format": "biome format --write .", + "format:check": "biome format .", + "lint": "biome lint .", + "lint:fix": "biome lint --write .", + "typecheck": "tsc --build", + "test": "vitest run", + "test:watch": "vitest", + "check": "biome check . && tsc --build && vitest run" + } +} diff --git a/packages/application/package.json b/packages/application/package.json new file mode 100644 index 0000000..c3a58d6 --- /dev/null +++ b/packages/application/package.json @@ -0,0 +1,11 @@ +{ + "name": "@initiative/application", + "version": "0.0.0", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "dependencies": { + "@initiative/domain": "workspace:*" + } +} diff --git a/packages/application/src/index.ts b/packages/application/src/index.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/packages/application/src/index.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/application/tsconfig.json b/packages/application/tsconfig.json new file mode 100644 index 0000000..c1de3b1 --- /dev/null +++ b/packages/application/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src"], + "references": [{ "path": "../domain" }] +} diff --git a/packages/domain/package.json b/packages/domain/package.json new file mode 100644 index 0000000..2ec35c4 --- /dev/null +++ b/packages/domain/package.json @@ -0,0 +1,8 @@ +{ + "name": "@initiative/domain", + "version": "0.0.0", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts" +} diff --git a/packages/domain/src/__tests__/layer-boundaries.test.ts b/packages/domain/src/__tests__/layer-boundaries.test.ts new file mode 100644 index 0000000..8f78ea0 --- /dev/null +++ b/packages/domain/src/__tests__/layer-boundaries.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from "vitest"; +import { checkLayerBoundaries } from "../../../../scripts/check-layer-boundaries.mjs"; + +describe("layer boundaries", () => { + it("domain and application packages have no forbidden imports", () => { + const violations = checkLayerBoundaries(); + if (violations.length > 0) { + const messages = violations.map( + (v) => + `${v.file}:${v.line} imports "${v.importPath}" (forbidden: ${v.forbidden})`, + ); + throw new Error(`Layer boundary violations:\n ${messages.join("\n ")}`); + } + expect(violations).toHaveLength(0); + }); +}); diff --git a/packages/domain/src/index.ts b/packages/domain/src/index.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/packages/domain/src/index.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/domain/tsconfig.json b/packages/domain/tsconfig.json new file mode 100644 index 0000000..6a77bfd --- /dev/null +++ b/packages/domain/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src"], + "exclude": ["src/__tests__"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..ce76670 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1550 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@biomejs/biome': + specifier: 2.0.0 + version: 2.0.0 + typescript: + specifier: ^5.8.0 + version: 5.9.3 + vitest: + specifier: ^3.0.0 + version: 3.2.4 + + apps/web: + dependencies: + '@initiative/application': + specifier: workspace:* + version: link:../../packages/application + '@initiative/domain': + specifier: workspace:* + version: link:../../packages/domain + react: + specifier: ^19.0.0 + version: 19.2.4 + react-dom: + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) + devDependencies: + '@types/react': + specifier: ^19.0.0 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.0.0 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': + specifier: ^4.3.0 + version: 4.7.0(vite@6.4.1) + vite: + specifier: ^6.2.0 + version: 6.4.1 + + packages/application: + dependencies: + '@initiative/domain': + specifier: workspace:* + version: link:../domain + + packages/domain: {} + +packages: + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@biomejs/biome@2.0.0': + resolution: {integrity: sha512-BlUoXEOI/UQTDEj/pVfnkMo8SrZw3oOWBDrXYFT43V7HTkIUDkBRY53IC5Jx1QkZbaB+0ai1wJIfYwp9+qaJTQ==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.0.0': + resolution: {integrity: sha512-QvqWYtFFhhxdf8jMAdJzXW+Frc7X8XsnHQLY+TBM1fnT1TfeV/v9vsFI5L2J7GH6qN1+QEEJ19jHibCY2Ypplw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.0.0': + resolution: {integrity: sha512-5JFhls1EfmuIH4QGFPlNpxJQFC6ic3X1ltcoLN+eSRRIPr6H/lUS1ttuD0Fj7rPgPhZqopK/jfH8UVj/1hIsQw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.0.0': + resolution: {integrity: sha512-Bxsz8ki8+b3PytMnS5SgrGV+mbAWwIxI3ydChb/d1rURlJTMdxTTq5LTebUnlsUWAX6OvJuFeiVq9Gjn1YbCyA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@2.0.0': + resolution: {integrity: sha512-BAH4QVi06TzAbVchXdJPsL0Z/P87jOfes15rI+p3EX9/EGTfIjaQ9lBVlHunxcmoptaA5y1Hdb9UYojIhmnjIw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@2.0.0': + resolution: {integrity: sha512-tiQ0ABxMJb9I6GlfNp0ulrTiQSFacJRJO8245FFwE3ty3bfsfxlU/miblzDIi+qNrgGsLq5wIZcVYGp4c+HXZA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@2.0.0': + resolution: {integrity: sha512-09PcOGYTtkopWRm6mZ/B6Mr6UHdkniUgIG/jLBv+2J8Z61ezRE+xQmpi3yNgUrFIAU4lPA9atg7mhvE/5Bo7Wg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@2.0.0': + resolution: {integrity: sha512-vrTtuGu91xNTEQ5ZcMJBZuDlqr32DWU1r14UfePIGndF//s2WUAmer4FmgoPgruo76rprk37e8S2A2c0psXdxw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.0.0': + resolution: {integrity: sha512-2USVQ0hklNsph/KIR72ZdeptyXNnQ3JdzPn3NbjI4Sna34CnxeiYAaZcZzXPDl5PYNFBivV4xmvT3Z3rTmyDBg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/rollup-android-arm-eabi@4.59.0': + resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.59.0': + resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.59.0': + resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.59.0': + resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.59.0': + resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.59.0': + resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.59.0': + resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.59.0': + resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.59.0': + resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.59.0': + resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.59.0': + resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.59.0': + resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.59.0': + resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.59.0': + resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.59.0': + resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.59.0': + resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.59.0': + resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.59.0': + resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.59.0': + resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.59.0': + resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.59.0': + resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} + cpu: [x64] + os: [win32] + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + baseline-browser-mapping@2.10.0: + resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} + engines: {node: '>=6.0.0'} + hasBin: true + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + caniuse-lite@1.0.30001776: + resolution: {integrity: sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw==} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + electron-to-chromium@1.5.307: + resolution: {integrity: sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + postcss@8.5.8: + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} + engines: {node: ^10 || ^12 || >=14} + + react-dom@19.2.4: + resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + peerDependencies: + react: ^19.2.4 + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react@19.2.4: + resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + engines: {node: '>=0.10.0'} + + rollup@4.59.0: + resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@6.4.1: + resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + +snapshots: + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.0': + dependencies: + '@babel/types': 7.29.0 + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@biomejs/biome@2.0.0': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.0.0 + '@biomejs/cli-darwin-x64': 2.0.0 + '@biomejs/cli-linux-arm64': 2.0.0 + '@biomejs/cli-linux-arm64-musl': 2.0.0 + '@biomejs/cli-linux-x64': 2.0.0 + '@biomejs/cli-linux-x64-musl': 2.0.0 + '@biomejs/cli-win32-arm64': 2.0.0 + '@biomejs/cli-win32-x64': 2.0.0 + + '@biomejs/cli-darwin-arm64@2.0.0': + optional: true + + '@biomejs/cli-darwin-x64@2.0.0': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.0.0': + optional: true + + '@biomejs/cli-linux-arm64@2.0.0': + optional: true + + '@biomejs/cli-linux-x64-musl@2.0.0': + optional: true + + '@biomejs/cli-linux-x64@2.0.0': + optional: true + + '@biomejs/cli-win32-arm64@2.0.0': + optional: true + + '@biomejs/cli-win32-x64@2.0.0': + optional: true + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/rollup-android-arm-eabi@4.59.0': + optional: true + + '@rollup/rollup-android-arm64@4.59.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.59.0': + optional: true + + '@rollup/rollup-darwin-x64@4.59.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.59.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.59.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.59.0': + optional: true + + '@rollup/rollup-openbsd-x64@4.59.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.59.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.59.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.59.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.59.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.59.0': + optional: true + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/react-dom@19.2.3(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + + '@vitejs/plugin-react@4.7.0(vite@6.4.1)': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 6.4.1 + transitivePeerDependencies: + - supports-color + + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.4(vite@6.4.1)': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.4.1 + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.4': + dependencies: + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.1.0 + + '@vitest/snapshot@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.4 + + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + + assertion-error@2.0.1: {} + + baseline-browser-mapping@2.10.0: {} + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.10.0 + caniuse-lite: 1.0.30001776 + electron-to-chromium: 1.5.307 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + cac@6.7.14: {} + + caniuse-lite@1.0.30001776: {} + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + check-error@2.1.3: {} + + convert-source-map@2.0.0: {} + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-eql@5.0.2: {} + + electron-to-chromium@1.5.307: {} + + es-module-lexer@1.7.0: {} + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + escalade@3.2.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + expect-type@1.3.0: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + js-tokens@4.0.0: {} + + js-tokens@9.0.1: {} + + jsesc@3.1.0: {} + + json5@2.2.3: {} + + loupe@3.2.1: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + node-releases@2.0.27: {} + + pathe@2.0.3: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + postcss@8.5.8: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + react-dom@19.2.4(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + + react-refresh@0.17.0: {} + + react@19.2.4: {} + + rollup@4.59.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.59.0 + '@rollup/rollup-android-arm64': 4.59.0 + '@rollup/rollup-darwin-arm64': 4.59.0 + '@rollup/rollup-darwin-x64': 4.59.0 + '@rollup/rollup-freebsd-arm64': 4.59.0 + '@rollup/rollup-freebsd-x64': 4.59.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 + '@rollup/rollup-linux-arm-musleabihf': 4.59.0 + '@rollup/rollup-linux-arm64-gnu': 4.59.0 + '@rollup/rollup-linux-arm64-musl': 4.59.0 + '@rollup/rollup-linux-loong64-gnu': 4.59.0 + '@rollup/rollup-linux-loong64-musl': 4.59.0 + '@rollup/rollup-linux-ppc64-gnu': 4.59.0 + '@rollup/rollup-linux-ppc64-musl': 4.59.0 + '@rollup/rollup-linux-riscv64-gnu': 4.59.0 + '@rollup/rollup-linux-riscv64-musl': 4.59.0 + '@rollup/rollup-linux-s390x-gnu': 4.59.0 + '@rollup/rollup-linux-x64-gnu': 4.59.0 + '@rollup/rollup-linux-x64-musl': 4.59.0 + '@rollup/rollup-openbsd-x64': 4.59.0 + '@rollup/rollup-openharmony-arm64': 4.59.0 + '@rollup/rollup-win32-arm64-msvc': 4.59.0 + '@rollup/rollup-win32-ia32-msvc': 4.59.0 + '@rollup/rollup-win32-x64-gnu': 4.59.0 + '@rollup/rollup-win32-x64-msvc': 4.59.0 + fsevents: 2.3.3 + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.4: {} + + typescript@5.9.3: {} + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + vite-node@3.2.4: + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.4.1 + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@6.4.1: + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.8 + rollup: 4.59.0 + tinyglobby: 0.2.15 + optionalDependencies: + fsevents: 2.3.3 + + vitest@3.2.4: + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@6.4.1) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 6.4.1 + vite-node: 3.2.4 + why-is-node-running: 2.3.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + yallist@3.1.1: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..0e5a073 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - "packages/*" + - "apps/*" diff --git a/scripts/check-layer-boundaries.mjs b/scripts/check-layer-boundaries.mjs new file mode 100644 index 0000000..3a8193c --- /dev/null +++ b/scripts/check-layer-boundaries.mjs @@ -0,0 +1,109 @@ +import { readdirSync, readFileSync } from "node:fs"; +import { join, relative } from "node:path"; + +const ROOT = new URL("..", import.meta.url).pathname.replace(/\/$/, ""); + +const FORBIDDEN = { + "packages/domain/src": [ + "@initiative/application", + "apps/", + "react", + "react-dom", + "vite", + ], + "packages/application/src": ["apps/", "react", "react-dom", "vite"], +}; + +/** Directories to skip when collecting files */ +const SKIP_DIRS = new Set(["__tests__", "node_modules"]); + +/** @param {string} dir */ +function collectTsFiles(dir) { + /** @type {string[]} */ + const results = []; + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const full = join(dir, entry.name); + if (entry.isDirectory()) { + if (!SKIP_DIRS.has(entry.name)) { + results.push(...collectTsFiles(full)); + } + } else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) { + results.push(full); + } + } + return results; +} + +/** + * Check if an import path matches a forbidden module. + * "vite" should match "vite" and "vite/foo" but not "vitest". + * @param {string} importPath + * @param {string} forbidden + */ +function matchesForbidden(importPath, forbidden) { + if (forbidden.endsWith("/")) { + return importPath.startsWith(forbidden); + } + return importPath === forbidden || importPath.startsWith(`${forbidden}/`); +} + +/** @returns {{ file: string, line: number, importPath: string, forbidden: string }[]} */ +export function checkLayerBoundaries() { + /** @type {{ file: string, line: number, importPath: string, forbidden: string }[]} */ + const violations = []; + + for (const [srcDir, forbidden] of Object.entries(FORBIDDEN)) { + const absDir = join(ROOT, srcDir); + let files; + try { + files = collectTsFiles(absDir); + } catch { + continue; + } + + for (const file of files) { + const content = readFileSync(file, "utf-8"); + const lines = content.split("\n"); + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const match = line.match( + /(?:import|from)\s+["']([^"']+)["']|require\s*\(\s*["']([^"']+)["']\s*\)/, + ); + if (!match) continue; + const importPath = match[1] || match[2]; + for (const f of forbidden) { + if (matchesForbidden(importPath, f)) { + violations.push({ + file: relative(ROOT, file), + line: i + 1, + importPath, + forbidden: f, + }); + } + } + } + } + } + + return violations; +} + +// Run as CLI if invoked directly +if ( + process.argv[1] && + (process.argv[1].endsWith("check-layer-boundaries.mjs") || + process.argv[1] === new URL(import.meta.url).pathname) +) { + const violations = checkLayerBoundaries(); + if (violations.length > 0) { + console.error("Layer boundary violations found:"); + for (const v of violations) { + console.error( + ` ${v.file}:${v.line} — imports "${v.importPath}" (forbidden: ${v.forbidden})`, + ); + } + process.exit(1); + } else { + console.log("No layer boundary violations found."); + } +} diff --git a/specs/001-advance-turn/plan.md b/specs/001-advance-turn/plan.md new file mode 100644 index 0000000..49f6c12 --- /dev/null +++ b/specs/001-advance-turn/plan.md @@ -0,0 +1,349 @@ +# Implementation Plan: Advance Turn + +**Branch**: `001-advance-turn` | **Date**: 2026-03-03 | **Spec**: [spec.md](./spec.md) +**Input**: Feature specification from `/specs/001-advance-turn/spec.md` + +## Summary + +Implement the AdvanceTurn domain operation as a pure function that +transitions an Encounter to the next combatant, wrapping rounds and +emitting TurnAdvanced / RoundAdvanced domain events. Stand up the +pnpm monorepo skeleton, Biome tooling, and Vitest test harness so +that all constitution merge-gate requirements are satisfied from the +first commit. + +## Technical Context + +**Node**: 22 LTS (pinned via `.nvmrc`) +**Language/Version**: TypeScript 5.8 (strict mode) +**Primary Dependencies**: React 19 (pin to major; minor upgrades +allowed), Vite 6.2 +**Storage**: In-memory only (MVP baseline) +**Testing**: Vitest 3.0 +**Lint/Format**: Biome 2.0.0 (exact version, single tool — no +Prettier, no ESLint) +**Package Manager**: pnpm 10.6 (pinned via `packageManager` field +in root `package.json`) +**Target Platform**: Static web app (modern browsers) +**Project Type**: Monorepo — library packages + web app +**Performance Goals**: N/A (walking skeleton) +**Constraints**: Domain package must have zero React/Vite imports +**Scale/Scope**: Single feature, ~5 source files, ~1 test file + +## Constitution Check + +| Principle | Status | Notes | +|-----------|--------|-------| +| I. Deterministic Domain Core | PASS | `advanceTurn` is a pure function; no I/O, randomness, or clocks | +| II. Layered Architecture | PASS | `packages/domain` → `packages/application` → `apps/web`; strict dependency direction enforced by automated import check | +| III. Agent Boundary | N/A | Agent layer out of scope for this feature | +| IV. Clarification-First | PASS | No ambiguous decisions remain; spec fully clarified | +| V. Escalation Gates | PASS | Scope strictly limited to spec; out-of-scope items listed | +| VI. MVP Baseline Language | PASS | No permanent bans; "MVP baseline does not include" used | +| VII. No Gameplay Rules | PASS | No gameplay mechanics in plan or constitution | +| Merge Gate | PASS | `pnpm check` script runs format, lint, typecheck, test | + +## Project Structure + +### Documentation (this feature) + +```text +specs/001-advance-turn/ +├── spec.md +└── plan.md +``` + +### Source Code (repository root) + +```text +packages/ +├── domain/ +│ ├── package.json +│ ├── tsconfig.json +│ └── src/ +│ ├── types.ts # CombatantId, Combatant, Encounter +│ ├── events.ts # TurnAdvanced, RoundAdvanced, DomainEvent +│ ├── advance-turn.ts # advanceTurn pure function +│ └── index.ts # public barrel export +├── application/ +│ ├── package.json +│ ├── tsconfig.json +│ └── src/ +│ ├── ports.ts # EncounterStore port interface +│ ├── advance-turn-use-case.ts +│ └── index.ts +apps/ +└── web/ + ├── package.json + ├── tsconfig.json + ├── vite.config.ts + ├── index.html + └── src/ + ├── main.tsx + ├── App.tsx + └── hooks/ + └── use-encounter.ts + +# Testing (co-located with domain package) +packages/domain/ +└── src/ + └── __tests__/ + └── advance-turn.test.ts + +# Root config +├── .nvmrc # pins Node 22 +├── pnpm-workspace.yaml +├── biome.json +├── tsconfig.base.json +└── package.json # packageManager field pins pnpm; root scripts +``` + +**Structure Decision**: pnpm workspace monorepo with two packages +(`domain`, `application`) and one app (`web`). Domain is +framework-agnostic TypeScript. Application imports domain only. +Web app (React + Vite) imports both. + +## Tooling & Merge Gate + +### Scripts (root package.json) + +```jsonc +{ + "scripts": { + "format": "biome format --write .", + "format:check": "biome format .", + "lint": "biome lint .", + "lint:fix": "biome lint --write .", + "typecheck": "tsc --build", + "test": "vitest run", + "test:watch": "vitest", + "check": "biome check . && tsc --build && vitest run" + } +} +``` + +`pnpm check` is the single merge gate: format + lint + typecheck + +test. The layer boundary check runs as a Vitest test (see below), +so it executes as part of `vitest run` — no separate script needed. + +### Layer Boundary Enforcement + +Biome does not natively support cross-package import restrictions. +A lightweight `scripts/check-layer-boundaries.mjs` script will: + +1. Scan `packages/domain/src/**/*.ts` — assert zero imports from + `@initiative/application`, `apps/`, `react`, `vite`. +2. Scan `packages/application/src/**/*.ts` — assert zero imports + from `apps/`, `react`, `vite`. +3. Exit non-zero on violation with a clear error message. + +This script is invoked by a Vitest test +(`packages/domain/src/__tests__/layer-boundaries.test.ts`) so it +runs automatically as part of `vitest run` inside `pnpm check`. +No separate `check:layer` script is needed — the layer boundary +check is guaranteed to execute on every merge-gate run. + +### Biome Configuration (biome.json) + +```jsonc +{ + "$schema": "https://biomejs.dev/schemas/2.0.0/schema.json", + "organizeImports": { "enabled": true }, + "formatter": { + "enabled": true, + "indentStyle": "tab", + "lineWidth": 80 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + } +} +``` + +### TypeScript Configuration + +- `tsconfig.base.json` at root: strict mode, composite projects, + path aliases (`@initiative/domain`, `@initiative/application`). +- Each package extends `tsconfig.base.json` with its own + `include`/`references`. +- `apps/web/tsconfig.json` references both packages. + +## Milestones + +### Milestone 1: Tooling & Domain (walking skeleton) + +Stand up monorepo, Biome, Vitest, TypeScript project references, +and implement the complete AdvanceTurn domain logic with all tests. + +**Exit criteria**: `pnpm check` passes. All 8 acceptance scenarios +green. Layer boundary check green. No React or Vite dependencies in +`packages/domain` or `packages/application`. + +### Milestone 2: Application + Minimal Web Shell + +Wire up the application use case and a minimal React UI that +displays the encounter state and has a single "Next Turn" button. + +**Exit criteria**: `pnpm check` passes. Clicking "Next Turn" in the +browser advances the turn with correct round wrapping. The app +builds with `vite build`. + +## Task List + +### Phase 1: Setup (Milestone 1) + +- [X] **T001** Initialize pnpm workspace and root config + - Create `pnpm-workspace.yaml` listing `packages/*` and `apps/*` + - Create `.nvmrc` pinning Node 22 + - Create root `package.json` with `packageManager` field pinning + pnpm 10.6 and scripts (check, test, lint, format, typecheck) + - Create `biome.json` at root + - Create `tsconfig.base.json` (strict, composite, path aliases) + - **Acceptance**: `pnpm install` succeeds; `biome check .` runs + without config errors + +- [X] **T002** [P] Create `packages/domain` package skeleton + - `package.json` (name: `@initiative/domain`, no dependencies) + - `tsconfig.json` extending base, composite: true + - Empty `src/index.ts` + - **Acceptance**: `tsc --build packages/domain` succeeds + +- [X] **T003** [P] Create `packages/application` package skeleton + - `package.json` (name: `@initiative/application`, + depends on `@initiative/domain`) + - `tsconfig.json` extending base, references domain + - Empty `src/index.ts` + - **Acceptance**: `tsc --build packages/application` succeeds + +- [X] **T004** [P] Create `apps/web` package skeleton + - `package.json` with React, Vite, depends on both packages + - `tsconfig.json` referencing both packages + - `vite.config.ts` (minimal) + - `index.html` + `src/main.tsx` + `src/App.tsx` (placeholder) + - **Acceptance**: `pnpm --filter web dev` starts; `vite build` + succeeds + +- [X] **T005** Configure Vitest + - Add `vitest` as root dev dependency + - Create `vitest.config.ts` at root (workspace mode) or per + package as needed + - Verify `pnpm test` runs (0 tests, exits clean) + - **Acceptance**: `pnpm test` exits 0 + +- [X] **T006** Create layer boundary check script + - `scripts/check-layer-boundaries.mjs`: scans domain and + application source for forbidden imports + - `packages/domain/src/__tests__/layer-boundaries.test.ts`: + wraps the script as a Vitest test + - **Acceptance**: test passes on clean skeleton; fails if a + forbidden import is manually added (verify, then remove) + +### Phase 2: Domain Implementation (Milestone 1) + +- [ ] **T007** Define domain types in `packages/domain/src/types.ts` + - `CombatantId` (branded string or opaque type) + - `Combatant` (carries a CombatantId) + - `Encounter` (combatants array, activeIndex, roundNumber) + - Factory function `createEncounter` that validates INV-1, INV-2, + INV-3 + - **Acceptance**: types compile; `createEncounter([])` returns + error; `createEncounter([a])` returns valid Encounter + +- [ ] **T008** [P] Define domain events in + `packages/domain/src/events.ts` + - `TurnAdvanced { previousCombatantId, newCombatantId, + roundNumber }` + - `RoundAdvanced { newRoundNumber }` + - `DomainEvent = TurnAdvanced | RoundAdvanced` + - **Acceptance**: types compile; events are plain data (no + classes with methods) + +- [ ] **T009** Implement `advanceTurn` in + `packages/domain/src/advance-turn.ts` + - Signature: `(encounter: Encounter) => + { encounter: Encounter; events: DomainEvent[] } | DomainError` + - Implements FR-001 through FR-005 + - Returns error for empty combatant list (INV-1) + - Emits TurnAdvanced on every call (INV-5) + - Emits TurnAdvanced then RoundAdvanced on wrap (event order + contract) + - **Acceptance**: compiles; satisfies type contract + +- [ ] **T010** Write tests for all 8 acceptance scenarios + + invariants in + `packages/domain/src/__tests__/advance-turn.test.ts` + - Scenarios 1–8 from spec (Given/When/Then) + - INV-1: empty encounter rejected + - INV-2: activeIndex always in bounds (property check across + scenarios) + - INV-3: roundNumber never decreases + - INV-4: determinism — same input produces same output (call + twice, assert deep equal) + - INV-5: every success emits at least TurnAdvanced + - Event ordering: on wrap, events array is + [TurnAdvanced, RoundAdvanced] in that order + - **Acceptance**: `pnpm test` — all tests green; `pnpm check` — + full pipeline green + +- [ ] **T011** Export public API from `packages/domain/src/index.ts` + - Re-export types, events, `advanceTurn`, `createEncounter` + - **Acceptance**: consuming packages can + `import { advanceTurn } from "@initiative/domain"` + +**Milestone 1 checkpoint**: `pnpm check` passes (format + lint + +typecheck + test + layer boundaries). All 8 scenarios + invariants +green. + +### Phase 3: Application + Web Shell (Milestone 2) + +- [ ] **T012** Define port interface in + `packages/application/src/ports.ts` + - `EncounterStore` port: `get(): Encounter`, `save(e: Encounter)` + - **Acceptance**: compiles; no imports from adapters or React + +- [ ] **T013** Implement `AdvanceTurnUseCase` in + `packages/application/src/advance-turn-use-case.ts` + - Accepts `EncounterStore` port + - Calls `advanceTurn` from domain, saves result, returns events + - **Acceptance**: compiles; imports only from `@initiative/domain` + and local ports + +- [ ] **T014** Export public API from + `packages/application/src/index.ts` + - Re-export use case and port types + - **Acceptance**: consuming app can import from + `@initiative/application` + +- [ ] **T015** Implement `useEncounter` hook in + `apps/web/src/hooks/use-encounter.ts` + - In-memory implementation of `EncounterStore` port (React state) + - Exposes current encounter state + `advanceTurn` action + - Initializes with a hardcoded 3-combatant encounter for demo + - **Acceptance**: hook compiles; usable in a React component + +- [ ] **T016** Wire up `App.tsx` + - Display: current combatant name, round number, combatant list + with active indicator + - Single "Next Turn" button calling the use case + - Display emitted events (optional, for demo clarity) + - **Acceptance**: `vite build` succeeds; clicking "Next Turn" + cycles through combatants and increments rounds correctly + +**Milestone 2 checkpoint**: `pnpm check` passes. App runs in +browser. Full round-trip from button click → domain pure function → +UI update verified manually. + +## Risks & Open Questions + +| # | Item | Severity | Mitigation | +|---|------|----------|------------| +| 1 | pnpm workspace + TypeScript project references can have path resolution quirks with Vite | Low | Use `vite-tsconfig-paths` plugin if needed; test early in T004 | +| 2 | Biome config format may change across versions | Low | Pinned to exact 2.0.0; `$schema` in config validates structure | +| 3 | Layer boundary script is a lightweight grep — not a full architectural fitness function | Low | Sufficient for walking skeleton; can upgrade to a Biome plugin or `dependency-cruiser` later if needed | + +## Complexity Tracking + +No constitution violations. No complexity justifications needed. diff --git a/specs/001-advance-turn/spec.md b/specs/001-advance-turn/spec.md new file mode 100644 index 0000000..e0ef176 --- /dev/null +++ b/specs/001-advance-turn/spec.md @@ -0,0 +1,160 @@ +# Feature Specification: Advance Turn + +**Feature Branch**: `001-advance-turn` +**Created**: 2026-03-03 +**Status**: Draft +**Input**: Walking-skeleton domain feature — deterministic turn advancement + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Advance Turn (Priority: P1) + +A game master running an encounter advances the turn to the next +combatant in initiative order. When the last combatant in the round +finishes, the round number increments and play wraps to the first +combatant. + +**Why this priority**: This is the irreducible core of an initiative +tracker. Without turn advancement, no other feature has meaning. + +**Independent Test**: Can be fully tested as a pure state transition +with no I/O, persistence, or UI. Given an Encounter value and an +AdvanceTurn action, assert the resulting Encounter value and emitted +domain events. + +**Acceptance Scenarios**: + +1. **Given** an encounter with combatants [A, B, C], activeIndex 0, + roundNumber 1, + **When** AdvanceTurn, + **Then** activeIndex is 1, roundNumber is 1, + and a TurnAdvanced event is emitted with + previousCombatantId A, newCombatantId B, roundNumber 1. + +2. **Given** an encounter with combatants [A, B, C], activeIndex 1, + roundNumber 1, + **When** AdvanceTurn, + **Then** activeIndex is 2, roundNumber is 1, + and a TurnAdvanced event is emitted with + previousCombatantId B, newCombatantId C, roundNumber 1. + +3. **Given** an encounter with combatants [A, B, C], activeIndex 2, + roundNumber 1, + **When** AdvanceTurn, + **Then** activeIndex is 0, roundNumber is 2, + and events are emitted in order: TurnAdvanced + (previousCombatantId C, newCombatantId A, roundNumber 2) then + RoundAdvanced (newRoundNumber 2). + +4. **Given** an encounter with combatants [A, B, C], activeIndex 2, + roundNumber 5, + **When** AdvanceTurn, + **Then** activeIndex is 0, roundNumber is 6, + and events are emitted in order: TurnAdvanced then RoundAdvanced + (verifies round increment is not hardcoded to 2). + +5. **Given** an encounter with a single combatant [A], activeIndex 0, + roundNumber 1, + **When** AdvanceTurn, + **Then** activeIndex is 0, roundNumber is 2, + and events are emitted in order: TurnAdvanced + (previousCombatantId A, newCombatantId A, roundNumber 2) then + RoundAdvanced (newRoundNumber 2). + +6. **Given** an encounter with combatants [A, B], activeIndex 0, + roundNumber 1, + **When** AdvanceTurn is applied twice in sequence, + **Then** after the first: activeIndex 1, roundNumber 1; + after the second: activeIndex 0, roundNumber 2. + +7. **Given** an encounter with an empty combatant list, + **When** AdvanceTurn, + **Then** the operation MUST fail with an invalid-encounter error. + No events are emitted. State is unchanged. + +8. **Given** an encounter with combatants [A, B, C], activeIndex 0, + roundNumber 1, + **When** AdvanceTurn is applied three times, + **Then** the encounter completes a full round cycle: + activeIndex returns to 0 and roundNumber is 2. + +--- + +### Edge Cases + +- Empty combatant list: AdvanceTurn MUST reject with an error. +- Single combatant: every advance wraps and increments the round. +- Large round numbers: no overflow or special-case behavior; round + increments uniformly. + +## Domain Model *(mandatory)* + +### Key Entities + +- **Combatant**: An identified participant in the encounter. For this + feature, a combatant is an opaque identity (e.g., a name or id). + The MVP baseline does not include HP, conditions, or stats. +- **Encounter**: The aggregate root. Contains an ordered list of + combatants (pre-sorted by initiative), an activeIndex pointing to + the current combatant, and a roundNumber (positive integer, + starting at 1). + +### Domain Events + +- **TurnAdvanced**: Emitted on every successful AdvanceTurn. + Carries: previousCombatantId, newCombatantId, roundNumber. +- **RoundAdvanced**: Emitted when activeIndex wraps past the last + combatant. Carries: newRoundNumber. + +When a round boundary is crossed, both TurnAdvanced and +RoundAdvanced MUST be emitted in that order (TurnAdvanced first). +This emission order is part of the observable domain contract and +MUST be verified by tests. + +### Invariants + +- **INV-1**: An encounter MUST have at least one combatant. + Operations on an empty encounter MUST fail. +- **INV-2**: activeIndex MUST always satisfy + 0 <= activeIndex < len(combatants). +- **INV-3**: roundNumber MUST be a positive integer (>= 1) and MUST + only increase (never decrease or reset). +- **INV-4**: AdvanceTurn MUST be a pure function of the current + encounter state. Given identical input, output MUST be identical. +- **INV-5**: Every successful AdvanceTurn MUST emit at least one + domain event (TurnAdvanced). No silent state changes. + +## Requirements *(mandatory)* + +### Functional Requirements + +- **FR-001**: The domain MUST expose an AdvanceTurn operation that + accepts an Encounter and returns the next Encounter state plus + emitted domain events. +- **FR-002**: AdvanceTurn MUST increment activeIndex by 1, wrapping + to 0 when past the last combatant. +- **FR-003**: When activeIndex wraps to 0, roundNumber MUST + increment by 1. +- **FR-004**: AdvanceTurn on an empty encounter MUST return an error + without modifying state or emitting events. +- **FR-005**: Domain events MUST be returned as values from the + operation, not dispatched via side effects. + +### Out of Scope (MVP baseline does not include) + +- Initiative rolling or combatant ordering logic +- Hit points, damage, conditions, or status effects +- Adding or removing combatants mid-encounter +- Persistence, serialization, or storage +- UI, CLI, or any adapter layer +- Agent behavior or suggestions + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: All 8 acceptance scenarios pass as deterministic, + pure-function tests with no I/O dependencies. +- **SC-002**: Invariants INV-1 through INV-5 are verified by tests. +- **SC-003**: The domain module has zero imports from application, + adapter, or agent layers (layer boundary compliance). diff --git a/specs/001-advance-turn/tasks.md b/specs/001-advance-turn/tasks.md new file mode 100644 index 0000000..2f65b5d --- /dev/null +++ b/specs/001-advance-turn/tasks.md @@ -0,0 +1,128 @@ +# Tasks: Advance Turn + +**Input**: Design documents from `/specs/001-advance-turn/` +**Prerequisites**: plan.md (required), spec.md (required) + +**Organization**: Tasks follow the phased structure from plan.md. There is only one user story (US1 — Advance Turn, P1), so phases map directly to the plan's milestones. + +## Format: `[ID] [P?] [Story?] Description` + +- **[P]**: Can run in parallel (different files, no dependencies) +- **[US1]**: User Story 1 — Advance Turn +- Exact file paths included in every task + +--- + +## Phase 1: Setup (Milestone 1 — Tooling) + +**Purpose**: Initialize pnpm monorepo, Biome, TypeScript, Vitest, and layer boundary enforcement + +- [X] T001 Initialize pnpm workspace and root config — create `pnpm-workspace.yaml`, `.nvmrc` (Node 22), root `package.json` (with `packageManager` pinning pnpm 10.6 and scripts: check, test, lint, format, typecheck), `biome.json`, and `tsconfig.base.json` (strict, composite, path aliases) +- [X] T002 [P] Create `packages/domain` package skeleton — `packages/domain/package.json` (`@initiative/domain`, no deps), `packages/domain/tsconfig.json` (extends base, composite), empty `packages/domain/src/index.ts` +- [X] T003 [P] Create `packages/application` package skeleton — `packages/application/package.json` (`@initiative/application`, depends on `@initiative/domain`), `packages/application/tsconfig.json` (extends base, references domain), empty `packages/application/src/index.ts` +- [X] T004 [P] Create `apps/web` package skeleton — `apps/web/package.json` (React 19, Vite 6.2, depends on both packages), `apps/web/tsconfig.json`, `apps/web/vite.config.ts`, `apps/web/index.html`, `apps/web/src/main.tsx`, `apps/web/src/App.tsx` (placeholder) +- [X] T005 Configure Vitest — add `vitest` as root dev dependency, create `vitest.config.ts` at root (workspace mode or per-package), verify `pnpm test` exits 0 +- [X] T006 Create layer boundary check — `scripts/check-layer-boundaries.mjs` (scans domain/application for forbidden imports) and `packages/domain/src/__tests__/layer-boundaries.test.ts` (wraps script as Vitest test) + +**Checkpoint**: `pnpm install` succeeds, `biome check .` runs, `tsc --build` compiles, `pnpm test` exits 0 with layer boundary test green. + +--- + +## Phase 2: Domain Implementation — User Story 1: Advance Turn (Priority: P1) (Milestone 1) + +**Goal**: Implement the complete AdvanceTurn domain logic as a pure function with all 8 acceptance scenarios and invariant tests. + +**Independent Test**: Pure state transition — given an Encounter value and AdvanceTurn action, assert resulting Encounter and emitted domain events. No I/O, persistence, or UI needed. + +- [ ] T007 [US1] Define domain types in `packages/domain/src/types.ts` — `CombatantId` (branded/opaque), `Combatant`, `Encounter` (combatants, activeIndex, roundNumber), factory `createEncounter` enforcing INV-1, INV-2, INV-3 +- [ ] T008 [P] [US1] Define domain events in `packages/domain/src/events.ts` — `TurnAdvanced`, `RoundAdvanced`, `DomainEvent` union (plain data, no classes) +- [ ] T009 [US1] Implement `advanceTurn` in `packages/domain/src/advance-turn.ts` — pure function `(Encounter) => { encounter, events } | DomainError`, implements FR-001 through FR-005 +- [ ] T010 [US1] Write tests for all 8 acceptance scenarios + invariants in `packages/domain/src/__tests__/advance-turn.test.ts` — scenarios 1–8, INV-1 through INV-5, event ordering on round wrap +- [ ] T011 [US1] Export public API from `packages/domain/src/index.ts` — re-export types, events, `advanceTurn`, `createEncounter` + +**Checkpoint (Milestone 1)**: `pnpm check` passes (format + lint + typecheck + test + layer boundaries). All 8 scenarios + invariants green. No React/Vite imports in domain or application. + +--- + +## Phase 3: Application + Web Shell (Milestone 2) + +**Goal**: Wire up the application use case and minimal React UI with a "Next Turn" button. + +- [ ] T012 Define port interface in `packages/application/src/ports.ts` — `EncounterStore` port: `get(): Encounter`, `save(e: Encounter)` +- [ ] T013 Implement `AdvanceTurnUseCase` in `packages/application/src/advance-turn-use-case.ts` — accepts `EncounterStore`, calls `advanceTurn`, saves result, returns events +- [ ] T014 Export public API from `packages/application/src/index.ts` — re-export use case and port types +- [ ] T015 Implement `useEncounter` hook in `apps/web/src/hooks/use-encounter.ts` — in-memory `EncounterStore` via React state, exposes encounter state + `advanceTurn` action, hardcoded 3-combatant demo +- [ ] T016 Wire up `apps/web/src/App.tsx` — display current combatant, round number, combatant list with active indicator, "Next Turn" button, emitted events + +**Checkpoint (Milestone 2)**: `pnpm check` passes. `vite build` succeeds. Clicking "Next Turn" cycles combatants and increments rounds correctly. + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +- **Phase 1 (Setup)**: No dependencies — start immediately +- **Phase 2 (Domain)**: Depends on Phase 1 completion +- **Phase 3 (App + Web)**: Depends on Phase 2 completion (needs domain types and `advanceTurn`) + +### Within Phase 1 + +- T001 must complete first (workspace and root config) +- T002, T003, T004 can run in parallel [P] after T001 +- T005 depends on T001 (needs root package.json) +- T006 depends on T002 and T005 (needs domain package + Vitest) + +### Within Phase 2 + +- T007 must complete first (types needed by everything) +- T008 can run in parallel [P] with T007 (events are independent types) +- T009 depends on T007 and T008 (uses types and events) +- T010 depends on T009 (tests the implementation) +- T011 depends on T007, T008, T009 (exports all public API) + +### Within Phase 3 + +- T012 first (port interface) +- T013 depends on T012 (uses port) +- T014 depends on T013 (exports use case) +- T015 depends on T014 (uses application layer) +- T016 depends on T015 (uses hook) + +--- + +## Parallel Opportunities + +```text +# After T001 completes: +T002, T003, T004 — all package skeletons in parallel + +# After T007 starts: +T008 — domain events can be written in parallel with types + +# Independent stories: only one user story (US1), so parallelism is within-phase only +``` + +--- + +## Implementation Strategy + +### MVP First (Milestone 1) + +1. Complete Phase 1: Setup (T001–T006) +2. Complete Phase 2: Domain (T007–T011) +3. **STOP and VALIDATE**: `pnpm check` passes, all 8 scenarios green + +### Full Feature (Milestone 2) + +4. Complete Phase 3: App + Web Shell (T012–T016) +5. **VALIDATE**: `pnpm check` passes, app runs in browser + +--- + +## Notes + +- All task IDs (T001–T016) match plan.md — no scope expansion +- Single user story (US1: Advance Turn) — no cross-story dependencies +- Tests (T010) are included as specified in plan.md and spec.md +- Domain package must have zero React/Vite imports (enforced by T006) diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..ceb7f35 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "strict": true, + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "composite": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "verbatimModuleSyntax": true + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1e9dca2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,8 @@ +{ + "files": [], + "references": [ + { "path": "packages/domain" }, + { "path": "packages/application" }, + { "path": "apps/web" } + ] +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..07a81fe --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["packages/*/src/**/*.test.ts"], + passWithNoTests: true, + }, +});