- In `shaper::refresh`, replace full document walks with `changed_shaper_groups`,
diffing `before` and `after` layers and short-circuiting on pointer-equal (`Arc::ptr_eq`)
subtrees in O(1).
- Return whether Shaper groups rebuilt or modified artwork (`Result<bool>`),
allowing `Session::edit` to skip the second `cmd::objects_on_path::refresh`
call when no artwork was modified.
- In `textwrap::refresh`, check pointer equality on layers against `before.layers`,
skipping untouched layers in O(1) and checking only modified layers.
Rarely set fields move into one optional boxed NodeExtras, and large NodeKind payloads (images,
live shapes, blends, envelopes) are boxed. Every object, and every undo step that copies it,
shrinks: a 50k-path move keeps ~24.5 MB per step instead of ~40 MB. Files stay byte-identical
(Node keeps its flat JSON through a borrowed Serialize and a from-wire Deserialize, no flatten),
and loading is slightly faster. Undo and redo reuse their step's size estimate instead of
walking the document again (8.3 -> 5.6 ms for a 50k-path move).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmMmc1J8XeNLxhRihWv9cc
# Conflicts:
# crates/engine/src/cmd/graph.rs
# crates/svg/src/export.rs
Text Wrap and the variable bindings share checked_ids_or_selection in
cmd/mod.rs instead of each keeping its own copy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>