plans/README.md
Generated by improve-skill runs against commit 42bfbe3ed (v12.40.0):
/improve next (direction audit, 2026-06-10): feature/roadmap plans./improve deep (full nine-category audit, 2026-06-10): DX, perf, tests, hygiene plans./improve focused audit (2026-06-11): MotionValue-as-signals / push-vs-pull waste, maintainer-selected. (Sibling plan 011 was dropped 2026-06-22 — see the Dropped note below.)/improve focused on the Reorder component + multidimensional reorder (2026-06-11, interactive; maintainer selected all four findings)./improve focused on the drag gesture + vanilla drag on the PR #3748 shared projection tree (2026-06-11, maintainer-requested). Supersedes the earlier "Framework-agnostic MotionNode / vanilla drag" deferral below: the maintainer chose the animate-layout-batched-commits shared projection tree as the substrate rather than waiting for the effects/VE unification./improve focused on hover, press, inView and resize (2026-06-11, non-interactive; plans written for the top findings by leverage per the skill's default — maintainer should review the deferred items from this run below before assuming they were decided)./improve focused on the frameloop (frame/batcher/render-step, 2026-06-11, interactive; maintainer selected all three findings): exception recovery + non-keepAlive batcher stall, same-step cancelFrame, test gaps./improve focused on the spring generator + bundle-size savings (2026-06-11, interactive; maintainer selected all four findings). All four findings were verified empirically against the built package during the audit. 030/031/033 edit disjoint regions of spring.ts — execute in that order; 032 is an independent reproduce-first investigation (issue #2791).inView() to motion-dom" item deferred by the 022–026 run./improve focused on <motion.div> bundle size (2026-06-11, interactive; maintainer selected all four findings). Baseline measured at 42bfbe3ed after a fresh yarn build && yarn measure: the motion bundle is 38.56 kB gz vs a 34.9 kB budget, with 6 of 9 budgets failing and nothing enforcing them; growth was attributed by npm-tarball bisection (jumps at v12.24.0, v12.25.0, v12.32–33, v12.36.0).Also see
issues/README.md— open-PR landing plans (one per open PR, generated 2026-06-11). Cross-links: PR #3748 is both aLayoutAnimationBuilder.tsrewrite (gated on the plan-009 sequencing decision, seeissues/pr-3748.mdStep 1) and a hard dependency of plan 020 below; PR #3749 (worktree-style-effect) is the effects/VE unification that plan 012 references.
Numbering collision note (resolved): two concurrent 2026-06-11 runs both initially wrote files numbered 011/012. The signals plans kept 011/012 (the ones in this table), the Reorder run took 015–018, and the drag-porting run renumbered its three plans to 019–021 (it had three plans, so the suggested 013/014 didn't fit; 013/014 are deliberately unused).
Second collision (2026-06-11, resolved): three later concurrent runs all started at 022. The hover/press/inView/resize run kept 022–026 (it registered in this README first). The frameloop run renumbered its three plans to 027–029 (old
022/023/024-frameloop-*.mdfiles removed; internal cross-references updated). The spring run renumbered its four plans to 030–033.
The 2026-06-10 runs were non-interactive, so plans were written for the top findings by leverage per the skill's default. The maintainer should review the "Deferred" and "Rejected" sections before assuming they were decided. Execute in the order below unless dependencies say otherwise. Each executor: read the plan fully before starting, honor its STOP conditions, and update your row when done.
Dropped (2026-06-22): plans 004 (design spike: WAAPI for transform shorthands), 006 (rotate exposed
UPDATE_SECRET_TOKEN+ secret-scan CI gate), and 011 (useCombineMotionValuesper-render churn) were removed at the maintainer's request. Note 006's Step 0 — rotating the exposed token — was a human action independent of the CI gate; dropping the plan does not un-expose the token, so handle that separately if it hasn't been done. Plan 011 was dropped as a net-negative micro-optimization: its savings land only on the React re-render path (the motion-value update path is already optimal), theuseConstant-based rewrite still allocates an initializer thunk per render so re-renders never become allocation-free, and it adds a high-risk render-phase latest-ref + manual resubscription bookkeeping plus bytes to a hot file — while the one genuinely-deferred cost it targeted (the per-unsubscribeframe.readauto-stop inMotionValue.on) is already owned by plan 012's derivation-graph design, which would collapse this hook into a thin adapter. Incidental cross-references to 011 in plans 012/020/021 andissues/issue-2280.mdare left as historical pointers (none are blocking dependencies).Plan 017 (scope Reorder auto-scroll state per group) was dropped: its sole justification —
resetAutoScrollState()cross-talk between two concurrent drags — is unreachable through the gesture pipeline. Mouse drags are single-pointer; secondary multi-touch points are rejected byisPrimaryPointerinPanSession(gestures/pan/PanSession.ts) before any handler attaches, so a second finger'sonDrag/autoScrollIfNeedednever fires; and same-axis drags are additionally gated by the global axis-keyed drag lock (setDragLock) + theonMoveno-lock early-return. The only residual path is simultaneous mouse-and-touchscreen input driving two different-axis Reorder groups at once, whose failure mode is a momentary, self-healing auto-scroll de-arm — not worth the per-groupWeakMapbookkeeping. Plan 018 needed 017 only for a cosmeticresetAutoScrollState(group)signature change; it works as-is against the current no-arg reset, which foraxis="both"already clears both the x and y scrollable ancestors of the single active group.
| Plan | Title | Priority | Effort | Depends on | Status |
|---|---|---|---|---|---|
| 001 | Promote animateLayout() into motion-dom public API | P1 | S | — | TODO |
| 007 | Lint motion-dom/motion-utils + blocking CI lint job | P1 | M | — | TODO |
| 005 | Grid/distance-based stagger() | P3 | S | — | TODO |
| 008 | Single-parse box-shadow projection scale correction | P2 | S | — | TODO |
| 009 | LayoutAnimationBuilder characterization tests | P2 | M | — | TODO |
| 003 | Enable WAAPI acceleration for color properties | P2 | M | — | TODO |
| 010 | Remove dead root devDependencies | P3 | S | 007 (soft) | TODO |
| 012 | Design spike: unified mark-dirty/pull MotionValue derivation graph (doc only) | P2 | M | — (soft: effects/VE unification direction) | TODO |
| 015 | Fix conditional hook call in Reorder.Item | P2 | S | — | TODO |
| 016 | Actionable Reorder-in-LazyMotion warning + docs | P3 | S | — | TODO |
| 018 | Multidimensional reorder (axis="both", positional collision detection) | P2 | L | 015 | TODO |
| 019 | Port the pan/drag gesture engine to motion-dom (behavior-preserving) | P1 | M | — | TODO |
| 020 | Ship a vanilla drag() API in motion-dom on the shared projection tree | P1 | L | 019 (hard), PR #3748 merged (hard), 009 (soft) | TODO |
| 021 | Drag QoL: inertia hard-stop, resize throttling, direction-lock fairness | P2 | M | 019 (hard) | TODO |
| 022 | Fix press() end-event filtering (secondary-pointer teardown, swallowed drag cancel) | P1 | S | — | TODO |
| 023 | Fix keyboard-press listener lifecycle (phantom pointerup/pointercancel) | P1 | S | — | TODO |
| 024 | Fix hover() double onHoverStart on re-enter during press | P2 | S | — | TODO |
| 025 | Add unit tests for resize() + wire up dead ResizeObserver mock | P2 | S | — | TODO |
| 026 | Fix InViewFeature stale viewport.once closure | P3 | S | — | TODO |
| 027 | Make the frameloop survive throwing callbacks + fix non-keepAlive batcher stall | P1 | S | — | TODO |
| 028 | Make cancelFrame cancel same-step callbacks queued for the current frame | P2 | S | — | TODO |
| 029 | Close frameloop test gaps (microtask, useManualTiming, 8-step order, delta clamp) | P3 | S | 027, 028 (soft) | TODO |
| 030 | Make visualDuration work without bounce/duration | P1 | S | — | TODO |
| 031 | Fix overdamped spring snap (#1207) via exponential form + unified per-tick update | P1 | M | 030 (soft) | TODO |
| 032 | Reproduce and fix NaN spring-animating polygon points (#2791) | P2 | M | — | TODO |
| 033 | Replace findSpring Newton-Raphson with exact closed form (~−1kB min) | P2 | M | 030, 031 (soft) | TODO |
| 034 | Move inView() from framer-motion to motion-dom (behavior-preserving + first unit tests) | P3 | M | — (026 soft: same feature file, trivial merge) | TODO |
| 035 | Make bundle-size budgets a blocking gate (CI + prepack) and re-baseline | P1 | S | — (007 soft: both add a CircleCI job) | TODO |
| 036 | Restore DCE of dev warnings broken by process.env?.NODE_ENV | P1 | S | 035 (soft: budget ratchet step) | TODO |
| 037 | Stop scale correctors leaking into the minimal m bundle | P2 | S | 035 (soft), 008 (soft: disjoint files, re-run scale-correction suite) | TODO |
| 038 | [audit] filesize pass over heaviest non-contended motion-dom modules | P2 | M | 035 (soft); coordinate 030–033, 019–021 | TODO |
Status values: TODO | IN PROGRESS | DONE | BLOCKED (with one-line reason) | REJECTED (with one-line rationale)
010 after 007 (soft): 007 settles the lint story before 010 removes the never-wired lint-staged.
009 should land before any refactor of LayoutAnimationBuilder.ts (including fallout from the worktree-animate-layout-v2 rewrite) — it exists to make such changes deliberate. 001 deliberately avoids LayoutAnimationBuilder.ts internals, so 001 and 009 are independent.
Direction plans 001, 002, 003 and 005 are mutually independent; their order above is leverage order (001 is near-free and closes the largest parity gap; 005 is a trivial win; 003/002 are medium).
012 is independent of all earlier plans and is doc-only; its design must be reviewed against the worktree-style-effect direction before any implementation plan is written. Its implementation follow-up would subsume the React adapter layer (use-combine-values.ts / use-computed.ts) — which is why the standalone per-render churn optimization (former plan 011) was dropped rather than landed ahead of it.
Reorder plans: 015 is small and independent; land it before 018, which rewrites the same file (Item.tsx). 016 is JSDoc + one warning string — merges trivially in any order. 018 is the headline (issue #1400) and carries the feel risk; its STOP conditions are the contract for when to escalate instead of tuning thresholds.
Gesture plans 022/023 both edit packages/motion-dom/src/gestures/press/index.ts but disjoint regions (022: onPointerEnd; 023: the focus-listener/tabIndex block + keyboard.ts) — either order, trivial merge; whichever lands second re-runs the press jest suite. 024 (hover), 025 (resize tests), 026 (viewport feature) are fully independent of everything. 025 should land before or with plan 021's resize-throttling step — it is the regression gate 021 currently lacks. All of 022–024 edit motion-dom source: yarn build from repo root is required before framer-motion jest sees the change (plans state this).
034 (inView move) is independent of everything except a soft collision with 026: both edit packages/framer-motion/src/motion/features/viewport/index.ts (026: the onIntersectionUpdate body; 034: top-of-file threshold const + import) — either order, trivial rebase, re-run the viewport jest pattern after the second lands. 034 makes InViewOptions/ViewChangeHandler/inViewThresholds newly public via the star-export chains (additive).
Note for 022/023 reviewers: prior audit rejected "onClick fires inconsistently after Reorder drag (#2682)" as out of the Reorder audit's scope — it lives in exactly the tap/drag interaction layer plan 022 fixes; re-test #2682 after 022 lands.
Frameloop plans: 027 and 028 touch different functions in render-step.ts (027: process + batcher.ts; 028: cancel) — independent, trivial rebase for whichever lands second. 029 is test-only but edits the same test files both fix plans add to (index.test.ts, batcher.test.ts) — land it last to avoid merge conflicts; if 027 hasn't landed, 029 creates batcher.test.ts itself. 028 carries the semantic risk (cancelled same-step callbacks no longer get a final tick — animations tick as frame.update keepAlive jobs); its Step 4 full-suite comparison is the gate, CI E2E the final browser-path check. If 028 and spring plan 031 land close together, re-run the spring suite after the rebase.
Spring plans: 030 (one-line durationKeys fix), 031 (generator-branch rewrite, lines ~282–426), and 033 (findSpring region, lines ~50–162) all edit spring.ts in disjoint regions — execute 030 → 031 → 033 to avoid rebase churn; each re-runs the full spring suite. 033 was upgraded from a doc-only spike to an implementation plan after the audit proved the closed form is the exact root of the same envelope equation (velocity is hardcoded to 0 at the only call site); its STOP conditions fall back to the spike doc if the ≤0.2% equivalence gate fails. 032 (NaN, #2791) is independent — the suspect is the complex-value mixer, not spring.ts — and is hard-gated on reproducing first (repo policy: no repro → no fix). Note 031 deliberately does NOT touch the 20s maxGeneratorDuration WAAPI truncation — if slow springs remain visibly cut off in browsers after 031, that cap is the follow-up (see 031's maintenance notes).
Drag plans: 019 is the behavior-preserving move + the getOptions constructor seam; it must land first (020 and 021 edit the moved file). 020 additionally requires PR #3748 (animate-layout-batched-commits) merged — its Step 1 extracts the shared projection tree out of LayoutAnimationBuilder.ts, which is exactly the refactor plan 009's characterization tests exist to guard (run them if they exist; soft dependency). 021 is independent of 020; if both land, re-run 020's HTML drag specs after 021. Note 018 (Reorder) and 021 both touch drag behavior near drag-to-reorder flows — whichever lands second re-runs the other's Cypress gates.
Bundle-size plans: execute 035 → 036 → 037 → 038. 035 re-baselines budgets to current actuals and makes them blocking (CircleCI measure job + prepack); 036/037/038 each end with a conditional budget-ratchet step that only applies once 035 is DONE — they land fine without it, the gate just stays loose. 035 and 007 both add independent CircleCI jobs (trivial merge). 037 and 008 touch adjacent scale-correction code but disjoint files (008: scale-box-shadow.ts internals; 037: where the corrector map is populated) — either order, whichever lands second re-runs the scale-correction jest suite. 038 must check this table for IN PROGRESS on 030–033 (own spring.ts) and 019–021 (move drag/pan) before touching anything; its exclusion list (create-projection-node, VisualElement, drag/pan, stats) is deliberate — measurement-only findings there, no edits. If branch cleanup/strip-unused-stats lands mid-execution, re-measure baselines rather than trusting plan byte tables.
node/types.ts's 1092-line option surface: variants, whileDrag, etc.) remains deferred behind worktree-style-effect; 019/020 deliberately ship one gesture, not the node API.layoutCurve) — a layoutCurve prop + layout curve calculations were built during the arc() work (commits 84ec58a74, 0c6c5ff08, example in e22909ac9) and removed before the 12.40.0 release; transition.path/arc() shipped for keyframes only. Reviving it is blocked on the effects composition/slot model (the same debt that makes pathRotation compose at three render sites). Revisit after effects unification.x/scale/rotate shorthands (PERFORMANCE_AUDIT.md rec #1, verified still open at packages/motion-dom/src/animation/waapi/utils/accelerated-values.ts): the single biggest perf lever in the codebase. The design spike for it (formerly plan 004) was dropped, so both the spike and the implementation are unplanned — pending the effects-unification substrate question.engines fields in published packages): all real lag, all touch the release pipeline; each deserves its own opt-in plan.__tests__; no typecheck script anywhere): worthwhile follow-up to 007.GroupAnimation.getAll() on empty/divergent groups (packages/motion-dom/src/animation/GroupAnimation.ts:29-33, known TODO): one-line guard; fold into the next animation PR (noted in plan 009's maintenance notes).use-scroll.ts:139 JSON.stringify(options.offset) in dep array; unexplained skipped tests (projection/node/__tests__/group.test.ts, parse-transform.test.ts 3D cases, waapi tests): small, real, low-leverage.LayoutAnimationBuilder, arc() undocumented outside source): concrete cost but below the planning cut; partially addressed by plan 001's docs requirements.render/html/utils/build-styles.ts:27-66, render.ts:13-16, VisualElement.ts:670-688): one value change per frame rebuilds every tracked value through getValueAsType, rebuilds the full transform string, and rewrites every key to element.style. Confirmed the single biggest per-frame waste — but the fix IS the effects/VisualElement unification (worktree-style-effect); per-key dirty rendering already exists in MotionValueState. A standalone "diff styles in renderHTML" patch would be throwaway work. Not planned; finish the migration.m component migration (the real fix for LazyMotion incompat, issues #2232/#2094): breaking for standalone Reorder users (drag/layout features would silently stop working unless loaded); needs a major-version decision. Plan 016 ships the honest-warning stopgap; the migration is recorded in its maintenance notes.touch-action output; spec'd as a follow-up in 018's maintenance notes (row/column-band detection over registered boxes).onReorder signature (#2603): real; both want an imperative "move item i→j" entry point that plan 018's {from, to}-based checkReorder makes natural. Plan after 018 lands.onClick fires inconsistently after Reorder drag (#2682): tap/drag gesture-interaction layer, not Reorder code; out of this audit's scope.inView() from framer-motion to motion-dom (NEEDS-DECISION)inView with features/viewport/observers.ts's shared-observer model is excluded (it changes observer lifetimes and fire timing) and remains a separate future decision — see plan 034's maintenance notes.press() is documented Enter-only (press/index.ts:37), but the WAI-ARIA button pattern expects Space too. Wrinkle that likely motivated Enter-only: gesture listeners are passive: true (utils/setup.ts:16), so Space on a non-button element would scroll the page with no way to preventDefault. Supporting Space properly means a non-passive keydown listener for that key. Behavior + a11y decision, not a plain bug fix. If approved, must build on plan 023's removeEndListeners (see its maintenance notes).From the 2026-06-11 signals audit (push-vs-pull review of MotionValue):
set() (motion-dom/src/value/index.ts:349-375): a handful of field writes against the frame-cached time.now(); making velocity lazy would complicate updatedAt/prevFrameValue semantics for negligible gain. Not worth doing.transformValue (deps collected once at creation; documented "no conditionals" contract): signal runtimes re-track per run, but re-tracking costs bytes and the constraint is documented. Working as intended (revisit only inside plan 012's design).frame.preRender Set dedup + insertion order means a diamond (x→a, x→b, (a,b)→c) computes c once per frame in topological order, and updateAndNotify's equality cutoff stops converged chains. No finding.MotionValueState dirty-chain writes a garbage latest.transform = "none" per change (effects/MotionValueState.ts:28-37 with the placeholder node from effects/style/index.ts:34): real but trivially cheap; naturally disappears under plan 012's design. Not worth a standalone fix.From the direction audit:
resize() / springValue / mapValue / transformValue / animateView are not publicly exported" (audit subagents reported these): FALSE. packages/framer-motion/src/dom.ts:1 is export * from "motion-dom" and packages/motion/src/index.ts re-exports framer-motion/dom, so every motion-dom export is already public on the motion package. Any future "missing export" claim must be checked against that chain first.recordStats as a public performance/APM API: contradicted by current maintainer direction — branch cleanup/strip-unused-stats ("Strip unused stats internals from shipped bundle") is actively slimming stats, and a motion/debug entry point already exists for what's kept. Decision belongs to the maintainer; not a plan.motion/mini with gesture exports: viable but low value; contradicts the mini bundle's size-first purpose without evidence of demand.MotionCSSVariables typed-but-unused interface (framer-motion/src/motion/types.ts:47-53): real TODO, but a type-level QoL item, not direction-level; fold into routine type maintenance.From the deep audit (vetted false positives — do not re-report):
defaultEasing splice vs slice (animation/generators/keyframes.ts:21): splice(0, n-1) returns the same first n-1 elements slice would; the mutated temp array is discarded. Unidiomatic, not a bug.AnimatePresence/PopChild.tsx:129-130): ${x}px !important where x = "left: 123" yields valid CSS (left: 123px !important). By design.gestures/drag/__tests__/index.test.tsx:61): the inner expect(...).toBe(...) executes and throws on failure; outer wrap is dead but harmless. Cosmetic only.80d85dbeb exists only on branch worktree-style-effect, not main. Effects remain exported-but-internally-unused on main.67365fb75); churn data was historical.^12.39.0 vs framer-motion 12.40.0 "drift": caret range covers it; lerna publishes only changed packages. Working as intended.css-variables-conversion.ts:16: verified bounded regex; the disable comment is justified.claude.yml GitHub Action "injection": gated by the Anthropic action with read-only permissions; standard pattern, by design.From the drag audit (2026-06-11):
PanSession.startScrollTracking walks all ancestors with getComputedStyle on every drag start (PanSession.ts:178-199): once per gesture, not per frame; negligible. Not worth caching.onDragTransitionEnd fires even when no momentum animation ran (VisualElementDragControls.ts:510-511, Promise.all over undefineds resolves immediately): arguably by design ("transition end" = "settled"); changing it is a breaking-ish behavior change with no demand. Rejected.latestPointerEvent/latestPanInfo "leak": both are nulled in onSessionEnd (VisualElementDragControls.ts:227-235); no retention bug. False positive.PanSession.ts:399-413); plan 021 deliberately leaves PanSession untouched. Re-investigate only with a fresh repro against current main.DragControls + portal memory leak (#2444): not reproduced in this audit — subscribe() returns an unsubscribe and DragGesture.unmount calls it (gestures/drag/index.ts:40-42). Needs a repro before any fix (per repo policy: no repro → no fix).dragSnapToCursor as a declarative prop (#2677): real, small feature; deferred — decide alongside vanilla DragOptions naming when plan 020 is reviewed.From the frameloop audit (2026-06-11):
sync/cancelSync legacy exports (frameloop/index-legacy.ts, exported at motion-dom/src/index.ts:320): removal is a public breaking change for negligible bytes; defer to the next major. Not worth a plan now.1000/60 (batcher.ts:51): wrong for one frame on 120Hz displays, but no better guess exists before two real frames; by design (plan 029 pins it as contract).processBatch ignores the rAF timestamp argument in favor of performance.now() (batcher.ts:46): likely deliberate (cross-browser rAF-timestamp quirks); changing it risks more than it gains. Rejected.c8dcf8a70 ("[audit] motion-dom/frameloop: reduce per-frame allocations"); nothing meaningful left at this scale.From the hover/press/inView/resize audit (2026-06-11):
resize() observes content-box but reports border-box (handle-element.ts:15-28, no box option passed to observe()): real inconsistency — a border/padding-only change won't fire the callback — but changing the observed box alters callback timing for all existing consumers with no reported breakage. Documented in plan 025's maintenance notes; fix only on a user report.resizeElement subscriptions (handle-element.ts:69, Set semantics — first cleanup silences both): edge case with no plausible real-world hit; characterized as KNOWN BEHAVIOR in plan 025's suite rather than fixed.observers.ts IntersectionObserver registry never disconnects empty observers (features/viewport/observers.ts:53-60): bounded by unique root+options combos per page; revisit only inside the inView-relocation work above.useInView root RefObject in deps reads .current at effect time (use-in-view.ts:36,42): standard React ref pattern; refs attached in the same commit are visible. Not a bug.removeEventListener without options in press teardown (press/index.ts:72-73): only capture affects removal matching and EventOptions doesn't expose capture. Correct as-is.inView() unobserve-when-no-callback-returned (viewport/index.ts:50): documented once-per-element semantics, by design.framer-motion/src/gestures/press.ts:12-14): browsers don't dispatch pointer events on disabled controls; the React-layer check covers synthetic keyboard events. No vanilla-layer fix needed.From the spring audit (2026-06-11):
restSpeed: 0 / restDelta: 0 silently fall back to defaults (spring.ts:275-280, ||=): protective by design — restDelta: 0 requires exact float equality and the spring would never finish. Working as intended.mass passed alongside duration/bounce ignores the duration (verified: duration: 800, mass: 3 settles at 3260ms): mass is a physics key and the documented precedence is physics-keys-override-duration-keys (spring.ts:180). By design.follow-value.ts stale .then() race suspected (old animation's then clearing the new value.animation): false positive — JSAnimation.stop() tears down without calling notifyFinished(), so an interrupted animation's finished promise never resolves and the cleanup never fires for stale animations.findSpring derivative passes ω² into calcAngularFreq (spring.ts:123, mathematically dubious-looking Framer port): irrelevant once plan 033 deletes the Newton-Raphson solver entirely; do not fix independently.linear() path (calc-duration.ts maxGeneratorDuration; 10s in pregenerate.ts): real, pre-existing, deliberately out of 031's scope — file separately if browser-visible after 031 lands.From the bundle-size audit (2026-06-11):
arc()/pathRotation suspected of bloating the motion bundle (17 commits landed after the last dist build): FALSE — fresh HEAD build measured byte-identical size bundles; the feature tree-shakes cleanly out of the default graph (injectable via transition.path by design). Good pattern to repeat.m.div (SVG render machinery entered the core graph with v12.24.0's {type:"svg"}): tag detection is runtime (m.circle must work without configuration), so the static import is structural; making it lazy changes m's contract and belongs to the effects/VE unification era, not a size fix. Revisit as a design spike only on demand.size-rollup-*.js(.map) files (no files field in framer-motion's package.json): package-size, not bundle-size; harmless to installs and useful for historical bisection (this audit used them). Leave as is.From the Reorder audit (2026-06-11):
registerItem re-sorts the order array on every call (Reorder/Group.tsx:113): O(n·n log n) per measure pass but invisible at realistic list sizes; plan 018 removes the sort for axis="both" anyway. Not worth a standalone fix.values break indexOf-based reorder application (Reorder/Group.tsx:129-130): duplicates already break React keys in the documented values.map usage; at most a dev-mode warning, below the planning cut.checkReorder velocity gate as a standalone 1D bug: real quirk (direction chosen by velocity sign can pick the wrong neighbor on jittery pointers) but folded into plan 018's geometric rewrite rather than fixed twice.