Two bugs in the handles, and the second one meant a peg still had no movable
pivot — which is the complaint pegs exist to answer.
`.peg-grab` was drawn LAST, on top of the four scale corners, and at r=7 against
corners spanning 5.2 to 8.8 from the middle it swallowed the inner half of each
one. It is drawn first now, at r=5, so it stops short of them.
And dragging a peg's cross is a TRANSLATE, not a new pivot. It writes the peg's
`pos`, and a peg is a parent, so everything under it comes along — `peg.mjs` said
so all along: "dragging the peg cross moved its child — dx=18.000". That is the
right behaviour for the drag and no way to say "put the pivot here", so a peg
could be made with its pivot wherever `nest/peg` happened to put it and never
moved again.
⌥-drag now relocates it, which is After Effects' pan-behind split. `nest/repivot`
moves the peg and solves
pinv(child)' = local'⁻¹ · local · pinv(child)
for each child, because only `local(peg) · pinv(child)` reaches a child, so
preserving that product holds the picture exactly still — verified as a drift of
0, both in `nest-test` and through real ⌥-pointer events in `peg.mjs`. The
child's own channels are never touched, so this works over a measured child,
which is the case the peg exists for.
Refused on a peg whose position is animated, rather than quietly wrong: the
compensation depends on the peg's own transform, so a keyed position wants a
different `pinv` on every frame and one stored matrix is not it. The message says
to put a peg over it instead.
One edit at the end of the drag, not per pointermove: a repivot moves nothing on
screen by construction, so only the cross needs to follow the pointer.
603 CLJS tests, and peg.mjs and onion.mjs pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HkinzDz1VtahZVsujAGRBD
A peg was reachable and not usable. It is a `:group`, so it draws nothing, and
both of the stage's ways in key off a DRAWN op: `pick/choose` hit-tests the ops,
so a click never found it, and `handles` hangs its box, corners and turn knob off
`pick/bounds-of`, so it got only the pivot cross — which is `pointer-events:
none`. So the pivot `gesture/refusal` tells you to make could be made, and then
only typed at in the inspector, and reselected only from a timeline row.
Every assertion about the maths passed throughout, which is the point: nothing
under `domain/` can see this.
So the handles fall back to a fixed-size rosette about the peg's own origin —
cross to move, knob to turn, four corners to scale. Fixed size, in stage pixels,
because there is no drawing for it to be proportional to. The cross gets a
transparent `.peg-grab` disc rather than taking the handler itself, since
`.pivot` must stay `pointer-events: none` everywhere else: it is a mark on the
picture and must not eat a click meant for the shape under it.
`test/browser/peg.mjs` asserts the handles as DOM and drags through them with
real pointer events, because that is the half a unit test cannot reach: a peg
appears with no drift at all, is selected, renders one knob, four corners and no
box, and dragging its cross moves its child by exactly the drag.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HkinzDz1VtahZVsujAGRBD