diff --git a/.claude/skills/speckit-analyze/SKILL.md b/.claude/skills/speckit-analyze/SKILL.md
index 0dfd0a5c89..3485735941 100644
--- a/.claude/skills/speckit-analyze/SKILL.md
+++ b/.claude/skills/speckit-analyze/SKILL.md
@@ -31,7 +31,6 @@ You **MUST** consider the user input before proceeding (if not empty).
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
- For each executable hook, output the following based on its `optional` flag:
-
- **Optional hook** (`optional: true`):
```
@@ -223,7 +222,6 @@ After reporting, check if `.specify/extensions.yml` exists in the project root.
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
- For each executable hook, output the following based on its `optional` flag:
-
- **Optional hook** (`optional: true`):
```
diff --git a/.claude/skills/speckit-checklist/SKILL.md b/.claude/skills/speckit-checklist/SKILL.md
index f63a03866d..0ae8ebecd5 100644
--- a/.claude/skills/speckit-checklist/SKILL.md
+++ b/.claude/skills/speckit-checklist/SKILL.md
@@ -52,7 +52,6 @@ You **MUST** consider the user input before proceeding (if not empty).
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
- For each executable hook, output the following based on its `optional` flag:
-
- **Optional hook** (`optional: true`):
```
@@ -83,19 +82,16 @@ You **MUST** consider the user input before proceeding (if not empty).
## Execution Steps
1. **Setup**: Run `.specify/scripts/bash/check-prerequisites.sh --json` from repo root and parse JSON for FEATURE_DIR and AVAILABLE_DOCS list.
-
- All file paths must be absolute.
- For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
2. **Clarify intent (dynamic)**: Derive up to THREE initial contextual clarifying questions (no pre-baked catalog). They MUST:
-
- Be generated from the user's phrasing + extracted signals from spec/plan/tasks
- Only ask about information that materially changes checklist content
- Be skipped individually if already unambiguous in `$ARGUMENTS`
- Prefer precision over breadth
Generation algorithm:
-
1. Extract signals: feature domain keywords (e.g., auth, latency, UX, API), risk indicators ("critical", "must", "compliance"), stakeholder hints ("QA", "review", "security team"), and explicit deliverables ("a11y", "rollback", "contracts").
2. Cluster signals into candidate focus areas (max 4) ranked by relevance.
3. Identify probable audience & timing (author, reviewer, QA, release) if not explicit.
@@ -109,14 +105,12 @@ You **MUST** consider the user input before proceeding (if not empty).
- Scenario class gap (e.g., "No recovery flows detected—are rollback / partial failure paths in scope?")
Question formatting rules:
-
- If presenting options, generate a compact table with columns: Option | Candidate | Why It Matters
- Limit to A–E options maximum; omit table if a free-form answer is clearer
- Never ask the user to restate what they already said
- Avoid speculative categories (no hallucination). If uncertain, ask explicitly: "Confirm whether X belongs in scope."
Defaults when interaction impossible:
-
- Depth: Standard
- Audience: Reviewer (PR) if code-related; Author otherwise
- Focus: Top 2 relevance clusters
@@ -124,27 +118,23 @@ You **MUST** consider the user input before proceeding (if not empty).
Output the questions (label Q1/Q2/Q3). After answers: if ≥2 scenario classes (Alternate / Exception / Recovery / Non-Functional domain) remain unclear, you MAY ask up to TWO more targeted follow‑ups (Q4/Q5) with a one-line justification each (e.g., "Unresolved recovery path risk"). Do not exceed five total questions. Skip escalation if user explicitly declines more.
3. **Understand user request**: Combine `$ARGUMENTS` + clarifying answers:
-
- Derive checklist theme (e.g., security, review, deploy, ux)
- Consolidate explicit must-have items mentioned by user
- Map focus selections to category scaffolding
- Infer any missing context from spec/plan/tasks (do NOT hallucinate)
4. **Load feature context**: Read from FEATURE_DIR:
-
- spec.md: Feature requirements and scope
- plan.md (if exists): Technical details, dependencies
- tasks.md (if exists): Implementation tasks
**Context Loading Strategy**:
-
- Load only necessary portions relevant to active focus areas (avoid full-file dumping)
- Prefer summarizing long sections into concise scenario/requirement bullets
- Use progressive disclosure: add follow-on retrieval only if gaps detected
- If source docs are large, generate interim summary items instead of embedding raw text
5. **Generate checklist** - Create "Unit Tests for Requirements":
-
- Create `FEATURE_DIR/checklists/` directory if it doesn't exist
- Generate unique checklist filename:
- Use short, descriptive name based on domain (e.g., `ux.md`, `api.md`, `security.md`)
@@ -156,7 +146,6 @@ You **MUST** consider the user input before proceeding (if not empty).
**CORE PRINCIPLE - Test the Requirements, Not the Implementation**:
Every checklist item MUST evaluate the REQUIREMENTS THEMSELVES for:
-
- **Completeness**: Are all necessary requirements present?
- **Clarity**: Are requirements unambiguous and specific?
- **Consistency**: Do requirements align with each other?
@@ -164,7 +153,6 @@ You **MUST** consider the user input before proceeding (if not empty).
- **Coverage**: Are all scenarios/edge cases addressed?
**Category Structure** - Group items by requirement quality dimensions:
-
- **Requirement Completeness** (Are all necessary requirements documented?)
- **Requirement Clarity** (Are requirements specific and unambiguous?)
- **Requirement Consistency** (Do requirements align without conflicts?)
@@ -178,13 +166,11 @@ You **MUST** consider the user input before proceeding (if not empty).
**HOW TO WRITE CHECKLIST ITEMS - "Unit Tests for English"**:
❌ **WRONG** (Testing implementation):
-
- "Verify landing page displays 3 episode cards"
- "Test hover states work on desktop"
- "Confirm logo click navigates home"
✅ **CORRECT** (Testing requirements quality):
-
- "Are the exact number and layout of featured episodes specified?" [Completeness]
- "Is 'prominent display' quantified with specific sizing/positioning?" [Clarity]
- "Are hover state requirements consistent across all interactive elements?" [Consistency]
@@ -195,7 +181,6 @@ You **MUST** consider the user input before proceeding (if not empty).
**ITEM STRUCTURE**:
Each item should follow this pattern:
-
- Question format asking about requirement quality
- Focus on what's WRITTEN (or not written) in the spec/plan
- Include quality dimension in brackets [Completeness/Clarity/Consistency/etc.]
@@ -205,49 +190,41 @@ You **MUST** consider the user input before proceeding (if not empty).
**EXAMPLES BY QUALITY DIMENSION**:
Completeness:
-
- "Are error handling requirements defined for all API failure modes? [Gap]"
- "Are accessibility requirements specified for all interactive elements? [Completeness]"
- "Are mobile breakpoint requirements defined for responsive layouts? [Gap]"
Clarity:
-
- "Is 'fast loading' quantified with specific timing thresholds? [Clarity, Spec §NFR-2]"
- "Are 'related episodes' selection criteria explicitly defined? [Clarity, Spec §FR-5]"
- "Is 'prominent' defined with measurable visual properties? [Ambiguity, Spec §FR-4]"
Consistency:
-
- "Do navigation requirements align across all pages? [Consistency, Spec §FR-10]"
- "Are card component requirements consistent between landing and detail pages? [Consistency]"
Coverage:
-
- "Are requirements defined for zero-state scenarios (no episodes)? [Coverage, Edge Case]"
- "Are concurrent user interaction scenarios addressed? [Coverage, Gap]"
- "Are requirements specified for partial data loading failures? [Coverage, Exception Flow]"
Measurability:
-
- "Are visual hierarchy requirements measurable/testable? [Acceptance Criteria, Spec §FR-1]"
- "Can 'balanced visual weight' be objectively verified? [Measurability, Spec §FR-2]"
**Scenario Classification & Coverage** (Requirements Quality Focus):
-
- Check if requirements exist for: Primary, Alternate, Exception/Error, Recovery, Non-Functional scenarios
- For each scenario class, ask: "Are [scenario type] requirements complete, clear, and consistent?"
- If scenario class missing: "Are [scenario type] requirements intentionally excluded or missing? [Gap]"
- Include resilience/rollback when state mutation occurs: "Are rollback requirements defined for migration failures? [Gap]"
**Traceability Requirements**:
-
- MINIMUM: ≥80% of items MUST include at least one traceability reference
- Each item should reference: spec section `[Spec §X.Y]`, or use markers: `[Gap]`, `[Ambiguity]`, `[Conflict]`, `[Assumption]`
- If no ID system exists: "Is a requirement & acceptance criteria ID scheme established? [Traceability]"
**Surface & Resolve Issues** (Requirements Quality Problems):
Ask questions about the requirements themselves:
-
- Ambiguities: "Is the term 'fast' quantified with specific metrics? [Ambiguity, Spec §NFR-1]"
- Conflicts: "Do navigation requirements conflict between §FR-10 and §FR-10a? [Conflict]"
- Assumptions: "Is the assumption of 'always available podcast API' validated? [Assumption]"
@@ -255,13 +232,11 @@ You **MUST** consider the user input before proceeding (if not empty).
- Missing definitions: "Is 'visual hierarchy' defined with measurable criteria? [Gap]"
**Content Consolidation**:
-
- Soft cap: If raw candidate items > 40, prioritize by risk/impact
- Merge near-duplicates checking the same requirement aspect
- If >5 low-impact edge cases, create one item: "Are edge cases X, Y, Z addressed in requirements? [Coverage]"
**🚫 ABSOLUTELY PROHIBITED** - These make it an implementation test, not a requirements test:
-
- ❌ Any item starting with "Verify", "Test", "Confirm", "Check" + implementation behavior
- ❌ References to code execution, user actions, system behavior
- ❌ "Displays correctly", "works properly", "functions as expected"
@@ -270,7 +245,6 @@ You **MUST** consider the user input before proceeding (if not empty).
- ❌ Implementation details (frameworks, APIs, algorithms)
**✅ REQUIRED PATTERNS** - These test requirements quality:
-
- ✅ "Are [requirement type] defined/specified/documented for [scenario]?"
- ✅ "Is [vague term] quantified/clarified with specific criteria?"
- ✅ "Are requirements consistent between [section A] and [section B]?"
@@ -381,7 +355,6 @@ Check if `.specify/extensions.yml` exists in the project root.
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
- For each executable hook, output the following based on its `optional` flag:
-
- **Optional hook** (`optional: true`):
```
diff --git a/.claude/skills/speckit-clarify/SKILL.md b/.claude/skills/speckit-clarify/SKILL.md
index b072158718..bf19034d7f 100644
--- a/.claude/skills/speckit-clarify/SKILL.md
+++ b/.claude/skills/speckit-clarify/SKILL.md
@@ -31,7 +31,6 @@ You **MUST** consider the user input before proceeding (if not empty).
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
- For each executable hook, output the following based on its `optional` flag:
-
- **Optional hook** (`optional: true`):
```
@@ -68,7 +67,6 @@ Note: This clarification workflow is expected to run (and be completed) BEFORE i
Execution steps:
1. Run `.specify/scripts/bash/check-prerequisites.sh --json --paths-only` from repo root **once** (combined `--json --paths-only` mode / `-Json -PathsOnly`). Parse minimal JSON payload fields:
-
- `FEATURE_DIR`
- `FEATURE_SPEC`
- (Optionally capture `IMPL_PLAN`, `TASKS` for future chained flows.)
@@ -78,26 +76,22 @@ Execution steps:
2. Load the current spec file. Perform a structured ambiguity & coverage scan using this taxonomy. For each category, mark status: Clear / Partial / Missing. Produce an internal coverage map used for prioritization (do not output raw map unless no questions will be asked).
Functional Scope & Behavior:
-
- Core user goals & success criteria
- Explicit out-of-scope declarations
- User roles / personas differentiation
Domain & Data Model:
-
- Entities, attributes, relationships
- Identity & uniqueness rules
- Lifecycle/state transitions
- Data volume / scale assumptions
Interaction & UX Flow:
-
- Critical user journeys / sequences
- Error/empty/loading states
- Accessibility or localization notes
Non-Functional Quality Attributes:
-
- Performance (latency, throughput targets)
- Scalability (horizontal/vertical, limits)
- Reliability & availability (uptime, recovery expectations)
@@ -106,44 +100,36 @@ Execution steps:
- Compliance / regulatory constraints (if any)
Integration & External Dependencies:
-
- External services/APIs and failure modes
- Data import/export formats
- Protocol/versioning assumptions
Edge Cases & Failure Handling:
-
- Negative scenarios
- Rate limiting / throttling
- Conflict resolution (e.g., concurrent edits)
Constraints & Tradeoffs:
-
- Technical constraints (language, storage, hosting)
- Explicit tradeoffs or rejected alternatives
Terminology & Consistency:
-
- Canonical glossary terms
- Avoided synonyms / deprecated terms
Completion Signals:
-
- Acceptance criteria testability
- Measurable Definition of Done style indicators
Misc / Placeholders:
-
- TODO markers / unresolved decisions
- Ambiguous adjectives ("robust", "intuitive") lacking quantification
For each category with Partial or Missing status, add a candidate question opportunity unless:
-
- Clarification would not materially change implementation or validation strategy
- Information is better deferred to planning phase (note internally)
3. Generate (internally) a prioritized queue of candidate clarification questions (maximum 5). Do NOT output them all at once. Apply these constraints:
-
- Maximum of 5 total questions across the whole session.
- Each question must be answerable with EITHER:
- A short multiple‑choice selection (2–5 distinct, mutually exclusive options), OR
@@ -155,10 +141,8 @@ Execution steps:
- If more than 5 categories remain unresolved, select the top 5 by (Impact \* Uncertainty) heuristic.
4. Sequential questioning loop (interactive):
-
- Present EXACTLY ONE question at a time.
- For multiple‑choice questions:
-
- **Analyze all options** and determine the **most suitable option** based on:
- Best practices for the project type
- Common patterns in similar implementations
@@ -174,7 +158,6 @@ Execution steps:
| B | |
| C | (add D/E as needed up to 5) |
| Short | Provide a different short answer (<=5 words) (Include only if free-form alternative is appropriate) |
-
- After the table, add: `You can reply with the option letter (e.g., "A"), accept the recommendation by saying "yes" or "recommended", or provide your own short answer.`
- For short‑answer style (no meaningful discrete options):
@@ -194,7 +177,6 @@ Execution steps:
- If no valid questions exist at start, immediately report no critical ambiguities.
5. Integration after EACH accepted answer (incremental update approach):
-
- Maintain in-memory representation of the spec (loaded once at start) plus the raw file contents.
- For the first integrated answer in this session:
- Ensure a `## Clarifications` section exists (create it just after the highest-level contextual/overview section per the spec template if missing).
@@ -213,7 +195,6 @@ Execution steps:
- Keep each inserted clarification minimal and testable (avoid narrative drift).
6. Validation (performed after EACH write plus final pass):
-
- Clarifications session contains exactly one bullet per accepted answer (no duplicates).
- Total asked (accepted) questions ≤ 5.
- Updated sections contain no lingering vague placeholders the new answer was meant to resolve.
@@ -256,7 +237,6 @@ Check if `.specify/extensions.yml` exists in the project root.
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
- For each executable hook, output the following based on its `optional` flag:
-
- **Optional hook** (`optional: true`):
```
diff --git a/.claude/skills/speckit-constitution/SKILL.md b/.claude/skills/speckit-constitution/SKILL.md
index a4f9246a19..2ca95e138b 100644
--- a/.claude/skills/speckit-constitution/SKILL.md
+++ b/.claude/skills/speckit-constitution/SKILL.md
@@ -31,7 +31,6 @@ You **MUST** consider the user input before proceeding (if not empty).
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
- For each executable hook, output the following based on its `optional` flag:
-
- **Optional hook** (`optional: true`):
```
@@ -68,12 +67,10 @@ You are updating the project constitution at `.specify/memory/constitution.md`.
Follow this execution flow:
1. Load the existing constitution at `.specify/memory/constitution.md`.
-
- Identify every placeholder token of the form `[ALL_CAPS_IDENTIFIER]`.
**IMPORTANT**: The user might require less or more principles than the ones used in the template. If a number is specified, respect that - follow the general template. You will update the doc accordingly.
2. Collect/derive values for placeholders:
-
- If user input (conversation) supplies a value, use it.
- Otherwise infer from existing repo context (README, docs, prior constitution versions if embedded).
- For governance dates: `RATIFICATION_DATE` is the original adoption date (if unknown ask or mark TODO), `LAST_AMENDED_DATE` is today if changes are made, otherwise keep previous.
@@ -84,14 +81,12 @@ Follow this execution flow:
- If version bump type ambiguous, propose reasoning before finalizing.
3. Draft the updated constitution content:
-
- Replace every placeholder with concrete text (no bracketed tokens left except intentionally retained template slots that the project has chosen not to define yet—explicitly justify any left).
- Preserve heading hierarchy and comments can be removed once replaced unless they still add clarifying guidance.
- Ensure each Principle section: succinct name line, paragraph (or bullet list) capturing non‑negotiable rules, explicit rationale if not obvious.
- Ensure Governance section lists amendment procedure, versioning policy, and compliance review expectations.
4. Consistency propagation checklist (convert prior checklist into active validations):
-
- Read `.specify/templates/plan-template.md` and ensure any "Constitution Check" or rules align with updated principles.
- Read `.specify/templates/spec-template.md` for scope/requirements alignment—update if constitution adds/removes mandatory sections or constraints.
- Read `.specify/templates/tasks-template.md` and ensure task categorization reflects new or removed principle-driven task types (e.g., observability, versioning, testing discipline).
@@ -99,7 +94,6 @@ Follow this execution flow:
- Read any runtime guidance docs (e.g., `README.md`, `docs/quickstart.md`, or agent-specific guidance files if present). Update references to principles changed.
5. Produce a Sync Impact Report (prepend as an HTML comment at top of the constitution file after update):
-
- Version change: old → new
- List of modified principles (old title → new title if renamed)
- Added sections
@@ -108,7 +102,6 @@ Follow this execution flow:
- Follow-up TODOs if any placeholders intentionally deferred.
6. Validation before final output:
-
- No remaining unexplained bracket tokens.
- Version line matches report.
- Dates ISO format YYYY-MM-DD.
@@ -147,7 +140,6 @@ Check if `.specify/extensions.yml` exists in the project root.
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
- For each executable hook, output the following based on its `optional` flag:
-
- **Optional hook** (`optional: true`):
```
diff --git a/.claude/skills/speckit-implement/SKILL.md b/.claude/skills/speckit-implement/SKILL.md
index 305afad8de..1c02df0825 100644
--- a/.claude/skills/speckit-implement/SKILL.md
+++ b/.claude/skills/speckit-implement/SKILL.md
@@ -31,7 +31,6 @@ You **MUST** consider the user input before proceeding (if not empty).
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
- For each executable hook, output the following based on its `optional` flag:
-
- **Optional hook** (`optional: true`):
```
@@ -64,7 +63,6 @@ You **MUST** consider the user input before proceeding (if not empty).
1. Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute. For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
2. **Check checklists status** (if FEATURE_DIR/checklists/ exists):
-
- Scan all checklist files in the checklists/ directory
- For each checklist, count:
- Total items: All lines matching `- [ ]` or `- [X]` or `- [x]`
@@ -81,12 +79,10 @@ You **MUST** consider the user input before proceeding (if not empty).
```
- Calculate overall status:
-
- **PASS**: All checklists have 0 incomplete items
- **FAIL**: One or more checklists have incomplete items
- **If any checklist is incomplete**:
-
- Display the table with incomplete item counts
- **STOP** and ask: "Some checklists are incomplete. Do you want to proceed with implementation anyway? (yes/no)"
- Wait for user response before continuing
@@ -98,7 +94,6 @@ You **MUST** consider the user input before proceeding (if not empty).
- Automatically proceed to step 3
3. Load and analyze the implementation context:
-
- **REQUIRED**: Read tasks.md for the complete task list and execution plan
- **REQUIRED**: Read plan.md for tech stack, architecture, and file structure
- **IF EXISTS**: Read data-model.md for entities and relationships
@@ -108,11 +103,9 @@ You **MUST** consider the user input before proceeding (if not empty).
- **IF EXISTS**: Read quickstart.md for integration scenarios
4. **Project Setup Verification**:
-
- **REQUIRED**: Create/verify ignore files based on actual project setup:
**Detection & Creation Logic**:
-
- Check if the following command succeeds to determine if the repository is a git repo (create/verify .gitignore if so):
```sh
@@ -131,7 +124,6 @@ You **MUST** consider the user input before proceeding (if not empty).
**If ignore file missing**: Create with full pattern set for detected technology
**Common Patterns by Technology** (from plan.md tech stack):
-
- **Node.js/JavaScript/TypeScript**: `node_modules/`, `dist/`, `build/`, `*.log`, `.env*`
- **Python**: `__pycache__/`, `*.pyc`, `.venv/`, `venv/`, `dist/`, `*.egg-info/`
- **Java**: `target/`, `*.class`, `*.jar`, `.gradle/`, `build/`
@@ -148,7 +140,6 @@ You **MUST** consider the user input before proceeding (if not empty).
- **Universal**: `.DS_Store`, `Thumbs.db`, `*.tmp`, `*.swp`, `.vscode/`, `.idea/`
**Tool-Specific Patterns**:
-
- **Docker**: `node_modules/`, `.git/`, `Dockerfile*`, `.dockerignore`, `*.log*`, `.env*`, `coverage/`
- **ESLint**: `node_modules/`, `dist/`, `build/`, `coverage/`, `*.min.js`
- **Prettier**: `node_modules/`, `dist/`, `build/`, `coverage/`, `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`
@@ -156,14 +147,12 @@ You **MUST** consider the user input before proceeding (if not empty).
- **Kubernetes/k8s**: `*.secret.yaml`, `secrets/`, `.kube/`, `kubeconfig*`, `*.key`, `*.crt`
5. Parse tasks.md structure and extract:
-
- **Task phases**: Setup, Tests, Core, Integration, Polish
- **Task dependencies**: Sequential vs parallel execution rules
- **Task details**: ID, description, file paths, parallel markers [P]
- **Execution flow**: Order and dependency requirements
6. Execute implementation following the task plan:
-
- **Phase-by-phase execution**: Complete each phase before moving to the next
- **Respect dependencies**: Run sequential tasks in order, parallel tasks [P] can run together
- **Follow TDD approach**: Execute test tasks before their corresponding implementation tasks
@@ -171,7 +160,6 @@ You **MUST** consider the user input before proceeding (if not empty).
- **Validation checkpoints**: Verify each phase completion before proceeding
7. Implementation execution rules:
-
- **Setup first**: Initialize project structure, dependencies, configuration
- **Tests before code**: If you need to write tests for contracts, entities, and integration scenarios
- **Core development**: Implement models, services, CLI commands, endpoints
@@ -179,7 +167,6 @@ You **MUST** consider the user input before proceeding (if not empty).
- **Polish and validation**: Unit tests, performance optimization, documentation
8. Progress tracking and error handling:
-
- Report progress after each completed task
- Halt execution if any non-parallel task fails
- For parallel tasks [P], continue with successful tasks, report failed ones
@@ -197,7 +184,6 @@ You **MUST** consider the user input before proceeding (if not empty).
Note: This command assumes a complete task breakdown exists in tasks.md. If tasks are incomplete or missing, suggest running `/speckit-tasks` first to regenerate the task list.
10. **Check for extension hooks**: After completion validation, check if `.specify/extensions.yml` exists in the project root.
-
- If it exists, read it and look for entries under the `hooks.after_implement` key
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
@@ -206,7 +192,6 @@ Note: This command assumes a complete task breakdown exists in tasks.md. If task
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
- For each executable hook, output the following based on its `optional` flag:
-
- **Optional hook** (`optional: true`):
```
diff --git a/.claude/skills/speckit-plan/SKILL.md b/.claude/skills/speckit-plan/SKILL.md
index b0087f09a6..c1344f3928 100644
--- a/.claude/skills/speckit-plan/SKILL.md
+++ b/.claude/skills/speckit-plan/SKILL.md
@@ -31,7 +31,6 @@ You **MUST** consider the user input before proceeding (if not empty).
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
- For each executable hook, output the following based on its `optional` flag:
-
- **Optional hook** (`optional: true`):
```
@@ -66,7 +65,6 @@ You **MUST** consider the user input before proceeding (if not empty).
2. **Load context**: Read FEATURE_SPEC and `.specify/memory/constitution.md`. Load IMPL_PLAN template (already copied).
3. **Execute plan workflow**: Follow the structure in IMPL_PLAN template to:
-
- Fill Technical Context (mark unknowns as "NEEDS CLARIFICATION")
- Fill Constitution Check section from constitution
- Evaluate gates (ERROR if violations unjustified)
@@ -78,7 +76,6 @@ You **MUST** consider the user input before proceeding (if not empty).
4. **Stop and report**: Command ends after Phase 2 planning. Report branch, IMPL_PLAN path, and generated artifacts.
5. **Check for extension hooks**: After reporting, check if `.specify/extensions.yml` exists in the project root.
-
- If it exists, read it and look for entries under the `hooks.after_plan` key
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
@@ -87,7 +84,6 @@ You **MUST** consider the user input before proceeding (if not empty).
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
- For each executable hook, output the following based on its `optional` flag:
-
- **Optional hook** (`optional: true`):
```
@@ -118,7 +114,6 @@ You **MUST** consider the user input before proceeding (if not empty).
### Phase 0: Outline & Research
1. **Extract unknowns from Technical Context** above:
-
- For each NEEDS CLARIFICATION → research task
- For each dependency → best practices task
- For each integration → patterns task
@@ -144,13 +139,11 @@ You **MUST** consider the user input before proceeding (if not empty).
**Prerequisites:** `research.md` complete
1. **Extract entities from feature spec** → `data-model.md`:
-
- Entity name, fields, relationships
- Validation rules from requirements
- State transitions if applicable
2. **Define interface contracts** (if project has external interfaces) → `/contracts/`:
-
- Identify what interfaces the project exposes to users or other systems
- Document the contract format appropriate for the project type
- Examples: public APIs for libraries, command schemas for CLI tools, endpoints for web services, grammars for parsers, UI contracts for applications
diff --git a/.claude/skills/speckit-specify/SKILL.md b/.claude/skills/speckit-specify/SKILL.md
index bfaa994a1a..b5cfb49688 100644
--- a/.claude/skills/speckit-specify/SKILL.md
+++ b/.claude/skills/speckit-specify/SKILL.md
@@ -31,7 +31,6 @@ You **MUST** consider the user input before proceeding (if not empty).
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
- For each executable hook, output the following based on its `optional` flag:
-
- **Optional hook** (`optional: true`):
```
@@ -66,7 +65,6 @@ The text the user typed after `/speckit-specify` in the triggering message **is*
Given that feature description, do this:
1. **Generate a concise short name** (2-4 words) for the feature:
-
- Analyze the feature description and extract the most meaningful keywords
- Create a 2-4 word short name that captures the essence of the feature
- Use action-noun format when possible (e.g., "add-user-auth", "fix-payment-bug")
@@ -89,7 +87,6 @@ Given that feature description, do this:
Specs live under the default `specs/` directory unless the user explicitly provides `SPECIFY_FEATURE_DIRECTORY`.
**Resolution order for `SPECIFY_FEATURE_DIRECTORY`**:
-
1. If the user explicitly provided `SPECIFY_FEATURE_DIRECTORY` (e.g., via environment variable, argument, or configuration), use it as-is
2. Otherwise, auto-generate it under `specs/`:
- Check `.specify/init-options.json` for `branch_numbering`
@@ -99,7 +96,6 @@ Given that feature description, do this:
- Set `SPECIFY_FEATURE_DIRECTORY` to `specs/`
**Create the directory and spec file**:
-
- `mkdir -p SPECIFY_FEATURE_DIRECTORY`
- Copy `.specify/templates/spec-template.md` to `SPECIFY_FEATURE_DIRECTORY/spec.md` as the starting point
- Set `SPEC_FILE` to `SPECIFY_FEATURE_DIRECTORY/spec.md`
@@ -113,7 +109,6 @@ Given that feature description, do this:
This allows downstream commands (`/speckit-plan`, `/speckit-tasks`, etc.) to locate the feature directory without relying on git branch name conventions.
**IMPORTANT**:
-
- You must only create one feature per `/speckit-specify` invocation
- The spec directory name and the git branch name are independent — they may be the same but that is the user's choice
- The spec directory and file are always created by this command, never by the hook
@@ -121,7 +116,6 @@ Given that feature description, do this:
4. Load `.specify/templates/spec-template.md` to understand required sections.
5. Follow this execution flow:
-
1. Parse user description from arguments
If empty: ERROR "No feature description provided"
2. Extract key concepts from description
@@ -190,23 +184,19 @@ Given that feature description, do this:
```
b. **Run Validation Check**: Review the spec against each checklist item:
-
- For each item, determine if it passes or fails
- Document specific issues found (quote relevant spec sections)
c. **Handle Validation Results**:
-
- **If all items pass**: Mark checklist complete and proceed to step 8
- **If items fail (excluding [NEEDS CLARIFICATION])**:
-
1. List the failing items and specific issues
2. Update the spec to address each issue
3. Re-run validation until all items pass (max 3 iterations)
4. If still failing after 3 iterations, document remaining issues in checklist notes and warn user
- **If [NEEDS CLARIFICATION] markers remain**:
-
1. Extract all [NEEDS CLARIFICATION: ...] markers from the spec
2. **LIMIT CHECK**: If more than 3 markers exist, keep only the 3 most critical (by scope/security/UX impact) and make informed guesses for the rest
3. For each clarification needed (max 3), present options to user in this format:
@@ -244,14 +234,12 @@ Given that feature description, do this:
d. **Update Checklist**: After each validation iteration, update the checklist file with current pass/fail status
8. **Report completion** to the user with:
-
- `SPECIFY_FEATURE_DIRECTORY` — the feature directory path
- `SPEC_FILE` — the spec file path
- Checklist results summary
- Readiness for the next phase (`/speckit-clarify` or `/speckit-plan`)
9. **Check for extension hooks**: After reporting completion, check if `.specify/extensions.yml` exists in the project root.
-
- If it exists, read it and look for entries under the `hooks.after_specify` key
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
@@ -260,7 +248,6 @@ Given that feature description, do this:
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
- For each executable hook, output the following based on its `optional` flag:
-
- **Optional hook** (`optional: true`):
```
diff --git a/.claude/skills/speckit-tasks/SKILL.md b/.claude/skills/speckit-tasks/SKILL.md
index e8aa2f420e..b612ac8d12 100644
--- a/.claude/skills/speckit-tasks/SKILL.md
+++ b/.claude/skills/speckit-tasks/SKILL.md
@@ -31,7 +31,6 @@ You **MUST** consider the user input before proceeding (if not empty).
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
- For each executable hook, output the following based on its `optional` flag:
-
- **Optional hook** (`optional: true`):
```
@@ -64,13 +63,11 @@ You **MUST** consider the user input before proceeding (if not empty).
1. **Setup**: Run `.specify/scripts/bash/setup-tasks.sh --json` from repo root and parse FEATURE_DIR, TASKS_TEMPLATE, and AVAILABLE_DOCS list. `FEATURE_DIR` and `TASKS_TEMPLATE` must be absolute paths when provided. `AVAILABLE_DOCS` is a list of document names/relative paths available under `FEATURE_DIR` (for example `research.md` or `contracts/`). For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
2. **Load design documents**: Read from FEATURE_DIR:
-
- **Required**: plan.md (tech stack, libraries, structure), spec.md (user stories with priorities)
- **Optional**: data-model.md (entities), contracts/ (interface contracts), research.md (decisions), quickstart.md (test scenarios)
- Note: Not all projects have all documents. Generate tasks based on what's available.
3. **Execute task generation workflow**:
-
- Load plan.md and extract tech stack, libraries, project structure
- Load spec.md and extract user stories with their priorities (P1, P2, P3, etc.)
- If data-model.md exists: Extract entities and map to user stories
@@ -82,7 +79,6 @@ You **MUST** consider the user input before proceeding (if not empty).
- Validate task completeness (each user story has all needed tasks, independently testable)
4. **Generate tasks.md**: Read the tasks template from TASKS_TEMPLATE (from the JSON output above) and use it as structure. If TASKS_TEMPLATE is empty, fall back to `.specify/templates/tasks-template.md`. Fill with:
-
- Correct feature name from plan.md
- Phase 1: Setup tasks (project initialization)
- Phase 2: Foundational tasks (blocking prerequisites for all user stories)
@@ -96,7 +92,6 @@ You **MUST** consider the user input before proceeding (if not empty).
- Implementation strategy section (MVP first, incremental delivery)
5. **Report**: Output path to generated tasks.md and summary:
-
- Total task count
- Task count per user story
- Parallel opportunities identified
@@ -105,7 +100,6 @@ You **MUST** consider the user input before proceeding (if not empty).
- Format validation: Confirm ALL tasks follow the checklist format (checkbox, ID, labels, file paths)
6. **Check for extension hooks**: After tasks.md is generated, check if `.specify/extensions.yml` exists in the project root.
-
- If it exists, read it and look for entries under the `hooks.after_tasks` key
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
@@ -114,7 +108,6 @@ You **MUST** consider the user input before proceeding (if not empty).
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
- For each executable hook, output the following based on its `optional` flag:
-
- **Optional hook** (`optional: true`):
```
@@ -185,7 +178,6 @@ Every task MUST strictly follow this format:
### Task Organization
1. **From User Stories (spec.md)** - PRIMARY ORGANIZATION:
-
- Each user story (P1, P2, P3...) gets its own phase
- Map all related components to their story:
- Models needed for that story
@@ -195,12 +187,10 @@ Every task MUST strictly follow this format:
- Mark story dependencies (most stories should be independent)
2. **From Contracts**:
-
- Map each interface contract → to the user story it serves
- If tests requested: Each interface contract → contract test task [P] before implementation in that story's phase
3. **From Data Model**:
-
- Map each entity to the user story(ies) that need it
- If entity serves multiple stories: Put in earliest story or Setup phase
- Relationships → service layer tasks in appropriate story phase
diff --git a/.claude/skills/speckit-taskstoissues/SKILL.md b/.claude/skills/speckit-taskstoissues/SKILL.md
index 3c85be291d..0865ac6cc1 100644
--- a/.claude/skills/speckit-taskstoissues/SKILL.md
+++ b/.claude/skills/speckit-taskstoissues/SKILL.md
@@ -31,7 +31,6 @@ You **MUST** consider the user input before proceeding (if not empty).
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
- For each executable hook, output the following based on its `optional` flag:
-
- **Optional hook** (`optional: true`):
```
@@ -90,7 +89,6 @@ Check if `.specify/extensions.yml` exists in the project root.
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
- For each executable hook, output the following based on its `optional` flag:
-
- **Optional hook** (`optional: true`):
```
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index f9d8426466..491c297860 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -3,4 +3,7 @@
# git config blame.ignoreRevsFile .git-blame-ignore-revs
# chore(format): consolidate prettier configs — reformat sandboxes + __tests__ to root style
-397c97ec
+5e92d007
+
+# chore(format): replace prettier with oxfmt — repo-wide reformat
+4c496c8b
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
index a8eceaebc2..0c243b4c01 100644
--- a/.github/workflows/checks.yml
+++ b/.github/workflows/checks.yml
@@ -43,4 +43,4 @@ jobs:
run: pnpm lint
- name: Format
- run: pnpm prettier:check
+ run: pnpm format:check
diff --git a/.husky/pre-commit b/.husky/pre-commit
index 87af428e6f..a30ad34756 100755
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,4 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
-npx --no-install prettier --check "**/*.{ts,tsx,md}"
\ No newline at end of file
+npx --no-install oxfmt --check "**/*.{ts,tsx,md}"
\ No newline at end of file
diff --git a/.oxfmtrc.json b/.oxfmtrc.json
new file mode 100644
index 0000000000..94104d526a
--- /dev/null
+++ b/.oxfmtrc.json
@@ -0,0 +1,16 @@
+{
+ "$schema": "./node_modules/oxfmt/configuration_schema.json",
+ "arrowParens": "avoid",
+ "printWidth": 80,
+ "semi": false,
+ "singleQuote": true,
+ "tabWidth": 2,
+ "trailingComma": "es5",
+ "sortPackageJson": false,
+ "ignorePatterns": [
+ "**/dist/**",
+ "**/node_modules/**",
+ "**/api/**",
+ "**/public/**"
+ ]
+}
diff --git a/.prettierignore b/.prettierignore
deleted file mode 100644
index afea5c7993..0000000000
--- a/.prettierignore
+++ /dev/null
@@ -1,4 +0,0 @@
-**/dist/**
-**/node_modules/**
-**/api/**
-**/public/**
diff --git a/.prettierrc b/.prettierrc
deleted file mode 100644
index c2c0e77649..0000000000
--- a/.prettierrc
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "arrowParens": "avoid",
- "printWidth": 80,
- "semi": false,
- "singleQuote": true,
- "tabWidth": 2,
- "trailingComma": "es5"
-}
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000000..99e2f7ddf7
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+ "recommendations": ["oxc.oxc-vscode"]
+}
diff --git a/CLAUDE.md b/CLAUDE.md
index 4b1c19c195..40e07da14a 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -9,7 +9,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
- **Monorepo**: Turborepo + pnpm workspaces. Workspaces declared in `pnpm-workspace.yaml`: `packages/*`, `targets/*`, `demo`, `docs`.
- **Bundler**: `tsup` per package, sharing `tsup.config.base.ts` which emits CJS (dev + prod.min) and ESM (legacy, modern, modern.dev, modern.prod.min) plus a CJS entry shim that switches on `NODE_ENV`.
- **Tests**: Vitest in browser mode (Chromium via Playwright) for both unit and E2E projects, `vitest-browser-react` for rendering, `tsc --noEmit` (types). Root config: `vitest.config.ts`.
-- **Lint/format**: ESLint via shared `eslint-config-react-spring` package + Prettier. Husky `pre-commit` runs `prettier --check`; `commit-msg` runs commitlint with `@commitlint/config-conventional`.
+- **Lint/format**: [oxlint](https://oxc.rs/docs/guide/usage/linter) (root `.oxlintrc.json`) + [oxfmt](https://oxc.rs/docs/guide/usage/formatter) (root `.oxfmtrc.json`). Husky `pre-commit` runs `oxfmt --check`; `commit-msg` runs commitlint with `@commitlint/config-conventional`. VS Code users should install the [oxc extension](https://marketplace.visualstudio.com/items?itemName=oxc.oxc-vscode) — see `.vscode/extensions.json`.
## Common commands
@@ -31,7 +31,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
| Type-check | `pnpm test:ts` |
| Parallax E2E | `pnpm test:e2e` (programmatically serves `packages/parallax/test`, runs Vitest browser specs) |
| Lint | `pnpm lint` (turbo across packages) |
-| Format | `pnpm prettier:write` / `pnpm prettier:check` |
+| Format | `pnpm format` / `pnpm format:check` |
Note: `vitest.config.ts` aliases `@react-spring/*` to the package source under `packages/*/src/index.ts`, so unit tests run **without** a prior build. Anything outside Vitest (docs, publish-ci) needs `pnpm build` first.
diff --git a/docs/app/components/Code/Code.tsx b/docs/app/components/Code/Code.tsx
index 257a39b6c6..7a90363cd9 100644
--- a/docs/app/components/Code/Code.tsx
+++ b/docs/app/components/Code/Code.tsx
@@ -5,11 +5,10 @@ import { pre } from './Pre.css'
import clsx from 'clsx'
import { preCopy } from './Code.css'
-interface CodeProps
- extends Pick<
- LivePreviewProps,
- 'code' | 'defaultOpen' | 'showCode' | 'template'
- > {
+interface CodeProps extends Pick<
+ LivePreviewProps,
+ 'code' | 'defaultOpen' | 'showCode' | 'template'
+> {
id?: string
isLive?: boolean
showLineNumbers?: boolean
diff --git a/docs/app/helpers/sandboxes.ts b/docs/app/helpers/sandboxes.ts
index 11c7a0d4c3..feb4e88002 100644
--- a/docs/app/helpers/sandboxes.ts
+++ b/docs/app/helpers/sandboxes.ts
@@ -12,8 +12,10 @@ type CodesandboxSandboxResponse = {
data: CodesandboxSandbox
}
-export interface CodesandboxSandboxFetched
- extends Omit {
+export interface CodesandboxSandboxFetched extends Omit<
+ CodesandboxSandbox,
+ 'screenshot_url'
+> {
screenshotUrl: string
urlTitle: string
}
diff --git a/package.json b/package.json
index 6f8a450381..d631e60616 100644
--- a/package.json
+++ b/package.json
@@ -32,8 +32,8 @@
"docs:dev": "pnpm --filter @react-spring/docs dev",
"docs:build": "pnpm --filter @react-spring/docs build",
"demo:dev": "pnpm --filter @react-spring/demo dev",
- "prettier:write": "prettier --write \"**/*.{ts,tsx,md}\"",
- "prettier:check": "prettier --check \"**/*.{ts,tsx,md}\"",
+ "format": "oxfmt \"**/*.{ts,tsx,md}\"",
+ "format:check": "oxfmt --check \"**/*.{ts,tsx,md}\"",
"lint": "oxlint packages/*/src targets/*/src docs/app docs/scripts demo/src",
"package": "turbo run pack",
"postinstall": "remix setup node",
@@ -76,10 +76,9 @@
"flush-microtasks": "1.0.1",
"husky": "9.1.7",
"konva": "9.3.18",
+ "oxfmt": "0.51.0",
"oxlint": "1.66.0",
"playwright": "^1.52.0",
- "prettier": "3.4.2",
- "pretty-quick": "4.0.0",
"react": "^19",
"react-dom": "^19",
"react-konva": "^19",
diff --git a/packages/core/src/Controller.ts b/packages/core/src/Controller.ts
index 8230467c67..6249cdf35f 100644
--- a/packages/core/src/Controller.ts
+++ b/packages/core/src/Controller.ts
@@ -38,13 +38,12 @@ const BATCHED_EVENTS = ['onStart', 'onChange', 'onRest'] as const
let nextId = 1
/** Queue of pending updates for a `Controller` instance. */
-export interface ControllerQueue
- extends Array<
- ControllerUpdate & {
- /** The keys affected by this update. When null, all keys are affected. */
- keys: string[] | null
- }
- > {}
+export interface ControllerQueue extends Array<
+ ControllerUpdate & {
+ /** The keys affected by this update. When null, all keys are affected. */
+ keys: string[] | null
+ }
+> {}
export class Controller {
readonly id = nextId++
diff --git a/packages/core/src/SpringValue.ts b/packages/core/src/SpringValue.ts
index 97e20d99b0..4918348c0e 100644
--- a/packages/core/src/SpringValue.ts
+++ b/packages/core/src/SpringValue.ts
@@ -67,7 +67,8 @@ import {
declare const console: any
interface DefaultSpringProps
- extends Pick, 'pause' | 'cancel' | 'immediate' | 'config'>,
+ extends
+ Pick, 'pause' | 'cancel' | 'immediate' | 'config'>,
PickEventFns> {}
/**
diff --git a/packages/core/src/hooks/useInView.ts b/packages/core/src/hooks/useInView.ts
index 461d5b52e3..d3cef14c75 100644
--- a/packages/core/src/hooks/useInView.ts
+++ b/packages/core/src/hooks/useInView.ts
@@ -6,8 +6,10 @@ import { PickAnimated, SpringValues } from '../types'
import { useSpring, UseSpringProps } from './useSpring'
import { Valid } from '../types/common'
-export interface IntersectionArgs
- extends Omit {
+export interface IntersectionArgs extends Omit<
+ IntersectionObserverInit,
+ 'root' | 'threshold'
+> {
root?: React.MutableRefObject
once?: boolean
amount?: 'any' | 'all' | number | number[]
diff --git a/packages/core/src/interpolate.ts b/packages/core/src/interpolate.ts
index 284b8bfeb0..70a9c3e02f 100644
--- a/packages/core/src/interpolate.ts
+++ b/packages/core/src/interpolate.ts
@@ -15,7 +15,8 @@ export const to: Interpolator = (source: any, ...args: [any]) =>
/** @deprecated Use the `to` export instead */
export const interpolate: Interpolator = (source: any, ...args: [any]) => (
- deprecateInterpolate(), new Interpolation(source, args)
+ deprecateInterpolate(),
+ new Interpolation(source, args)
)
/** Extract the raw value types that are being interpolated */
diff --git a/packages/core/src/types/functions.ts b/packages/core/src/types/functions.ts
index 2725153e72..d6599f6c0d 100644
--- a/packages/core/src/types/functions.ts
+++ b/packages/core/src/types/functions.ts
@@ -59,8 +59,9 @@ interface AnyUpdateFn<
*
* The `T` parameter must be a set of animated values (as an object type).
*/
-interface UpdateValuesFn
- extends AnyUpdateFn> {
+interface UpdateValuesFn extends AnyUpdateFn<
+ Controller
+> {
(props: InlineToProps & ControllerProps): AsyncResult> // prettier-ignore
(
props: {
diff --git a/packages/core/src/types/props.ts b/packages/core/src/types/props.ts
index 0831d65946..a49a32c43f 100644
--- a/packages/core/src/types/props.ts
+++ b/packages/core/src/types/props.ts
@@ -128,12 +128,11 @@ export type GoalValue = T | FluidValue | UnknownProps | null | undefined
export type InlineToProps = Remap & { to?: undefined }>
/** A serial queue of spring updates. */
-export interface SpringChain
- extends Array<
- [T] extends [IsPlainObject]
- ? ControllerUpdate
- : SpringTo | SpringUpdate
- > {}
+export interface SpringChain extends Array<
+ [T] extends [IsPlainObject]
+ ? ControllerUpdate
+ : SpringTo | SpringUpdate
+> {}
/** A value that any `SpringValue` or `Controller` can animate to. */
export type SpringTo =
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 41578f5114..811acf1073 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -65,18 +65,15 @@ importers:
konva:
specifier: 9.3.18
version: 9.3.18
+ oxfmt:
+ specifier: 0.51.0
+ version: 0.51.0
oxlint:
specifier: 1.66.0
version: 1.66.0
playwright:
specifier: ^1.52.0
version: 1.60.0
- prettier:
- specifier: 3.4.2
- version: 3.4.2
- pretty-quick:
- specifier: 4.0.0
- version: 4.0.0(prettier@3.4.2)
react:
specifier: ^19
version: 19.2.6
@@ -1623,6 +1620,120 @@ packages:
'@open-draft/deferred-promise@2.2.0':
resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==}
+ '@oxfmt/binding-android-arm-eabi@0.51.0':
+ resolution: {integrity: sha512-Ni0sCqg5CIHaLIYFGj+ncbcumylvNC6FE4rfD0KfdmnWHbPJ+zev0qZCXKxy2hFVa0fYRK0yPzf5nzPbkZou7g==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [android]
+
+ '@oxfmt/binding-android-arm64@0.51.0':
+ resolution: {integrity: sha512-eu5lAZjuo0KAkp+M24EhDqfOwA8owQ8d7wyBlOUUGRbDLHpU3IRlDHp8Dif+YqGlxs6jra7yS6WQu/NkPhAxeg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [android]
+
+ '@oxfmt/binding-darwin-arm64@0.51.0':
+ resolution: {integrity: sha512-6LsUNIdURhhcIfIn8+xsOb61mSTa9msAHTeSGx9Jf4rsP/gN8PGCF+SKWPAQZbND2w/WBkqQ6303jqEEIXzMdQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@oxfmt/binding-darwin-x64@0.51.0':
+ resolution: {integrity: sha512-9aUMGmVxdHjYMsEAW1tNRoieTJXlVNDFkRvIR1J7LttJXWjVYCu2ekclLij2KJtxBxSQOYSHd12ME/adVGVbZg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [darwin]
+
+ '@oxfmt/binding-freebsd-x64@0.51.0':
+ resolution: {integrity: sha512-mkY1nhZTqYb+NHaAWxOCKISN6FwdrwMNsu17vTUA3wzUV2VJ+Paq15ZokRcsMU/2PUdHO73prxyeJpjXQ3MPpQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@oxfmt/binding-linux-arm-gnueabihf@0.51.0':
+ resolution: {integrity: sha512-wtFwNwE4+YCNuPaWoGDZeGsKvD6D1YSUNBJNn/rJBh7CrDBThFE+TBI5kY7vRW9rIOQRsbW2IpyyL3Du4Zqwiw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@oxfmt/binding-linux-arm-musleabihf@0.51.0':
+ resolution: {integrity: sha512-rnOaNx86G7iRKM6lsCIQMux0SMGNC/TEbFR+r7lpruJ12bnrIWgxd5w1PLqOvgR9r8ZJbpK/zfRKctJnh8/Jfg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@oxfmt/binding-linux-arm64-gnu@0.51.0':
+ resolution: {integrity: sha512-jOgDzSqWcICGRjsp4mc08FxKMN8vzP2Kgs4E0d2HUP99F+nJDQKklRV4Zuj+0gcBgjrzx2CbpqaIdUVPepCojA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@oxfmt/binding-linux-arm64-musl@0.51.0':
+ resolution: {integrity: sha512-KBUCdrH5bwVrAvI9gU/1S55oH6fzXjr++J/oVocdu7bYTks1l7DNNT+rLd/1TDdAEjObGwmfWamn7LC1m8A0DQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@oxfmt/binding-linux-ppc64-gnu@0.51.0':
+ resolution: {integrity: sha512-NapfjYsABFqTJ1Dn9Efq6sN5esaHconVKwVLbDGNQLrwpOx/g17mkwErHzU72PutL67nf3wNAkbq122H+zLxag==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@oxfmt/binding-linux-riscv64-gnu@0.51.0':
+ resolution: {integrity: sha512-5dlDt1dUZCVi6elIhiK1PWg9wpTzTcIuj0IZnSurvIoMrhOWqqTcc1dSTxcSkNaBZhfsNqRZdINI1zAgbKkJNQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@oxfmt/binding-linux-riscv64-musl@0.51.0':
+ resolution: {integrity: sha512-pgdWUJn0S5nulyiVdlFV8DzCUnGXkU99W5PSkkmbaZW+LrZBPxpezun4G0DDHbQaVYuJeCuKsXsGKGo77CkUTQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@oxfmt/binding-linux-s390x-gnu@0.51.0':
+ resolution: {integrity: sha512-2XTFUe97CbDGAI8vjwDfZ1HdakO0XIADyJ24idEg64SC4/K4in/OisXVnrW4NMK7I6TgC7EqRhC0Ln/nKhAemA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [s390x]
+ os: [linux]
+
+ '@oxfmt/binding-linux-x64-gnu@0.51.0':
+ resolution: {integrity: sha512-kQ1OuCqqt/yyf0ZN9VFxW1/JnlgJgii3Dr7pWf9vNBvrX1hv6g39/+mc5oGRHRGJFZtl3zsGDWR9c5N2B/gwBw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@oxfmt/binding-linux-x64-musl@0.51.0':
+ resolution: {integrity: sha512-ARTYqxHF475o96Gbn41hvSWSSRygPlRDXZZgZ9I2scU1y0qiWpCQyZCoefaQa0mwv+wwtZ+luS4YOzsRzM/izg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@oxfmt/binding-openharmony-arm64@0.51.0':
+ resolution: {integrity: sha512-QiC1XrCl6a6BmqMzduO8hdIRMf1m44hCkt2Q68KWkTvUB/E7fd2iomyNh6KnnRca5w6eBrRAAtLFqTh+xjsjJA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@oxfmt/binding-win32-arm64-msvc@0.51.0':
+ resolution: {integrity: sha512-NC/hJb9dtU23Zf8L7IVK95xnFjiQ7AfcLO2l5pb69TDEr958qxrtnB2CveeeNSCBFNIkgaTCfd/vHNSoG78l9g==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [win32]
+
+ '@oxfmt/binding-win32-ia32-msvc@0.51.0':
+ resolution: {integrity: sha512-2C45za4Rj36n8YIbhRL1PQbxmXJYf81WEcAgvj5I4ptRROG+A+81hREEN5bmCHADE1UfYaN312U6tkILoZZy6w==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [ia32]
+ os: [win32]
+
+ '@oxfmt/binding-win32-x64-msvc@0.51.0':
+ resolution: {integrity: sha512-73RqdAuVKQTkjZIDw08JaDHUM4lav5Qu+CaPwg4QbbA7k8o7LEW0p3UsfZ/F8dsO/pwVYh3RzFcanwLRTTahbQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [win32]
+
'@oxlint/binding-android-arm-eabi@1.66.0':
resolution: {integrity: sha512-f7kq8N51T4phpzqfBpA2qaVTI/KrkCmNwaj3t/97I/WLTDI+UhlP5GL9eER+zVxBhtlx5rKXWByJU1/zDAvyaw==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -5439,6 +5550,16 @@ packages:
outvariant@1.4.0:
resolution: {integrity: sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==}
+ oxfmt@0.51.0:
+ resolution: {integrity: sha512-l/AoAnaEOV7Q5/Z9kHOMDehVJnCgYN7wRoooWCTUMBMi16BJhLZqd9cmCnwcVFfVlzkt53zK2KLPFNp8vSsoDg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+ peerDependencies:
+ svelte: ^5.0.0
+ peerDependenciesMeta:
+ svelte:
+ optional: true
+
oxlint@1.66.0:
resolution: {integrity: sha512-N4LLxYLd94KEBqXDMDM5f+2PUpItTjDLreXe2Gn5KhjhCK4Qp2YUXaBi8Yu325ryOgKwt22m45fpD7nPOn69Yw==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -5581,10 +5702,6 @@ packages:
resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
engines: {node: '>=8.6'}
- picomatch@3.0.2:
- resolution: {integrity: sha512-cfDHL6LStTEKlNilboNtobT/kEa30PtAf2Q1OgszfrG/rpVl1xaFWT9ktfkS306GmHgmnad1Sw4wabhlvFtsTw==}
- engines: {node: '>=10'}
-
picomatch@4.0.4:
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
engines: {node: '>=12'}
@@ -5710,11 +5827,6 @@ packages:
engines: {node: '>=10.13.0'}
hasBin: true
- prettier@3.4.2:
- resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==}
- engines: {node: '>=14'}
- hasBin: true
-
pretty-format@27.5.1:
resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
@@ -5727,13 +5839,6 @@ packages:
resolution: {integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==}
engines: {node: '>=10'}
- pretty-quick@4.0.0:
- resolution: {integrity: sha512-M+2MmeufXb/M7Xw3Afh1gxcYpj+sK0AxEfnfF958ktFeAyi5MsKY5brymVURQLgPLV1QaF5P4pb2oFJ54H3yzQ==}
- engines: {node: '>=14'}
- hasBin: true
- peerDependencies:
- prettier: ^3.0.0
-
proc-log@3.0.0:
resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -6533,6 +6638,10 @@ packages:
resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
engines: {node: ^18.0.0 || >=20.0.0}
+ tinypool@2.1.0:
+ resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==}
+ engines: {node: ^20.0.0 || >=22.0.0}
+
tinyrainbow@2.0.0:
resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
engines: {node: '>=14.0.0'}
@@ -8468,6 +8577,63 @@ snapshots:
'@open-draft/deferred-promise@2.2.0': {}
+ '@oxfmt/binding-android-arm-eabi@0.51.0':
+ optional: true
+
+ '@oxfmt/binding-android-arm64@0.51.0':
+ optional: true
+
+ '@oxfmt/binding-darwin-arm64@0.51.0':
+ optional: true
+
+ '@oxfmt/binding-darwin-x64@0.51.0':
+ optional: true
+
+ '@oxfmt/binding-freebsd-x64@0.51.0':
+ optional: true
+
+ '@oxfmt/binding-linux-arm-gnueabihf@0.51.0':
+ optional: true
+
+ '@oxfmt/binding-linux-arm-musleabihf@0.51.0':
+ optional: true
+
+ '@oxfmt/binding-linux-arm64-gnu@0.51.0':
+ optional: true
+
+ '@oxfmt/binding-linux-arm64-musl@0.51.0':
+ optional: true
+
+ '@oxfmt/binding-linux-ppc64-gnu@0.51.0':
+ optional: true
+
+ '@oxfmt/binding-linux-riscv64-gnu@0.51.0':
+ optional: true
+
+ '@oxfmt/binding-linux-riscv64-musl@0.51.0':
+ optional: true
+
+ '@oxfmt/binding-linux-s390x-gnu@0.51.0':
+ optional: true
+
+ '@oxfmt/binding-linux-x64-gnu@0.51.0':
+ optional: true
+
+ '@oxfmt/binding-linux-x64-musl@0.51.0':
+ optional: true
+
+ '@oxfmt/binding-openharmony-arm64@0.51.0':
+ optional: true
+
+ '@oxfmt/binding-win32-arm64-msvc@0.51.0':
+ optional: true
+
+ '@oxfmt/binding-win32-ia32-msvc@0.51.0':
+ optional: true
+
+ '@oxfmt/binding-win32-x64-msvc@0.51.0':
+ optional: true
+
'@oxlint/binding-android-arm-eabi@1.66.0':
optional: true
@@ -13092,6 +13258,30 @@ snapshots:
outvariant@1.4.0: {}
+ oxfmt@0.51.0:
+ dependencies:
+ tinypool: 2.1.0
+ optionalDependencies:
+ '@oxfmt/binding-android-arm-eabi': 0.51.0
+ '@oxfmt/binding-android-arm64': 0.51.0
+ '@oxfmt/binding-darwin-arm64': 0.51.0
+ '@oxfmt/binding-darwin-x64': 0.51.0
+ '@oxfmt/binding-freebsd-x64': 0.51.0
+ '@oxfmt/binding-linux-arm-gnueabihf': 0.51.0
+ '@oxfmt/binding-linux-arm-musleabihf': 0.51.0
+ '@oxfmt/binding-linux-arm64-gnu': 0.51.0
+ '@oxfmt/binding-linux-arm64-musl': 0.51.0
+ '@oxfmt/binding-linux-ppc64-gnu': 0.51.0
+ '@oxfmt/binding-linux-riscv64-gnu': 0.51.0
+ '@oxfmt/binding-linux-riscv64-musl': 0.51.0
+ '@oxfmt/binding-linux-s390x-gnu': 0.51.0
+ '@oxfmt/binding-linux-x64-gnu': 0.51.0
+ '@oxfmt/binding-linux-x64-musl': 0.51.0
+ '@oxfmt/binding-openharmony-arm64': 0.51.0
+ '@oxfmt/binding-win32-arm64-msvc': 0.51.0
+ '@oxfmt/binding-win32-ia32-msvc': 0.51.0
+ '@oxfmt/binding-win32-x64-msvc': 0.51.0
+
oxlint@1.66.0:
optionalDependencies:
'@oxlint/binding-android-arm-eabi': 1.66.0
@@ -13234,8 +13424,6 @@ snapshots:
picomatch@2.3.2: {}
- picomatch@3.0.2: {}
-
picomatch@4.0.4: {}
pidtree@0.6.0: {}
@@ -13341,8 +13529,6 @@ snapshots:
prettier@2.8.8: {}
- prettier@3.4.2: {}
-
pretty-format@27.5.1:
dependencies:
ansi-regex: 5.0.1
@@ -13359,17 +13545,6 @@ snapshots:
dependencies:
parse-ms: 2.1.0
- pretty-quick@4.0.0(prettier@3.4.2):
- dependencies:
- execa: 5.1.1
- find-up: 5.0.0
- ignore: 5.3.2
- mri: 1.2.0
- picocolors: 1.1.1
- picomatch: 3.0.2
- prettier: 3.4.2
- tslib: 2.8.1
-
proc-log@3.0.0: {}
process-nextick-args@2.0.1: {}
@@ -14361,6 +14536,8 @@ snapshots:
tinypool@1.1.1: {}
+ tinypool@2.1.0: {}
+
tinyrainbow@2.0.0: {}
tinyspy@4.0.4: {}
diff --git a/specs/002-vitest-browser-migration/plan.md b/specs/002-vitest-browser-migration/plan.md
index 38083b1c3e..82f0209cf9 100644
--- a/specs/002-vitest-browser-migration/plan.md
+++ b/specs/002-vitest-browser-migration/plan.md
@@ -147,7 +147,6 @@ Producing the file is part of this command's output below.
## Phase 1 — Design Artifacts
1. **`data-model.md`** — captures the test-infra entities:
-
- `RunnerConfig` (root `vitest.config.ts`) with two `projects`: `unit`, `e2e`.
- `SetupModule` (`packages/core/test/setup.ts`, referenced directly from `setupFiles`) — fields: `beforeEach reset list`, `helper globals`, `Globals.assign payload`.
- `TestHelpers` — the public contract (see contracts/).
@@ -169,18 +168,15 @@ The full plan is broken into work-items below. `/speckit-tasks` will turn these
### Work items
- **W1 — Add Vitest toolchain** (no behaviour change yet)
-
- Add `vitest`, `@vitest/browser`, `@vitest/coverage-v8`, `vitest-browser-react`, `playwright` to root `devDependencies`.
- Create `vitest.config.ts` with `unit` project: browser mode, Playwright/Chromium, alias map, `setupFiles: ['./packages/core/test/setup.ts']` (referenced directly — no separate root shim), coverage thresholds.
- Verify `pnpm vitest run` boots Chromium and discovers zero tests yet.
- **W2 — Port setup file**
-
- In `packages/core/test/setup.ts`: replace `jest.setTimeout(6e8)` → `vi.setConfig({ testTimeout: 6e8 })`; `jest.advanceTimersByTimeAsync` → `vi.advanceTimersByTimeAsync`; `beforeEach`/`afterEach` from `vitest`; `import { act } from '@testing-library/react'` → `import { act } from 'react'`. Keep `mockRaf`, `Globals.assign`, frame observers byte-for-byte equivalent.
- Enable Vitest fake timers globally in config (`fakeTimers: { toFake: ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'Date'] }` — explicitly _not_ faking `requestAnimationFrame` since `mock-raf` owns it).
- **W3 — Migrate unit tests**
-
- Search-and-replace `jest.fn` / `jest.mock` / `jest.spyOn` → `vi.fn` / `vi.mock` / `vi.spyOn` across `packages/**/*.test.ts(x)` and `targets/**/*.test.tsx`.
- Add `import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'` only where files relied on Jest globals not exposed by Vitest's default globals.
- **Swap `@testing-library/react` → `vitest-browser-react`** at every call site (8 files total). Concretely:
@@ -191,27 +187,23 @@ The full plan is broken into work-items below. `/speckit-tasks` will turn these
- Run `pnpm vitest run` — fix any divergence.
- **W4 — Re-implement parallax E2E**
-
- Create `tests/e2e/parallax.spec.ts` using Vitest browser + `@vitest/browser/context` (`page`, `userEvent`).
- Translate each `cy.findByTestId` → `screen.getByTestId` / `page.getByTestId`; `cy.scrollTo` → element scroll via `evaluate`; `cy.wait(4000)` → explicit `waitFor` on the transform value rather than wall-clock.
- Drop `matchImageSnapshot` calls (out of scope per spec).
- Wire the Vite fixture: `vitest.config.ts`'s `e2e` project gets a `setupFiles` that starts the Vite dev server on a free port and exposes `BASE_URL`, or use Vitest's `serve` integration directly.
- **W5 — Coverage**
-
- Configure `@vitest/coverage-v8` with the same `collectCoverageFrom` globs and thresholds.
- Add `test:cov` script: `vitest run --coverage`.
- Verify thresholds still pass.
- **W6 — Scripts and removals**
-
- `package.json`: `test:unit` → `vitest run --project unit`; `test:e2e` → `vitest run --project e2e`; `test:cov` → `vitest run --coverage --project unit`; keep `test` aggregate.
- Remove `jest`, `@swc/jest`, `@types/jest`, `cypress`, `@simonsmith/cypress-image-snapshot`, `@testing-library/cypress`, `start-server-and-test` from devDependencies.
- Delete `jest.config.js`, `cypress.config.ts`, `cypress/`.
- Run `pnpm install` to update the lockfile.
- **W7 — CI**
-
- `.github/workflows/tests.yml`:
- Drop `cypress/**` from the path filter.
- In `test-unit`, add a step before `pnpm test:unit`: `pnpm exec playwright install --with-deps chromium` (cached via `~/.cache/ms-playwright`).
diff --git a/specs/002-vitest-browser-migration/spec.md b/specs/002-vitest-browser-migration/spec.md
index 0552ea23da..453540ede5 100644
--- a/specs/002-vitest-browser-migration/spec.md
+++ b/specs/002-vitest-browser-migration/spec.md
@@ -78,7 +78,6 @@ As a contributor writing animation tests, I rely on the existing test helpers (`
- **FR-004**: The animation testing helpers (`advance`, `advanceByTime`, `advanceUntil`, `advanceUntilIdle`, `advanceUntilValue`, `getFrames`, `countBounces`, `setSkipAnimation`) MUST remain available with identical signatures and identical deterministic behaviour.
- **FR-005**: The global setup file currently at `packages/core/test/setup.ts` (which resets `rafz`, `frameLoop`, and `__raf` between tests) MUST be ported so that frame scheduling is reset between every test, with no cross-test leakage.
- **FR-006**: All existing test files MUST keep their test bodies (assertion logic, helper call sites, control flow) intact. The following **migration-only** edits are explicitly in scope and acceptable:
-
- Runner-global renames: `jest.*` → `vi.*`, Jest globals → Vitest imports.
- React rendering boundary swap: `@testing-library/react` → `vitest-browser-react`. The `render` return-type annotation (`RenderResult`) is dropped; `renderHook` is imported from `vitest-browser-react` (it ships one natively as of `0.1.1`).
- `act` source change: `@testing-library/react` → `react` (React 19 native).