Three changes from sherlock's Playwright debug (docs/sherlock-+port-bug.md):
1. Select the freshly-placed port. placePortAt now sets
state.selection = {kind:"port", id:port.id} before render() so the
inspector switches to the port panel and the .selected halo makes
the new circle visible — fixes m's "+Port does nothing" perception
(the port WAS being created server-side; it just rendered invisibly
stacked under an existing one and the inspector stayed on the device).
2. Snap-to-edge dedup. snapToDeviceEdge now takes the existing ports
on the device; if the computed (xOff, yOff) lands within 8px of a
peer on the same edge, slide along the edge in 16px steps until a
free slot is found. Eliminates pixel-perfect port stacks.
3. startDrag closure-capture. onUp asynchronously referenced
e.currentTarget after pointerup nulled it, throwing a TypeError
in the console on every click-only device selection. Capture
dragTarget in the outer closure and use that inside add/remove.