.specify/memory/constitution.md
The library is organised in strict bottom-up layers:
@react-spring/rafz → @react-spring/shared → @react-spring/animated →
@react-spring/core → targets/* → react-spring (umbrella).
Rationale: This is the load-bearing contract that lets the same animation engine power web, native, three, konva, and zdog without bifurcating code. Any cross-layer shortcut taken once propagates indefinitely.
@react-spring/core, @react-spring/animated, and @react-spring/shared MUST remain
free of platform APIs (DOM, React Native, three.js, etc.). Platform behaviour is
injected at the target boundary through:
Globals.assign({ batchedUpdates, createStringInterpolator, colors, ... }) for
runtime configuration.createHost(primitives, hostConfig) for the three seams applyAnimatedValues,
createAnimatedStyle, and getComponentProps.New target-specific code MUST live under targets/<name>/. Adding an animated.X
shorthand for a new host element MUST be done by editing that target's
primitives.ts, not by touching core.
Rationale: The whole reason core is target-agnostic is so we can ship a fifth,
sixth, or seventh renderer without rewriting the engine. Leaking platform code into
core silently destroys that optionality.
Animation behaviour MUST be covered by deterministic unit tests using the helpers in
packages/core/test/setup.ts:
advance, advanceByTime, advanceUntil, advanceUntilIdle, and
advanceUntilValue instead of real timers, jest.runAllTimers(), or
jest.advanceTimersByTime.rafz and frameLoop per test (the setup file already does this; do not
bypass it).SpringValue, Controller, SpringRef, interpolation,
or any hook MUST land with a test in the same PR.Rationale: Animation bugs are almost always timing bugs. The mock-raf based helpers are the only way to assert frame-accurate behaviour without flakiness, and they only work if every test goes through them.
All published packages share a single version line and are released together.
pnpm changeset).pnpm changeset pre enter alpha|beta|next first.Rationale: Targets share the core engine; staggered versions across packages would silently desynchronise users' lockfiles and produce hard-to-diagnose runtime mismatches. Changesets are the audit trail.
Code that runs inside rafz queues or FrameLoop (per-frame update, onFrame,
write) MUST:
write queue.SpringValue.advance,
FrameLoop.advance, or any target's applyAnimatedValues).frameLoop defaults to 'always'; targets that change it (e.g. targets/three
sets 'demand') MUST document the integration that drives ticks.
Rationale: react-spring is on the render-blocking path for every animation in every consuming app. A regression here is amplified across the entire ecosystem.
Every PR MUST pass, locally and in CI, before review approval:
pnpm lint — ESLint via eslint-config-react-spring is clean. no-console
errors are blocking except console.warn/console.error. Unused variables MUST
be _-prefixed.pnpm test:ts — tsc --noEmit is clean across all packages.pnpm test:unit — Vitest unit tests pass (browser mode, Chromium via Playwright).
The configured coverage thresholds (80% statements / 74% branches / 71% functions
/ 82% lines) MUST be met when running pnpm test:cov (@vitest/coverage-v8).pnpm prettier:check — Prettier reports no diff. The Husky pre-commit hook
enforces this; do not bypass it.commitlint
enforces this via the Husky commit-msg hook.Vitest browser E2E (pnpm test:e2e) covers @react-spring/parallax and MUST pass
locally for any change touching that package. CI runs the E2E job as part of the
standard workflow.
next. main may exist but is not the active line.<type>/<short-desc> (Conventional Commit prefix). When a
Linear ticket exists, prefer <type>/<LINEAR-ID>.vitest.config.ts's resolve.alias rewrites @react-spring/* to source, so
unit and E2E tests run without pnpm build. Anything outside Vitest (docs,
publish-ci) MUST be preceded by pnpm build.pnpm changeset → pnpm vers → pnpm release. Do not bump
versions by hand.This constitution supersedes ad-hoc conventions and individual preferences when they conflict. CLAUDE.md and other guidance files are subordinate to it.
Amendment procedure:
.specify/memory/constitution.md, bumps the version per
the policy below, and prepends a Sync Impact Report comment summarising the
change..specify/templates/*.md) and guidance docs
that reference principles changed by the amendment.next.Versioning policy (semantic):
Compliance review:
/speckit-plan MUST pass the Constitution Check gate
against this file before Phase 0 and again after Phase 1.Version: 1.0.1 | Ratified: 2026-05-21 | Last Amended: 2026-05-21