Memories use a system of progressive disclosure: Starting from a root memory, memories reference other memories using explicit references. The new system of hierarchical memories replaces AGENTS.md files. GitHub #9215 Co-authored-by: Michael Panchenko <michael.panchenko@oraios-ai.de> Co-authored-by: Codex <codex@openai.com>
2.8 KiB
File Mutations: Changes and Undo Architecture
Penpot mutates file data through change records. A change set is both the persistence payload and the basis for undo/redo, so UI actions, tests, backend file updates, and library/file tooling should drive the production change pipeline instead of ad hoc object-map mutation.
Change shape
Each change is a map such as {:type ... :id ... :page-id ...}. Common families:
:add-obj,:mod-obj,:del-obj: shape lifecycle.:mod-objcontains:operations, commonly{:type :set :attr ... :val ... :ignore-geometry ... :ignore-touched ...}or{:type :set-touched ...}.:add-component,:mod-component,:del-component: component/library metadata.:add-children,:remove-children,:reg-objects: tree and object-map edits.:set-option,:add-page,:mov-page, and related file/page metadata changes.
Each transaction carries :redo-changes and inverse :undo-changes. The undo stack stores transactions and can move its index backward/forward.
changes-builder API
common/src/app/common/files/changes_builder.cljc (usually alias pcb) is the fluent builder. Start from (pcb/empty-changes <it> <page-id>) or (pcb/empty-changes nil <page-id>) for tests.
High-value builder operations:
pcb/with-page-id,pcb/with-objects,pcb/with-library-data: set context for following operations.pcb/update-shapes ids update-fn: emits:mod-objwith diff-derived:setops. Options include{:with-objects? true},{:ignore-touched true}, and{:attrs #{...}}.pcb/add-objects,pcb/change-parent,pcb/remove-objects,pcb/resize-parents: shape/tree edits.pcb/add-component,pcb/update-component,pcb/mod-component: component/library edits.pcb/set-translation? true: marks the whole change set as translation-only, which lets component sync skip expensive work.
Applying changes in tests
thf/apply-changes in app.common.test-helpers.files is the test analog of the production applier. It validates by default; pass :validate? false only for intentionally-invalid intermediate states.
The applier uses the same process-operation multimethod as production (common/src/app/common/files/changes.cljc), so tests that use it exercise production behavior.
:touched and geometry
For component touched semantics and sync groups, read mem:common/component-data-model. For the exact set-shape-attr / second-pass behavior during change application, read mem:common/file-change-validation-migration-subtleties. For transform-specific ignore-geometry behavior, read mem:frontend/workspace-transform-subtleties.
Inspection
To inspect what a UI action emitted, use mem:frontend/cljs-repl with the snippets in mem:common/component-debugging-recipes rather than adding temporary source instrumentation.