openpencil/tools
Kayshen-X 947a8aa356 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.
2026-05-11 21:30:06 +08:00
..
check-jian-boundaries.sh feat(shell-native): extend widget stack to iOS + Android cargo check 2026-05-10 10:16:36 +08:00
check-wasm-bundle.sh build(step-1b): tools/check-wasm-bundle.sh — local A3 bundle gate 2026-05-09 21:44:00 +08:00
check-widget-boundary.sh feat(shell): selection handles + drag-create + per-node flags + LayerPanel polish 2026-05-11 21:30:06 +08:00
convert-locales.py fix(shell-core): convert-locales handles multi-line + double-quoted values 2026-05-10 19:04:38 +08:00
fetch-skia-artifact.sh build(step-1b): add tools/fetch-skia-artifact.sh (Variant A release path) 2026-05-09 21:04:00 +08:00