Files
penpot/plugins
Alonso Torres 014ec34249 🐛 Fix system crash on grid element deletion of a component (#10956)
* 🐛 Add regression test: main-side component edits break copy swap slots

Reproduces the :missing-slot referential-integrity failure ("Shape has been
swapped, should have swap slot") that crashes files with component copies.

Root cause: reordering or deleting a nested sub-head IN THE MAIN of a component,
while copies exist, does not propagate swap slots to the copies. find-near-match
matches a copy's sub-heads to the main's children by POSITION, so once the main's
order changes the copies' shape-refs no longer match their position and, lacking a
swap slot, fail referential-integrity validation.

- Copy-side edits are handled correctly (characterization tests, pass today).
- The two main-side tests fail today with :missing-slot and go green once the
  sync assigns swap slots to copies on a main reorder/delete.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* 🐛 Fix integrity crashes from copy/main child-order divergence

Copy sub-heads were matched to their main's children purely by position
(find-near-match), while several code paths reorder or mutilate one side
only. Any of them made file validation fail with :missing-slot ("Shape
has been swapped, should have swap slot"), crashing the workspace on the
next validated commit, or persisting a corrupt file whose later edits
crash. Reproduced live: deleting a sub-head inside a copy (which only
hides it) and then reflowing the copy's grid moved the hidden (cell-less)
child to the front of :shapes, knocking every sibling out of its
positional slot.

Four fixes, one per divergence path:

- validate/check-required-swap-slot: a sub-head whose shape-ref is still
  a child of the near main parent is a REORDER (the component sync
  realigns it), not a swap; a swap slot is required only when the ref
  points outside the near main parent (a real swap). This matches how
  the sync engine itself pairs children (by shape-ref, not by position).
  comp-processors/fix-missing-swap-slots (migration 0019) is aligned:
  adding slots to merely-reordered sub-heads would freeze them out of
  normal synchronization.

- changes/:reorder-children now refuses to alter the child structure of
  component copies unless allow-altering-copies is set, mirroring the
  is-valid-move? rule of :mov-objects; that structure is owned by the
  component sync engine. Grid reflows emitted this change type with no
  guard. pcb/reorder-grid-children also skips copy grids producer-side.

- layout/reorder-grid-children keeps children that participate in no
  cell (hidden or absolute positioned) at their original index instead
  of lumping them at the front: moving them gratuitously changed their
  z-order and, in copies, broke the positional matching. Note :shapes
  stays reversed relative to the sorted cell order for in-cell children.

- logic/generate-delete-shapes: deleting shapes from inside a main
  (without deleting the main root, whose copies keep working against the
  deleted component) now also deletes the copy shapes that reference
  them, transitively (copies of copies) and across all pages of the
  file, so no dangling shape-refs remain. Skipped for
  allow-altering-copies flows (component swap replaces the shape and the
  sync reconciles copies via swap slots). Cross-page removals build
  redo/undo changes against that page's objects directly, since the
  changes-builder mounts only the current page; their undo mov-objects
  carry allow-altering-copies so restoring inside a copy is not rejected
  by the new guard.

The regression tests assert the fixed semantics: main-side reorders and
deletes keep copies valid, the previously crashing full chain (unvalidated
main reorder + later copy edit) stays healthy, :reorder-children cannot
scramble copies, and reorder-grid-children keeps cell-less children in
place. The namespace is now also registered in the JS test runner.

AI-assisted-by: Claude Opus 4.8 (1M context)

*  Extend composable slot cases to the grid-reflow crash sweep

Case D (CopySubheadDeletePreservesSlots) now sweeps which copy sub-head
is deleted (first or last) and whether the copy root is resized
afterwards, forcing a grid reflow: the reflow used to move the hidden
(cell-less) child to the front of the copy's children, shifting every
sibling out of its positional slot. Deleting the FIRST sub-head masked
the bug (moving it to the front is a no-op), which is why the case
passed before this sweep. The foundation layout is grid accordingly.

Case E (MainReorderKeepsCopySlots) no longer hangs the app now that a
main-side reorder leaves a valid file, so its warning docstring is
replaced: it runs as a routine test (verified headless, passing) and is
safe in a "run all".

SlotIntegrity's doc is updated to the new validator semantics (a slot is
required only for real swaps, not reorders); the positional alignment it
asserts remains the correct, stronger steady-state invariant for these
cases. The lockfile change materializes the playwright devDependency
already declared in package.json.

AI-assisted-by: Claude Opus 4.8 (1M context)

* 📚 Record copy/main order-divergence invariants in memories

Swap-slot semantics (membership, not positional; slots only for real
swaps), the copy-structure guards on :mov-objects/:reorder-children, the
grid reorder stability for cell-less children, and the main-side delete
propagation in generate-delete-shapes.

AI-assisted-by: Claude Opus 4.8 (1M context)

* 🐛 Add adjustements to the code

---------

Co-authored-by: Michael Panchenko <michael.panchenko@oraios-ai.de>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-30 13:25:03 +02:00
..

Penpot Plugins

What can you find here?

We've been working in an MVP to allow users to develop their own plugins and use the existing ones.

There are 2 important folders to keep an eye on: apps and libs.

In the libs folder you'll find:

  • plugins-runtime: here you'll find the code that initializes the plugin and sets a few listeners to know when the penpot page/file/selection changes. It has its own README.
  • plugins-styles: basic css library with penpot styles in case you need help for styling your plugins.

In the apps folder you'll find some examples that use the libraries mentioned above.

pnpm run start:styles-example

Open in your browser: http://localhost:4202/

Run Penpot sample plugins

This guide will help you launch a Penpot plugin from the penpot-plugins repository. Before proceeding, ensure that you have Penpot running locally by following the setup instructions.

In the terminal, navigate to the penpot-plugins repository and run pnpm -r install to install the required dependencies. Then, run pnpm run start to launch the plugins runtime.

After installing the dependencies, choose a plugin to launch. You can either run one of the provided examples or create your own (see "Creating a plugin from scratch" below). To launch a plugin, Open a new terminal tab and run the appropriate startup script for the chosen plugin.

For instance, to launch the Contrast plugin, use the following command:

// for the contrast plugin
pnpm run start:plugin:contrast

Finally, open in your browser the specific port. In this specific example would be http://localhost:4302

A table listing the available plugins and their corresponding startup commands is provided below.

Sample plugins

Plugin Description PORT Start command Manifest URL
poc-state-plugin Sandbox plugin to test new plugins api functionality 4202 pnpm run start:plugin:poc-state http://localhost:4202/assets/manifest.json
contrast-plugin Sample plugin that gives you color contrast information 4202 pnpm run start:plugin:contrast http://localhost:4202/assets/manifest.json
icons-plugin Tool to add icons from Feather 4202 pnpm run start:plugin:icons http://localhost:4202/assets/manifest.json
lorem-ipsum-plugin Generate Lorem ipsum text 4202 pnpm run start:plugin:loremipsum http://localhost:4202/assets/manifest.json
create-palette-plugin Creates a board with all the palette colors 4202 pnpm run start:plugin:palette http://localhost:4202/assets/manifest.json
table-plugin Create or import table 4202 pnpm run start:table-plugin http://localhost:4202/assets/manifest.json
rename-layers-plugin Rename layers in bulk 4202 pnpm run start:plugin:renamelayers http://localhost:4202/assets/manifest.json
colors-to-tokens-plugin Generate tokens JSON file 4202 pnpm run start:plugin:colors-to-tokens http://localhost:4202/assets/manifest.json
poc-tokens-plugin Sandbox plugin to test tokens functionality 4202 pnpm run start:plugin:poc-tokens http://localhost:4202/assets/manifest.json

Web Apps

App Description PORT Start command URL
plugins-runtime Runtime for the plugins subsystem 4200 pnpm run start:app:runtime
example-styles Showcase of some of the Penpot styles that can be used in plugins 4201 pnpm run start:app:styles-example http://localhost:4201/

Creating a plugin from scratch

If you want to create a new plugin, read the following README

License

This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.

Copyright (c) KALEIDOS INC Sucursal en España SL

Penpot is a Kaleidos open source project