Pre-approve the ADR-022 library trace for the editor's client-state store (ADR-029: store on DesignDoc v1, React 19). Evaluates v5 specifically — the founder prototype used v4. Trace only — the package is not installed by this commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
105 lines
7.7 KiB
Markdown
105 lines
7.7 KiB
Markdown
---
|
|
package: zustand
|
|
version: "^5.0.0"
|
|
tier: feature
|
|
decision: approved
|
|
date: 2026-07-12
|
|
deciders: [implementer-agent]
|
|
adr: null
|
|
lastRevalidated: null
|
|
is-sub-processor: false
|
|
processes-pii: false
|
|
filter-results:
|
|
license: MIT
|
|
types: native
|
|
maintenance: active
|
|
boundary-fit: pass
|
|
shadow-check: pass
|
|
eu-residency: n/a
|
|
cve-scan: clean
|
|
named-consumer: pass
|
|
socketRisk: clean
|
|
verification-commands:
|
|
- "npm info zustand license"
|
|
- "npm info zustand types"
|
|
- 'cat package.json | grep -E ''"(zod|inversify|payload|@trpc/server|superjson|reflect-metadata)"'''
|
|
- "npm info zustand time.modified"
|
|
- "npm info zustand time --json | tail -5"
|
|
- "npm info zustand@5 dependencies peerDependenciesMeta --json"
|
|
- "pnpm audit --audit-level=moderate 2>&1 | head -40"
|
|
- "curl -s 'https://api.github.com/advisories?affects=zustand&per_page=10'"
|
|
accepted-cves: []
|
|
---
|
|
|
|
## Filter: license
|
|
|
|
<!-- Result: MIT -->
|
|
|
|
`npm info zustand license` returns `MIT`. The GitHub repository (`pmndrs/zustand`) confirms `spdx_id: MIT`. Within the allowlist.
|
|
|
|
## Filter: types
|
|
|
|
<!-- Result: native -->
|
|
|
|
Authored in TypeScript; the published manifest declares `"types": "./index.d.ts"`. No separate `@types/` package is needed (`@types/react` is an optional peer, only relevant to consumers already using React types).
|
|
|
|
## Filter: shadow-check
|
|
|
|
<!-- Result: pass -->
|
|
|
|
`zustand` is a client-side React state container. It duplicates no locked must-have (zod, inversify, payload, @trpc/server, superjson, reflect-metadata — root `package.json` grep matches only `zod`): `inversify` is server-side DI for use-case wiring, and `@tanstack/react-query` (already in the workspace via the feature-UI stack) owns the server-state cache — zustand covers client-only editor state (board registry, selection), a role no workspace library fills today. One version-skew note, not a shadow: `@xyflow/react` depends on `zustand@^4.4.0` for its internal store, so v4 will coexist in `node_modules` as React Flow's private implementation detail while `packages/editor` targets v5 directly; pnpm isolates the two majors. Pass.
|
|
|
|
## Filter: boundary-fit
|
|
|
|
<!-- Result: pass -->
|
|
|
|
Target is `packages/editor` (feature tier — the ADR-029 editor rebuild, seeded by the walking-skeleton PRD). `zustand` is a pure in-process state container: zero runtime dependencies, no `@sentry/*` or `@opentelemetry/sdk-*` imports (ADR-017 §4 unaffected), no cross-feature imports. Its four peer dependencies (`react`, `@types/react`, `immer`, `use-sync-external-store`) are all optional. ADR-006, ADR-010, ADR-017 all pass.
|
|
|
|
## Filter: maintenance
|
|
|
|
<!-- Result: active -->
|
|
|
|
Latest release `5.0.14` published `2026-05-28T10:17:58.503Z` — about six weeks before this trace. Steady patch cadence on the 5.x line (`5.0.9` 2025-11-30, `5.0.10` 2026-01-12, `5.0.11` 2026-02-01, `5.0.12` 2026-03-16, `5.0.13` 2026-05-05, `5.0.14` 2026-05-28). The `pmndrs/zustand` repository was last pushed `2026-07-10`, has ~58.6k stars and only 4 open issues, and is maintained by Poimandres (lead: Daishi Kato). ~36.7M npm downloads/week. Active.
|
|
|
|
## Filter: eu-residency
|
|
|
|
<!-- Result: n/a -->
|
|
|
|
Pure in-process state management. No network calls, no vendor-controlled endpoints, no telemetry. Not applicable.
|
|
|
|
## Filter: cve-scan
|
|
|
|
<!-- Result: clean -->
|
|
|
|
The GitHub Advisory Database reports zero advisories for `zustand` (any version). The package has zero runtime dependencies, so there is no transitive advisory surface. `pnpm audit --audit-level=moderate` at the workspace root surfaces only pre-existing advisories in dev tooling (`vitest` via `@stryker-mutator/vitest-runner`, `shell-quote` via `concurrently`) — none related to this package. Clean.
|
|
|
|
## Filter: named-consumer
|
|
|
|
<!-- Result: pass -->
|
|
|
|
`packages/editor` — the walking-skeleton PRD (`docs/work/prds/walking-skeleton.prd.md`) specifies a "minimal zustand store" that "holds registry + selection only" as the first code of the ADR-029 editor rebuild, whose store is designed around DesignDoc v1 and the iframe canvas (ADR-028). The platform-retrofit PRD explicitly batches this trace so walking-skeleton implementers do not stall on the ADR-022 pre-commit hook. The consumer is a scheduled, named package blocked today — not hypothetical.
|
|
|
|
This trace evaluates **v5 specifically**: the founder prototype used v4, but the rebuild targets v5 + React 19. v5 requires React >= 18 and uses React's native `useSyncExternalStore` (dropping v4's `use-sync-external-store` runtime dependency); its breaking changes versus v4 are removals of already-deprecated APIs and stricter equality semantics (`useShallow` for shallow-compared selectors) — compatible with the React 19 target of `apps/web-next` (ADR-029).
|
|
|
|
## Filter: socketRisk
|
|
|
|
<!-- Result: clean -->
|
|
|
|
The official Socket CLI requires an organization API token (`socket package score` fails with "requires a Socket API token"), which is unavailable in this environment, and socket.dev package pages are bot-gated (HTTP 403). Manual supply-chain audit performed instead.
|
|
|
|
`zustand@5.0.14` has **zero runtime dependencies**. All four peer dependencies (`@types/react`, `immer`, `react`, `use-sync-external-store`) are marked optional in `peerDependenciesMeta`. The published manifest declares no `preinstall`/`postinstall` lifecycle scripts, the package makes no network calls, and there is no minification/obfuscation in the published source. Poimandres has published the package continuously since 2019 under the same organization. Supply-chain risk assessed as clean.
|
|
|
|
## Prompt: replaces
|
|
|
|
Nothing in the workspace is retired — no client-state library exists in the monorepo today (`@tanstack/react-query` covers server-state caching, which zustand does not touch). At the product level it carries forward the founder prototype's store choice (zustand v4) into the conformance-native rebuild (ADR-029), one major version ahead; the prototype's `VeectNode` store shape is explicitly _not_ inherited — ADR-029 makes the editor store a view-model mapped to/from DesignDoc v1. What it displaces is the alternative of React context + `useReducer` for editor state.
|
|
|
|
## Prompt: migration-cost-out
|
|
|
|
**Mechanical.** Store definitions are plain factory functions (`create`/`set`/`get`) and components consume them through hooks — all confined to `packages/editor`; no other feature may import across the boundary. ADR-029's mapping layer keeps DesignDoc v1 (the persisted contract) independent of the store shape, so no persisted data or protocol couples to zustand. Ripping it out means rewriting the store modules and hook call sites within one package. The single caveat: the board's high-frequency interaction paths will use zustand's transient subscriptions (subscribe without re-render), and a replacement must offer an equivalent to keep the <16 ms/frame budget (ADR-028) — a design constraint on the successor, not a lock-in.
|
|
|
|
## Prompt: alternatives-considered
|
|
|
|
1. **jotai** — atom-based state, also by Poimandres. Granularity mismatch: the editor store is a document/view-model store with cohesive slices (registry, selection), not a constellation of independent atoms. Also loses the alignment bonus that `@xyflow/react` itself runs on zustand, which keeps one mental model across the board substrate and the editor store.
|
|
2. **Redux Toolkit** — mature and battle-tested, but heavier: reducer/action boilerplate for what is a single-package UI store, immer-by-default update overhead on 60 fps drag paths, and no first-class transient (non-rendering) subscription API comparable to zustand's `subscribe`.
|
|
3. **React context + `useReducer`** — zero dependencies, but architecturally disqualified for a canvas editor: every dispatch re-renders all consumers, there are no transient subscriptions, and selector-based subscription would have to be hand-built — precisely the re-render storm the <16 ms/frame interaction budget (ADR-028) forbids.
|