24179 Commits

Author SHA1 Message Date
Francis Santiago
6839b16735 📚 Document subpath configuration and migration 2026-10-09 12:26:57 +02:00
Alejandro Alonso
cda19e0d51
⬆️ Use Wasm EH and SjLj for render-wasm with Rust 1.99 (#12235)
Rust 1.91 links emscripten with JS exception handling, which cannot
mix with SUPPORT_LONGJMP=wasm. Bump the devenv to Rust 1.99 (native
Wasm EH) and align EMCC_CFLAGS / SKIA_BINARIES_URL with a Skia
-ljmp-wasm prebuild so +simd128 no longer hits invoke_*V TypeErrors.
2026-10-09 11:53:28 +02:00
Miguel de Benito Delgado
5afdeffa5f
♻️ Move frame type constants to webgl.cljs (#11949)
* ♻️ Move some constants out of app.render-wasm.api

- Moved to aapp.render-wasm.wasm where they can be imported
  without pulling half the editor with them
- Renamed one for consistency

* 🔥 Delete unused function, update docstring
2026-10-09 11:22:19 +02:00
Pablo Alba
76e9c132ef
✨ Send more enriched events to nitrate log (#12210)
* ✨ Send more enriched events to nitrate log

* ✨ Fix mr
2026-10-09 10:58:05 +02:00
María Valderrama
7745c1f052
✨ Make the whole current plan card clickable (#12238) 2026-10-09 10:46:33 +02:00
Alejandro Alonso
4eec950b96 Merge remote-tracking branch 'origin/staging' into develop 2026-10-09 10:00:13 +02:00
Alejandro Alonso
dd76faf17b Merge remote-tracking branch 'origin/main' into staging 2026-10-09 10:00:00 +02:00
Andrey Antukh
9b8dde2954
✨ Add rust toolchain setup on rust scripts (#12237) 2026-10-09 09:54:55 +02:00
Andrey Antukh
bcb7a8329c Merge remote-tracking branch 'origin/develop' into develop 2026-10-09 09:01:39 +02:00
Andrey Antukh
473a59b134 Merge remote-tracking branch 'origin/staging' into develop 2026-10-09 09:01:12 +02:00
María Valderrama
d0351a3ee2
✨ Add event name for create attributes (#12165)
* ✨ Add event name for create attributes

* 📎 Code review
2026-10-09 08:50:35 +02:00
Andrey Antukh
a36193e54b 🐛 Pass team-id in binfile v3 import tests
`resolve-and-link-libraries` asserts `::bfc/team-id`, and
`enable-backend-asserts` is now enabled in `scripts/ci` and
`tests-backend.yml`. Without a team-id, the v3 import tests
error instead of passing. Production always provides the
team (RPC import, management and debug routes); mirror that
in the tests.

Update `mem:backend/testing` and `mem:scripts/ci` with the
`enable-backend-asserts` behavior.

Verified with `./scripts/ci backend`: 1235 tests, 6384
assertions, 0 failures.

AI-assisted-by: deepseek-v4.1-flash
2026-10-08 17:33:40 +00:00
Andrey Antukh
ff4c4a4a34 📎 Backport .github and scripts/ci from develop 2026-10-08 19:24:58 +02:00
Luis de Dios
fad083314d
🎉 Expose per-side stroke width in the plugin API (#11994)
* ✨ Add stroke side-width helpers to common types

Add set-width-to-all-sides, set-width-to-single-side and side-width to
app.common.types.stroke, mirroring the border-radius helpers in
app.common.types.shape.radius. They back the per-side stroke width API
and are covered by unit tests.

AI-assisted-by: deepseek-v4.1-flash

* ✨ Round-trip per-side stroke widths through the plugin API

Add strokeWidthTop/Right/Bottom/Left to format-stroke and parse-stroke so
reading shape.strokes exposes the per-side widths and writing a stroke
back preserves them instead of collapsing to the uniform width.

parse-stroke prefers a live stroke proxy's hidden raw state when present,
so a commit only rewrites the attributes the plugin touched.

AI-assisted-by: deepseek-v4.1-flash

* ✨ Add per-side stroke widths to the plugin API types

Add the four per-side members to the Stroke interface and to the
borderWidth and dimension token property lists, and refresh the generated
API surface used by the plugin test suite.

AI-assisted-by: deepseek-v4.1-flash

* ✨ Expose per-side stroke width on the stroke proxy

Add strokeWidthTop/Right/Bottom/Left to StrokeProxy. Reads fall back to
the uniform strokeWidth; strokeWidth now writes all four sides, like
borderRadius writes all four corners; each side setter writes only its
own key and the top side also updates strokeWidth.

Reject per-side writes on shapes other than boards and rectangles with a
validation error naming the property, and expose the raw stroke map
through a hidden proxy member so a commit preserves untouched attributes.

AI-assisted-by: deepseek-v4.1-flash

* ✨ Support stroke width tokens per side in the plugin API

Token attribute expansion now accepts the uniform strokeWidth property and
expands it to the four per-side attributes, mirroring the design tab.
shape.tokens exposes the per-side keys and reports strokeWidth when all
four sides share one token, and applyToken routes through the shared
expander.

AI-assisted-by: deepseek-v4.1-flash

* 📚 Document per-side stroke width in the plugins changelog

Note the new Stroke members, the restored strokeWidth token property and
the shape-type validation in plugins/CHANGELOG.md.

AI-assisted-by: deepseek-v4.1-flash

* 🐛 Fix some minor issues

---------

Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
2026-10-08 18:09:06 +02:00
Eva Marco
9218448b29 🐛 Keep selection handles visible when editing measures
Changing position, size or rotation from the measures menu triggered
the bounding box cloaking, which sets the transform to :move for one
second. While it lasts the selection outline and handles are hidden,
and since the size badge is now shown during :move, only the badge
stayed on screen. Stop cloaking from these inputs so the selection
stays visible while its geometry is edited.

Closes #12162

AI-assisted-by: claude-opus-5-5
2026-10-08 17:58:36 +02:00
Eva Marco
dc6fc46a36 🐛 Refresh X/Y inputs when a flex child toggles absolute position
The measures menu is memoized and only re-renders when one of the
compared values changes. :layout-item-absolute was not among them, so
switching a flex child between static and absolute kept the X/Y inputs
in their previous disabled/enabled state until another compared value
changed or the shape was selected again.

AI-assisted-by: claude-opus-5-5
2026-10-08 17:58:36 +02:00
Alejandro Alonso
11bf8d1f67
🐛 Collapse text selection to edge on Left/Right (#12226)
With a range selected, Left and Right were moving one character from
the focus instead of collapsing to the selection start or end. Match
standard text-field behavior and keep Shift+arrows extending.

Closes #12207
2026-10-08 16:50:10 +02:00
Belén Albeza
2c08a065bb
🔧 Auto-detect architecture to set the right skia binaries for test scripts (#12224) 2026-10-08 16:50:00 +02:00
Elena Torró
91b30d387f
🐛 Fix Ctrl+Home/End moving only to the paragraph edge (#12225) 2026-10-08 16:37:05 +02:00
Alejandro Alonso
78d26243c7
🐛 Sync Design tab styles on text-editor click caret (#12205)
Click caret placement only moved the selection and never refreshed
current styles, so the Design tab kept showing the previous fill and
typography. Place the caret through place_caret_from_position so
StylesChanged is emitted like keyboard navigation.

Closes #12195
2026-10-08 15:42:21 +02:00
Belén Albeza
2ffdd16038
🐛 Fix typing after a triple-click in the text editor (#12218)
A triple-click can leave the browser selection outside the hidden input
surface (it reads as "\n"). That selection is not editable, so typed
keys fired keydown but no input, and never reached the WASM editor.

Move the selection back into the surface before plain keys reach the
browser, and after double and triple clicks.

Closes #12187

AI-assisted-by: claude-opus-5-5
2026-10-08 15:35:28 +02:00
Belén Albeza
c2a1d70476
🐛 Fix strikethrough tooltip shortcut (#12221)
The Strikethrough button tooltip showed Alt+Shift+Ctrl+5, but the
shortcut is Alt+Shift+5. The tooltip used meta-shift, which adds
Ctrl (Cmd on Mac). Build it with shift so it matches the command.

Closes #12217

AI-assisted-by: claude-opus-5-5
2026-10-08 15:26:11 +02:00
Elena Torró
29ea6e49b6
🐛 Step over whole emojis in the text editor (#12202)
Caret moves, deletes and caret rects use grapheme boundaries.
Closes #12188
2026-10-08 13:42:25 +02:00
Andrey Antukh
d6f62d8d7d ⬆️ Update opencode on devenv dockerfile 2026-10-08 13:13:10 +02:00
Andrey Antukh
c17eb2a804
📚 Document disable-email-verification team auto-add behavior (#11425)
The configuration docs claimed it is not possible to add someone to a
team without them accepting an invitation email, but the backend
auto-adds existing profiles to the team when the email verification
is disabled (create-invitation in app.rpc.commands.teams-invitations).

Replace the incorrect sentence with an accurate exception note, add
the missing disable-email-verification entry to the "Other flags"
reference, document the CLI-created profile auto-add behavior in the
docker getting-started guide, and describe the shortcut in the
developer authentication docs.

Closes #11424

Reported in #11376

AI-assisted-by: glm-5.3-flash
2026-10-08 12:44:39 +02:00
andrés gonzález
3779dd99ae
💄 Remove the duplicated MCP Server link from Key Features (#12193)
Product already links to the same page.

Refs #12132

AI-assisted-by: claude-opus-5-5
2026-10-08 12:00:54 +02:00
andrés gonzález
85ba670278
💄 Unify copy in the shortcuts settings, modals and sidebar (#12174)
* 💄 Unify copy in the shortcuts settings, modals and sidebar

Use one term per concept across the customizable shortcuts UI: "Custom"
and "Customize" for user changes, "shortcut" for the keys, and "Reset"
with "default" to go back to the defaults.

Rewrite the conflict message so it says what saving does, fix typos and
grammar, unify the empty states and rename the font size actions.

Give the reset all button, the search placeholder, the import column,
the invalid import error and the edit button aria-label their own keys,
and update the e2e tests to the new copy.

Closes #12173

AI-assisted-by: claude-opus-5-5

* 💄 Reword the shortcuts sidebar link and custom empty state

The sidebar link no longer names a settings section that does not exist
in Penpot. The empty state of the custom tab now says there are no
custom shortcuts and points to the All tab by name.

AI-assisted-by: claude-opus-5-5
2026-10-08 12:00:17 +02:00
Eva Marco
c9146a30ab
🐛 Keep layer search and tree scroll across sidebar tab switches (#12117)
Switching sidebar tabs unmounts the layers panel. The search text and
filters lived only in component state, so on return the still open
search bar was treated as a new search and cleared. Keep that state per
file in a session atom, as the assets panel already does.

Closing or clearing the search mounts the tree container again, but the
restore hook only ran on mount or page change, so the tree went back to
the top. Let use-restore-scroll take an active? flag so each list
restores its own position whenever it is shown again, and drop the
saved search position on close so a new search starts at the top.

Closes #11972

AI-assisted-by: claude-opus-5-5
2026-10-08 11:45:35 +02:00
Marina López
28c576e1e6
🐛 Preserve admin console URL path segments (#12196) 2026-10-08 11:38:13 +02:00
andrés gonzález
df0402b626
💄 Align the Help Center footer with penpot.app (#12179)
The footer had a hardcoded 2025 copyright and links that no longer match
penpot.app. The year is now set at build time. Sections, links and
social icons follow the current penpot.app footer, and the grid wraps
into two rows to fit the new sections.

Closes #12132

AI-assisted-by: claude-opus-5-5
2026-10-08 10:59:36 +02:00
Eva Marco
31a079a7a2
🐛 Allow creating tokens when the file is its own tokens source (#12182)
Setting the local file back as the tokens source, or unlinking the
library that was the source, stored the file's own id in
:tokens-source. ensure-tokens-lib only created a tokens-lib when
:tokens-source was nil, so a file without a tokens-lib never got one,
and every token change was dropped without any error.

Now ensure-tokens-lib also creates the tokens-lib when the source is
the file itself, which fixes files already in that state, and
set-tokens-source removes the attribute instead of storing the file's
own id.

Closes #12180

AI-assisted-by: claude-opus-5-5
2026-10-08 09:55:18 +02:00
Eva Marco
41efc3558a
🐛 Fix nested libraries vanishing from the libraries modal (#12031)
* 🐛 Fix nested libraries vanishing from the libraries modal

The "This file" tab nests each linked library under a linked library
that uses it, taken from the team shared files. A library with such a
parent was removed from the top level and only drawn under its parent
if the parent was at the top level itself. So:

- In a chain of two or more levels (file -> A -> B -> C), C was lost.
- In a cycle (A uses B and B uses A), both had a parent, none was at
  the top level, and both were lost.

The libraries showed while the shared files were loading, and vanished
when they arrived.

Build the list as a tree instead: direct libraries and libraries not
used by any other linked library are roots, nested libraries follow
their parent at any depth, and each library is added only once, even
when the dependencies form a cycle.

AI-assisted-by: claude-opus-5-5

* ♻️ Move the linked libraries tree to sort-linked-libraries

Extract the code that orders the linked libraries as a tree from the
file tab to a public function, so it can be tested on its own. Add
tests for a cycle with a direct root, a cycle with no root, a deep
chain and no dependencies yet.

AI-assisted-by: claude-opus-5-5

* ♻️ Use connected-to to find library parents when sorting

The caller already computes :connected-to for each linked library, the
reverse index of the dependencies map. sort-linked-libraries rebuilt
the same relation from dependencies. Read it from :connected-to
instead, so the function takes only the libraries and the component
memo no longer depends on dependencies.

Reword the cycle-without-root test to say it models the unknown direct
flag, and add tests for a diamond, a self-dependency, a cycle of three
without root, a parent that is not a linked library, and for no
library being lost or duplicated.

AI-assisted-by: claude-sonnet-5-5

---------

Co-authored-by: Andrés Moya <andres.moya@kaleidos.net>
2026-10-08 09:54:46 +02:00
Elena Torró
b3a62eeb4b
🐛 Paint layout children in canvas z-order in vector exports (#12171) 2026-10-08 09:07:33 +02:00
Belén Albeza
cb1118f904
🎉 Preserve Penpot's internal formatting when pasting (v3) (#12137)
* 🎉 Copy v3 editor text with its styles in a payload

Copy and cut in the v3 text editor put the styled selection in a
hidden span of text/html. Add its codec and the clean-up for pasting
into another file or team.

Refs #12047

AI-assisted-by: claude-opus-5-5

* 🎉 Paste text copied in Penpot at the v3 editor caret

Text copied in Penpot keeps its styles when pasted at the caret. The
first paragraph keeps the target's attrs; later ones bring their own.
The context menu paste now shares the keyboard paste path.

Refs #12047

AI-assisted-by: claude-opus-5-5

* 🎉 Paste text copied in Penpot on the canvas with its styles

Refs #12047

AI-assisted-by: claude-opus-5-5

* 🎉 Paste copied text shapes at the v3 editor caret with styles

Copied shapes now record their team. Shapes that are not texts paste
nothing into the editor.

Refs #12047

AI-assisted-by: claude-opus-5-5

* ✨ Replace custom fonts in shapes pasted across teams

Refs #12047

AI-assisted-by: claude-opus-5-5
2026-10-07 18:20:03 +02:00
Miguel de Benito Delgado
f252439d84
♻️ Factor GL context initialization out of canvas init (#11931)
* ♻️ Factor GL context initialization out of canvas init

- fixes setting of gl-context-handle to be after _init so an exception
  does not leave invalid state behind.
- api.init-canvas-context uses a new webgl.init-context!
- this decouples config from init, so we can reuse it in benchmarks
- some helpers move to the webgl ns

AI-assisted-by: muse-spark-1.3, deepseek-flash, glm-5.3

* 🐛 Release acquired GL context when renderer init fails

- Properly release resources if init fails
- Rethrown exceptions with the original cause
- Fix vacuous check in clear-canvas

AI-assisted-by: muse-spark-1.3, deepseek-flash, glm-5.3

* ✨ Test webgl context init and release

AI-assisted-by: muse-spark, glm-5.3

* 💄 Address review comments

* 💄 Drop `!` from some function names
2026-10-07 18:19:34 +02:00
Alejandro Alonso
30127e57a0
🐛 Skip spurious save when missing font cannot be remapped (#12164)
fix-deleted-fonts committed text and typography changes even when no
replacement font existed, so opening a page with a missing custom font
rewrote identical content and cleared position-data on every visit.

Only emit changes when the remapped content or typography actually
differs. Add focused unit coverage for the no-replacement and remapped
paths.

Closes #12151
2026-10-07 18:03:51 +02:00
Eva Marco
dcf0b197eb
🐛 Fix inner shadow covering the shape in the SVG renderer (#12141)
* 🐛 Fix inner shadow covering the shape in the SVG renderer

The svg filter components moved to the `*` syntax (#9448), but
filter-entry* still built their props as a raw JS object with
kebab-case keys. Raw JS props skip the kebab to camel conversion, so
the components read nil for filterId and filterIn, and no filter
primitive got its result or in2. With the chain broken, an inner
shadow was blended over the empty flood instead of the shape, hiding
the fill (and a board's children) under the shadow color. Exports
using these filters broke the same way. Pass the props in camelCase.

Closes #11967

AI-assisted-by: claude-opus-5-5

* ♻️ Build filter entry props with mf/props

Use mf/props instead of a raw JS object so the props keep the
kebab-case keys used across the codebase. The macro converts them to
camelCase at compile time, so there is no runtime cost, and the
comment about the manual conversion is no longer needed.

AI-assisted-by: claude-opus-5-5
2026-10-07 15:45:10 +02:00
Alonso Torres
f62f4024be
Merge pull request #12090 from oraios/mcp-global-plugin
🎉 Add global plugins and enable MCP across workspaces
2026-10-07 15:38:17 +02:00
alonso.torres
9517f2ac94 ♻️ Type the MCP plugin with the repo plugin types
Link the MCP plugin to plugins/libs/plugin-types instead of the
published 1.5.0 package, which lacks PenpotMgmt. The repo types
declare the penpotMgmt global, so the inline import in index.d.ts
goes away and the plugin checks against the API it runs with.

Type Context.management as PenpotMgmt without on/off, so new
management methods need no extra listing.

Mark the background plugin mock in load-plugin.spec.ts as
NonNullable: createPlugin can return undefined, and the build
type-check failed on it.
2026-10-07 15:21:16 +02:00
alonso.torres
a125977fce 🐛 Keep the MCP plugin working without penpotMgmt
Penpot only exposes penpotMgmt to plugins running with global
scope. Without it, the MCP plugin failed on load when registering
its workspacechange listener.

When penpotMgmt is missing, the plugin now builds the workspace
metadata from the open file and skips the listener. execute_code
receives penpotMgmt as null and waits for layout updates as before.
The tool description and instructions tell agents that only the
open file is available when penpotMgmt is null.
2026-10-07 14:58:32 +02:00
alonso.torres
dfff188409 ✨ Ask users to allow global plugins on install
A plugin with "scope": "global" keeps running across the dashboard
and files and can find and open the user's projects and files. Any
manifest could ask for this without the user being told.

Add the allow:global permission. The plugin registry adds it to
every manifest with global scope, so the install dialog lists it,
and a plugin that becomes global in a later version shows the
permissions update dialog again. The permission is valid in the
shared schema, which the backend checks when saving, and in the
runtime manifest schema. The built-in MCP plugin declares it too.

Document the scope property and the new permission for plugin
developers.
2026-10-07 14:58:32 +02:00
alonso.torres
a381761aa4 ✨ Add project and file creation to penpotMgmt
Add penpotMgmt.createProject({name, teamId?}) and
penpotMgmt.createFile({projectId, name}). Both require content:write,
trim the name and require 1 to 250 characters, and return the same
metadata as listProjects and listFiles without changing the current
view. When the new project or file belongs to the current team, the
dashboard state is updated so it shows up right away.

listProjects, listFiles and the creation methods share the summary
builders, so fileCount is now 0 instead of missing for new projects.

Update the plugin types, the MCP API docs and the MCP instructions.
2026-10-07 14:58:32 +02:00
alonso.torres
cf1fbb4d7c 🐛 Close plugins whose load is cancelled before they run
Unloading or replacing a plugin while its code was still being
fetched could leave a late instance running, or block reopening a
plugin that closed during startup.

Each load now carries an AbortController. Unloading, a newer load
of the same plugin or logout aborts it, and the plugin manager
closes itself on abort, also while it fetches its code.
createPlugin returns nothing for a plugin closed before its
sandbox exists, and failed loads clear their pending state so the
plugin can be loaded again.
2026-10-07 14:58:32 +02:00
alonso.torres
c4d2ca9cd1 ✨ Move the dashboard MCP menu to the page headers
Show the MCP menu next to the layout toggle in the header of the
projects, files and deleted pages, instead of in the sidebar. It
uses the secondary button style there and a visible dot colour
while disconnected.

Clicking the MCP button now toggles the menu instead of only
opening it. Dashboard events report "dashboard:header" as their
origin.
2026-10-07 14:58:32 +02:00
Dominik Jain
036f59266b 🐛 Discard cancelled plugin loads after unloading
Cause:
- Disabling MCP during script loading could leave a late plugin instance
  attached to a stopped controller, blocking connection after re-enabling.

Fix:
- Track pending loads and discard instances cancelled by unload.
- Prevent duplicate global loads and preserve newer replacement instances.
- Clear pending state after failures so a subsequent load can retry.

Validation:
- Pass 84 runtime tests, the runtime build, lint and formatting checks.
- Reproduce the race with a delayed script in the browser, then verify
  re-enabling restores the session ID, themed indicator and MCP execution.

AI-assisted-by: gpt-6
Signed-off-by: Dominik Jain <dominik.jain@oraios-ai.de>
2026-10-07 14:58:32 +02:00
Dominik Jain
2bb6cd557e ✨ Add project and file discovery to penpotMgmt
Discovery:
- Add listProjects and project-scoped listFiles with content:read checks.
- Return metadata from existing RPCs in dashboard and workspace contexts.
- Reject invalid IDs and preserve backend access errors.

Documentation:
- Keep usage in API type comments and include regenerated MCP API docs.
- Remove README implementation details and consolidate the changelog.
- Add usage information to the MCP server's initial instructions

Validation:
- Pass 8 frontend tests, 81 runtime tests and the runtime build.
- Verify both APIs through live MCP calls in dashboard and workspace.
- Pass type, lint, formatting and diff checks.

AI-assisted-by: gpt-6
Signed-off-by: Dominik Jain <dominik.jain@oraios-ai.de>
2026-10-07 14:58:32 +02:00
Dominik Jain
54cce66c13 ✨ Share the MCP connection menu with the dashboard
Connection status:
- Replace the dashboard's action-only link with an explicit connected
  label and indicator, using the theme's existing accent colour.
- Reflect the actual connection state independently of connection intent.

Shared controls:
- Extract the toolbar's MCP menu and styles into a shared component used
  by both the dashboard and workspace.
- Provide connection status, session ID, copy, connect and disconnect
  actions in both locations, preserving hover and keyboard behaviour.
- Keep dashboard and toolbar event origins distinct and update the
  dashboard connection instructions.

Validation:
- Confirm dashboard disconnect/reconnect, menu opening, keyboard
  navigation and Escape dismissal in the running app.
- Pass live compilation, targeted ClojureScript and SCSS lint,
  formatting checks and git diff checks.

AI-assisted-by: gpt-6
Signed-off-by: Dominik Jain <dominik.jain@oraios-ai.de>
2026-10-07 14:58:32 +02:00
Dominik Jain
be17fabc6a 🎉 Introduce global plugins and move MCP to application scope
Global plugin lifecycle:
- Add optional manifest scope "global" for plugins that remain active
  across dashboard and workspace navigation within one browser tab.
  Existing plugins retain workspace scope by default.
- Preserve global sandboxes when a workspace ends or another plugin
  loads, including when that other plugin fails to initialize.
- Avoid duplicate instances of an already-running global plugin.
- Close global plugins on explicit closure or logout, with idempotent
  cleanup of listeners, timers and UI resources. Global scope does not
  imply automatic startup or persistence across a browser reload.

Plugin API and navigation:
- Keep a permanently live penpot facade whose getters follow the current
  workspace, while user, theme and UI facilities remain available on
  the dashboard.
- Expose penpotMgmt to global plugins with workspace metadata,
  none/loading/ready status and workspacechange subscriptions.
- Add awaitable openFile(fileId, {teamId}) navigation in the current tab,
  defaulting to the current team and resolving only once the requested
  file and its active page are ready.
- Reject invalid identifiers, failed or superseded navigation and waits
  exceeding 30 seconds; release navigation watchers on completion.
- Guard workspace-bound operations while no workspace is ready and
  return null from root, currentFile and currentPage in that state.
- Require content:read for the initial management API. Callers must
  reacquire file, page and shape references after changing files.

MCP lifecycle and connection controls:
- Make MCP a global plugin and initialize its integrated controller per
  authenticated application session instead of per workspace.
- Load the enabled integration with a valid token on the dashboard as
  well as in a file, retaining its existing hidden-iframe implementation.
- Keep enabling and connecting separate: an enabled plugin starts idle,
  and each tab connects explicitly through dashboard or workspace
  controls.
- Preserve the iframe, WebSocket, heartbeat and reconnect machinery
  during navigation; disconnect on request and close on disable/logout.

MCP sessions and execution:
- Derive the short MCP session ID solely from currentUser.sessionId,
  excluding the file ID. Retain the SHA-256-based, 50-bit Base32 encoding
  to produce a stable, copyable 10-character identifier.
- Allow sessions without an open file and update workspace metadata over
  the existing connection without replacing its routing identity.
- Refresh context after the connection handshake so initialization does
  not leave stale file metadata.
- Expose penpotMgmt to execute_code, preserve execution storage across
  navigation and skip layout waits when no workspace is ready.
- Update API types, tool guidance, documentation and the changelog.

Validation:
- Cover authenticated startup, workspace survival, logout cleanup,
  readiness, navigation failure, duplicate loads and session routing.
- Verify that short IDs remain stable across files and reconnects and
  that disconnect cancels pending asynchronous ID initialization.
- Pass 16 focused frontend tests, 73 runtime tests, 30 MCP plugin tests,
  builds, TypeScript checks and frontend/runtime lint.
- Pass all 73 MCP server tests with --test-force-exit; the normal runner
  still stays alive after assertions finish.
- Verify a live dashboard-to-file-to-dashboard round trip without a
  document or iframe reload, preserving facade identity and storage.

AI-assisted-by: gpt-6
2026-10-07 14:58:32 +02:00
Alejandro Alonso
31000c3055
⚡ Enable rustc wasm SIMD for render-wasm builds (#12163)
Set RUSTFLAGS target-feature=+simd128 on the emscripten
target so our Rust code can autovec; Skia SIMD remains
via the -simd prebuild and EMCC -msimd128.
2026-10-07 14:52:46 +02:00
David Barragán Merino
6473dc85ab 👷 Notify new devenv images in penpotdev
Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-10-07 14:11:21 +02:00