@unrulysystems/native-motion-web
v0.1.0-alpha.0
Published
The web engine (SPEC-WEB-SHIM). A thin, validated shim over `motion/react`: wrapped `View` (`motion.div`), `Text` hosts (`motion.p` by default, inline `motion.span` with `as="span"`), and `Image` (`motion.img` with `src`/`alt` as DOM attributes) that add
Readme
@unrulysystems/native-motion-web
The web engine (SPEC-WEB-SHIM). A thin, validated shim over motion/react: wrapped View
(motion.div), Text hosts (motion.p by default, inline motion.span with as="span"), and
Image (motion.img with src/alt as DOM attributes) that add normalization and validation
but never animation semantics — on web, the engine IS motion/react (pinned [email protected],
peer). Presence (AnimatePresence/usePresence/useIsPresent) is a thin re-export of
motion/react's own primitives. The ratified universal subset includes variants and
whileTap/whileDrag state props; REQ-API-034's closed host set is View/Text/Image
(ratified frozen 2026-07-22, BRIEF Decisions — motion.create stays internal-only).
layout/layoutId are DECLARED since L4 and forward to real motion behind severity-law entry
gates.
Module map (src/)
| Module | Role |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| View.tsx | The internal wrapped-host seam for public View, Text, and Image: routes props (disposition table → normalize; declared component props → forward; other motion-interpretable keys → loud failure; genuine DOM attrs → through), validates targets with core's validateTarget, owns the FLAG 5a drag family, selects motion.p/motion.span for Text and motion.img for Image (childless; src required) — the verdict is single-sourced in core (resolveDragConfig, identical accept/refuse to native), Motion's shapes pass through, dragSnapPoints maps to dragTransition over core-validated points |
| MotionRoot.tsx | REQ-API-022 root primitive — pass-through on web (motion/react binds recognizers per element; no gesture root needed); exists on both entries for the one-import universal surface (value-key parity) |
| disposition.ts | The prop table (REQ-WEB-012): every public prop is pass-through / normalized / extension-mapped / rejected; compile-time exhaustive (satisfies), runtime-total. dragSnapPoints is CLASSIFIED extension-mapped so a dropped/drifted mapping is a type error (REQ-WEB-013); the runtime dragSnapPoints→dragTransition mapping over core-validated points is owned by View.tsx — the disposition is the guard, not the mechanism |
| normalize.ts | Pure/total prop pipeline; severity is a parameter — dev throws, production reports through the error channel and refuses the property (ratified law); pins box-sizing: border-box |
| snap.ts | The web MAPPING only (REQ-WEB-013): nearestSnap + snapPointsToDragTransition turn core-validated points into the dragTransition.modifyTarget projection. Drag-config VALIDATION is single-sourced in core (resolveDragConfig) so both engines' accepted sets are identical |
| errors.ts | The loud-failure vocabulary (MotionWebError + rejection/fidelity/incompatible) |
| mode.ts | ambientMode() — the one place the ambient NODE_ENV is read |
| jsdomWorld.ts | Test infrastructure: the shared jsdom world (install/restore-scoped globals via useJsdomWorld(), WAAPI spy) — import-order-sensitive by design; see its header |
Verification (Altitudes 3–6)
- Vitest verifies disposition/normalize/snap tables, jsdom end-state component tests (the REAL motion/react under jsdom — end states only, never mid-flight), SSR, the gesture cross-engine parity flip, the variants label-form parity cases, and the L4 layout/layoutId forwarding gates.
- Altitude 5 (
e2e/, Playwright, Chromium-only by ratification,*.e2e.tsso unit runners never collect them): 22 tests across 13 specs — WAAPI-vs-analytic trajectory scrub, core-FLIP math vs Chromium's rendered geometry (core-vs-browser, not native-vs-motion/react), real-mouse drag→snap, presence removal timing, mid-flight interrupt continuity, the variants label-form parity, the examples-gallery smoke with a zero-console-error net, the L4 App Store card choreography specs, the layoutId crossfade parity counterpart (LAYOUT_IDENTITY_PARITY_COUNTERPART), the Text layout-size proof, and the Image gallery crossfade proof. e2e/fixtures/examples.tsxis the examples gallery — documentation-by-example for the whole ratified surface; every card self-reports throughdata-*attributes so specs and humans read the same settle signal.
cd packages/native-motion-web && bunx vitest run && bun run e2e # or: nub run e2e:web (root)