@rig-c/review 0.0.0-stage → 0.2.1

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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +376 -2
  3. package/docs/DESIGN.md +72 -0
  4. package/docs/REVIEW_PACKAGE.md +627 -0
  5. package/examples/demo/SOURCE.md +5 -0
  6. package/examples/demo/chime.wav +0 -0
  7. package/examples/demo/lantern.svg +10 -0
  8. package/examples/demo/pose.json +244 -0
  9. package/examples/demo/review.json +67 -0
  10. package/package.json +49 -4
  11. package/src/animation-selection.ts +29 -0
  12. package/src/build.ts +8 -0
  13. package/src/cli.ts +116 -0
  14. package/src/compare-playhead.ts +55 -0
  15. package/src/compare-presentation.ts +43 -0
  16. package/src/compare-server.ts +63 -0
  17. package/src/compare-session.ts +26 -0
  18. package/src/compare.ts +57 -0
  19. package/src/comparison-eligibility.ts +54 -0
  20. package/src/control-server.ts +83 -0
  21. package/src/control.ts +132 -0
  22. package/src/feedback-export.ts +197 -0
  23. package/src/feedback-validation.ts +38 -0
  24. package/src/frame-clock.ts +36 -0
  25. package/src/front-worker.ts +2 -0
  26. package/src/front.ts +127 -0
  27. package/src/manifest.ts +140 -0
  28. package/src/open-protocol.ts +31 -0
  29. package/src/open-selection.ts +11 -0
  30. package/src/open-shell.ts +2 -0
  31. package/src/open-worker.ts +2 -0
  32. package/src/open.ts +142 -0
  33. package/src/pose.ts +27 -0
  34. package/src/probe-reference.ts +16 -0
  35. package/src/reference-frame-index.ts +16 -0
  36. package/src/reference-frames.ts +62 -0
  37. package/src/reference.ts +63 -0
  38. package/src/report.ts +88 -0
  39. package/src/server.ts +62 -0
  40. package/src/session-history.ts +99 -0
  41. package/src/session.ts +58 -0
  42. package/src/strip-export.ts +69 -0
  43. package/src/strip-render.ts +171 -0
  44. package/src/strip.ts +51 -0
  45. package/src/versions.ts +20 -0
  46. package/web/camera-view.ts +44 -0
  47. package/web/compare.css +140 -0
  48. package/web/compare.html +7 -0
  49. package/web/compare.ts +546 -0
  50. package/web/control.ts +67 -0
  51. package/web/index.html +13 -0
  52. package/web/inspection-persistence.ts +219 -0
  53. package/web/instance-access.ts +23 -0
  54. package/web/main.ts +972 -0
  55. package/web/part-selection.ts +136 -0
  56. package/web/renderer-status.ts +31 -0
  57. package/web/report-view.ts +51 -0
  58. package/web/rig-overlays.ts +150 -0
  59. package/web/styles.css +139 -0
  60. package/web/view-controls.ts +69 -0
  61. package/web/workspace-layout.ts +84 -0
  62. package/web/workspace-preferences.ts +95 -0
  63. package/web/workspace-ui.ts +91 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 firejune (Joon Kyoung)
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 CHANGED
@@ -1,3 +1,377 @@
1
- # Temporary Holding Version
1
+ # review
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A reusable local workspace for a person to inspect an agent's rig output, compare source references, and export feedback ready to become an issue. The person receives a package or opens an existing output folder; the agent does not build a new viewer for each rig.
4
+
5
+ The app provides one inspection workspace for setup geometry and motion, compiled-model playback through `@rig-c/play`, local image, video and pose-sequence references, checkpoints, audio audition, and saved structured feedback. **Motion is view, annotate and export only.** Navigation changes inspection time; it never retimes, authors keys, or writes `motion.json`. Modeling touch-up remains a design goal; this starter does not edit meshes, weights or source art.
6
+
7
+ ## Place in the toolchain
8
+
9
+ [Parts](https://github.com/rig-c/parts) → [Pose](https://github.com/rig-c/pose) →
10
+ [Core / rigc](https://github.com/rig-c/core) → **Review** →
11
+ [Play](https://github.com/rig-c/play) → **Adapter** (consumer integration).
12
+
13
+ Review is the human inspection and source-bound feedback gate in this delivery
14
+ flow. It inspects the upstream build without replacing Core's compiler gate or
15
+ authoring motion. Review itself uses the published Play runtime for previews;
16
+ the chain describes delivery and feedback responsibilities, not runtime import
17
+ order. Adapter denotes downstream consumer integration, without promising an
18
+ unreleased adapter API or changing existing compilation/playback dependencies.
19
+
20
+ ## Start
21
+
22
+ Install it beside the agent that produced the output, then open that output folder:
23
+
24
+ ```sh
25
+ npm i -D @rig-c/review
26
+ npx rig-review open /absolute/path/to/output
27
+ ```
28
+
29
+ `rig-review` runs under **Bun 1.3.0 or later**, which must be on `PATH`; CI runs the installed package on 1.3.0 and on the latest Bun. `open` prints a loopback URL and exits; see [One-command owner handoff](#one-command-owner-handoff). `npx rig-review --help` lists every command.
30
+
31
+ The package is 0.1.x: the CLI is the supported surface, and no importable API is promised yet (`exports` exposes only `package.json`).
32
+
33
+ ## Existing output handoff
34
+
35
+ Open the current output folder directly, without changing the producing agent or inserting a manifest:
36
+
37
+ ```sh
38
+ bun run check -- /absolute/path/to/output
39
+ bun run review -- /absolute/path/to/output
40
+ ```
41
+
42
+ The adapter recognizes root `skeleton.model.json` or the fixed rigc build layout `build/skeleton.model.json` (root model takes precedence), root `motion.json`, `report.json`, `skeleton.json`/`.skel`/`.atlas`, source videos (`.mp4`, `.webm`, `.mov`), image filenames containing `strip`, `contact`, `sheet`, `filmstrip` or `띠`, and canonical `*pose*.json` sequences. Model discovery checks only those two named paths; it never recursively searches directories. Model page images resolve relative to the selected model, while motion and references remain relative to the opened folder. It builds the normalized review manifest in memory and reads source files without changing them. Motion/report/skeleton evidence links are read only. A recognized `rig-pose-report/1` also supplies read-only event markers: frame-only events retain their producer interval and explicitly lack seek until a declared clock is present; explicit event seconds can seek. Empty reported categories show none, while missing fields remain missing. Report markers prefill known feedback coordinates and actual bone/slot names without creating a review decision or motion keys. It exposes each matching video/strip separately, without guessing synchronization or source-to-rig bindings.
43
+
44
+ When the report declares `fps` plus `frame0` (seconds offset) and a frame count, or exact `frameTimes[]`, the matching animation also shows a producer-frame readout and frame seek controls. Without a bounded declared clock it shows `rate unknown`; current unchanged v19 has no clock. Sample times come only from the report, and feedback context records the recalculated frame or null.
45
+
46
+ A raw `skeleton.json`/`.skel` can still be inspected as evidence, but cannot play through the compiled-model runtime. Playback requires an upstream gated rigc build's `skeleton.model.json` with portable local page images inside the opened folder. `check` validates the delivery contract and model envelope, not the upstream compiler gate or visual quality.
47
+
48
+ For a curated handoff, [REVIEW_PACKAGE.md](docs/REVIEW_PACKAGE.md) documents optional `review.json` with named checkpoints, references and angle axes:
49
+
50
+ ```sh
51
+ bun run review -- /absolute/path/to/package/review.json --checkpoint silhouette
52
+ ```
53
+
54
+ The server prints the exact link, for example `http://127.0.0.1:4317/?checkpoint=silhouette#cap=…`. The `#cap=` fragment is this server's write capability (see [Managed front and write capability](#managed-front-and-write-capability)). `--port 0` selects a free port. It binds only `127.0.0.1` and never opens a browser automatically. A folder containing `review.json` uses that explicit manifest.
55
+
56
+ ## Compare two outputs
57
+
58
+ Open two compiled packages against one explicitly selected local reference video:
59
+
60
+ ```sh
61
+ bun run review -- compare /absolute/output-a /absolute/output-b --reference /absolute/reference.mp4 --animation dance
62
+ bun run check -- compare /absolute/output-a /absolute/output-b --reference /absolute/reference.mp4 --animation dance
63
+ bun run feedback -- compare /absolute/output-a /absolute/output-b --reference /absolute/reference.mp4 --animation dance
64
+ ```
65
+
66
+ Both models must contain the named animation. The video is a separate read-only CLI input, served only through a fixed endpoint; it is never inserted into either package. A separate compare page uses one video playhead to seek both rigs at the same time. Frame readouts use each side's explicit producer clock; no video FPS or missing clock is inferred.
67
+
68
+ Compare feedback requires side `a` or `b` and carries both package source hashes, both exact rig/motion identities, the shared video hash and animation. A changed input on either side or in the video refuses saved feedback until restart. State stays outside all source inputs. Single-package commands and sessions retain their existing contract. See [the comparison contract](docs/REVIEW_PACKAGE.md#two-package-comparison).
69
+
70
+ ## Feedback ownership
71
+
72
+ Opening leaves status **pending**. The person explicitly saves **approved**, **changes-requested**, or **pending**, checkpoint outcomes/notes, and feedback containing an inclusive frame interval, a bone/slot/rig target, defect/quality category and one-line summary. Frame numbers are entered explicitly; the app does not invent a source FPS. The primary **리뷰 제출** action saves that explicit status and feedback. JSON and issue-ready Markdown exports carry the original source binding and reproducible inspection context. Each submission records `reviewedSource`: package revision, actual viewer/runtime versions, and separate exact-byte SHA-256 plus path for the reviewed rig and motion; an absent artifact stays null. An active compiled model is the rig identity; raw skeleton JSON is evidence with playback unavailable.
73
+
74
+ New submissions validate bone/slot names against the exact loaded model; the target field offers those names. A rig target names the package id. Source-only packages cannot validate a bone/slot target and say so explicitly. When a matching animation has a declared producer clock, the inclusive interval cannot end beyond the last declared sample at or before the actual clip end. Unknown clocks keep manual frame intervals without inventing a limit. `context.interval` records the chosen interval; inspection `time`/`frame` remain separate. Older invalid feedback stays readable with per-row warnings and exports its original evidence, but cannot be resubmitted until corrected or removed.
75
+
76
+ Feedback lives under `~/.local/state/rig-c-review/`, separate from delivered files. Identity, revision and a streaming SHA-256 of the normalized manifest plus declared files/model pages bind each saved session. Changed content starts a new pending session, even without a revision bump. Restart the server after changing inputs. Saving or exporting feedback never modifies a model, authors correction keys, writes motion output or publishes an issue automatically.
77
+
78
+ After the person submits, the producing agent retrieves the stored review without needing the hashed state filename:
79
+
80
+ ```sh
81
+ bun run feedback -- /absolute/path/to/output
82
+ # Or the same explicit review.json originally opened:
83
+ bun run feedback -- /absolute/path/to/package/review.json
84
+ ```
85
+
86
+ This read-only command prints the session JSON, including reviewed artifact identity and feedback. The exact current-tool session wins when present. After a viewer/runtime upgrade, if that exact session is absent, the CLI can retrieve the latest saved submission over the same content with its original recorded versions and `retrieval: {kind:"prior-tools", toolsChangedSince:true, matchingSubmissions, currentTools}`. It does not migrate approval into the current UI or change saved bytes. Changed inputs still return a fresh pending session; a prior submission is never borrowed across content, revision or reviewed-artifact changes. The same lookup applies to `feedback compare`, preserving both recorded side bindings, reference identity and animation. See [historical retrieval](docs/REVIEW_PACKAGE.md#historical-feedback-retrieval) for selection and validation rules.
87
+
88
+
89
+ For grouped issue-ready text, use the same input/reference binding with `--format issue`:
90
+
91
+ ```sh
92
+ rig-review feedback /absolute/output --reference /absolute/original.mp4 --format issue
93
+ rig-review feedback /absolute/a --compare /absolute/b --reference /absolute/original.mp4 --animation dance --format issue
94
+ ```
95
+
96
+ The header counts feedback records by defect/quality and by build; blocks group by category, then exact target kind/name. Overlapping intervals and A/B sides remain distinct items. Each block retains its id, inclusive interval, one-line summary and selected/full paired provenance. Historical tool identities and validation warnings remain visible; output never migrates approval, writes state or publishes an issue. Default output and `--format json` remain the existing session JSON. Only feedback accepts `--format`, with `json` or `issue` as its value.
97
+
98
+ ## Development
99
+
100
+ From a clone:
101
+
102
+ ```sh
103
+ bun install
104
+ bun run demo
105
+ ```
106
+
107
+ Open the printed loopback URL. The bundled original lantern study has source art, an explicitly synthetic canonical pose-sequence sample and an original synthetic chime. It has no compiled model: playback remains unavailable until a real model is supplied. Inside a clone, `bun run review -- <dir>`, `bun run check -- <dir>` and `bun src/cli.ts <command>` are the same CLI as `npx rig-review`.
108
+
109
+ ```sh
110
+ bun run typecheck
111
+ bun test
112
+ bun run build
113
+ bun run smoke
114
+ ```
115
+
116
+ `smoke` packs this tree, installs the tarball into an empty project, and runs the installed bin: `--help`, `open --help`, `check` on the bundled demo, and a served page whose browser bundle is built from the installed files. A file missing from `files` in `package.json` fails there, not after publishing.
117
+
118
+ The server bundles `web/main.ts` at startup using the pinned installed runtime, with no CDN. `build` verifies that browser bundle in memory. CI checks types, focused path/state/API tests, bundling and the demo package. Follow AGENTS.md for contributor verification policy. [DESIGN.md](docs/DESIGN.md) distinguishes the runnable foundation from future modeling touch-up and preserves the illustrative screen concept.
119
+
120
+ ### Releasing
121
+
122
+ Releases are cut by `.github/workflows/release.yml`. On every push to main, release-please keeps a `release: vX.Y.Z` pull request whose version comes from the conventional-commit subjects since the last release. Merging it tags the version, creates the GitHub release, runs typecheck and the install smoke on the tagged tree, and publishes to npm through trusted publishing (OIDC) with provenance. No npm token is stored. Nothing is published until a release PR is merged.
123
+
124
+ ## Frame strip export
125
+
126
+ The strip panel exports the explicitly entered inclusive feedback interval, or all current in-memory feedback intervals, as a PNG or JSON download. Batch selections include unsaved annotations; they do not submit or approve a review. Choose a declared reference video in single-package mode and an explicit clock basis A/B in compare mode. Every selected feedback interval must identify the selected animation. Separate intervals remain separate strip groups, including overlaps.
127
+
128
+ The same export is available without a browser:
129
+
130
+ ```sh
131
+ bun src/cli.ts strip /absolute/package/review.json --reference source-video --animation dance --frames 176:184 --out /absolute/external/strip.png
132
+ bun src/cli.ts strip compare /absolute/a /absolute/b --reference /absolute/reference.mp4 --animation dance --basis b --frames 176:184 --out /absolute/external/compare.png
133
+ bun src/cli.ts strip /absolute/package/review.json --reference source-video --animation dance --all-feedback --json
134
+ ```
135
+
136
+ Single `--reference` is a declared video **id**; compare uses the explicitly selected local video path. `--all-feedback` reads only the exact current saved session, and retains each item's side/interval. `--json` produces a selection/provenance plan without rendering; optionally add `--out`. PNG writes an additional `.png.json` provenance sidecar with the actual decoded samples and PNG hash. Destinations must be new files outside every input package, in existing directories without symlink parents. The server exposes download blobs, never a destination-path API.
137
+
138
+ Strip times require the selected report's exact `frameTimes` and matching animation. Missing or uniform-only clocks refuse PNG with `Report has no frameTimes — regenerate with pose 0.30.0 or later`; JSON retains requested frames with `time:null`. The other compare side may lack a clock: it can still be posed at the common seconds, while its frame label stays unknown. No frame rate is inferred from frame count, duration or video metadata.
139
+
140
+ PNG export uses the pinned published `@rig-c/play` canvas player and real Node Canvas2D, with `ffmpeg` (5.1 or later, for `-fps_mode`) and `ffprobe` available on PATH for reference decoding. Missing capabilities fail by name. Each requested sample chooses the nearest actual decoded reference PTS (ties earlier); both rigs seek that actual PTS using the viewer's fixed step. JSON distinguishes requested time, actual PTS, delta, decoded frame index, actual player times and independently known producer frames. Requests outside the common domain refuse instead of clamping. The PNG shows source rows, chronological frame columns, and full source hashes/revisions/tool and renderer versions. Batch groups stack vertically. Exports are bounded to64 intervals,256 total frames,64 megapixels and16384 pixels per dimension; larger selections must be split.
141
+
142
+ The footer fits its measured text while retaining full hashes. RGB/dark-tint features unsupported by the published Canvas2D path refuse with `STRIP_UNSUPPORTED_TINT`; they are not silently omitted.
143
+
144
+ ## One-command owner handoff
145
+
146
+ `open` validates and hashes an existing folder, starts a background loopback
147
+ instance or reuses the exact matching one, prints its hash-bound URL, and exits.
148
+ By default the URL goes through the managed front at `127.0.0.1:4318`; `--direct`
149
+ prints the instance's own port instead (see [Managed front and write capability](#managed-front-and-write-capability)):
150
+
151
+ ```sh
152
+ bun run open -- /absolute/output --reference /absolute/original.mp4
153
+ # Installed executable mapping:
154
+ rig-review open /absolute/output --reference /absolute/original.mp4 --animation dance
155
+ rig-review feedback /absolute/output --reference /absolute/original.mp4
156
+ rig-review open /absolute/a --compare /absolute/b --reference /absolute/original.mp4 --animation dance
157
+ rig-review feedback /absolute/a --compare /absolute/b --reference /absolute/original.mp4 --animation dance
158
+ ```
159
+
160
+ `open` chooses an explicit `--animation` first, then the normalized report's
161
+ `animation` after validating actual model membership, then a unique actual clip
162
+ (or unique common clip in compare). Thus `rig-review open /absolute/output
163
+ --reference /absolute/original.mp4` opens a report-bound `dance` without the flag
164
+ and prints `animation: dance (from report.json)` on stderr. Conflicting A/B reports,
165
+ a stale report name or multiple clips without a report refuse by name; no first
166
+ entry, filename or frame rate supplies a guess. Explicit invalid names never fall
167
+ back. The selected clip actually initializes single playback, while an explicit
168
+ checkpoint link or later manual choice takes priority. Incompatible automatic
169
+ checkpoints do not apply another clip's time or axis. Static/source-only packages
170
+ remain inspectable without claiming clip selection; explicit unvalidatable clips
171
+ refuse. Single review sessions remain package-wide; animation selection does not
172
+ create an animation-specific approval identity. Opening never
173
+ approves a review. The command prints the matching feedback retrieval command on
174
+ stderr; stdout contains the URL alone. A selected external video takes precedence
175
+ at automatic startup; an explicit checkpoint link or manual checkpoint selection
176
+ still chooses that checkpoint's reference.
177
+
178
+ The selected video is a separate CLI capability, not a manifest path escape or a
179
+ file copied into the source package. Its name and streaming byte hash, plus the
180
+ base package hash, appear as `referenceBinding` in single review responses,
181
+ sessions and feedback exports. Use the same `--reference` when retrieving that
182
+ submission; omitting it reads the separate reference-free identity. Changed
183
+ package or reference bytes start pending state. Existing historical retrieval
184
+ preserves earlier recorded tool versions over exactly the same reference content.
185
+
186
+ The single strip panel can export the selected external video using that same
187
+ binding. Headless equivalent (the original declared-reference form stays valid):
188
+
189
+ ```sh
190
+ rig-review strip /absolute/output --reference-video /absolute/original.mp4 --animation dance --frames 176:184 --out /absolute/external/strip.png
191
+ ```
192
+
193
+ Only tool-owned instances with matching content, canonical input locations, tool
194
+ fingerprint and a private capability handshake are reused. The URL hash is
195
+ checked before serving the app; a stale link never selects another instance's
196
+ package. Input hashes are freshly computed, not trusted from mtime caches. Idle
197
+ open-managed workers stop after 30 minutes. Their private registry, requests and
198
+ logs live beside feedback under `~/.local/state/rig-c-review/open/`; nothing is
199
+ written into supplied folders. `RIG_REVIEW_STATE_DIR` selects another state root
200
+ outside all input packages. Existing foreground review commands still work.
201
+
202
+ No browser is opened, no LAN listener is added, and no misleading phone/QR URL is
203
+ printed: every current instance binds only `127.0.0.1`.
204
+
205
+ The printed feedback retrieval command uses POSIX shell quoting for literal path
206
+ arguments. It is not a PowerShell or cmd.exe command string.
207
+
208
+ ### Managed front and write capability
209
+
210
+ `open` is **managed by default**. It ensures one detached loopback front at
211
+ `127.0.0.1:4318` (`RIG_REVIEW_FRONT_PORT` overrides) for the state directory and
212
+ prints `http://127.0.0.1:4318/instances/<key>/?input=…&handoff=…#cap=…`. Every
213
+ inspection therefore shares one stable browser origin, so Settings, panel sizes and
214
+ source-bound drafts survive a reopen. The front only routes: it takes the worker
215
+ address from that key's private open record, allows a closed per-method route
216
+ list, bounds bodies (16 KiB control, 1 MiB session/strip) and upstream time,
217
+ limits concurrent upstream streams, re-verifies the worker's nonce on every
218
+ request, and refuses an unknown, expired or replaced instance by name
219
+ (`FRONT_INSTANCE_UNKNOWN`, `FRONT_INSTANCE_EXPIRED`, `FRONT_INSTANCE_REPLACED`).
220
+ Worker error text is not passed through; failures become named `FRONT_*` codes.
221
+ Package assets keep their `sandbox` CSP through the front. It never reads packages.
222
+ Idle fronts exit after 30 minutes once no live instance remains.
223
+
224
+ If another listener holds the front port, `open` fails with
225
+ `FRONT_PORT_BUSY: 127.0.0.1:4318 is held by another listener; use --direct` and
226
+ does not fall back silently. `open --direct` keeps the per-instance port and
227
+ prints that worker's own URL.
228
+
229
+ One origin cannot isolate scripts that run on it, so writes are bound to a
230
+ per-instance capability rather than to the page's origin or the key in its path.
231
+ Each worker mints a 256-bit capability kept only in its private 0600 open record.
232
+ `open` (managed or direct) and the foreground `review`/`review compare` commands
233
+ print it in the `#cap=` fragment, which browsers never send to a server. The page
234
+ moves it into memory and that tab's `sessionStorage`, then removes the fragment
235
+ from the address bar. Every mutation sends it as `X-Review-Capability`; the front
236
+ checks it before forwarding and the worker compares it again in constant time
237
+ before its existing same-origin check. No endpoint returns it, so a page opened
238
+ for one instance cannot submit to another. A copied view link (without the
239
+ fragment) opens read-only and says *Read-only view: reopen with rig-review open to
240
+ edit*; run `open` again for a writable link.
241
+
242
+ ## Control the current inspection page
243
+
244
+ Opt in once when opening a fixed package, then keep that page open:
245
+
246
+ ```sh
247
+ rig-review open /absolute/output --reference /absolute/original.mp4 --control
248
+ rig-review control /absolute/output --reference /absolute/original.mp4
249
+ ```
250
+
251
+ The second command reads the connected page's actual inspection state and its
252
+ `pageId`/`revision`; it does not open a browser or start another worker. To change
253
+ that page, use the observed values and a closed inspection command:
254
+
255
+ ```sh
256
+ rig-review control /absolute/output --reference /absolute/original.mp4 --page '<pageId>' --expected-revision 0 --request-id theme-1 --command '{"type":"theme","value":"light"}'
257
+ ```
258
+
259
+ Use the same reference/compare/animation inputs as the controlled `open`. Multiple
260
+ pages require an explicit `--page`. The URL and source binding stay unchanged;
261
+ existing ordinary servers do not become controllable. Starting the opt-in worker
262
+ is a one-time handoff, not a new link for each parameter change.
263
+
264
+ Commands cover appearance, automatic layout, synchronized or independent pane
265
+ cameras, playback, single animation, time and actual inspection layers. Camera
266
+ pan is measured in viewport CSS pixels (right/down positive). New inspections
267
+ start with camera sync on: zoom relative to fit and pan normalized by each pane's
268
+ viewport move all visible measurable reference/rig panes together. Turn sync off
269
+ with `{"type":"camera-sync","value":false}` to inspect each pane independently;
270
+ turning it off preserves current views. Turning it on aligns the visible panes
271
+ to the last active pane. Camera/fit targets are `rig|reference` in single review
272
+ and `a|b|reference` in compare. For legacy clients, compare `target:"rig"` explicitly
273
+ moves both A/B even when sync is off; its CSS readback aliases A. Comparison retains its
274
+ fixed animation and actual video-master seek path. A single-video time request
275
+ uses the actual decoded timestamp for both reference and rig; the applied
276
+ acknowledgement reports that time, which may differ from the requested seconds.
277
+ Unsupported features, stale
278
+ page/source/revision, unsubmitted feedback drafts and out-of-range values refuse
279
+ by name. No command changes a package/reference, authors motion, or submits or
280
+ approves feedback. Local controls and remote readback use the same inspection
281
+ state. A live view-link/history change supersedes pending remote work, and
282
+ commands refuse while that view is being restored.
283
+
284
+ Remote inspection readback also includes versioned numeric `view` coordinates
285
+ when the page can measure them: rig/world center and zoom, reference/image center
286
+ and zoom, each panel's current CSS viewport size and visible rectangle. World
287
+ coordinates use Y up; reference image coordinates use Y down. Compare reports
288
+ actual A and B rectangles separately; unavailable measurements are null. The
289
+ canonical view also reports `cameraSync` and `activeCamera`. These
290
+ are the same current cameras used by the local controls, rather than a second
291
+ simulated view. Camera commands still use the documented viewport CSS pan.
292
+
293
+ The CLI waits up to10 seconds for the page's actual acknowledgment. `queued` or
294
+ `dispatched` is not success; non-applied commands exit2 and may carry
295
+ `timedOut:true`. Reuse the same `--request-id` and original request to obtain its
296
+ receipt without applying it twice. `--wait-ms 0` returns the queued receipt;
297
+ `--wait-ms` is bounded to30000. A timeout does not mean an already delivered action
298
+ was rolled back. Tokens stay in private tool state, not URLs or command output.
299
+
300
+ ## Keep and share an inspection
301
+
302
+ The view link stores the current numeric inspection in a bounded, versioned
303
+ `#review=` fragment. It includes the exact loaded source/model binding, selected
304
+ animation/time and reference selection, appearance/layers, camera sync and the
305
+ active pane, and each canonical camera center with visible bounds. Reloading a matching link restores a paused view;
306
+ it does not restart an idle loop or submit a review. Coordinates adapt to the
307
+ current viewport instead of replaying another window's CSS pan offsets.
308
+ Historical links without a sync mode restore with sync off to preserve their
309
+ saved views; fresh inspections start with sync on. Legacy modeling/motion
310
+ workspace selections are accepted without restoring a top-level workspace switch. A link
311
+ for a different source binding is refused.
312
+
313
+ Ordinary form inputs, selected panels, view state and unsent feedback/checkpoint
314
+ choices are kept in browser local storage for the same source. The draft scope
315
+ includes the source hash (`single:<id>:<sourceHash>`; compare uses both sides'
316
+ id and hash), so two revisions of one package keep separate drafts. Opening a page
317
+ deletes no draft; saving evicts the least-recently-written drafts beyond 32, but
318
+ never one written within the last 24 hours. Browser
319
+ Settings (below) remain, while a source-bound appearance override does not
320
+ carry over. URL view state takes precedence over the locally saved view. Each
321
+ Inspector section's own scroll position is part of this source-bound draft.
322
+
323
+ Local recovery compares the original submitted server-session fingerprint,
324
+ including its update time. It cannot replace a newer server review: conflicting
325
+ or corrupt saved data remains available to download, with an explicit warning;
326
+ recovering a conflicting draft requires choosing a pending review. Storage
327
+ errors never count as a successful save. Nothing is written to the server or
328
+ input package merely by restoring a page. Browser storage is convenient local
329
+ recovery, not a durable backup; export feedback separately when it must be kept.
330
+ Control capabilities are excluded from links and browser persistence.
331
+
332
+ ## Workspace settings and panels
333
+
334
+ The left, right and bottom panels are resizable. Drag a separator, or focus it
335
+ and use the arrow keys (Shift for larger steps) and Home/End. Escape, a
336
+ cancelled pointer or window blur during a drag returns the previous size. The
337
+ stage keeps a usable minimum. When the window is too narrow, the left and then
338
+ the right panel, and when it is too short, the Inspector, become explicit
339
+ drawers opened from buttons above the stage instead of shrinking to nothing. Responsive clamping
340
+ never overwrites the desired size, so a wider window restores it.
341
+
342
+ Settings, in the lower left, holds browser defaults: Theme, Sync cameras by
343
+ default, Repeat idle by default, and Reset layout. Defaults seed a new
344
+ inspection only. A view link or source draft keeps its own appearance, camera
345
+ sync and idle choice, and restoring it neither changes the saved defaults nor
346
+ starts playback. Reset layout restores panel sizes and nothing else.
347
+
348
+ These defaults and desired panel sizes are one versioned, closed record in
349
+ browser `localStorage` (`rig-review-preferences/1`). It holds no source path,
350
+ hash, feedback, review decision or capability. The first load migrates the
351
+ earlier same-origin appearance preference. If storage is denied, unavailable
352
+ or the record is corrupt, the page uses defaults and every control keeps
353
+ working for that page; a corrupt record is refused rather than overwritten
354
+ until you change a setting. Several open tabs merge their writes per field, so
355
+ changing the theme in one tab does not undo a panel size changed in another.
356
+ Separate browser profiles keep separate preferences.
357
+
358
+ Browser storage is per origin. Managed opens share the front's one origin, so
359
+ Settings, panel sizes and drafts carry across reopens. `--direct` and foreground
360
+ serving use per-port origins and keep them separate.
361
+
362
+
363
+ ## Inspect a part
364
+
365
+ The right Parts explorer selects an actual model slot on a specific single/A/B side. All rows follow the public held snapshot's exact draw-order z, including hidden/null slots. Search matches the exact source slot names: a row is shown when its name contains the typed text, case-sensitively, with no case folding or normalization. Arrow keys/Home/End move between result buttons, Enter/Space selects, and Escape returns to search. Whole rig restores full drawing. Selecting a drawn row fits its conservative world bounds within the existing camera zoom range; Sync cameras applies the same normalized camera action to every visible pane, while independent mode changes only that selected pane. Hidden/empty geometry reports `PART_FIT_UNAVAILABLE` and keeps the camera.
366
+
367
+ Solo preserves the full held pose's resolved clips and filters only drawing. In context, the whole rig remains visible with an amber dashed conservative selection box; this box is geometry/clip bounds, not exact source-alpha coverage. Selection changes no track, skin, set, motion, source asset or review decision. The selected slot's side/mode survives source-bound links and local drafts. The Rig inspector shows actual posed mesh counts or region corners, page/region, current skin/set context and declared attachment candidates. **Use slot for feedback** explicitly prefills the target; selecting a row alone submits nothing.
368
+
369
+ The pinned published `@rig-c/play`1.12.0 supplies `slotIdentity()` and frame generation. The explorer atomically retains the public identity snapshot with the same-generation held frame: exact attachment key, owning skin, active set/skin, full z order and explicit hidden/no-attachment/timeline-null/set-hide reasons. Hidden/null slots keep null keys with their reported reason; declarations and texture/UV names never replace that public result. An identity-known attachment need not have a visible primitive or positive fit bounds. The current UI follows its selected slot as animation changes, updating the displayed identity to the actual held frame.
370
+
371
+ Source-bound links/drafts and explicit feedback preserve that side/slot plus exact attachment/owning skin/set/active skin/null reason. A restored explicit target is checked after restoring its animation/time/set and drawing; `PART_IDENTITY_MISMATCH` rolls back rather than silently substituting another key. Older slot-only links remain readable and resolve from the current authoritative snapshot. Runtime generation is current observation/feedback evidence, never a persistent identity. Named vertex weights and original source alpha remain unavailable in this inspection; packed pages are not original part images.
372
+
373
+ This explorer is inspection. Producer-backed optimization and candidate Apply/undo/export remain separate work; the explorer does not generate mesh, rig or motion bytes.
374
+
375
+ ## License
376
+
377
+ MIT. See [LICENSE](LICENSE).
package/docs/DESIGN.md ADDED
@@ -0,0 +1,72 @@
1
+ # Human rig inspection and modeling touch-up direction
2
+
3
+ ## Product boundary
4
+
5
+ The delivery unit is an existing output folder or an optional curated review package handed from an agent to a person. This reusable app owns inspection, reference comparison, annotations and feedback export. Upstream compiler, splitter and runtime remain their respective tools. The existing producing agent needs no change to open its output here.
6
+
7
+ The person uses one inspection workspace with **View**, **Rig**, **Playback** and **Feedback** sections. Rig covers setup geometry, source art, pivot/joint placement, meshes, weights, draw order and angle coverage; Playback covers frame intervals, reference video/poses, contact/deformation and audio inspection. Motion is strictly **view, annotate and export**: time/angle controls navigate inspection. This app does not author animation keys, retime motion or write `motion.json`. Future modeling touch-up remains in scope as a design goal; it is not implemented by this starter.
8
+
9
+ ## Implemented foundation
10
+
11
+ The CLI opens `review.json` or an existing output directory. The directory adapter builds a manifest in memory from `skeleton.*`, `motion.json`, `report.json`, source video, contact strips and canonical pose-sequence evidence. It never changes delivered output. A compiled model uses the published play runtime. Raw Spine output is evidence with playback explicitly unavailable. Source-only data never becomes a fabricated playback success.
12
+
13
+ Inspection supports local references, checkpoint context, runtime playback when a real compatible model exists, available angle/audio facilities and explicit review state. The canonical `rig-pose-sequence/1` format documented by `@rig-c/pose` preserves frame times, person IDs, joint states/coordinates/confidence and optional camera metadata. The demo pose is an authored synthetic transport example, not extracted from footage.
14
+
15
+ Feedback has an inclusive integer frame interval, target `{kind: bone|slot|rig, name}`, category `defect|quality`, and one-line summary. Optional context records the checkpoint, time, reference/person and camera/axis state. The person can export data and issue-ready Markdown; this app neither posts issues automatically nor writes proposed motion keys. Notes must be useful to the producing agent without interpreting a free-form key-edit JSON blob.
16
+
17
+ Identity, revision and content hash bind every decision. A submission also records separate exact rig/motion hashes and paths, packageRevision, viewerVersion and installed runtimeVersion; absent source stays null. The producing agent can retrieve the saved JSON with the read-only feedback CLI after the UI closes. Opening never means reviewed. The person explicitly approves, requests changes or returns to pending, independently of checkpoint outcomes. Changed inputs do not inherit prior approval. Feedback writes remain outside source, are bounded and atomic, and require the app's exact Origin.
18
+
19
+ ## Modeling touch-up direction
20
+
21
+ Future modeling interactions should select actual parts, bones, pivots and mesh vertices in the posed view, inspect weights and ordering, and adjust bounded setup geometry with before/after comparison. Angle inspection needs declared axes and measured coverage; one scalar animation dial does not establish compound view synthesis.
22
+
23
+ The modeling requirements to preserve are local mesh-density adjustment, edge/curvature refinement, direct source-image comparison, a chest-region center/radius control, an integrated parts viewer with preset switching, and a face/body angle deformation sweep. These need actual geometry/source evidence and explicit acceptance criteria; they are not implemented by illustrative controls or a successful parser load.
24
+
25
+ Any future modeling edit needs provenance, undo/redo, explicit units and an exportable patch identifying the original hash and authoritative authoring target. A named adapter must apply it and rerun the owning tool's validation. Compiled-model rewrites are insufficient when source files are authoritative; conflicts must be named. This is a future modeling requirement, not permission to build a motion authoring tool.
26
+
27
+ ## Acceptance progression
28
+
29
+ 1. Delivery and feedback: one command opens unchanged output; declared assets load; explicit frame/target/category feedback saves and exports; changed source invalidates approval.
30
+ 2. Runtime inspection: a real gated artifact plays and seeks through the public runtime, survives reference and Inspector section changes and names missing dependencies. Source-only fixtures cannot close this item.
31
+ 3. Modeling touch-up: a bounded real geometry edit round-trips into authoritative source, reruns its gate and presents measured before/after evidence with undo/conflict controls.
32
+
33
+ ## Screen prototype
34
+
35
+ [design-prototype.html](design-prototype.html) preserves the interactive screen concept. It is **illustrative**, with sample controls/data, separate from the implemented app. Its older motion key/curve editing concept is superseded by the current view/annotate/export boundary. It does not demonstrate completed mesh, motion, angle or audio editing.
36
+
37
+ ## Inspection strips
38
+
39
+ An inclusive annotation interval can also become a derived frame strip. The single view selects one declared source video; compare retains its explicit shared video and chooses a producer clock basis for each interval. A batch preserves separate feedback intervals and side provenance. Source rows run down the sheet and requested producer frames run across it. A vertical batch repeats this grid rather than merging overlapping annotations.
40
+
41
+ Exact report `frameTimes` selects the requested seconds. Actual video decoding selects the nearest presented PTS (ties earlier), which drives all rig rows through the same published player/poser and fixed seek step as inspection. Requested frames, decoded PTS and each side's independently known frame remain distinct data. Missing clocks do not become a guessed rate or an empty rendered cell: PNG names the regeneration requirement and JSON can preserve an unmapped selection.
42
+
43
+ The export uses dedicated real canvas players and leaves the live playhead/session alone. PNG pixels, full source identity/footer and JSON are derived output outside the input packages. Exporting never approves a review or authors motion. Browser download controls share the bounded backend plan; a CLI can render and write new external files without providing a filesystem endpoint to the browser. Real CLI canvas/video tests do not establish browser WebGL or interactive presentation parity.
44
+
45
+
46
+ ## Implemented slot inspection and public identity snapshots
47
+
48
+ The right Parts explorer provides source-hash-bound slot selection, held-pose clipped Solo, Whole/context, conservative bounds fit, a diagnostic selection box, actual mesh/region counts and reproducible selection context. This is inspection rather than geometry editing. All rows use the public held identity snapshot's exact z order, including hidden/null slots. Current source-bound links/drafts and control observations retain side/slot/mode.
49
+
50
+ Published Play1.12.0 supplies a frozen `slotIdentity()` snapshot coherent with `posedFrame().generation`. Review retains copied frame/identity together only after equal-generation and source-slot/order checks, uses the player's default public filter, and never seeks/reposes/changes physics merely to read identity. Exact owning skin/key, active set/skin and null reasons come from that snapshot, not a unique-declaration or texture match. A resolved identity can remain known without visible geometry or fit bounds. Clip declarations do not invent keys when the API reports no drawable attachment.
51
+
52
+ Live selection follows a slot. A source-bound bookmark captures its exact identity tuple at that pose; restore validates it after applying time/animation/set and drawing, with transactional rollback on a named mismatch. Old slot-only bookmarks resolve through current public identity. Generation belongs to current readback/feedback, not a stable source target. Named weights and original part alpha remain unavailable without an exact original-source mapping. A conservative bounds box and counts are not source-faithfulness or physics validation.
53
+
54
+ A future optimization operation sends explicit source-bound selection/bounds to the owning producer. That producer returns a new package revision/hash and provenance including producer version/request identity; Review never regenerates rig, motion or mesh bytes. Candidate Apply/undo/export requires its own implemented contract and acceptance, rather than being inferred from the explorer.
55
+
56
+ ## Workspace geometry and browser preferences
57
+
58
+ Left, right and bottom panels have pointer and keyboard separators with separator semantics: orientation, the controlled panel, an accessible name and the actual reachable minimum/maximum/current pixels. Arrow keys act only when the separator itself has focus, so text-entry arrows are never intercepted. A drag holds pointer capture; cancel, lost capture, blur, Escape and disposal restore the previous desired size and release it.
59
+
60
+ The allocator measures the layout rectangle minus stage chrome and keeps a 240×180 CSS-pixel stage minimum. It shrinks side panels proportionally toward their own minima; when the width still cannot hold them it turns the left, then the right panel, and when the height cannot hold it the Inspector, into a native modal drawer opened from a trigger outside it. Effective sizes are computed from desired sizes and never written back; a separator inverts the allocator so a clamped pixel value is not saved as the desktop desire.
61
+
62
+ Browser defaults live in one closed, versioned record: theme, default camera sync, default idle repeat and the three desired panel sizes. Each field carries a logical stamp, so stale whole-record writes from other tabs converge per field without wall-clock ordering; an equal-stamp disagreement quarantines only that field until an explicit edit repairs it. Denied, unavailable or corrupt storage falls back to in-page defaults. The legacy same-origin appearance key migrates once, only when no new record exists. Reset layout changes geometry only.
63
+
64
+ Content state stays source-bound. Each Inspector section's x/y scroll offset is captured before its panel hides and restored after it shows, after two frames of layout, and again when its content extent changes. Restoring clamps to the current extent without replacing the remembered offset, and intentional user scrolling supersedes a pending restore. Offsets are kept in the source draft's UI state, never in the global record or a copied view link.
65
+
66
+ Browser storage is origin-scoped. Managed opens (the default) share one loopback front origin that routes `/instances/<key>/…` to each worker, so Settings, sizes and drafts carry across reopens; `--direct` keeps per-port origins. Because one origin cannot isolate its scripts, writes require a per-instance capability handed off only in the printed `#cap=` fragment and checked by both the front and the worker; a fragment-less copied link is read-only. Draft scopes carry the source hash, so revisions of one package never clobber each other's drafts.
67
+
68
+ ## Comparison eligibility
69
+
70
+ A layout is offered only for inputs that actually loaded and rendered. One pure decision (`src/comparison-eligibility.ts`) takes load outcomes, never declared paths: the rig counts after its first successful draw, an image after `decode()`, a pose after it parses, and a video after `seeked` at the held time with a decoded frame. A new video stays pending until then; a held time outside its duration is refused as `REFERENCE_OUT_OF_DOMAIN`, never clamped.
71
+
72
+ Single review keeps the requested layout in the source-bound draft and shows it when usable; otherwise the most useful loaded single pane is shown. Unavailable layout buttons stay visible, disabled, with the named reason as their description, and no placeholder pane stands in for a missing input. Two-build comparison keeps its identity and domain checks: a failed side leaves the survivor as a static single pane with the failed side named, feedback needs the settled pair, and a real zero-duration domain allows static inspection without playback. Modeling-candidate comparison is reported as unavailable (no producer handoff); no candidate UI exists. Remote observation reports the requested and effective layouts and each reason.