motionloom 2.5.0 → 2.6.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/AGENTS.md +2 -0
- package/CHANGELOG.md +52 -1
- package/README.md +20 -8
- package/SECURITY.md +6 -7
- package/SKILL.md +5 -3
- package/agent-card.json +4 -4
- package/agent-surfaces.json +1 -1
- package/bin/motionloom.mjs +5 -4
- package/capability-registry.json +26 -26
- package/dev-lab/public/action-library.js +899 -0
- package/dev-lab/public/devlab.js +722 -0
- package/dev-lab/public/index.html +163 -0
- package/dev-lab/public/runtime-bridge.js +109 -0
- package/docs/CHECKLIST.md +15 -2
- package/docs/DEV-LAB-RUNTIME.md +160 -0
- package/docs/DEV-LAB-STATE-TRANSITIONS.md +121 -0
- package/docs/STATUS.md +3 -3
- package/docs/releases/2.5.1.md +21 -0
- package/docs/releases/2.6.0.md +38 -0
- package/docs/releases/npm-publish-from-workstation.md +6 -6
- package/package.json +12 -4
- package/requirements.txt +1 -0
- package/rig-adapter-registry.json +3 -3
- package/schemas/browser-review-candidate.schema.json +37 -0
- package/schemas/devlab-runtime.schema.json +140 -0
- package/schemas/devlab-state-machine.schema.json +103 -0
- package/scripts/capture-runtime-telemetry.py +16 -8
- package/scripts/devlab.py +3 -10
- package/scripts/docs-audit.py +13 -1
- package/scripts/package-consumer-smoke.mjs +76 -0
- package/scripts/review-hook.py +255 -19
- package/scripts/runtime-adapters.mjs +28 -3
- package/scripts/skill-doctor.py +27 -1
- package/tests/runtime-harness/index.html +24 -0
- package/tests/runtime-harness/main.jsx +111 -0
- package/tests/scripts/run_tests.py +41 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self'; frame-src 'self'; object-src 'none'; base-uri 'none'; form-action 'none'" />
|
|
7
|
+
<title>MotionLoom Dev Lab</title>
|
|
8
|
+
<style>
|
|
9
|
+
:root { color-scheme:dark; --bg:#0d0f14; --panel:#151821; --panel2:#1b1f2a; --line:#303646; --line2:#41485b; --text:#eef1f6; --muted:#929aaa; --amber:#f5a524; --good:#52d49b; --bad:#ff7070; --blue:#77a9ff; --stage:#12151c; }
|
|
10
|
+
* { box-sizing:border-box; }
|
|
11
|
+
html,body { margin:0; min-height:100%; background:var(--bg); color:var(--text); font:14px/1.45 ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; }
|
|
12
|
+
button,input,select,textarea { font:inherit; }
|
|
13
|
+
button { border:1px solid var(--line2); border-radius:7px; padding:8px 11px; color:var(--text); background:#242936; cursor:pointer; }
|
|
14
|
+
button:hover:not(:disabled) { border-color:#616a80; background:#2b3140; }
|
|
15
|
+
button:active:not(:disabled) { transform:translateY(1px); }
|
|
16
|
+
button:disabled { cursor:not-allowed; opacity:.38; }
|
|
17
|
+
button.primary { color:#1b1305; background:var(--amber); border-color:var(--amber); font-weight:800; }
|
|
18
|
+
button.danger { color:#ffe9e9; border-color:#6f363b; background:#382126; }
|
|
19
|
+
button.active { border-color:var(--amber); box-shadow:0 0 0 1px var(--amber) inset; }
|
|
20
|
+
header { height:58px; display:flex; align-items:center; justify-content:space-between; padding:0 18px; border-bottom:1px solid var(--line); background:#12151c; gap:14px; }
|
|
21
|
+
.brand { display:flex; align-items:center; gap:12px; min-width:0; }
|
|
22
|
+
.brand strong { letter-spacing:.12em; font-size:12px; text-transform:uppercase; white-space:nowrap; }
|
|
23
|
+
.mode-badge { padding:4px 8px; border:1px solid var(--line2); border-radius:999px; color:var(--muted); font:11px ui-monospace,monospace; white-space:nowrap; }
|
|
24
|
+
.mode-badge.live { color:var(--good); border-color:#286b50; background:#10291f; }
|
|
25
|
+
.mode-badge.fallback { color:var(--amber); border-color:#6d5423; background:#2b2415; }
|
|
26
|
+
.header-right { display:flex; align-items:center; gap:10px; min-width:0; }
|
|
27
|
+
#timecode { color:var(--amber); font:12px ui-monospace,monospace; white-space:nowrap; }
|
|
28
|
+
.app { display:grid; grid-template-columns:220px minmax(0,1fr) 330px; height:calc(100vh - 58px); min-height:600px; }
|
|
29
|
+
nav,aside { min-width:0; background:var(--panel); overflow:auto; }
|
|
30
|
+
nav { border-right:1px solid var(--line); padding:16px 12px; }
|
|
31
|
+
aside { border-left:1px solid var(--line); padding:18px; }
|
|
32
|
+
.eyebrow,h2 { margin:0 0 10px; color:var(--muted); font:700 11px/1.3 ui-monospace,monospace; letter-spacing:.11em; text-transform:uppercase; }
|
|
33
|
+
#animations { display:flex; flex-direction:column; gap:6px; }
|
|
34
|
+
.animation-btn { width:100%; display:grid; grid-template-columns:10px 1fr auto; align-items:center; gap:8px; text-align:left; padding:9px 9px; border-color:transparent; background:transparent; }
|
|
35
|
+
.animation-btn:hover { background:#202431; }
|
|
36
|
+
.animation-btn.selected { border-color:#655224; background:#2c2519; }
|
|
37
|
+
.animation-dot { width:7px; height:7px; border-radius:50%; background:#596173; }
|
|
38
|
+
.animation-btn.visited .animation-dot { background:var(--good); }
|
|
39
|
+
.animation-name { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
40
|
+
.animation-meta { color:var(--muted); font:10px ui-monospace,monospace; }
|
|
41
|
+
.workbench { min-width:0; min-height:0; display:flex; flex-direction:column; padding:14px; gap:12px; }
|
|
42
|
+
.stage-shell { min-height:0; flex:1; border:1px solid var(--line); border-radius:10px; overflow:hidden; background:#0b0d12; position:relative; }
|
|
43
|
+
.stage { width:100%; height:100%; min-height:380px; display:flex; align-items:center; justify-content:center; position:relative; overflow:hidden; background-color:var(--stage); }
|
|
44
|
+
.stage.bg-checker { background-color:#161922; background-image:linear-gradient(45deg,#20242f 25%,transparent 25%),linear-gradient(-45deg,#20242f 25%,transparent 25%),linear-gradient(45deg,transparent 75%,#20242f 75%),linear-gradient(-45deg,transparent 75%,#20242f 75%); background-size:28px 28px; background-position:0 0,0 14px,14px -14px,-14px 0; }
|
|
45
|
+
.stage.bg-dark { background:#11141b; }
|
|
46
|
+
.stage.bg-light { background:#e7e8ec; }
|
|
47
|
+
.stage.bg-transparent { background:transparent; }
|
|
48
|
+
.stage.bg-project { background:radial-gradient(circle at 50% 40%,#202638 0,#11141b 58%,#0b0d12 100%); }
|
|
49
|
+
.stage-content { position:relative; width:100%; height:100%; min-height:380px; display:flex; align-items:center; justify-content:center; transform-origin:50% 50%; }
|
|
50
|
+
#frame { max-width:92%; max-height:calc(100% - 30px); object-fit:contain; user-select:none; -webkit-user-drag:none; }
|
|
51
|
+
#frame.pixelated { image-rendering:pixelated; image-rendering:crisp-edges; }
|
|
52
|
+
#runtime-frame { width:100%; height:100%; min-height:380px; border:0; background:transparent; }
|
|
53
|
+
.stage-overlay { pointer-events:none; position:absolute; inset:0; }
|
|
54
|
+
.overlay-grid { display:none; background-image:linear-gradient(#ffffff10 1px,transparent 1px),linear-gradient(90deg,#ffffff10 1px,transparent 1px); background-size:24px 24px; }
|
|
55
|
+
.overlay-grid.on { display:block; }
|
|
56
|
+
.overlay-bounds { display:none; inset:8%; border:1px dashed #78a8ff99; }
|
|
57
|
+
.overlay-bounds.on { display:block; }
|
|
58
|
+
.overlay-baseline { display:none; left:8%; right:8%; height:1px; background:#ffb12eaa; top:75%; box-shadow:0 0 0 1px #0004; }
|
|
59
|
+
.overlay-baseline.on { display:block; }
|
|
60
|
+
.overlay-pivot { display:none; width:14px; height:14px; left:50%; top:75%; transform:translate(-50%,-50%); border:1px solid #ff7676; border-radius:50%; }
|
|
61
|
+
.overlay-pivot::before,.overlay-pivot::after { content:""; position:absolute; background:#ff7676; }
|
|
62
|
+
.overlay-pivot::before { width:20px; height:1px; left:-4px; top:6px; }
|
|
63
|
+
.overlay-pivot::after { width:1px; height:20px; left:6px; top:-4px; }
|
|
64
|
+
.overlay-pivot.on { display:block; }
|
|
65
|
+
.stage-top { position:absolute; top:10px; left:12px; right:12px; z-index:4; display:flex; align-items:flex-start; justify-content:space-between; gap:10px; pointer-events:none; }
|
|
66
|
+
.stage-label,.stage-mode { padding:5px 8px; background:#0b0e14cc; border:1px solid #303646cc; border-radius:6px; font:10px ui-monospace,monospace; letter-spacing:.07em; }
|
|
67
|
+
.stage-label { color:var(--muted); }
|
|
68
|
+
.stage-mode { color:var(--amber); text-align:right; }
|
|
69
|
+
#empty { max-width:520px; color:var(--muted); text-align:center; padding:24px; }
|
|
70
|
+
.runtime-error { position:absolute; left:14px; bottom:14px; right:14px; z-index:5; display:none; padding:10px 12px; border:1px solid #774047; background:#301c21e8; color:#ffd5d5; border-radius:7px; font:12px ui-monospace,monospace; }
|
|
71
|
+
.runtime-error.on { display:block; }
|
|
72
|
+
.transport { border:1px solid var(--line); border-radius:10px; background:var(--panel); padding:10px 12px; }
|
|
73
|
+
.transport-row { display:flex; align-items:center; gap:7px; flex-wrap:wrap; }
|
|
74
|
+
.transport-row .spacer { flex:1; }
|
|
75
|
+
.speed-select,.bg-select { color:var(--text); background:#242936; border:1px solid var(--line2); border-radius:7px; padding:7px 9px; }
|
|
76
|
+
.timeline { margin-top:10px; }
|
|
77
|
+
input[type=range] { width:100%; accent-color:var(--amber); }
|
|
78
|
+
.timeline-meta { display:flex; justify-content:space-between; gap:12px; color:var(--muted); font:11px ui-monospace,monospace; }
|
|
79
|
+
.tool-row { display:flex; gap:7px; flex-wrap:wrap; margin-top:9px; padding-top:9px; border-top:1px solid #282d39; }
|
|
80
|
+
.tool-row button,.tool-row select { font-size:12px; padding:6px 8px; }
|
|
81
|
+
.inspector-title { margin:0; font-size:22px; line-height:1.2; }
|
|
82
|
+
#description { margin:6px 0 0; color:var(--muted); }
|
|
83
|
+
.meta-grid { display:grid; grid-template-columns:1fr 1fr; gap:7px; margin-top:14px; }
|
|
84
|
+
.pill { border:1px solid var(--line); border-radius:6px; background:#11141b; padding:8px; min-width:0; }
|
|
85
|
+
.pill b { display:block; color:var(--muted); font:10px ui-monospace,monospace; letter-spacing:.06em; }
|
|
86
|
+
.pill span { display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; margin-top:2px; }
|
|
87
|
+
.runtime-state { margin:0; max-height:170px; overflow:auto; padding:10px; border:1px solid var(--line); border-radius:6px; background:#0f1117; color:#b9c0ce; white-space:pre-wrap; font:11px/1.4 ui-monospace,monospace; }
|
|
88
|
+
.check { display:flex; gap:9px; padding:9px 0; border-bottom:1px solid #272c37; }
|
|
89
|
+
.check input { accent-color:var(--good); margin-top:3px; }
|
|
90
|
+
.check small { color:var(--muted); display:block; margin-top:2px; }
|
|
91
|
+
textarea { width:100%; min-height:90px; resize:vertical; color:var(--text); background:#0f1117; border:1px solid var(--line); border-radius:6px; padding:9px; }
|
|
92
|
+
.review-actions { display:flex; gap:7px; flex-wrap:wrap; margin-top:10px; }
|
|
93
|
+
#status { margin-top:10px; font:11px/1.4 ui-monospace,monospace; }
|
|
94
|
+
.ok { color:var(--good); }.warn { color:var(--bad); }.info { color:var(--blue); }
|
|
95
|
+
.progress-note { margin-top:9px; color:var(--muted); font:11px ui-monospace,monospace; }
|
|
96
|
+
@media (max-width:1050px) { .app { grid-template-columns:180px minmax(0,1fr) 285px; } }
|
|
97
|
+
@media (max-width:820px) { header { height:auto; min-height:58px; flex-wrap:wrap; padding:10px 12px; } .app { height:auto; min-height:0; grid-template-columns:1fr; } nav { border-right:0; border-bottom:1px solid var(--line); } #animations { flex-direction:row; overflow:auto; } .animation-btn { width:auto; min-width:130px; } aside { border-left:0; border-top:1px solid var(--line); } .workbench { min-height:620px; } }
|
|
98
|
+
</style>
|
|
99
|
+
</head>
|
|
100
|
+
<body>
|
|
101
|
+
<header>
|
|
102
|
+
<div class="brand"><strong>MotionLoom Dev Lab</strong><span class="mode-badge" id="mode-badge">LOADING</span></div>
|
|
103
|
+
<div class="header-right"><span id="timecode">00:00.000 / 00:00.000</span><button id="fullscreen" type="button">Fullscreen</button></div>
|
|
104
|
+
</header>
|
|
105
|
+
<div class="app">
|
|
106
|
+
<nav aria-label="Animation actions">
|
|
107
|
+
<div class="eyebrow">Animations</div>
|
|
108
|
+
<div id="animations"><span id="animation-empty" class="progress-note">Loading actions…</span></div>
|
|
109
|
+
<div id="review-progress" class="progress-note"></div>
|
|
110
|
+
</nav>
|
|
111
|
+
|
|
112
|
+
<main class="workbench">
|
|
113
|
+
<div class="stage-shell" id="stage-shell">
|
|
114
|
+
<div class="stage bg-checker" id="stage" data-snapshot>
|
|
115
|
+
<div class="stage-top"><div class="stage-label" id="stage-label">LOADING SCENE</div><div class="stage-mode" id="stage-mode"></div></div>
|
|
116
|
+
<div class="stage-content" id="stage-content">
|
|
117
|
+
<div class="empty" id="empty">Loading exact candidate evidence…</div>
|
|
118
|
+
<img id="frame" alt="Animation frame" hidden />
|
|
119
|
+
<iframe id="runtime-frame" title="Live animation runtime" hidden></iframe>
|
|
120
|
+
</div>
|
|
121
|
+
<div class="stage-overlay overlay-grid" id="overlay-grid"></div>
|
|
122
|
+
<div class="stage-overlay overlay-bounds" id="overlay-bounds"></div>
|
|
123
|
+
<div class="stage-overlay overlay-baseline" id="overlay-baseline"></div>
|
|
124
|
+
<div class="stage-overlay overlay-pivot" id="overlay-pivot"></div>
|
|
125
|
+
<div class="runtime-error" id="runtime-error"></div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<section class="transport" aria-label="Animation playback controls">
|
|
130
|
+
<div class="transport-row">
|
|
131
|
+
<button id="step-back" type="button" title="Previous frame">−1f</button>
|
|
132
|
+
<button id="play" type="button">Play</button>
|
|
133
|
+
<button id="pause" type="button">Pause</button>
|
|
134
|
+
<button id="restart" type="button">Restart</button>
|
|
135
|
+
<button id="step-forward" type="button" title="Next frame">+1f</button>
|
|
136
|
+
<button id="loop" type="button">Loop</button>
|
|
137
|
+
<select id="speed" class="speed-select" aria-label="Playback speed"><option value="0.25">0.25×</option><option value="0.5">0.5×</option><option value="1" selected>1×</option><option value="2">2×</option></select>
|
|
138
|
+
<div class="spacer"></div>
|
|
139
|
+
<span id="framecode" class="animation-meta">Frame —</span>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="timeline"><input id="scrubber" type="range" min="0" max="1000" value="0" aria-label="Animation progress" /><div class="timeline-meta"><span>0%</span><span id="progress-label">0.0%</span><span>100%</span></div></div>
|
|
142
|
+
<div class="tool-row">
|
|
143
|
+
<select id="background" class="bg-select" aria-label="Stage background"><option value="checker">Checker</option><option value="dark">Dark</option><option value="light">Light</option><option value="transparent">Transparent</option><option value="project">Project</option></select>
|
|
144
|
+
<button id="grid" type="button">Grid</button><button id="bounds" type="button">Bounds</button><button id="baseline" type="button">Baseline</button><button id="pivot" type="button">Pivot</button>
|
|
145
|
+
<button id="zoom-out" type="button">−</button><button id="zoom-reset" type="button">Fit</button><button id="zoom-in" type="button">+</button><span id="zoom-label" class="animation-meta">100%</span>
|
|
146
|
+
</div>
|
|
147
|
+
</section>
|
|
148
|
+
</main>
|
|
149
|
+
|
|
150
|
+
<aside>
|
|
151
|
+
<h1 class="inspector-title" id="title">Scene</h1><p id="description">Loading manifest and candidate binding.</p>
|
|
152
|
+
<div class="meta-grid" id="meta"></div>
|
|
153
|
+
<h2>Runtime inspector</h2><pre class="runtime-state" id="runtime-state">Waiting for runtime…</pre>
|
|
154
|
+
<h2>Quality checklist</h2><div id="checks"></div>
|
|
155
|
+
<h2>Fix notes</h2><textarea id="notes" placeholder="Describe a precise fix request, timing issue, visual defect, or confirmation…"></textarea>
|
|
156
|
+
<div class="review-actions"><button class="danger" id="request-changes" type="button">Request changes</button><button class="primary" id="confirm" type="button">Approve candidate</button><button id="reset" type="button">Reset review</button></div>
|
|
157
|
+
<div id="status"></div>
|
|
158
|
+
</aside>
|
|
159
|
+
</div>
|
|
160
|
+
<script src="/devlab.js" defer></script>
|
|
161
|
+
<script src="/action-library.js" defer></script>
|
|
162
|
+
</body>
|
|
163
|
+
</html>
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
const REQUEST_SOURCE = "motionloom-devlab";
|
|
3
|
+
const RESPONSE_SOURCE = "motionloom-runtime";
|
|
4
|
+
let adapter = null;
|
|
5
|
+
|
|
6
|
+
function asAnimations(value) {
|
|
7
|
+
return Array.isArray(value) ? value.map((item) => {
|
|
8
|
+
if (typeof item === "string") return { id: item, label: item };
|
|
9
|
+
return item && typeof item === "object" ? item : null;
|
|
10
|
+
}).filter(Boolean) : [];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function invoke(command, payload = {}) {
|
|
14
|
+
if (!adapter) throw new Error("MotionLoom runtime adapter is not attached");
|
|
15
|
+
if (command === "handshake") {
|
|
16
|
+
const animations = typeof adapter.listAnimations === "function"
|
|
17
|
+
? asAnimations(await adapter.listAnimations())
|
|
18
|
+
: asAnimations(adapter.animations);
|
|
19
|
+
return {
|
|
20
|
+
ready: adapter.ready !== false,
|
|
21
|
+
runtime: adapter.runtime || "unknown",
|
|
22
|
+
framework: adapter.framework || "unknown",
|
|
23
|
+
animations,
|
|
24
|
+
capabilities: {
|
|
25
|
+
selectAnimation: typeof adapter.selectAnimation === "function",
|
|
26
|
+
transition: typeof adapter.triggerTransition === "function",
|
|
27
|
+
play: typeof adapter.play === "function",
|
|
28
|
+
pause: typeof adapter.pause === "function",
|
|
29
|
+
restart: typeof adapter.restart === "function" || typeof adapter.seek === "function" || typeof adapter.setProgress === "function",
|
|
30
|
+
seek: typeof adapter.seek === "function" || typeof adapter.setProgress === "function",
|
|
31
|
+
step: typeof adapter.stepFrames === "function",
|
|
32
|
+
speed: typeof adapter.setSpeed === "function",
|
|
33
|
+
loop: typeof adapter.setLoop === "function",
|
|
34
|
+
state: typeof adapter.getState === "function"
|
|
35
|
+
},
|
|
36
|
+
state: typeof adapter.getState === "function" ? await adapter.getState() : {}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (command === "listAnimations") {
|
|
40
|
+
return typeof adapter.listAnimations === "function"
|
|
41
|
+
? asAnimations(await adapter.listAnimations())
|
|
42
|
+
: asAnimations(adapter.animations);
|
|
43
|
+
}
|
|
44
|
+
if (command === "selectAnimation") {
|
|
45
|
+
if (typeof adapter.selectAnimation !== "function") throw new Error("selectAnimation is not supported");
|
|
46
|
+
return adapter.selectAnimation(payload.id);
|
|
47
|
+
}
|
|
48
|
+
if (command === "triggerTransition") {
|
|
49
|
+
if (typeof adapter.triggerTransition !== "function") throw new Error("triggerTransition is not supported");
|
|
50
|
+
return adapter.triggerTransition({
|
|
51
|
+
id: payload.id,
|
|
52
|
+
from: payload.from,
|
|
53
|
+
to: payload.to,
|
|
54
|
+
trigger: payload.trigger,
|
|
55
|
+
payload: payload.payload && typeof payload.payload === "object" ? payload.payload : {}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (command === "play" || command === "pause" || command === "setSpeed" || command === "setLoop" || command === "stepFrames") {
|
|
59
|
+
const method = command;
|
|
60
|
+
if (typeof adapter[method] !== "function") throw new Error(`${command} is not supported`);
|
|
61
|
+
if (command === "setSpeed") return adapter[method](payload.rate);
|
|
62
|
+
if (command === "setLoop") return adapter[method](Boolean(payload.enabled));
|
|
63
|
+
if (command === "stepFrames") return adapter[method](Number(payload.delta || 0));
|
|
64
|
+
return adapter[method]();
|
|
65
|
+
}
|
|
66
|
+
if (command === "restart") {
|
|
67
|
+
if (typeof adapter.restart === "function") return adapter.restart();
|
|
68
|
+
if (typeof adapter.seek === "function") return adapter.seek(0);
|
|
69
|
+
if (typeof adapter.setProgress === "function") return adapter.setProgress(0);
|
|
70
|
+
throw new Error("restart is not supported");
|
|
71
|
+
}
|
|
72
|
+
if (command === "seek") {
|
|
73
|
+
const progress = Math.max(0, Math.min(1, Number(payload.progress || 0)));
|
|
74
|
+
if (typeof adapter.seek === "function") return adapter.seek(progress);
|
|
75
|
+
if (typeof adapter.setProgress === "function") return adapter.setProgress(progress);
|
|
76
|
+
throw new Error("seek is not supported");
|
|
77
|
+
}
|
|
78
|
+
if (command === "getState") {
|
|
79
|
+
return typeof adapter.getState === "function" ? adapter.getState() : {};
|
|
80
|
+
}
|
|
81
|
+
throw new Error(`Unknown MotionLoom runtime command: ${command}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
window.addEventListener("message", async (event) => {
|
|
85
|
+
const data = event.data;
|
|
86
|
+
if (!data || data.source !== REQUEST_SOURCE || !data.id || typeof data.command !== "string") return;
|
|
87
|
+
try {
|
|
88
|
+
const result = await invoke(data.command, data.payload || {});
|
|
89
|
+
event.source?.postMessage({ source: RESPONSE_SOURCE, id: data.id, ok: true, result }, "*");
|
|
90
|
+
} catch (error) {
|
|
91
|
+
event.source?.postMessage({
|
|
92
|
+
source: RESPONSE_SOURCE,
|
|
93
|
+
id: data.id,
|
|
94
|
+
ok: false,
|
|
95
|
+
error: error instanceof Error ? error.message : String(error)
|
|
96
|
+
}, "*");
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
window.MotionLoomRuntimeBridge = {
|
|
101
|
+
attach(nextAdapter) {
|
|
102
|
+
if (!nextAdapter || typeof nextAdapter !== "object") throw new Error("A runtime adapter object is required");
|
|
103
|
+
adapter = nextAdapter;
|
|
104
|
+
window.parent?.postMessage({ source: RESPONSE_SOURCE, event: "attached" }, "*");
|
|
105
|
+
return nextAdapter;
|
|
106
|
+
},
|
|
107
|
+
detach() { adapter = null; }
|
|
108
|
+
};
|
|
109
|
+
})();
|
package/docs/CHECKLIST.md
CHANGED
|
@@ -8,6 +8,19 @@ A scene is only ready for the confirm-into-PR step when every item below passes.
|
|
|
8
8
|
- [ ] Bone rotations follow parent-first order; no child rotates before its parent (body rigs).
|
|
9
9
|
- [ ] No frame pops at loop seam — first and last frames are visually continuous (looping scenes).
|
|
10
10
|
|
|
11
|
+
## Interactive Dev Lab runtime
|
|
12
|
+
- [ ] Any candidate with runnable animation/action data exposes `src/output/<scene>/devlab-runtime.json` using `schemas/devlab-runtime.schema.json`; captured PNG checkpoints are fallback evidence, not a substitute for an available live runtime.
|
|
13
|
+
- [ ] Every reviewable action/clip declared by the candidate (for example idle, walk, run, attack, state-machine animation, marker segment or project-specific action) is discoverable in Dev Lab without hard-coded action names.
|
|
14
|
+
- [ ] The live candidate supports every control it declares: select action, play, pause, restart, normalized seek/scrub, frame step where meaningful, playback speed and loop. Unsupported controls are disabled and never simulated as a passing runtime feature.
|
|
15
|
+
- [ ] Sprite-sequence playback renders the exact declared frame bytes; iframe runtimes expose the `runtime-bridge.js` controller and report current animation/time/progress/frame state where the runtime can provide it.
|
|
16
|
+
- [ ] The stage is usable for visual inspection: selectable background, zoom/fit, fullscreen and optional grid/bounds/baseline/pivot overlays are available without altering candidate bytes.
|
|
17
|
+
- [ ] `scripts/review-hook.py` hash-binds `devlab-runtime.json` plus every declared runtime file into the browser candidate. Path traversal, symlinked runtime files, missing files, duplicate action IDs and runtime byte drift fail closed.
|
|
18
|
+
- [ ] If a hash-bound live runtime fails to load, Dev Lab says `LIVE RUNTIME UNAVAILABLE`, may show captured evidence for diagnosis, and blocks approval. It must never relabel snapshot fallback as live runtime.
|
|
19
|
+
- [ ] When `review_policy.require_all_animations` is true, the user must inspect every `review_required` action before approval; a change request remains available at any time.
|
|
20
|
+
- [ ] Deterministic snapshot capture drives the same `window.__lab.selectAnimation()` / `window.__lab.seek()` controller used by interactive review for live candidates.
|
|
21
|
+
|
|
22
|
+
See [`DEV-LAB-RUNTIME.md`](DEV-LAB-RUNTIME.md) for the descriptor and runtime bridge contracts.
|
|
23
|
+
|
|
11
24
|
## Brand binding
|
|
12
25
|
- [ ] Primary/accent colors come from `project-context.json` (or an explicit user override recorded in the spec).
|
|
13
26
|
- [ ] Theme slots are used for every user-tunable color, never hardcoded hex inside the animation payload.
|
|
@@ -39,14 +52,14 @@ A scene is only ready for the confirm-into-PR step when every item below passes.
|
|
|
39
52
|
- [ ] Every Agent/internal-skill/provider output used by the scene has a hash-bound generation receipt, control track and export manifest; no receipt stores secrets or self-asserts approval.
|
|
40
53
|
- [ ] The selected artifact adapter is declared in `artifact-adapter-registry.json`; `scaffold` or `static-validated` status is not presented as runtime verification or production eligibility.
|
|
41
54
|
- [ ] `runtime-candidate.json` binds the intake bundle to the matching asset identity, action-set and frame geometry/atlas/map contracts; mismatched refs or hashes block the candidate.
|
|
42
|
-
- [ ] A
|
|
55
|
+
- [ ] A rigged or motion-capture candidate declares skeleton, sockets, action/event coverage, export target and adapter evidence in `rig-compatibility.json`.
|
|
43
56
|
- [ ] Artifact Intake, runtime candidate and rig findings are present in the task handoff/Dev Lab rail and visibly retain `review_required` until the user records a decision.
|
|
44
57
|
|
|
45
58
|
## PR readiness
|
|
46
59
|
- [ ] Snapshot PNGs exist for 0/50/100%.
|
|
47
60
|
- [ ] `visual-truth.json` binds real baseline/candidate frames to source, manifest and available runtime/Motion IR hashes; `approval` remains `false`.
|
|
48
61
|
- [ ] `motion-spec.json` is bound to the exact `project-context.json` hash (implements == planned).
|
|
49
|
-
- [ ] Dev Lab URL tested on mobile viewport and desktop.
|
|
62
|
+
- [ ] Dev Lab URL tested on mobile viewport and desktop, including live transport controls when `devlab-runtime.json` is present.
|
|
50
63
|
- [ ] The production provenance check passes with `--mode production` and reports `production_eligible: true`.
|
|
51
64
|
- [ ] If `manifest.json` declares `consistency_ref`, `quality-gate.py --require-asset-consistency` passes for its declared `consistency_kind`.
|
|
52
65
|
- [ ] If `manifest.json` declares an Artifact Intake bundle, `quality-gate.py --require-artifact-intake` passes and the selected adapter/runtime evidence meets the requested mode.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Dev Lab live runtime contract
|
|
2
|
+
|
|
3
|
+
Dev Lab is the human review surface for the exact MotionLoom candidate. Static 0/50/100 snapshots remain deterministic evidence, but they are not an interactive runtime. A scene that can expose live playback SHOULD add `devlab-runtime.json` beside `manifest.json` and `motion-spec.json`.
|
|
4
|
+
|
|
5
|
+
The descriptor is scene-local and hash-bound by `scripts/review-hook.py`. Every path in `files`, `entrypoint`, and `animations[].frames` must be a relative path inside the scene directory. Path traversal, absolute paths, symlinked files, missing files, duplicate animation ids, or references outside `files` fail closed. The browser candidate records the resulting runtime bundle SHA-256; changing any declared runtime byte invalidates that candidate.
|
|
6
|
+
|
|
7
|
+
## Sprite sequence example
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"schema_version": "1.0",
|
|
12
|
+
"mode": "sprite-sequence",
|
|
13
|
+
"files": [
|
|
14
|
+
"sprites/idle-00.png",
|
|
15
|
+
"sprites/idle-01.png",
|
|
16
|
+
"sprites/walk-00.png",
|
|
17
|
+
"sprites/walk-01.png"
|
|
18
|
+
],
|
|
19
|
+
"default_animation": "idle",
|
|
20
|
+
"groups": [
|
|
21
|
+
{ "id": "locomotion", "label": "Locomotion", "order": 10 },
|
|
22
|
+
{ "id": "combat", "label": "Combat", "order": 20 }
|
|
23
|
+
],
|
|
24
|
+
"animations": [
|
|
25
|
+
{
|
|
26
|
+
"id": "idle",
|
|
27
|
+
"label": "Idle",
|
|
28
|
+
"group": "locomotion",
|
|
29
|
+
"tags": ["stance", "loop"],
|
|
30
|
+
"fps": 8,
|
|
31
|
+
"frames": ["sprites/idle-00.png", "sprites/idle-01.png"],
|
|
32
|
+
"loop": true,
|
|
33
|
+
"review_required": true
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "walk",
|
|
37
|
+
"label": "Walk",
|
|
38
|
+
"group": "locomotion",
|
|
39
|
+
"tags": ["movement", "grounded"],
|
|
40
|
+
"fps": 12,
|
|
41
|
+
"frames": ["sprites/walk-00.png", "sprites/walk-01.png"],
|
|
42
|
+
"loop": true,
|
|
43
|
+
"review_required": true
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"controls": {
|
|
47
|
+
"play": true,
|
|
48
|
+
"pause": true,
|
|
49
|
+
"restart": true,
|
|
50
|
+
"seek": true,
|
|
51
|
+
"step": true,
|
|
52
|
+
"speed": true,
|
|
53
|
+
"loop": true
|
|
54
|
+
},
|
|
55
|
+
"viewport": {
|
|
56
|
+
"canvas_width": 1920,
|
|
57
|
+
"canvas_height": 1920,
|
|
58
|
+
"pixel_art": true,
|
|
59
|
+
"baseline_y": 1626,
|
|
60
|
+
"pivot": { "x": 940, "y": 1626 },
|
|
61
|
+
"background": "checker"
|
|
62
|
+
},
|
|
63
|
+
"review_policy": { "require_all_animations": true }
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Dev Lab renders the declared frame bytes directly and drives them with a real clock. Play, pause, restart, scrub, frame-step, loop and speed therefore operate on the same candidate frames that are being reviewed. Action names such as `idle`, `walk`, `run`, `attack`, `hurt` or project-specific clips are data, not hard-coded UI.
|
|
68
|
+
|
|
69
|
+
## Large action libraries
|
|
70
|
+
|
|
71
|
+
`animations` has no product-level fixed action vocabulary. A consumer can add `jump`, `dash`, `parry`, `skill-fireball`, `teleport-strike`, emotes, cinematic clips, state-machine entries, or any other valid action id and Dev Lab will expose it automatically.
|
|
72
|
+
|
|
73
|
+
For larger character or UI sets, use optional `groups` plus `animations[].group` and `animations[].tags`. Dev Lab turns those fields into an Action Library with search, group chips, collapsible group sections, and filters for review-required, unreviewed, looping, and one-shot actions. Group metadata changes presentation only; it does not grant runtime capability or approval.
|
|
74
|
+
|
|
75
|
+
Examples of useful project-defined groups are `locomotion`, `combat`, `skills`, `reactions`, `emotes`, or domain-specific categories. These names are not reserved by MotionLoom. Actions without a declared group remain visible under `Other` rather than being dropped.
|
|
76
|
+
|
|
77
|
+
Search matches action id, label, group label, tags, and declared events. Filtering never weakens the review gate: hidden required actions still count as unreviewed until the user actually selects them. The selected action remains visible in the `Unreviewed` filter so the reviewer does not lose the current runtime context immediately after opening it.
|
|
78
|
+
|
|
79
|
+
## Iframe runtime example
|
|
80
|
+
|
|
81
|
+
Use `mode: "iframe"` when the candidate already has a browser runtime (for example Rive, Lottie, GSAP, Framer Motion, Three.js or a project-specific player). The descriptor adds an `entrypoint`, and that entrypoint exposes a controller through `runtime-bridge.js`:
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<script src="/runtime-bridge.js"></script>
|
|
85
|
+
<script type="module">
|
|
86
|
+
const adapter = {
|
|
87
|
+
runtime: "example-player@1",
|
|
88
|
+
framework: "custom",
|
|
89
|
+
animations: [{ id: "idle", label: "Idle", loop: true }],
|
|
90
|
+
listAnimations() { return this.animations; },
|
|
91
|
+
selectAnimation(id) { /* select exact clip */ },
|
|
92
|
+
play() { /* play */ },
|
|
93
|
+
pause() { /* pause */ },
|
|
94
|
+
restart() { /* restart */ },
|
|
95
|
+
seek(progress) { /* normalized 0..1 */ },
|
|
96
|
+
stepFrames(delta) { /* optional */ },
|
|
97
|
+
setSpeed(rate) { /* optional */ },
|
|
98
|
+
setLoop(enabled) { /* optional */ },
|
|
99
|
+
triggerTransition(request) { /* optional: drive a real runtime state transition */ },
|
|
100
|
+
getState() { return { progress: 0, currentTime: 0, duration: 1, frame: 0, totalFrames: 60, state: "idle" }; }
|
|
101
|
+
};
|
|
102
|
+
MotionLoomRuntimeBridge.attach(adapter);
|
|
103
|
+
</script>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The parent Dev Lab communicates by `postMessage`; the runtime never grants approval. If the live runtime cannot load or a declared control is unavailable, Dev Lab disables that control and shows an explicit live-runtime failure/fallback state. It must not relabel captured PNG evidence as live runtime.
|
|
107
|
+
|
|
108
|
+
## State and transition review
|
|
109
|
+
|
|
110
|
+
A candidate may additionally declare `devlab-state-machine.json` when the reviewer needs to test behavior across clips instead of inspecting each action in isolation. The state-machine file only participates in the candidate runtime hash when it is explicitly listed in `devlab-runtime.json.files`; undeclared state-machine bytes are not accepted as review evidence.
|
|
111
|
+
|
|
112
|
+
A minimal portable contract can use `select-animation` transitions:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"schema_version": "1.0",
|
|
117
|
+
"initial_state": "idle",
|
|
118
|
+
"states": [
|
|
119
|
+
{ "id": "idle", "label": "Idle", "animation": "idle" },
|
|
120
|
+
{ "id": "run", "label": "Run", "animation": "run" },
|
|
121
|
+
{ "id": "attack", "label": "Attack", "animation": "attack" },
|
|
122
|
+
{ "id": "hurt", "label": "Hurt", "animation": "hurt" }
|
|
123
|
+
],
|
|
124
|
+
"transitions": [
|
|
125
|
+
{ "id": "idle-run", "from": "idle", "to": "run", "mode": "select-animation", "review_required": true },
|
|
126
|
+
{ "id": "run-attack", "from": "run", "to": "attack", "mode": "select-animation", "review_required": true },
|
|
127
|
+
{ "id": "attack-hurt", "from": "attack", "to": "hurt", "mode": "select-animation", "review_required": true },
|
|
128
|
+
{ "id": "hurt-idle", "from": "hurt", "to": "idle", "mode": "select-animation", "review_required": true }
|
|
129
|
+
],
|
|
130
|
+
"sequences": [
|
|
131
|
+
{
|
|
132
|
+
"id": "combat-roundtrip",
|
|
133
|
+
"label": "Combat roundtrip",
|
|
134
|
+
"review_required": true,
|
|
135
|
+
"steps": [
|
|
136
|
+
{ "transition": "idle-run", "wait_ms": 150 },
|
|
137
|
+
{ "transition": "run-attack", "wait_ms": 150 },
|
|
138
|
+
{ "transition": "attack-hurt", "wait_ms": 150 },
|
|
139
|
+
{ "transition": "hurt-idle", "wait_ms": 150 }
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
],
|
|
143
|
+
"review_policy": {
|
|
144
|
+
"require_all_transitions": true,
|
|
145
|
+
"require_all_sequences": true
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`select-animation` is the portable lane for sprite/clip runtimes: the transition selects the target state's declared animation and may auto-play it. `runtime-trigger` is stricter and is intended for a real state machine such as a Rive state machine or a project-specific runtime. For that mode the iframe adapter must implement `triggerTransition(request)`. Dev Lab sends the declared trigger/payload and only counts the transition as inspected after the runtime reports the target state through `getState()`. It never silently downgrades a failed or unsupported `runtime-trigger` transition into a clip switch.
|
|
151
|
+
|
|
152
|
+
The State/Transition Tester shows the current state, legal outgoing transitions, required transition coverage, declared review sequences and transition history. Review sequences execute their declared transition steps with bounded waits and may be stopped or reset by the reviewer. They are review helpers, not autonomous approval mechanisms.
|
|
153
|
+
|
|
154
|
+
## Review semantics
|
|
155
|
+
|
|
156
|
+
Dev Lab records which actions the reviewer actually selected. When `review_policy.require_all_animations` is true, approval is disabled until every `review_required` action has been inspected. When a state-machine contract is present, its `review_policy` can independently require all review-required transitions and review-required sequences before approval is enabled. A user may still request changes at any time.
|
|
157
|
+
|
|
158
|
+
Review evidence records action, transition and sequence coverage plus runtime/state history. Playback success, transition success, runtime readiness, hashes, snapshots, telemetry and automated checks remain evidence only; `approved` is always an explicit user review decision.
|
|
159
|
+
|
|
160
|
+
The deterministic snapshot harness continues to use `window.__lab.selectAnimation()` and `window.__lab.seek()`. For live candidates those calls drive the same runtime controller shown to the user; for legacy scenes without `devlab-runtime.json`, Dev Lab clearly enters `captured-evidence` mode and retains the older checkpoint viewer as a compatibility fallback.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Dev Lab state/transition tester
|
|
2
|
+
|
|
3
|
+
MotionLoom Dev Lab can review more than isolated animation clips. A live candidate may also expose a state/transition contract so a reviewer can exercise flows such as `Idle -> Run -> Attack -> Hurt -> Idle` against the same candidate runtime.
|
|
4
|
+
|
|
5
|
+
The state-machine contract is optional. When used, place `devlab-state-machine.json` beside `devlab-runtime.json` and list `devlab-state-machine.json` in the runtime descriptor's `files` array. This is mandatory: the state-machine bytes must participate in the candidate runtime bundle hash. Dev Lab ignores unbound state-machine data and blocks approval if a hash-bound state-machine contract is invalid.
|
|
6
|
+
|
|
7
|
+
The schema is `schemas/devlab-state-machine.schema.json`.
|
|
8
|
+
|
|
9
|
+
## Example
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"schema_version": "1.0",
|
|
14
|
+
"initial_state": "idle",
|
|
15
|
+
"states": [
|
|
16
|
+
{ "id": "idle", "label": "Idle", "animation": "idle" },
|
|
17
|
+
{ "id": "run", "label": "Run", "animation": "run" },
|
|
18
|
+
{ "id": "attack", "label": "Attack", "animation": "attack" },
|
|
19
|
+
{ "id": "hurt", "label": "Hurt", "animation": "hurt" }
|
|
20
|
+
],
|
|
21
|
+
"transitions": [
|
|
22
|
+
{
|
|
23
|
+
"id": "start-run",
|
|
24
|
+
"label": "Start running",
|
|
25
|
+
"from": "idle",
|
|
26
|
+
"to": "run",
|
|
27
|
+
"trigger": "move",
|
|
28
|
+
"mode": "select-animation",
|
|
29
|
+
"auto_play": true,
|
|
30
|
+
"review_required": true
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "attack",
|
|
34
|
+
"from": "run",
|
|
35
|
+
"to": "attack",
|
|
36
|
+
"trigger": "attack",
|
|
37
|
+
"mode": "select-animation",
|
|
38
|
+
"auto_play": true,
|
|
39
|
+
"review_required": true
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "take-hit",
|
|
43
|
+
"from": "attack",
|
|
44
|
+
"to": "hurt",
|
|
45
|
+
"trigger": "hit",
|
|
46
|
+
"mode": "select-animation",
|
|
47
|
+
"auto_play": true,
|
|
48
|
+
"review_required": true
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "recover",
|
|
52
|
+
"from": "hurt",
|
|
53
|
+
"to": "idle",
|
|
54
|
+
"trigger": "recover",
|
|
55
|
+
"mode": "select-animation",
|
|
56
|
+
"auto_play": true,
|
|
57
|
+
"review_required": true
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"sequences": [
|
|
61
|
+
{
|
|
62
|
+
"id": "combat-cycle",
|
|
63
|
+
"label": "Combat cycle",
|
|
64
|
+
"review_required": true,
|
|
65
|
+
"steps": [
|
|
66
|
+
{ "transition": "start-run", "wait_ms": 250 },
|
|
67
|
+
{ "transition": "attack", "wait_ms": 450 },
|
|
68
|
+
{ "transition": "take-hit", "wait_ms": 300 },
|
|
69
|
+
{ "transition": "recover", "wait_ms": 200 }
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
"review_policy": {
|
|
74
|
+
"require_all_transitions": true,
|
|
75
|
+
"require_all_sequences": false
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Transition modes
|
|
81
|
+
|
|
82
|
+
`select-animation` is the portable mode. Dev Lab changes to the target state's declared animation through the existing live runtime controller and optionally starts playback. It is appropriate for sprite-sequence candidates and simple clip-oriented runtimes. This mode does not claim that a blend tree or engine state machine was exercised.
|
|
83
|
+
|
|
84
|
+
`runtime-trigger` is for runtimes that own real state-machine behavior, such as a Rive state machine, a game-runtime adapter, or another project-specific player. The iframe adapter must expose `triggerTransition(request)` through `runtime-bridge.js` and `getState()` must expose an observable `state`/`currentState` or an animation that maps to a declared state. Dev Lab sends the exact transition request and verifies that the target state becomes observable before counting the transition as inspected. It never silently downgrades a `runtime-trigger` transition to a clip switch.
|
|
85
|
+
|
|
86
|
+
Example adapter:
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
MotionLoomRuntimeBridge.attach({
|
|
90
|
+
runtime: "game-preview@1",
|
|
91
|
+
framework: "custom",
|
|
92
|
+
animations: [
|
|
93
|
+
{ id: "idle", label: "Idle", loop: true },
|
|
94
|
+
{ id: "run", label: "Run", loop: true },
|
|
95
|
+
{ id: "attack", label: "Attack", loop: false }
|
|
96
|
+
],
|
|
97
|
+
triggerTransition(request) {
|
|
98
|
+
// Dispatch the real trigger/input into the runtime state machine.
|
|
99
|
+
// request: { id, from, to, trigger, payload }
|
|
100
|
+
},
|
|
101
|
+
getState() {
|
|
102
|
+
return {
|
|
103
|
+
state: "idle",
|
|
104
|
+
animation: "idle",
|
|
105
|
+
progress: 0,
|
|
106
|
+
currentTime: 0,
|
|
107
|
+
duration: 1
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Review behavior
|
|
114
|
+
|
|
115
|
+
The State Machine panel follows the runtime's current state, shows only transitions that are legal from that state (plus wildcard transitions), and records successful transition observations. Review sequences execute declared transitions in order; each wait is explicit and bounded.
|
|
116
|
+
|
|
117
|
+
If `review_policy.require_all_transitions` is true, every transition with `review_required != false` must be exercised before Dev Lab allows approval. `require_all_sequences` applies the same rule to review-required sequences. Search/filtering in the Action Library cannot bypass these requirements.
|
|
118
|
+
|
|
119
|
+
A transition is counted only after its command succeeds and the target state is observable. Unsupported runtime-trigger transitions remain disabled. A reviewer can always request changes without completing transition coverage.
|
|
120
|
+
|
|
121
|
+
Transition execution, state observability, sequence completion, runtime hashes, snapshots, and automated smoke tests are evidence only. None of them grant `production_approved`, runtime approval, or visual approval. The final approval remains an explicit user action.
|
package/docs/STATUS.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## Current release posture
|
|
6
6
|
|
|
7
|
-
MotionLoom's repository source and package manifest
|
|
7
|
+
MotionLoom's repository source and package manifest carry **2.6.0 release metadata**. The publication state of the npm registry and GitHub Release/tag must be verified against the latest release workflow rather than inferred from this document. The repository has a cross-platform CLI, one-command project onboarding, durable Project Memory, Agent interoperability surfaces, artifact-first handoff, runtime evidence, Visual Truth, Remediation Learning, signed attestation, AI-first asset provenance, a truthful code-authored runtime lane and an interactive browser-based Dev Lab review workbench. Dev Lab now supports hash-bound live playback, arbitrary project-defined action libraries, and optional state/transition review flows in addition to deterministic snapshot evidence. The protected manual release workflow verifies package/changelog/release-note alignment before publication.
|
|
8
8
|
|
|
9
9
|
The repository is **engineering-ready for continued integration work**, not a universal production certification for every animation framework or every host project. CI status must be read from the latest GitHub Actions run, not inferred from historical audit prose.
|
|
10
10
|
|
|
@@ -16,7 +16,7 @@ The repository is **engineering-ready for continued integration work**, not a un
|
|
|
16
16
|
| Rive, GSAP and Framer Motion | Adapter and fixture evidence | Framework-specific runtime adapters and smoke checks can render representative scenes through browser/runtime paths | Equivalent coverage to the Lottie path across arbitrary projects |
|
|
17
17
|
| Project Memory | Contract and relocation evidence | Stable project identity, atomic persistence, integrity guard, freshness states and path rebinding after relocation | Automatic correctness of an Agent's inferred decision |
|
|
18
18
|
| Semantic intelligence | Deterministic evaluation evidence | Project graph, provenance, Motion IR, continuity, fix-plan and adversarial/deep-stress contracts; bounded analyzer reports scan budgets and truncation | Human-level design judgment, external-project product-value evidence or guaranteed first-pass acceptance |
|
|
19
|
-
| Dev Lab |
|
|
19
|
+
| Dev Lab | Interactive review infrastructure evidence | Identity-bound candidate loading, live runtime playback controls, arbitrary Action Library discovery, state/transition and review-sequence testing, deterministic snapshots, user checklist and review export | A full visual authoring editor, automatic approval or PR authorization |
|
|
20
20
|
|
|
21
21
|
## Normative sources
|
|
22
22
|
|
|
@@ -30,4 +30,4 @@ Use the following order when sources disagree:
|
|
|
30
30
|
|
|
31
31
|
## Known next work
|
|
32
32
|
|
|
33
|
-
The bounded analyzer has
|
|
33
|
+
The bounded analyzer has been exercised against a labeled external corpus; see the [dated evidence note](audits/external-project-corpus-2026-08-13.md). The 2.5.x publication chain established npm/GitHub release provenance, and 2.6.0 adds a substantially richer Dev Lab review surface without weakening the human approval boundary. The highest-value next evidence work is consumer-project review of real action sets and transitions, followed by paired product evaluation across more real projects and repeating the same provenance verification for each new release. Missing external projects are reported as `insufficient_evidence`, never as a pass. None of these items should weaken the user-review gate or convert runtime success or heuristic warnings into approval.
|