@unrulysystems/native-motion 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +55 -0
  4. package/android/build.gradle +24 -0
  5. package/android/src/main/AndroidManifest.xml +1 -0
  6. package/android/src/main/java/com/unrulysystems/nativemotion/GestureExclusionModule.kt +55 -0
  7. package/android/src/main/java/com/unrulysystems/nativemotion/NativeMotionPackage.kt +27 -0
  8. package/package.json +57 -0
  9. package/react-native.config.js +14 -0
  10. package/src/native/driver/instantWindow.ts +31 -0
  11. package/src/native/driver/strictModeReplay.ts +20 -0
  12. package/src/native/driver/uiLayoutEngine.ts +845 -0
  13. package/src/native/driver/uiLayoutGraph.ts +338 -0
  14. package/src/native/driver/uiValueChannel.ts +4640 -0
  15. package/src/native/driver/workletDriver.ts +3860 -0
  16. package/src/native/motion/AnimatePresence.tsx +1605 -0
  17. package/src/native/motion/LayoutGroup.tsx +163 -0
  18. package/src/native/motion/MotionConfig.tsx +400 -0
  19. package/src/native/motion/MotionRoot.tsx +250 -0
  20. package/src/native/motion/MotionView.tsx +6761 -0
  21. package/src/native/motion/addScaleCorrector.ts +97 -0
  22. package/src/native/motion/colorEndpointUiFeed.ts +32 -0
  23. package/src/native/motion/colorProjection.ts +39 -0
  24. package/src/native/motion/composeTransform.ts +271 -0
  25. package/src/native/motion/constraintMeasure.ts +157 -0
  26. package/src/native/motion/deferredPendingSnapshots.ts +25 -0
  27. package/src/native/motion/discreteProjection.ts +36 -0
  28. package/src/native/motion/dragAncestorPanContext.ts +28 -0
  29. package/src/native/motion/dragControls.ts +152 -0
  30. package/src/native/motion/dragGestureWiring.ts +1168 -0
  31. package/src/native/motion/dragHandoffBinding.ts +179 -0
  32. package/src/native/motion/dragHubStream.ts +756 -0
  33. package/src/native/motion/dragPropagationLock.ts +57 -0
  34. package/src/native/motion/driverValueChannel.ts +3288 -0
  35. package/src/native/motion/externalDragDriver.ts +214 -0
  36. package/src/native/motion/frameData.ts +31 -0
  37. package/src/native/motion/gestureBinding.ts +107 -0
  38. package/src/native/motion/gestureStateGate.ts +563 -0
  39. package/src/native/motion/gestureStateResolver.ts +286 -0
  40. package/src/native/motion/identityValueChannelControllerAdapter.ts +2043 -0
  41. package/src/native/motion/identityValueChannelControllerReconciliation.ts +176 -0
  42. package/src/native/motion/identityValueChannelLaneMarker.ts +14 -0
  43. package/src/native/motion/imperativeAnimate.ts +816 -0
  44. package/src/native/motion/keyframeTiming.ts +7 -0
  45. package/src/native/motion/layoutIdBinding.ts +261 -0
  46. package/src/native/motion/layoutIdFlightConfig.ts +51 -0
  47. package/src/native/motion/layoutProjection.ts +89 -0
  48. package/src/native/motion/layoutScroll.ts +204 -0
  49. package/src/native/motion/layoutTransition.ts +1302 -0
  50. package/src/native/motion/lengthLayoutContext.tsx +100 -0
  51. package/src/native/motion/lengthLayoutHost.ts +44 -0
  52. package/src/native/motion/mappedKeys.ts +184 -0
  53. package/src/native/motion/motionViewController.ts +3180 -0
  54. package/src/native/motion/nativeHostMarker.ts +22 -0
  55. package/src/native/motion/panSessionWiring.ts +127 -0
  56. package/src/native/motion/pathTransition.ts +101 -0
  57. package/src/native/motion/popLayout.ts +176 -0
  58. package/src/native/motion/presenceBinding.ts +382 -0
  59. package/src/native/motion/scaleCorrectorRegistry.ts +123 -0
  60. package/src/native/motion/scrollValues.ts +260 -0
  61. package/src/native/motion/serializablePayload.ts +151 -0
  62. package/src/native/motion/severity.ts +16 -0
  63. package/src/native/motion/shippedSurface.ts +843 -0
  64. package/src/native/motion/staticLengthGate.ts +86 -0
  65. package/src/native/motion/styleBaseGate.ts +72 -0
  66. package/src/native/motion/styleValueBinding.ts +657 -0
  67. package/src/native/motion/systemGestureExclusion.ts +61 -0
  68. package/src/native/motion/tapGestureWiring.ts +215 -0
  69. package/src/native/motion/transformOrder.ts +38 -0
  70. package/src/native/motion/transformStringBinding.ts +200 -0
  71. package/src/native/motion/transformTemplateGate.ts +45 -0
  72. package/src/native/motion/transitionGate.ts +435 -0
  73. package/src/native/motion/useAnimate.ts +52 -0
  74. package/src/native/motion/useCycle.ts +44 -0
  75. package/src/native/motion/useInstantTransition.ts +68 -0
  76. package/src/native/motion/useReducedMotion.ts +58 -0
  77. package/src/native/motion/useScroll.ts +162 -0
  78. package/src/native/motion/useViewportScroll.ts +29 -0
  79. package/src/native/motion/valueChannel.ts +7121 -0
  80. package/src/native/motion/valueHooks.ts +901 -0
  81. package/src/native/motion/variantChildRegistry.ts +67 -0
  82. package/src/native/motion/variantContext.tsx +172 -0
  83. package/src/native/motion/variantProps.ts +598 -0
  84. package/src/native.ts +159 -0
  85. package/src/verification/harnessMetrics.ts +78 -0
  86. package/src/verification/probe/LayoutIdentityWorkletProbe.tsx +251 -0
  87. package/src/verification/probe/WorkletParityProbe.tsx +162 -0
  88. package/src/verification/probe/layoutIdentityProbeEngine.ts +429 -0
  89. package/src/verification/screens/ArcPathChecksScreen.tsx +273 -0
  90. package/src/verification/screens/BooleanAnimateChecksScreen.tsx +358 -0
  91. package/src/verification/screens/ChoreographyGalleryScreen.tsx +1010 -0
  92. package/src/verification/screens/ColorBindingChecksScreen.tsx +568 -0
  93. package/src/verification/screens/CompletionChecksScreen.tsx +294 -0
  94. package/src/verification/screens/ConformanceScreen.tsx +573 -0
  95. package/src/verification/screens/ContentionProbeScreen.tsx +224 -0
  96. package/src/verification/screens/DriverSmokeScreen.tsx +99 -0
  97. package/src/verification/screens/DurationOnlyTweenChecksScreen.tsx +294 -0
  98. package/src/verification/screens/DynamicDragConfigChecksScreen.tsx +237 -0
  99. package/src/verification/screens/FrameDataChecksScreen.tsx +252 -0
  100. package/src/verification/screens/GestureChecksScreen.tsx +685 -0
  101. package/src/verification/screens/InstantTransitionChecksScreen.tsx +839 -0
  102. package/src/verification/screens/LayoutAnimationStartChecksScreen.tsx +648 -0
  103. package/src/verification/screens/LayoutChecksScreen.tsx +822 -0
  104. package/src/verification/screens/LayoutCommitSpikeScreen.tsx +142 -0
  105. package/src/verification/screens/MotionViewChecksScreen.tsx +930 -0
  106. package/src/verification/screens/PresenceChecksScreen.tsx +614 -0
  107. package/src/verification/screens/ReducedMotionChecksScreen.tsx +614 -0
  108. package/src/verification/screens/RestThresholdChecksScreen.tsx +634 -0
  109. package/src/verification/screens/ScaleCorrectorChecksScreen.tsx +425 -0
  110. package/src/verification/screens/SharedLayoutContinuityChecksScreen.tsx +338 -0
  111. package/src/verification/screens/SharedLayoutCrossfadeChecksScreen.tsx +425 -0
  112. package/src/verification/screens/TransitionDefaultSelectionChecksScreen.tsx +514 -0
  113. package/src/verification/screens/ViewportScrollAliasChecksScreen.tsx +303 -0
  114. package/src/verification/screens/conformanceBanner.ts +21 -0
  115. package/src/verification/screens/proofConsoleTap.ts +18 -0
  116. package/src/verification.ts +47 -0
  117. package/src/web.ts +98 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # @unrulysystems/native-motion
2
+
3
+ ## 0.1.0-alpha.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Initial alpha prerelease: the M3-era public surface (layout, presence, drag, gestures, variants,
8
+ orchestration, value types) with the cross-engine conformance suite published alongside the
9
+ runtime packages.
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies
14
+ - @unrulysystems/native-motion-core@0.1.0-alpha.0
15
+ - @unrulysystems/native-motion-web@0.1.0-alpha.0
16
+ - @unrulysystems/native-motion-conformance@0.1.0-alpha.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Unruly Systems LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # @unrulysystems/native-motion
2
+
3
+ The single public import (REQ-UNIVERSAL-001): one specifier resolving to two engine entries —
4
+ AND, since the dogfood extraction (`specs/DOGFOOD-MIGRATION-BUILD-PACKET.md`), the home of the
5
+ native runtime itself.
6
+
7
+ - `src/web.ts` — `'use client'`, an EXPLICIT re-export of the universal surface from the web
8
+ engine (`@unrulysystems/native-motion-web`); web-internal helpers stay out of the namespace.
9
+ - `src/native.ts` — the native runtime entry: the universal surface (`View`, `ViewProps`, `Text`,
10
+ `TextProps`, `Image`, `ImageProps`, `AnimatePresence` (+ pin-catalog `onRest` alias of
11
+ `onExitComplete`), `usePresence`, `useIsPresent`, `MotionRoot`, `MotionRootProps`,
12
+ `MotionLengthLayoutProvider` (parent/viewport geometry for measure-resolved `%`/`calc`),
13
+ `LayoutScrollOffsetProvider` + `recordLayoutScrollOffsetFromEvent` /
14
+ `createLayoutScrollOffsetRef` (REQ-LAYOUT-022(c) live container-scroll feed), `DragInfo` + the
15
+ canonical core types) over the engine in `src/native/{driver,motion}` (the worklet driver, the
16
+ bindings, the UI-thread layout-identity engine). `View`, `Text`, and `Image` share the FLAG 5a
17
+ drag family (`drag` (`'x' | 'y' | true` — both-axes ships at R15 D1, REQ-API-041),
18
+ `dragConstraints`, `dragElastic`, `dragSnapPoints`, `dragPropagation` (Motion's nested-drag
19
+ lock, R15 D1),
20
+ `onDragStart`/`onDrag`/`onDragEnd`), the FLAG 5b layout family (`layout`, `layoutId`,
21
+ `layoutScroll`), and distinct property/layout lifecycle callbacks (`onAnimationComplete`,
22
+ `onLayoutAnimationStart`, `onLayoutAnimationComplete`); `Text` accepts ordinary React Native
23
+ text children while `Image`
24
+ is childless and requires a valid `source`. Text's universal `as="p" | "span"` selector is
25
+ validated on native (nested Text accepts only `span`, preventing a divergent invalid text tree)
26
+ even though RN has no DOM display-mode split; the web entry uses it to preserve a catalog
27
+ paragraph/inline choice. `MotionRoot` owns the gesture recognizer root (a gesture-carrying host
28
+ outside it fails loud). Capabilities the native surface has not pulled yet are absent —
29
+ premature use is a build error, never a stub. The host element set is CLOSED at these three RN
30
+ leaf primitives (ratified 2026-07-22, BRIEF Decisions): container classes adjudicate to `View`,
31
+ controls animate through a `View` wrapper, and `motion.create` stays internal-only.
32
+ - `src/verification.ts` — the engine's intrinsic verification harness (probes, deterministic
33
+ check screens, the choreography gallery). SHELL-ONLY by law: the proof-harness app shell
34
+ mounts it; it is never part of a consumer example (REQ-EXHARNESS-010).
35
+
36
+ ## Dual skew-checked resolution (the ratified architecture)
37
+
38
+ The manifest ships BOTH resolution mechanisms — the `exports` `react-native` condition (newer
39
+ Metro) AND the top-level `react-native` field (older Metro floor) — and two guards keep them
40
+ from ever disagreeing:
41
+
42
+ 1. `scripts/check-resolution-skew.mjs` (wired into `nub run check`): asserts the condition and
43
+ the field point at the same file, that `browser`/`import`/`default` agree and differ from
44
+ the native entry, and that both entries exist on disk.
45
+ 2. `src/resolution.test.ts`: resolves the export map with `resolve.exports` — react-native →
46
+ native, browser/import/default → web, no engine-mixing fall-through.
47
+
48
+ Type identity across entries is a COMPILE-TIME gate: `src/type-identity.keys.ts` fails
49
+ `nub run typecheck` if a native value export is absent on web while allowing intentional pinned
50
+ Motion additions to the web namespace. `src/type-identity.assert.ts` fails if the universal View-,
51
+ Text-, and Image-prop subsets (`initial`/`animate`/`exit`/`transition`/`onAnimationComplete`/
52
+ `onLayoutAnimationStart`/`onLayoutAnimationComplete`/`layout`/`layoutId`) or the pinned
53
+ AnimatePresence props stop being
54
+ structurally identical, or if the presence-hook signatures drift from the pinned `motion/react`
55
+ shapes (portable across test runners because it is not a test at all).
@@ -0,0 +1,24 @@
1
+ apply plugin: 'com.android.library'
2
+ apply plugin: 'org.jetbrains.kotlin.android'
3
+
4
+ android {
5
+ namespace 'com.unrulysystems.nativemotion'
6
+ compileSdk 36
7
+
8
+ defaultConfig {
9
+ minSdk 24
10
+ }
11
+
12
+ compileOptions {
13
+ sourceCompatibility JavaVersion.VERSION_17
14
+ targetCompatibility JavaVersion.VERSION_17
15
+ }
16
+
17
+ kotlinOptions {
18
+ jvmTarget = '17'
19
+ }
20
+ }
21
+
22
+ dependencies {
23
+ implementation 'com.facebook.react:react-android'
24
+ }
@@ -0,0 +1 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" />
@@ -0,0 +1,55 @@
1
+ package com.unrulysystems.nativemotion
2
+
3
+ import android.graphics.Rect
4
+ import android.os.Build
5
+ import android.view.View
6
+ import com.facebook.react.bridge.ReactApplicationContext
7
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
8
+ import com.facebook.react.bridge.ReactMethod
9
+ import com.facebook.react.fabric.FabricUIManager
10
+ import com.facebook.react.module.annotations.ReactModule
11
+ import com.facebook.react.uimanager.UIManagerHelper
12
+ import com.facebook.react.uimanager.common.UIManagerType
13
+
14
+ /**
15
+ * REQ-GESTURE-032 — Android drag surfaces tell the system *this drag is mine*.
16
+ *
17
+ * `View.setSystemGestureExclusionRects` is the host API; Motion does not have an equivalent
18
+ * because web has no edge-back strip. Mapping: no-op on web and on API < 29. Not a public
19
+ * prop — a drag host registers its own bounds automatically.
20
+ */
21
+ @ReactModule(name = GestureExclusionModule.NAME)
22
+ class GestureExclusionModule(
23
+ private val reactContext: ReactApplicationContext,
24
+ ) : ReactContextBaseJavaModule(reactContext) {
25
+ override fun getName(): String = NAME
26
+
27
+ @ReactMethod
28
+ fun excludeView(viewTag: Double) {
29
+ applyOnView(viewTag.toInt()) { view ->
30
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return@applyOnView
31
+ view.systemGestureExclusionRects = listOf(Rect(0, 0, view.width, view.height))
32
+ }
33
+ }
34
+
35
+ @ReactMethod
36
+ fun clearView(viewTag: Double) {
37
+ applyOnView(viewTag.toInt()) { view ->
38
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return@applyOnView
39
+ view.systemGestureExclusionRects = emptyList()
40
+ }
41
+ }
42
+
43
+ private fun applyOnView(tag: Int, block: (View) -> Unit) {
44
+ reactContext.runOnUiQueueThread {
45
+ val uiManager =
46
+ UIManagerHelper.getUIManager(reactContext, UIManagerType.FABRIC) as? FabricUIManager
47
+ val view = uiManager?.resolveView(tag) ?: return@runOnUiQueueThread
48
+ block(view)
49
+ }
50
+ }
51
+
52
+ companion object {
53
+ const val NAME = "NativeMotionGestureExclusion"
54
+ }
55
+ }
@@ -0,0 +1,27 @@
1
+ package com.unrulysystems.nativemotion
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfo
7
+ import com.facebook.react.module.model.ReactModuleInfoProvider
8
+
9
+ class NativeMotionPackage : BaseReactPackage() {
10
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? =
11
+ if (name == GestureExclusionModule.NAME) GestureExclusionModule(reactContext) else null
12
+
13
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider =
14
+ ReactModuleInfoProvider {
15
+ mapOf(
16
+ GestureExclusionModule.NAME to
17
+ ReactModuleInfo(
18
+ GestureExclusionModule.NAME,
19
+ GestureExclusionModule::class.java.name,
20
+ false,
21
+ false,
22
+ false,
23
+ false,
24
+ ),
25
+ )
26
+ }
27
+ }
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@unrulysystems/native-motion",
3
+ "version": "0.1.0-alpha.0",
4
+ "license": "MIT",
5
+ "files": [
6
+ "src",
7
+ "CHANGELOG.md",
8
+ "android/build.gradle",
9
+ "android/src",
10
+ "react-native.config.js",
11
+ "!src/**/*.test.*",
12
+ "!src/**/*.assert.ts",
13
+ "!src/type-identity.keys.ts",
14
+ "!src/**/*Fixtures.ts",
15
+ "!src/native/motion/reactNativeRequireStub.ts"
16
+ ],
17
+ "type": "module",
18
+ "main": "./src/web.ts",
19
+ "react-native": "./src/native.ts",
20
+ "exports": {
21
+ ".": {
22
+ "react-native": "./src/native.ts",
23
+ "browser": "./src/web.ts",
24
+ "import": "./src/web.ts",
25
+ "default": "./src/web.ts"
26
+ },
27
+ "./driver-mapped-keys": "./src/native/motion/mappedKeys.ts",
28
+ "./native-public-surface": "./src/native/motion/shippedSurface.ts",
29
+ "./verification": "./src/verification.ts"
30
+ },
31
+ "scripts": {
32
+ "test": "vitest run"
33
+ },
34
+ "dependencies": {
35
+ "@unrulysystems/native-motion-conformance": "0.1.0-alpha.0",
36
+ "@unrulysystems/native-motion-core": "0.1.0-alpha.0",
37
+ "@unrulysystems/native-motion-web": "0.1.0-alpha.0"
38
+ },
39
+ "devDependencies": {
40
+ "@babel/core": "^7.29.7",
41
+ "@types/babel__core": "^7.20.5",
42
+ "@types/node": "^24.0.0",
43
+ "@types/react-dom": "^19",
44
+ "motion": "12.42.2",
45
+ "motion-dom": "12.42.2",
46
+ "react-dom": "19.2.3",
47
+ "resolve.exports": "^2.0.3",
48
+ "vitest": "^4.1.9"
49
+ },
50
+ "peerDependencies": {
51
+ "react": "*",
52
+ "react-native": "*",
53
+ "react-native-gesture-handler": ">=2.28.0",
54
+ "react-native-reanimated": ">=4.1.0",
55
+ "react-native-worklets": ">=0.5.0"
56
+ }
57
+ }
@@ -0,0 +1,14 @@
1
+ // The mobile app also lists this package in apps/mobile/react-native.config.js — Expo's
2
+ // RN autolinker does not discover workspace packages from this file alone.
3
+ module.exports = {
4
+ dependency: {
5
+ platforms: {
6
+ android: {
7
+ sourceDir: './android',
8
+ packageImportPath: 'import com.unrulysystems.nativemotion.NativeMotionPackage;',
9
+ packageInstance: 'new NativeMotionPackage()',
10
+ },
11
+ ios: null,
12
+ },
13
+ },
14
+ }
@@ -0,0 +1,31 @@
1
+ // U7b — the GLOBAL instant-window authority: the `MotionGlobalConfig.instantAnimations`
2
+ // analogue (pin: motion-dom@12.42.2 animation/interfaces/motion-value.ts:100-109). While the
3
+ // window is open, EVERY property-lane animation start on the PRODUCT (worklet) driver — any
4
+ // component — takes the
5
+ // U7a instant commit (skip the generator, final keyframe on the driver's next update tick,
6
+ // completion fires) with `delay` ZEROED. The pin's asymmetry (F2): the authored `type: false`
7
+ // honors a nonzero delay; the global window does not. Reduced motion is orthogonal — never
8
+ // composed here. The reference driver (core's host-agnostic parity oracle) never consults the
9
+ // window by design; the imperative playback lane does not consult it either (U7b review minor
10
+ // — a demand for that class should REFUSE loud there like U7a's `type: false` arm, never
11
+ // silently run full-duration).
12
+ //
13
+ // Plain JS-side module state: the consult happens at JS-side transition resolution
14
+ // (workletDriver's toPlainTransition), before anything crosses to the UI runtime, so the flag
15
+ // never marshals. `useInstantTransition` opens the window and relocks it two post-render frames
16
+ // later under a token guard; `disableInstantTransitions` is the pin's sibling escape hatch for a
17
+ // stuck window.
18
+
19
+ let instantWindowOpen = false
20
+
21
+ export function openInstantWindow(): void {
22
+ instantWindowOpen = true
23
+ }
24
+
25
+ export function closeInstantWindow(): void {
26
+ instantWindowOpen = false
27
+ }
28
+
29
+ export function isInstantWindowOpen(): boolean {
30
+ return instantWindowOpen
31
+ }
@@ -0,0 +1,20 @@
1
+ // REQ-DRIVER-022 StrictMode-replay phase (BRIEF Decisions 2026-07-10, registry-port round 7
2
+ // major 5). React StrictMode's dev-only setup→cleanup→setup effect replay runs the hook's
3
+ // dispose() cleanup and then replays setup against the SAME render-created binding — now
4
+ // disposed. Resurrection (recreate the driver and rebind shared values on replay) is a deferred
5
+ // capability with its own future rung; until it lands the replayed setup fails loud instead of
6
+ // leaving a mounted component holding a permanently disposed binding. RN-free on purpose: the
7
+ // mobile vitest suite pins this contract while the binding itself stays device/e2e territory.
8
+ //
9
+ // Severity law note: StrictMode double-invocation exists only in development builds, so this
10
+ // throw is dev-only by construction — production never reaches the replay phase.
11
+
12
+ export function assertSetupNotReplayedOnDisposed(disposed: boolean): void {
13
+ if (!disposed) return
14
+ throw new Error(
15
+ 'useWorkletDriver: this binding was disposed by a StrictMode setup→cleanup→setup effect ' +
16
+ 'replay. StrictMode remount replay is a deferred capability — resurrection (recreate the ' +
17
+ 'driver and rebind shared values on replay) is its own future rung (BRIEF Decisions ' +
18
+ '2026-07-10; REQ-DRIVER-022). Unwrap this tree from <StrictMode> until that rung lands.',
19
+ )
20
+ }