feat(shell): selection handles + drag-create + per-node flags + LayerPanel polish
Re-apply 4 reset commits (1854dfa6 → b94274c6) bundled with session
follow-ons. Native + web hosts share the new behavior end-to-end.
Selection + canvas interaction:
- bounded Frame drag now translates descendants too
- 8 selection handles with hover-cursor feedback
- thinner selection outline + smaller AA handles
- handle-drag resize for rect/ellipse/polygon/line/frame/text
- drag-to-create shapes / frames / text from the active tool
- per-NodeKind hit-test (oval / triangle / line slack / point-in-poly)
- rotation pivot is kind-aware (handles negative-size Lines)
Per-node flags (TS parity):
- Node.hidden / locked / collapsed / fill_type (moved off Document.ui)
- mutators gated by is_editable / is_subtree_editable so locked /
hidden subtrees can't be translated, resized, rotated, recolored,
or deleted as collateral
Multi-select + marquee + clipboard + keyboard shortcuts:
- selected_set + anchor; shift+click toggles set membership
- marquee rect-select with screen-px threshold + ADD-only shift
- copy / cut / paste / duplicate / nudge / reorder / select-all
- escape one-layer-per-press priority cascade (property-focus →
locale picker → shape picker → fill-type picker → chat → selection)
- Cmd-letter chord guards (!shift) so Cmd-Shift-letter doesn't fall
through to text input; !modifier guards on named keys
LayerPanel polish:
- hover-reveal eye/lock affordances (TS parity)
- Eye → EyeOff icon when hidden; Lock → LockOpen when unlocked
- locked Lock renders in warm orange
- chevron expand/collapse for container rows; collapsed subtree
hides from tree (paint/hit-test unaffected)
- `+` add-page button wired end-to-end (mints fresh id past
max_node_id + 1, names "Page N", overflow-safe)
- smaller, refined trailing icons (12 px @ 1.2 stroke)
- 18 px chevron-to-kind-icon gap
RenderBackend trait grew fill_oval / stroke_oval / fill_polygon /
stroke_polygon / rotate so both native and web backends can paint
the new node shapes.
Refactor:
- split native widget_host.rs (1799 lines) into spine + 7 sibling
submodules under widget_host/ to stay under the 800-line ceiling
- split web widget_host.rs into spine + paint + keyboard siblings
- amend tools/check-widget-boundary.sh + spec § 1.4 to allow
widget_host/* sibling files; tighten `// glue:` marker rule to
the immediately-preceding line (rustfmt-stable)
Stop-hook iterations addressed:
- allocator overflow guards (checked_add) on duplicate / paste /
add_page paths
- subtree-size precheck before any id mint in deep_clone
- hidden subtree skipped in paint AND selection overlay
- nested protected delete leak closed via is_subtree_editable
- per-FocusKind hex/numeric input gating; sticky `#` prefix on hex
- ScaleFactorChanged refreshes viewport from window.inner_size()
122 shell-core tests pass; cargo fmt --all --check clean;
cargo check --workspace clean; widget boundary check clean.