The retarget lane shipped server-side in #5893 (POST /api/rigging/models/:id/retarget, GET /api/rigging/clips) but has no UI. client/src/components/media/RigPanel.jsx still only offers the auto-skin action, so the only way to animate a rigged character today is to call the endpoint by hand.
This is specifically the control that drives a retarget. #5894 covers the separate concern of making an already-animated record selectable and playable by the CoS and Eidoverse avatar surfaces.
Why the diagnostic/write split needs UI, not a default
mode defaults to diagnostic on purpose: the head-zone cleanup edits skin weights a human never reviewed, so a run measures the proposal first and the user opts into the edit second. Without UI that second step is unreachable — a user can never see "this would re-bind 340 of 12,000 vertices, cap is 240" and decide. Surfacing that number is the whole point of the two modes; a panel that just posts mode: 'write' throws the design away.
Scope
- A clip picker in
RigPanel.jsx fed by GET /api/rigging/clips, disabled with an explaining line when the library is empty (clips are user-dropped GLB files under data/rigging/clips/, so an empty roster is the normal first-run state — not an error).
- An
apiRigging.js wrapper pair (listRiggingClips, retargetImageTo3dModel) plus its client/src/services/README.md row.
- Run the retarget in diagnostic mode, render the returned
summary (proposedCleanupVertices / cleanupCapVertices / cleanupOverCap, clip, clipDuration, sampledFrames, maxJointTranslation), and offer an explicit "apply cleanup" action that re-runs in write mode. When cleanupOverCap is true, say so and explain that a write run will refuse rather than offering an action that cannot succeed.
- Render the measured refusals verbatim — the server already returns the sentence naming the numbers (
RIGGING_SKELETON_INCOMPATIBLE, RIGGING_RETARGET_GATE_FAILED), so the panel must not collapse them into a generic failure toast.
- Persist nothing new: the record's
retarget field already carries status/summary/error, so the panel reads it the way it reads rig today.
- Show the published animation once ready (
retarget.assetPath) rather than leaving the user to guess whether the run landed.
Acceptance criteria
- With an empty clip library the retarget control explains why it is unavailable instead of rendering a dead picker.
- A diagnostic run shows the proposed cleanup, the cap, and the motion numbers; nothing is written.
- The write action is reachable only after a diagnostic run, and is refused-with-reason (not offered) when the proposal is over cap.
- A gate refusal reaches the user as the server's own sentence.
- Component tests cover the empty-library state, the diagnostic → write handoff, the over-cap branch, and the refusal rendering, following the existing
RigPanel.test.jsx conventions.
Files/areas: client/src/components/media/RigPanel.jsx, client/src/services/apiRigging.js, client/src/services/README.md, related tests.
Related to #5893
The retarget lane shipped server-side in #5893 (
POST /api/rigging/models/:id/retarget,GET /api/rigging/clips) but has no UI.client/src/components/media/RigPanel.jsxstill only offers the auto-skin action, so the only way to animate a rigged character today is to call the endpoint by hand.This is specifically the control that drives a retarget. #5894 covers the separate concern of making an already-animated record selectable and playable by the CoS and Eidoverse avatar surfaces.
Why the diagnostic/write split needs UI, not a default
modedefaults todiagnosticon purpose: the head-zone cleanup edits skin weights a human never reviewed, so a run measures the proposal first and the user opts into the edit second. Without UI that second step is unreachable — a user can never see "this would re-bind 340 of 12,000 vertices, cap is 240" and decide. Surfacing that number is the whole point of the two modes; a panel that just postsmode: 'write'throws the design away.Scope
RigPanel.jsxfed byGET /api/rigging/clips, disabled with an explaining line when the library is empty (clips are user-dropped GLB files underdata/rigging/clips/, so an empty roster is the normal first-run state — not an error).apiRigging.jswrapper pair (listRiggingClips,retargetImageTo3dModel) plus itsclient/src/services/README.mdrow.summary(proposedCleanupVertices/cleanupCapVertices/cleanupOverCap,clip,clipDuration,sampledFrames,maxJointTranslation), and offer an explicit "apply cleanup" action that re-runs inwritemode. WhencleanupOverCapis true, say so and explain that a write run will refuse rather than offering an action that cannot succeed.RIGGING_SKELETON_INCOMPATIBLE,RIGGING_RETARGET_GATE_FAILED), so the panel must not collapse them into a generic failure toast.retargetfield already carries status/summary/error, so the panel reads it the way it readsrigtoday.retarget.assetPath) rather than leaving the user to guess whether the run landed.Acceptance criteria
RigPanel.test.jsxconventions.Files/areas:
client/src/components/media/RigPanel.jsx,client/src/services/apiRigging.js,client/src/services/README.md, related tests.Related to #5893