motionloom 2.0.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.
- package/LICENSE +21 -0
- package/README.md +179 -0
- package/SKILL.md +122 -0
- package/agent-card.json +161 -0
- package/assets/library/ATTRIBUTION.md +6 -0
- package/assets/library/README.md +20 -0
- package/assets/library/avatar-base.svg +19 -0
- package/assets/library/error-alert.json +1 -0
- package/assets/library/rive/ATTRIBUTION.md +12 -0
- package/assets/library/rive/state-machine-test.riv +0 -0
- package/assets/library/success-check.json +1 -0
- package/bin/motionloom.mjs +81 -0
- package/docs/BROWSER-REVIEW-E2E.md +78 -0
- package/docs/CATEGORIES.md +16 -0
- package/docs/CHECKLIST.md +31 -0
- package/docs/DEEP-AUDIT-WORKING-NOTES.md +22 -0
- package/docs/FRAMEWORK-SELECTION.md +26 -0
- package/docs/PROJECT-MANIFEST.md +37 -0
- package/docs/ROADMAP-INTELLIGENCE.md +224 -0
- package/docs/audits/1.10.0-attestation-research-notes.md +19 -0
- package/docs/audits/1.8.0-trust-boundary-hardening.md +56 -0
- package/docs/audits/1.9.0-evidence-interoperability-threat-model.md +37 -0
- package/docs/audits/2.0.0-attestation-acceptance.md +30 -0
- package/docs/releases/1.5.0.md +25 -0
- package/docs/releases/1.6.0.md +27 -0
- package/docs/releases/1.7.0.md +23 -0
- package/docs/releases/1.8.0.md +23 -0
- package/docs/releases/1.9.0.md +21 -0
- package/docs/releases/2.0.0.md +21 -0
- package/docs/releases/npm-publish-from-workstation.md +88 -0
- package/docs/research/AGENT-PROTOCOL-FINDINGS.md +43 -0
- package/examples/report-demo/REPORT.md +50 -0
- package/examples/report-demo/artifact-manifest.json +25 -0
- package/examples/report-demo/decision-log.jsonl +0 -0
- package/examples/report-demo/execution-report.json +70 -0
- package/examples/report-demo/handoff.json +22 -0
- package/examples/report-demo/issue-register.json +5 -0
- package/examples/report-demo/task.json +13 -0
- package/package.json +95 -0
- package/project-context.example.json +26 -0
- package/references/browser-review-contract.md +32 -0
- package/references/dotlottie-source-notes.md +21 -0
- package/references/intelligence-core.md +98 -0
- package/references/reporting-contract.md +38 -0
- package/references/runtime-capability.md +12 -0
- package/references/signed-attestation.md +31 -0
- package/schemas/artifact-manifest.schema.json +22 -0
- package/schemas/browser-review-candidate.schema.json +24 -0
- package/schemas/capability-registry.schema.json +61 -0
- package/schemas/continuity-report.schema.json +52 -0
- package/schemas/evidence-verifier-report.schema.json +35 -0
- package/schemas/execution-report.schema.json +35 -0
- package/schemas/fix-plan.schema.json +44 -0
- package/schemas/handoff.schema.json +19 -0
- package/schemas/motion-ir.schema.json +74 -0
- package/schemas/project-graph.schema.json +70 -0
- package/schemas/provenance.schema.json +76 -0
- package/schemas/runtime-evidence.schema.json +48 -0
- package/schemas/runtime-telemetry.schema.json +50 -0
- package/schemas/scene-manifest.schema.json +45 -0
- package/schemas/semantic-benchmark.schema.json +26 -0
- package/schemas/semantic-lint-report.schema.json +48 -0
- package/schemas/signed-attestation.schema.json +95 -0
- package/schemas/task.schema.json +39 -0
- package/schemas/trust-policy.schema.json +53 -0
- package/scripts/analyze.sh +13 -0
- package/scripts/attestation-keygen.py +63 -0
- package/scripts/attestation-verifier.py +178 -0
- package/scripts/attestation.py +288 -0
- package/scripts/capture-runtime-telemetry.sh +37 -0
- package/scripts/devlab.sh +77 -0
- package/scripts/eval-intelligence.py +377 -0
- package/scripts/evidence-verifier.py +222 -0
- package/scripts/fetch-library.sh +57 -0
- package/scripts/intelligence.py +1543 -0
- package/scripts/manifest.py +61 -0
- package/scripts/pr.sh +103 -0
- package/scripts/quality-gate.py +378 -0
- package/scripts/render-node.mjs +53 -0
- package/scripts/render.sh +37 -0
- package/scripts/report-contract.py +181 -0
- package/scripts/report.py +588 -0
- package/scripts/review-hook.py +199 -0
- package/scripts/runtime-adapters.mjs +187 -0
- package/scripts/skill-doctor.py +150 -0
- package/scripts/to-dotlottie.mjs +99 -0
- package/scripts/to-dotlottie.sh +25 -0
- package/scripts/validate-lottie.py +102 -0
- package/src/core/analyzer.py +226 -0
- package/src/core/snapshot.py +124 -0
- package/src/core/spec.py +240 -0
- package/src/output/browser-review-smoke/animation.json +57 -0
- package/src/output/browser-review-smoke/browser-review.json +21 -0
- package/src/output/browser-review-smoke/manifest.json +22 -0
- package/src/output/browser-review-smoke/motion-spec.json +28 -0
- package/src/output/browser-review-smoke/snapshot/.render-meta.json +10 -0
- package/src/output/browser-review-smoke/snapshot/frame-00.png +0 -0
- package/src/output/browser-review-smoke/snapshot/frame-100.png +0 -0
- package/src/output/browser-review-smoke/snapshot/frame-50.png +0 -0
- package/src/rig/README.md +35 -0
- package/src/rig/cutout_rig.py +211 -0
- package/templates/framer-motion/ui-micro.tsx +50 -0
- package/templates/gsap/scroll-scene.js +54 -0
- package/templates/lottie/README.md +21 -0
- package/templates/lottie/react-component.tsx +82 -0
- package/templates/lottie/scaffold/animation.json +57 -0
- package/templates/lottie/scaffold/character-rig.svg +19 -0
- package/templates/lottie/vanilla.js +68 -0
- package/templates/rive/README.md +36 -0
- package/tests/evals/intelligence-cases.json +131 -0
- package/tests/scripts/run_tests.py +843 -0
- package/tests/scripts/test_attestation.py +172 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ui-micro.tsx — Canonical Framer Motion micro-interaction component.
|
|
3
|
+
* Durations and easings come from motion-spec.json / the project manifest,
|
|
4
|
+
* never from inline magic numbers.
|
|
5
|
+
*/
|
|
6
|
+
import { motion, type MotionProps, useReducedMotion } from "framer-motion";
|
|
7
|
+
|
|
8
|
+
export interface MicroSpec {
|
|
9
|
+
duration: number; // seconds, from motion-spec.json
|
|
10
|
+
easing: string; // canonical easing name
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const EASE_MAP: Record<string, [number, number, number, number]> = {
|
|
14
|
+
"ease-in-out": [0.42, 0, 0.58, 1],
|
|
15
|
+
"ease-out": [0, 0, 0.58, 1],
|
|
16
|
+
"ease-in": [0.42, 0, 1, 1],
|
|
17
|
+
spring: [0.34, 1.56, 0.64, 1] as unknown as [number, number, number, number],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** Shared press-scale treatment for buttons/cards (the project-wide default). */
|
|
21
|
+
export const pressable: MotionProps = {
|
|
22
|
+
whileHover: { scale: 1.03, transition: { type: "spring", stiffness: 400, damping: 25 } },
|
|
23
|
+
whileTap: { scale: 0.97 },
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Wrap any child with the project's entrance motion. Use on cards, rows,
|
|
28
|
+
* and list items. The stagger offset is computed, not magic.
|
|
29
|
+
*/
|
|
30
|
+
export function entrance({ duration, easing }: MicroSpec, index = 0) {
|
|
31
|
+
return {
|
|
32
|
+
initial: { opacity: 0, y: 12 },
|
|
33
|
+
animate: { opacity: 1, y: 0 },
|
|
34
|
+
transition: {
|
|
35
|
+
duration,
|
|
36
|
+
ease: EASE_MAP[easing] ?? EASE_MAP["ease-in-out"],
|
|
37
|
+
delay: index * duration * 0.25,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Reduced-motion aware variant component. */
|
|
43
|
+
export function MotionSafe(props: MotionProps & { as?: keyof JSX.IntrinsicElements }) {
|
|
44
|
+
const reduce = useReducedMotion();
|
|
45
|
+
const { as = "div", ...rest } = props;
|
|
46
|
+
return motion[as]({
|
|
47
|
+
...rest,
|
|
48
|
+
transition: reduce ? { duration: 0 } : rest.transition,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scroll-scene.js — Canonical GSAP scroll-linked scene template.
|
|
3
|
+
* Uses ScrollTrigger with scrub; never attaches raw scroll listeners
|
|
4
|
+
* (ScrollTrigger already batches and rAF-schedules updates).
|
|
5
|
+
*
|
|
6
|
+
* Bind duration/easing to motion-spec.json; never hardcode magic numbers.
|
|
7
|
+
*/
|
|
8
|
+
import gsap from "gsap";
|
|
9
|
+
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
|
10
|
+
|
|
11
|
+
gsap.registerPlugin(ScrollTrigger);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} trigger CSS selector of the pin/trigger element
|
|
15
|
+
* @param {HTMLElement[]} targets elements to animate, in motion order
|
|
16
|
+
* @param {object} spec motion-spec.json values
|
|
17
|
+
*/
|
|
18
|
+
export function buildScrollScene(trigger, targets, spec) {
|
|
19
|
+
const tl = gsap.timeline({
|
|
20
|
+
scrollTrigger: {
|
|
21
|
+
trigger,
|
|
22
|
+
start: "top 80%",
|
|
23
|
+
end: "bottom 20%",
|
|
24
|
+
scrub: 0.6, // smooth scrub; 0 for hard lock
|
|
25
|
+
toggleActions: "play none none reverse",
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
targets.forEach((el, i) => {
|
|
30
|
+
tl.to(
|
|
31
|
+
el,
|
|
32
|
+
{
|
|
33
|
+
opacity: 1,
|
|
34
|
+
y: 0,
|
|
35
|
+
duration: 0.25,
|
|
36
|
+
ease: spec.easing ?? "power2.out",
|
|
37
|
+
},
|
|
38
|
+
i * 0.08, // stagger bound to scene, not magic
|
|
39
|
+
);
|
|
40
|
+
}, 0);
|
|
41
|
+
|
|
42
|
+
return tl;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Reduced-motion guard: collapse the whole timeline into a single fade.
|
|
47
|
+
* Call once at boot.
|
|
48
|
+
*/
|
|
49
|
+
export function honorReducedMotion(tl) {
|
|
50
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
51
|
+
tl.scrollTrigger.disable();
|
|
52
|
+
tl.progress(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Lottie / dotLottie Templates
|
|
2
|
+
|
|
3
|
+
Scaffold every Lottie scene from this directory. The workflow: copy `scaffold/` into `src/output/<scene>/`, replace `assets/svg-source.svg` with the authoritative vector art, run the generation prompt against the spec, then convert the resulting `animation.json` into `animation.lottie` (see `scripts/to-dotlottie.sh`).
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
| File | Purpose |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `scaffold/animation.json` | Minimal valid Bodymovin v5.12 document — the canonical shape every generated file must validate against |
|
|
10
|
+
| `scaffold/character-rig.svg` | Cutout avatar ready for `src/rig/cutout_rig.py build` |
|
|
11
|
+
| `player.html` | Standalone dotLottie player for quick local inspection (`open player.html`) |
|
|
12
|
+
| `react-component.tsx` | Production React component using `@lottiefiles/dotlottie-react` with worker offload and reduced-motion support |
|
|
13
|
+
| `vanilla.js` | Production vanilla-js bootstrap with `DotLottieWorker` and visibility freeze |
|
|
14
|
+
|
|
15
|
+
## Generation prompt recipe (for coding agents)
|
|
16
|
+
|
|
17
|
+
> Create a Lottie animation from the SVG path in `<source url>`. Reveal the path with an animation that follows the natural path direction. Apply a premium gradient bound to the project's primary/accent tokens. Use ease-in-out timing, a transparent background, preserve the original SVG geometry, 60 fps, total `<N>` frames. Expose the primary color as a dotLottie slot named `primary-color`.
|
|
18
|
+
|
|
19
|
+
## Validation
|
|
20
|
+
|
|
21
|
+
A generated file is accepted only if `scripts/validate-lottie.py` passes: version header, frame count vs. spec, layer count under budget, and slot names matching the brand spec.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* production-dotlottie.tsx — Canonical React component for a dotLottie scene.
|
|
3
|
+
* Copied into the host project per scene. Binds brand theme slots at runtime,
|
|
4
|
+
* offloads rendering to a Web Worker, honors prefers-reduced-motion, and
|
|
5
|
+
* exposes a uniform seek() so the Dev Lab snapshot harness can drive it.
|
|
6
|
+
*/
|
|
7
|
+
import { useEffect, useRef, useState } from "react";
|
|
8
|
+
import {
|
|
9
|
+
DotLottieReact,
|
|
10
|
+
type DotLottie,
|
|
11
|
+
} from "@lottiefiles/dotlottie-react";
|
|
12
|
+
|
|
13
|
+
interface SceneProps {
|
|
14
|
+
src: string; // /scenes/<name>/animation.lottie
|
|
15
|
+
ariaLabel?: string; // required for accessibility
|
|
16
|
+
className?: string;
|
|
17
|
+
/** Runtime brand overrides: { slotId: color|value } */
|
|
18
|
+
theme?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Uniform API consumed by the Dev Lab snapshot harness and tests. */
|
|
22
|
+
export interface LabHandle {
|
|
23
|
+
seek: (pct: number) => void; // 0-1
|
|
24
|
+
play: () => void;
|
|
25
|
+
pause: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
declare global {
|
|
29
|
+
interface Window {
|
|
30
|
+
__lab?: Record<string, LabHandle>;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default function DotLottieScene({
|
|
35
|
+
src,
|
|
36
|
+
ariaLabel = "Animated illustration",
|
|
37
|
+
className,
|
|
38
|
+
theme = {},
|
|
39
|
+
}: SceneProps) {
|
|
40
|
+
const ref = useRef<DotLottie | null>(null);
|
|
41
|
+
const [ready, setReady] = useState(false);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const d = ref.current;
|
|
45
|
+
if (!d) return;
|
|
46
|
+
const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
47
|
+
d.setLoop(!reduced); // looping decorations pause under reduced motion
|
|
48
|
+
// Apply brand theme slots (see project-context.json brand.primary)
|
|
49
|
+
const rules = Object.entries(theme).map(([id, value]) => ({ id, value }));
|
|
50
|
+
if (rules.length) d.setThemeData(JSON.stringify({ rules }));
|
|
51
|
+
// Register the uniform lab handle for snapshot/testing harnesses
|
|
52
|
+
window.__lab = window.__lab || {};
|
|
53
|
+
window.__lab[src] = {
|
|
54
|
+
seek: (pct) => {
|
|
55
|
+
if (!d.isLoaded) return;
|
|
56
|
+
d.setLoop(false);
|
|
57
|
+
d.setFrame(pct * (d.totalFrames - 1));
|
|
58
|
+
},
|
|
59
|
+
play: () => d.play(),
|
|
60
|
+
pause: () => d.pause(),
|
|
61
|
+
};
|
|
62
|
+
return () => {
|
|
63
|
+
delete window.__lab?.[src];
|
|
64
|
+
};
|
|
65
|
+
}, [src, theme]);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div role="img" aria-label={ariaLabel} className={className}>
|
|
69
|
+
{!ready && <span aria-hidden className="sr-only" />}
|
|
70
|
+
<DotLottieReact
|
|
71
|
+
src={src}
|
|
72
|
+
autoplay
|
|
73
|
+
loop
|
|
74
|
+
style={{ width: "100%", height: "100%" }}
|
|
75
|
+
dotLottieRefCallback={(dotLottie) => {
|
|
76
|
+
ref.current = dotLottie;
|
|
77
|
+
dotLottie.addEventListener("load", () => setReady(true));
|
|
78
|
+
}}
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"v": "5.12.0",
|
|
3
|
+
"fr": 60,
|
|
4
|
+
"ip": 0,
|
|
5
|
+
"op": 60,
|
|
6
|
+
"w": 512,
|
|
7
|
+
"h": 512,
|
|
8
|
+
"nm": "generated-scene",
|
|
9
|
+
"ddd": 0,
|
|
10
|
+
"assets": [],
|
|
11
|
+
"layers": [
|
|
12
|
+
{
|
|
13
|
+
"ddd": 0,
|
|
14
|
+
"ind": 1,
|
|
15
|
+
"ty": 4,
|
|
16
|
+
"nm": "shape-layer",
|
|
17
|
+
"sr": 1,
|
|
18
|
+
"ks": {
|
|
19
|
+
"o": { "a": 0, "k": 100 },
|
|
20
|
+
"r": { "a": 0, "k": 0 },
|
|
21
|
+
"p": { "a": 0, "k": [256, 256, 0] },
|
|
22
|
+
"a": { "a": 0, "k": [0, 0, 0] },
|
|
23
|
+
"s": {
|
|
24
|
+
"a": 1,
|
|
25
|
+
"k": [
|
|
26
|
+
{ "t": 0, "s": [80, 80, 100], "i": { "x": [0.4], "y": [1] }, "o": { "x": [0.6], "y": [0] } },
|
|
27
|
+
{ "t": 30, "s": [105, 105, 100], "i": { "x": [0.4], "y": [1] }, "o": { "x": [0.6], "y": [0] } },
|
|
28
|
+
{ "t": 60, "s": [100, 100, 100] }
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"ao": 0,
|
|
33
|
+
"shapes": [
|
|
34
|
+
{
|
|
35
|
+
"ty": "gr",
|
|
36
|
+
"it": [
|
|
37
|
+
{ "ty": "el", "p": { "a": 0, "k": [0, 0] }, "s": { "a": 0, "k": [120, 120] } },
|
|
38
|
+
{
|
|
39
|
+
"ty": "fl",
|
|
40
|
+
"c": { "a": 0, "k": [0.145, 0.388, 0.922, 1] },
|
|
41
|
+
"o": { "a": 0, "k": 100 },
|
|
42
|
+
"r": 1
|
|
43
|
+
},
|
|
44
|
+
{ "ty": "tr", "p": { "a": 0, "k": [0, 0] }, "a": { "a": 0, "k": [0, 0] }, "s": { "a": 0, "k": [100, 100] }, "r": { "a": 0, "k": 0 }, "o": { "a": 0, "k": 100 } }
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"ip": 0,
|
|
49
|
+
"op": 60,
|
|
50
|
+
"st": 0
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
"markers": [
|
|
54
|
+
{ "cm": "loop-start", "tm": 0, "dr": 30 },
|
|
55
|
+
{ "cm": "loop-end", "tm": 30, "dr": 30 }
|
|
56
|
+
]
|
|
57
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 450">
|
|
2
|
+
<!-- Authoritative base art. Replace shapes below with licensed/owned character art.
|
|
3
|
+
Part names MUST match the bone list in src/rig/cutout_rig.py:
|
|
4
|
+
head, chest, l/r_upper_arm, l/r_forearm, l/r_thigh, l/r_shin, l/r_foot -->
|
|
5
|
+
<g id="parts">
|
|
6
|
+
<circle data-part="head" cx="250" cy="90" r="42" fill="#2d2d3a"/>
|
|
7
|
+
<rect data-part="chest" x="208" y="140" width="84" height="96" rx="22" fill="#2d2d3a"/>
|
|
8
|
+
<rect data-part="l_upper_arm" x="178" y="148" width="26" height="78" rx="13" fill="#45455a"/>
|
|
9
|
+
<rect data-part="r_upper_arm" x="296" y="148" width="26" height="78" rx="13" fill="#45455a"/>
|
|
10
|
+
<rect data-part="l_forearm" x="176" y="228" width="22" height="70" rx="11" fill="#45455a"/>
|
|
11
|
+
<rect data-part="r_forearm" x="302" y="228" width="22" height="70" rx="11" fill="#45455a"/>
|
|
12
|
+
<rect data-part="l_thigh" x="216" y="238" width="30" height="84" rx="15" fill="#37374a"/>
|
|
13
|
+
<rect data-part="r_thigh" x="254" y="238" width="30" height="84" rx="15" fill="#37374a"/>
|
|
14
|
+
<rect data-part="l_shin" x="218" y="324" width="26" height="80" rx="13" fill="#37374a"/>
|
|
15
|
+
<rect data-part="r_shin" x="256" y="324" width="26" height="80" rx="13" fill="#37374a"/>
|
|
16
|
+
<ellipse data-part="l_foot" cx="231" cy="408" rx="24" ry="10" fill="#1f1f2e"/>
|
|
17
|
+
<ellipse data-part="r_foot" cx="269" cy="408" rx="24" ry="10" fill="#1f1f2e"/>
|
|
18
|
+
</g>
|
|
19
|
+
</svg>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* production-vanilla.js — Canonical vanilla-JS bootstrap for a dotLottie scene.
|
|
3
|
+
* Uses DotLottieWorker to keep the main thread free, freezes when offscreen,
|
|
4
|
+
* honors prefers-reduced-motion, and registers the uniform window.__lab handle.
|
|
5
|
+
*/
|
|
6
|
+
import { DotLottieWorker } from "@lottiefiles/dotlottie-web";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {HTMLCanvasElement} canvas
|
|
10
|
+
* @param {string} src
|
|
11
|
+
* @param {object} [opts]
|
|
12
|
+
* @param {Record<string, unknown>} [opts.theme] brand slot overrides
|
|
13
|
+
* @param {string} [opts.ariaLabel]
|
|
14
|
+
*/
|
|
15
|
+
export function mountLottieScene(canvas, src, opts = {}) {
|
|
16
|
+
const dotLottie = new DotLottieWorker({
|
|
17
|
+
canvas,
|
|
18
|
+
src,
|
|
19
|
+
autoplay: true,
|
|
20
|
+
loop: true,
|
|
21
|
+
renderConfig: { autoResize: true },
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
25
|
+
|
|
26
|
+
dotLottie.addEventListener("load", () => {
|
|
27
|
+
if (reduced) dotLottie.setLoop(false);
|
|
28
|
+
if (opts.theme) {
|
|
29
|
+
const rules = Object.entries(opts.theme).map(([id, value]) => ({ id, value }));
|
|
30
|
+
dotLottie.setThemeData(JSON.stringify({ rules }));
|
|
31
|
+
}
|
|
32
|
+
// Uniform lab handle for the Dev Lab snapshot harness
|
|
33
|
+
window.__lab = window.__lab || {};
|
|
34
|
+
window.__lab[src] = {
|
|
35
|
+
seek: (pct) => {
|
|
36
|
+
dotLottie.setLoop(false);
|
|
37
|
+
dotLottie.setFrame(pct * (dotLottie.totalFrames - 1));
|
|
38
|
+
},
|
|
39
|
+
play: () => dotLottie.play(),
|
|
40
|
+
pause: () => dotLottie.pause(),
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Interactive hooks: hover plays, click restarts
|
|
45
|
+
canvas.addEventListener("mouseenter", () => dotLottie.play());
|
|
46
|
+
canvas.addEventListener("mouseleave", () => dotLottie.pause());
|
|
47
|
+
canvas.addEventListener("click", () => {
|
|
48
|
+
dotLottie.setFrame(0);
|
|
49
|
+
dotLottie.setLoop(false);
|
|
50
|
+
dotLottie.play();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Cleanup on removal
|
|
54
|
+
const observer = new MutationObserver((records) => {
|
|
55
|
+
for (const r of records) {
|
|
56
|
+
for (const node of r.removedNodes) {
|
|
57
|
+
if (node === canvas) {
|
|
58
|
+
dotLottie.destroy();
|
|
59
|
+
observer.disconnect();
|
|
60
|
+
delete window.__lab?.[src];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
observer.observe(canvas.parentElement ?? document.body, { childList: true });
|
|
66
|
+
|
|
67
|
+
return dotLottie;
|
|
68
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Rive Templates
|
|
2
|
+
|
|
3
|
+
Rive scenes are editor-authored, so this template set covers the runtime side: the React component with state-machine inputs bound to project tokens, and the rules for naming inputs so the analyzer can drive them.
|
|
4
|
+
|
|
5
|
+
## Runtime component pattern
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { useRive, StateMachineInput, InputType } from "@rive-app/react-canvas";
|
|
9
|
+
|
|
10
|
+
export default function RiveScene({ src, theme }: { src: string; theme?: Record<string, unknown> }) {
|
|
11
|
+
const { RiveComponent, rive } = useRive({
|
|
12
|
+
src,
|
|
13
|
+
stateMachines: "Main",
|
|
14
|
+
autoplay: true,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!rive) return;
|
|
19
|
+
// Inputs follow the canonical naming: <token>_<slot> (e.g. primary_color)
|
|
20
|
+
for (const [name, value] of Object.entries(theme ?? {})) {
|
|
21
|
+
const input = rive.stateMachineInputs("Main")?.find((i) => i.name === name);
|
|
22
|
+
if (input?.type === InputType.Number) input.value = Number(value);
|
|
23
|
+
}
|
|
24
|
+
}, [rive, theme]);
|
|
25
|
+
|
|
26
|
+
return <RiveComponent style={{ width: "100%", height: "100%" }} />;
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Rules for editor authors
|
|
31
|
+
|
|
32
|
+
Name every color-controlling input `<token>_<slot>` so the analyzer's brand tokens map automatically. Keep the state machine flat (≤6 states) for UI assets. Export at 60 fps from the Rive editor; keep the .riv under 300 KB for UI scenes.
|
|
33
|
+
|
|
34
|
+
## License note
|
|
35
|
+
|
|
36
|
+
Rive runtimes are Apache-2.0 and free to bundle. The Rive editor is subject to Rive's own terms — record this in the scene manifest `license_note`.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "0.1",
|
|
3
|
+
"suite": "motionloom-intelligence-core",
|
|
4
|
+
"cases": [
|
|
5
|
+
{
|
|
6
|
+
"id": "verified-runtime-selection",
|
|
7
|
+
"class": "positive",
|
|
8
|
+
"assertion": "A verified runtime with fresh matching evidence can be selected."
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "scaffold-runtime-blocked",
|
|
12
|
+
"class": "negative",
|
|
13
|
+
"assertion": "A scaffold-only runtime cannot be selected as production verified."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "stale-capability-evidence",
|
|
17
|
+
"class": "negative",
|
|
18
|
+
"assertion": "A verified capability whose evidence is older than policy is rejected."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "tampered-capability-evidence",
|
|
22
|
+
"class": "negative",
|
|
23
|
+
"assertion": "A capability with a mismatched evidence hash is rejected."
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "graph-edge-corruption",
|
|
27
|
+
"class": "negative",
|
|
28
|
+
"assertion": "A graph edge referencing an unknown node is rejected."
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "replay-artifact-tamper",
|
|
32
|
+
"class": "negative",
|
|
33
|
+
"assertion": "A replay bundle fails after a bound artifact is changed."
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "foreign-task-candidate",
|
|
37
|
+
"class": "negative",
|
|
38
|
+
"assertion": "A browser-review candidate belonging to another task is rejected."
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"id": "p1-human-review-warning-preserved",
|
|
42
|
+
"class": "positive",
|
|
43
|
+
"assertion": "A human-review semantic warning remains visible and is not converted into approval."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "p1-generic-intent-warning",
|
|
47
|
+
"class": "negative",
|
|
48
|
+
"assertion": "A generic Motion IR intent produces a deterministic low-specificity warning."
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "p1-continuity-context-drift",
|
|
52
|
+
"class": "negative",
|
|
53
|
+
"assertion": "A context hash change between adjacent scenes is reported as continuity drift."
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"id": "p1-fix-plan-selective-rerun",
|
|
57
|
+
"class": "positive",
|
|
58
|
+
"assertion": "A fix plan is handoff-bound and contains a selective rerun scope for its findings."
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "p1-perf-duration-budget-warning",
|
|
62
|
+
"class": "negative",
|
|
63
|
+
"assertion": "A UI-context animation over 500 ms produces a non-blocking performance budget warning."
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "p1-perf-fps-warning",
|
|
67
|
+
"class": "negative",
|
|
68
|
+
"assertion": "A Motion IR below 30 FPS produces a non-blocking frame-rate warning."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": "p1-perceptual-easing-linear-warning",
|
|
72
|
+
"class": "negative",
|
|
73
|
+
"assertion": "Linear easing in a UI-context track produces a non-blocking perceptual warning."
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "p1-perceptual-reduced-motion-warning",
|
|
77
|
+
"class": "negative",
|
|
78
|
+
"assertion": "An explicit none reduced-motion policy produces a non-blocking accessibility risk warning."
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": "p1-benchmark-execution-time",
|
|
82
|
+
"class": "positive",
|
|
83
|
+
"assertion": "The semantic lint benchmark records rule coverage and passes when p95 execution remains below threshold."
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"id": "p2-runtime-verifier-clean",
|
|
87
|
+
"class": "positive",
|
|
88
|
+
"assertion": "The external verifier accepts internally consistent runtime telemetry while keeping approval false."
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"id": "p2-runtime-verifier-tamper",
|
|
92
|
+
"class": "negative",
|
|
93
|
+
"assertion": "A changed telemetry file is rejected when its bound SHA-256 no longer matches."
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"id": "p2-runtime-verifier-cross-task",
|
|
97
|
+
"class": "negative",
|
|
98
|
+
"assertion": "Telemetry bound to a different task identity is rejected."
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"id": "p2-runtime-verifier-symlink",
|
|
102
|
+
"class": "negative",
|
|
103
|
+
"assertion": "A telemetry path traversing a symlink is rejected before evidence is trusted."
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"id": "p2-attestation-clean",
|
|
107
|
+
"class": "positive",
|
|
108
|
+
"assertion": "A DSSE-compatible attestation signed by an active trusted Ed25519 key verifies without granting approval."
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"id": "p2-attestation-payload-tamper",
|
|
112
|
+
"class": "negative",
|
|
113
|
+
"assertion": "A signed attestation whose payload bytes are changed fails with the stable payload mismatch exit code."
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"id": "p2-attestation-binding-mismatch",
|
|
117
|
+
"class": "negative",
|
|
118
|
+
"assertion": "An attestation presented for a different expected task identity is rejected before trust is granted."
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"id": "p2-attestation-revoked-signer",
|
|
122
|
+
"class": "negative",
|
|
123
|
+
"assertion": "A mathematically valid signature from a revoked signer is rejected by fail-closed trust policy."
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"id": "p2-attestation-unknown-signer",
|
|
127
|
+
"class": "negative",
|
|
128
|
+
"assertion": "A signature whose key id is absent from the configured trust policy is rejected."
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
}
|