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
7.7 KiB
package, version, tier, decision, date, deciders, adr, lastRevalidated, is-sub-processor, processes-pii, filter-results, verification-commands, accepted-cves
| package | version | tier | decision | date | deciders | adr | lastRevalidated | is-sub-processor | processes-pii | filter-results | verification-commands | accepted-cves | |||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| zustand | ^5.0.0 | feature | approved | 2026-07-12 |
|
null | null | false | false |
|
|
Filter: license
npm info zustand license returns MIT. The GitHub repository (pmndrs/zustand) confirms spdx_id: MIT. Within the allowlist.
Filter: types
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
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
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
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
Pure in-process state management. No network calls, no vendor-controlled endpoints, no telemetry. Not applicable.
Filter: cve-scan
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
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
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
- 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/reactitself runs on zustand, which keeps one mental model across the board substrate and the editor store. - 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. - 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.