mirror of
https://github.com/penpot/penpot.git
synced 2026-06-09 17:02:05 +00:00
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>
3.4 KiB
3.4 KiB
Frontend Plugin API Runtime Subtleties
Type declarations vs runtime
- The Plugin API is a public facade over internal frontend/common data. Do not expect Plugin API property names, value shapes, or behavior boundaries to match internal CLJS attrs or helper APIs; inspect the relevant proxy and internal code path before using Plugin API observations in production internals or tests.
plugins/libs/plugin-types/index.d.tscontains TypeScript declarations only. Runtime objects are CLJS proxies built underfrontend/src/app/plugins/*.cljswithobj/reify.shape.cljsbuilds shape proxies with hidden ids and per-property CLJS implementations.library.cljsbuilds library proxies such asLibraryComponentProxy.shape.cljs,library.cljs, and related namespaces break circular dependencies with mutable nil vars patched fromapp.pluginsat load time. If a proxy constructor appears nil, check the patching path infrontend/src/app/plugins.cljs.
Key Domain Namespaces
app.common.types.component(aliasedctk) — component predicates:instance-root?,instance-head?,in-component-copy?,is-variant?app.common.types.container(aliasedctn) — container/tree operations:in-any-component?,get-instance-root,get-head-shape,inside-component-main?app.common.types.file(aliasedctf) — file-level operations:resolve-component,get-ref-shape
Runtime initialization and permissions
- The frontend initializes
@penpot/plugins-runtimeonly afterfeatures/initializeand only when featureplugins/runtimeis active. It also installs the runtimeisPluginErrorpredicate into frontend error handling. - Manifest parsing expands write permissions to read permissions (
content:write=>content:read, etc.). Permission checks also allow the all-zero plugin id and the hard-coded MCP plugin id. - Manifest URL origin differs by manifest version: v1 clears the path; v2 joins
.to the plugin URL. Existing plugin ids are reused by matching manifest name and host. - The MCP plugin id is defined in
app.plugins.registerto avoid a circular dependency with workspace MCP code.
Proxy behavior
- Public Plugin API objects are lightweight handles, not durable snapshots. Most getters locate fresh state from
app.main.store/stateusing hidden$id,$file,$page, etc. not-validlogs by default but throws when the plugin flagthrowValidationErrorsis enabled. The MCP execute-code handler deliberately enables that flag while running code.naturalChildOrderingandthrowValidationErrorsare stored per plugin under[:plugins :flags plugin-id ...]; changing default behavior affects automation and MCP diagnostics.- Plugin data is stored under keyword namespaces: private data uses
(keyword "plugin" plugin-id), shared data uses(keyword "shared" namespace).
Events and history
- Plugin listeners are watches on the global store and callbacks are debounced about 10ms. Callback exceptions are caught and logged so plugin code does not crash the app.
selectionchangecallbacks receive arrays of shape id strings, whilefilechange,pagechange, andshapechangereturn proxies.contentsavefires only when persistence status transitions to:saved; it calls the callback with no value.- Plugin history
undoBlockBegincreates a workspace undo transaction with a JSSymbol;undoBlockFinishcommits that symbol. Missing finish eventually relies on the workspace transaction timeout.