mirror of
https://github.com/penpot/penpot.git
synced 2026-10-30 06:20:32 +00:00
Fixes in the helpers that walk, instantiate, compare and absorb components
(Phase 3c).
- List each shape once in get-children-in-instance.
The recursion handed each child the vector built so far, ancestors
included, and the caller added what came back to its own copy of that
vector, so every ancestor came back once per child below it (F59). It now
threads one result through a reduce. Both callers, the repair of
:shape-ref-cycle and migration legacy-65, make a set of the result, so
they see the same shapes.
container-test counts the shapes of an instance.
- Walk every ancestor in has-any-copy-parent?.
It recursed with the parent's id where it expects a shape, so it only ever
looked at the direct parent (F63). It now walks up to the root, which is
its own parent. Users never saw it: every descendant of a copy is a copy,
so the direct parent gave the right answer.
container-test checks a shape two levels below the copy.
- Start a new copy without the remote-synced mark of its main.
Instantiating a component cleared the touched groups of the shapes it
copies from the main, but not :remote-synced, so a nested copy in the main
that had been synced from a remote main passed the mark on to every new
copy of the component, whose shapes refer to their near main (F18).
make-component-instance now clears it too.
container-test: a new copy of a component whose nested copy is touched and
remote-synced has neither mark.
- Compare the stored shapes of components by value.
mod-component marks a component modified when the change alters any of its
keys, which ctk/diff-components reports. For :objects, the shapes a
deleted component stores, it compared the ids found by walking :children,
a shape format older than the objects map, so it read nil on both sides
and never saw a change there (F96). It now compares every key, :objects
included, by value.
container-test: a change to a stored shape of a deleted component is a
difference; the same component is none.
- Keep nested copies and annotations of absorbed components.
When a file absorbs a library's components (the library is deleted, or a
binfile export merges libraries), each used component gets a main in the
file, a copy of the library main with the same ids (F89):
- every shape of that main lost its :shape-ref, so the copies nested in it
stopped being copies of anything;
- every head in it was pointed at the local file, even one whose component
lives in a third library;
- the component lost its annotation.
The main now keeps the refs of its nested copies, which with the same ids
point at the absorbed mains of the components they nest (a file that shows
a nested copy uses its component, so that one is absorbed too); only heads
of the absorbed library are pointed at the local file; the annotation is
copied. A variant still becomes a plain component.
absorb-assets-test: the nested copy of an absorbed main keeps its ref and
is a copy of the local component; the annotation stays.
- Say what the component placement rule checks.
invalid-structure-for-component? said it kept a shape out of its own
subtree, but its check compares components only: a plain shape moved into
its own child passes, and the move itself refuses it (F58). The comments
now say what it checks; the behavior stays, as the layers panel relies on
it to do nothing for such a drop (decision (s)).
Benchmark (lower quantile, ms; baseline / now / ratio):
deepest-edit-through-three-levels 35.2 / 17.0 / 0.48
edit-through-a-chain-of-dependents 68.9 / 33.5 / 0.49
library-update-into-copies 83.1 / 40.1 / 0.48
main-edit-among-many-shapes 4.8 / 2.1 / 0.43
main-edit-into-10-copies 1.8 / 0.8 / 0.47
main-edit-into-100-copies 15.4 / 8.3 / 0.54
main-edit-into-1000-copies 153.1 / 82.3 / 0.54
variant-switch-into-copies 32.1 / 18.6 / 0.58
AI-assisted-by: claude-opus-5-5