23969 Commits

Author SHA1 Message Date
Andrey Antukh
5baffdc213 📎 Update changelog 2.18.0 2026-09-23 10:41:57 +02:00
María Valderrama
3a5481c577
✨ Error page for instances without admin-console configuration (#11812)
* ✨ Error page for instances without admin-console configuration (#11649)

* ✨ Error page for instances without admin-console configuration

* 📎 Code review

* 🐛 Fix link nitrate-not-configured
2026-09-23 10:36:40 +02:00
Andrey Antukh
f56d4c56d9 Merge remote-tracking branch 'origin/main' into staging 2026-09-23 10:33:24 +02:00
David Barragán Merino
40ef4a90d5 📚 Document Admin Console setup for Docker (>= 2.18)
Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-22 20:00:54 +02:00
David Barragán Merino
d842b835d3 📚 Reorder menu entries for setup methods
Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-22 20:00:54 +02:00
David Barragán Merino
c26408a568 🐳 Deploy the admin-console service in the Docker Compose example 2026-09-22 19:58:15 +02:00
Andrey Antukh
70890bb900
🐛 Store audit initiator as plain string for shared-key callers (#11629)
Shared-key callers (exporter, admin-console) arrive as keywords on 
auth-key-id, so transit persisted them as ~:exporter while regular traffic
stored app. Coerce with d/name at the single origin so every audit and
telemetry copy carries a plain string. Adds regression tests for the origin
 and the push-audit-events path, including caller spoofing precedence.

Closes #11628 

AI-assisted-by: muse-spark-1.3-contributor
2026-09-22 19:44:44 +02:00
David Barragán Merino
ec62799793 ⬆️ Align mcp runtime image to the same Node version
Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-22 19:20:24 +02:00
David Barragán Merino
267134e779 ⬆️ Align mcp runtime image to the same Node version
Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-22 19:19:44 +02:00
David Barragán Merino
091fe456ad 🐳 Migrate media-processor image to DHI and wire up its build"
Migrate docker/images/Dockerfile.media-processor from ubuntu:26.04 to
dhi.io/node (Debian 13/trixie), which also drops the manual Node tarball
download since the base image ships it. The -dev tag stays as the final
image: fontforge, woff2 and the graphics libraries are needed at runtime.

scripts/build now assembles the release bundle under target/ (dist/ plus
the manifests and a generated setup script), the way the other modules do,
since esbuild leaves the runtime dependencies external. manage.sh gains
build-media-processor-bundle and build-media-processor-docker-image, both
wired into build-bundle and build-docker-images.

The CI workflows are intentionally left untouched: the module is still work
in progress and its images are not published yet, so this only enables
local builds."

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-22 18:52:05 +02:00
David Barragán Merino
d8d345d8bd 🐛 Install gzip for tar -xzf in mcp's pnpm install
Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-22 18:31:09 +02:00
bameda
0622680691 ♻️ Make sha mean the full commit SHA, short_sha the 12-char one
sha is the full 40-char SHA in build-bundle.yml's output and
build-docker.yml's new checkout-pinning input, matching git and
GitHub's own convention. short_sha stays internal to each workflow
for the S3 keys and image tags that already used it — build-bundle.yml
no longer exposes it externally since nothing outside consumed it.

No S3 key, image tag, or marker value changes anywhere.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-22 18:30:56 +02:00
David Barragán Merino
a534e49abc 🐛 Install gzip for tar -xzf in mcp's pnpm install
Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-22 18:29:52 +02:00
bameda
7525019120 ♻️ Make sha mean the full commit SHA, short_sha the 12-char one
sha is the full 40-char SHA in build-bundle.yml's output and
build-docker.yml's new checkout-pinning input, matching git and
GitHub's own convention. short_sha stays internal to each workflow
for the S3 keys and image tags that already used it — build-bundle.yml
no longer exposes it externally since nothing outside consumed it.

No S3 key, image tag, or marker value changes anywhere.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-22 18:29:52 +02:00
bameda
5901af4187 🐛 Restore Dockerfile.exporter wiped by a bad conflict resolution
Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-22 17:59:24 +02:00
Andrey Antukh
cc0941343d 📚 Add backend audit-log memory documentation
Add a new memory file documenting the backend audit log system:
purpose, storage schema, RPC producers, frontend ingestion,
webhooks/error-reporter/telemetry consumers, and Nexus archival.

Also wire a reference to it from the backend core memory so it
is discoverable through the memory graph.

AI-assisted-by: longcat-2.0
2026-09-22 15:49:46 +00:00
bameda
c6b8854311 🐛 Restore Dockerfile.exporter wiped by a bad conflict resolution
Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-22 17:33:03 +02:00
makesomethingshit
a9697fa582
🐛 Align WASM viewer layers during zoom (#11793)
* 🐛 Align WASM viewer layers during zoom

Keep the absolute WASM layer at the logical viewer size so its canvas CSS

box and hotspot SVG remain aligned as zoom changes.

Add a Playwright regression covering DOM bounds, drawing-buffer sizing, and

hotspot clicks at zoom 1 and below 1.

Closes #11689

AI-assisted-by: GPT-5
Signed-off-by: makesomethingshit <junsoo1172@gmail.com>

* 🐛 Align WASM zoom regression clicks

Use the rendered WASM DOM selector and design-space points mapped through

canvas bounds for visual clicks.

Wait for the canvas and SVG bounds before checking zoomed-out positions.

AI-assisted-by: GPT-5
Signed-off-by: makesomethingshit <junsoo1172@gmail.com>

* 🐛 Fix WASM zoom render wait

Use Screen2 coordinates for the visual interaction hotspot.

Wait for non-empty canvas pixels after each viewer render.

AI-assisted-by: GPT-5
Signed-off-by: makesomethingshit <junsoo1172@gmail.com>

* 🐛 Match WASM render marker in zoom spec

Require the expected frame screenshot marker before reading canvas pixels.

Keep resized buffers blocked until the new frame draws.

AI-assisted-by: GPT-5
Signed-off-by: makesomethingshit <junsoo1172@gmail.com>

* ✨ Run WASM zoom regression in DPR project

Move viewer zoom coverage under the render-wasm Playwright project.

Assert DPR-scaled buffers and use the stable Zoom out role.

AI-assisted-by: GPT-5
Signed-off-by: makesomethingshit <junsoo1172@gmail.com>

* 🐛 Simplify WASM render wait

Signed-off-by: makesomethingshit <junsoo1172@gmail.com>

* 🐛 Remove unreliable WASM viewer regression

Signed-off-by: makesomethingshit <junsoo1172@gmail.com>

---------

Signed-off-by: makesomethingshit <junsoo1172@gmail.com>
2026-09-22 17:13:28 +02:00
bameda
5e99e89e5b 🐛 Pin build-docker to the commit build-bundle actually bundled
_DEVELOP and _STAGING chain build-bundle.yml -> build-docker.yml,
both invoked with gh_ref as a branch name. Each reusable workflow
re-resolves that branch with its own checkout, at its own time.
build-docker's checkout happens minutes after build-bundle's, after
new commits can have landed on the branch, so it can compute a
different sha than the one build-bundle actually bundled and
uploaded to S3.

The Prepare job then requests a key that was never uploaded:

  fatal error: An error occurred (404) when calling the HeadObject
  operation: Key "penpot-sha-<newer-sha>.zip" does not exist

Expose build-bundle.yml's resolved sha as a workflow_call output and
thread it through _DEVELOP, _STAGING and _ADHOC as build-docker.yml's
new optional sha input, which pins its checkout instead of
re-resolving gh_ref. gh_ref keeps naming the branch tag to move.

_TAG is unaffected: it builds from a pushed git tag, which cannot
move the way a branch can, so build-bundle and build-docker already
agree on one commit there.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-22 16:37:06 +02:00
David Barragán Merino
0d2632fc8c 🐛 Create /usr/local/bin before extracting pnpm in DHI images
dhi.io/node:24.20.0-debian13-dev does not pre-create /usr/local/bin
the way a regular Debian image does, so tar -xzf ... -C /usr/local/bin
failed with "Cannot open: No such file or directory" right after the
checksum check passed. Introduced in #11790 when pnpm moved from
Corepack to a downloaded standalone binary.

Dockerfile.media-processor and docker/devenv/Dockerfile are unaffected:
both extract into /opt/node/bin, which already exists from the prior
Node.js install step.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-22 16:36:41 +02:00
bameda
9e472ee452 🐛 Pin build-docker to the commit build-bundle actually bundled
_DEVELOP and _STAGING chain build-bundle.yml -> build-docker.yml,
both invoked with gh_ref as a branch name. Each reusable workflow
re-resolves that branch with its own checkout, at its own time.
build-docker's checkout happens minutes after build-bundle's, after
new commits can have landed on the branch, so it can compute a
different sha than the one build-bundle actually bundled and
uploaded to S3.

The Prepare job then requests a key that was never uploaded:

  fatal error: An error occurred (404) when calling the HeadObject
  operation: Key "penpot-sha-<newer-sha>.zip" does not exist

Expose build-bundle.yml's resolved sha as a workflow_call output and
thread it through _DEVELOP, _STAGING and _ADHOC as build-docker.yml's
new optional sha input, which pins its checkout instead of
re-resolving gh_ref. gh_ref keeps naming the branch tag to move.

_TAG is unaffected: it builds from a pushed git tag, which cannot
move the way a branch can, so build-bundle and build-docker already
agree on one commit there.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-22 16:28:54 +02:00
David Barragán Merino
eba8f34f04 🔧 Release admin-console images on final tags 2026-09-22 15:53:53 +02:00
David Barragán Merino
1f4750a86f 🐳 Replace MailCatcher with Mailpit in self-hosting compose
Mirrors the devenv change in 3385a65 (docker/devenv/docker-compose.infra.yml).
Mailpit keeps messages in memory only (no persistence), consistent with
mailcatcher's previous behavior; the volume and MP_DATABASE env var are
left commented for anyone who wants to opt in later. UI port changed from
1080 to Mailpit's native 8025.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-22 15:52:33 +02:00
Andrey Antukh
2f679eaa0e
💥 Remove client-provided id from creation RPC commands (#11784)
The seven creation commands no longer accept an optional client
id: create-file, create-project, create-team,
create-team-with-invitations, upload-file-media-object,
create-file-media-object-from-url and assemble-file-media-object.
The server always generates the identifier; a sent id is ignored.

Malli maps are open and the RPC layer never strips unknown params,
so the handlers that would still honor an id (create-file,
create-project) now drop it explicitly. Internal callers that pass
remapped ids (project duplicate, binfile import) keep working.

Closes #11783

AI-assisted-by: muse-spark-1.3-contributor
2026-09-22 15:51:50 +02:00
David Barragán Merino
6498619f60 🐛 Create /usr/local/bin before extracting pnpm in DHI images
dhi.io/node:24.20.0-debian13-dev does not pre-create /usr/local/bin
the way a regular Debian image does, so tar -xzf ... -C /usr/local/bin
failed with "Cannot open: No such file or directory" right after the
checksum check passed. Introduced in #11790 when pnpm moved from
Corepack to a downloaded standalone binary.

Dockerfile.media-processor and docker/devenv/Dockerfile are unaffected:
both extract into /opt/node/bin, which already exists from the prior
Node.js install step.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-22 15:49:48 +02:00
Andrey Antukh
38212e757c
🐛 Bound decompressed size of JSON entries on binfile v3 import (#11607)
* 🐛 Bound decompressed size of JSON entries on binfile v3 import

Every JSON/text zip entry (manifest, files, pages, shapes, colors, components, typographies, tokens, plugin-data) was decompressed without any size limit, letting a small .penpot archive exhaust the backend heap (GHSA-qcw7-v626-g6cf). Only binary storage blobs were guarded. Reuse the existing size-limiting-stream guard on the text path: 20 MiB cap per entry, 200 MiB cumulative budget per import job, plus a cheap declared-size pre-check. Both limits are configurable and wired through the binfile, management and debug entry points. Adds zip-bomb regression tests for the file entry, the synchronous manifest read and the cumulative budget. Closes #11606

AI-assisted-by: muse-spark-1.3-contributor

* ♻️ Uniform binfile import limits behind init-limits

Move the binfile import limits to a single source of truth in app.binfile.common (default-* vars) and drop the duplicated entries from config/default; env overrides keep working through the schema. Resolve all limits once per job with init-limits (::max-size, ::total-max, ::current-size, ::max-object-size, ::max-zip-entries) instead of rebuilding the map per zip entry. Thread cfg as the first arg through the v3 readers, collapse read-plain-entry into read-entry, and give size-limiting-stream a single explicit-counter arity. v1 keeps using the compiled default (mechanical rename only). No behavior change. AI-assisted-by: muse-spark-1.3-contributor

* ♻️ Rename binfile limits to text-entry/binary-entry terms

Use text-entry/binary-entry vocabulary consistently across config keys, bfc input keys, default-* vars and the limits resolved by setup-limits (::max-text-entry-size, ::max-text-total-size, ::current-text-size, ::max-binary-entry-size). Rename init-limits to setup-limits. No behavior change. AI-assisted-by: muse-spark-1.3-contributor

* ♻️ Rename cumulative text counter and document binary limit

Rename ::current-text-size to ::accumulated-total-text-size for clarity and expand the default-max-binary-entry-size comment to match the other limit vars. No behavior change. AI-assisted-by: muse-spark-1.3-contributor

* ♻️ Harden binfile guards and prove budget accumulation

Add a regression test that only passes when text bytes accumulate across entries (budget between largest entry and summed total; verified red against a per-entry atom). Include the entry name in streaming-guard errors, count skipped bytes against the budget with a direct unit test, and forward all four limit keys in get-manifest. No behavior change. AI-assisted-by: muse-spark-1.3-contributor

* ♻️ Thread cfg through get-manifest

get-manifest now takes the caller cfg and resolves limits with setup-limits like the import job itself, instead of building a single-use mini-cfg from cf/get. No behavior change. AI-assisted-by: muse-spark-1.3-contributor
2026-09-22 15:16:59 +02:00
Andrey Antukh
2041473cc4
🐛 Add regression test for viewer zoom url loop (#11821)
Lock in the fix from 31b73460c3 (#11803) with a regression
test for the exact reported scenario: loading the viewer
with a URL that already contains `zoom=fill`.

At 2.18.0-RC5 `update-zoom-querystring` navigated without
any comparison, so the load sequence bundle-fetched →
zoom-to-fill → update-zoom-querystring → nav → navigated
re-ran forever and crashed the page with React error #185
("maximum update depth exceeded"). The guard added in
31b73460c3 breaks the cycle; the new test asserts that a
bundle fetch against a `zoom=fill` route emits no
navigation events.

Also updates the dashboard/viewer frontend memory to
document the guard and the loop it prevents.

AI-assisted-by: glm-5.3-flash
2026-09-22 14:58:32 +02:00
Andrey Antukh
96c44c4c30
🐛 Trim linked-library data in view-only bundle for share links (#11627)
* 🐛 Trim linked-library data in view-only bundle for share links

The anonymous get-view-only-bundle RPC merged each linked library whole,
exposing library pages the share link never granted. For share-link
permissions, each library is now reduced to the narrow data keys with
its own pages dropped and only the components referenced by the allowed
pages kept (nested references followed); membership bundles are
unchanged.

Closes #11617

AI-assisted-by: muse-spark-1.3-contributor

* 🐛 Resolve nested library components via main instance in bundle trim

Stored components carry no objects, so the transitive walk missed nested
components. Follow references through the main-instance subtree instead,
share the narrow data keys between the primary and library scopes, and
cover component filtering with a real-instance RPC test.

Review follow-ups F1-F3 for #11617

AI-assisted-by: muse-spark-1.3-contributor

* 🐛 Address review findings on view-only library trim

Hoist invariant refs out of the fixpoint, make the cross-library
main-instance fallback deterministic, pin the trimmed envelope,
and add RPC tests for disallowed-page isolation and
cross-library nesting.

Follow-ups to #11617

AI-assisted-by: muse-spark-1.3-contributor

* ♻️ Index libraries by id with d/index-by

Replace the manual into/juxt index with the shared helper.
No behavior change.

AI-assisted-by: muse-spark-1.3-contributor
2026-09-22 14:43:04 +02:00
Andrey Antukh
bc3cb4bddf
🌐 Complete Catalan translations in frontend (#11741)
* 🌐 Complete Catalan translations in frontend

Complete the Catalan (ca.po) locale to 100% coverage against en.po,
using es.po as support reference. Adds the 1439 missing entries
across workspace, dashboard, labels, shortcuts, subscription,
errors, modals and onboarding, keeping vosaltres treatment and
IEC/Termcat terminology consistent with the existing strings.
Normalizes placeholders and plural forms, drops the 14 stale
obsolete entries and canonicalizes the file with the repo
translations script.

Closes #11739

AI-assisted-by: muse-spark-1.3-contributor

* 📚 Add frontend translations memory with Catalan criteria

Record the PO workflow, the sync fuzzy-flag gotcha and the
Catalan glossary and tone agreed upon while completing ca.po,
and link the new memory from the frontend core routing.

AI-assisted-by: muse-spark-1.3-contributor

* 🔧 Add gettext to devenv image

Provide msgfmt and msgattrib in the dev environment for
checking PO translation files.

AI-assisted-by: muse-spark-1.3-contributor

* 🌐 Fix Catalan translations and add PO checker

Review of the missing-whitespace pattern found ~90 glued words
across 75 entries, plus 4 lost plural forms and 2 placeholder
mismatches verified against tr call sites. All fixed in ca.po.

Adds frontend/scripts/check-translations.js (vocabulary-free PO
QA: glued words, punctuation, placeholders, plurals) with
--self-test, wired as pnpm run check-translations and
documented in mem:frontend/translations.

AI-assisted-by: muse-spark-1.3-contributor

* 🌐 Multi-locale PO checker with word catalogs

Split the checker engine from its word lists: ca/es catalogs now
live in scripts/check-translations/words.<locale>.txt and all
messages are in English. Adds an es seed (calibrated to zero
errors) and fixes 7 typos it found in es.po. Universal checks
(placeholders, plurals, punctuation) run without a catalog.

AI-assisted-by: muse-spark-1.3-contributor

* 🌐 Merge PO checker into translations.js

Fold check-translations.js into translations.js as a check
subcommand reusing its locale helpers; word lists stay in
scripts/check-translations/words.<locale>.txt. Also fixes the
getopts stopEarly bug that made -l useless after the command
(sync -l ca synced every locale), drops dead lodash import
and code, unifies help and exit codes. Removes the
check-translations package alias; use translations.js
check -l <locale> with explicit -l.

AI-assisted-by: muse-spark-1.3-contributor

* 🌐 Keep unused placeholders out of the gate

Reverts the %s-stripping on unused auth.terms-privacy-agreement:
the links mirror its markdown sibling and a reactivation may
need them. Placeholder mismatches on #, unused keys now warn
instead of failing, and the rule is recorded in
mem:frontend/translations.

AI-assisted-by: muse-spark-1.3-contributor
2026-09-22 14:10:01 +02:00
Andrey Antukh
2255266d45
✨ Enable closed schemas for RPC methods (#11136)
* ✨ Enable closed schemas for RPC methods

* 🐛 Fix duplicate make-dummy-request test helper definition

The branch added a variadic DummyRequest/make-dummy-request pair but
left the pre-existing single-arg definition in place. Because it was
loaded last, zero-arg (make-dummy-request) calls added by
prepare-rpc-params and rpc-nitrate-test threw ArityException, which
broke 384 tests and caused 14 downstream assertion failures.

Remove the stale duplicate so the variadic definition is the only
one, and drop the now-unused yrq alias and duplicate yres alias.

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

* ✨ Add focused tests for make-dummy-request helper

Pin the call contract of make-dummy-request, which the suite uses
in three styles: no arguments, a single options map, and keyword
arguments. The helper's redefinition shadowing in 8ca95adb98 was
only caught by a full-suite run with hundreds of unrelated errors;
these tests fail locally in a focused --focus run.

Cover the zero-arg defaults, map and keyword overrides, the
:body-bytes -> ByteArrayInputStream wrapping, :body-stream
precedence, and cookie readback. Also clarify the docstring to
list all supported call styles.

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

* 🚑 Prevent RPC client params from overriding auth context

Strip qualified keys from decoded request params before merging
them with the server-built auth context, so transit bodies can
no longer override ::profile-id, ::auth-type or ::token-perms.
Adds a regression test proving the override and the fix.

AI-assisted-by: muse-spark-1.3-contributor

* 📚 Merge backend subtleties memories under generic name

Rename rpc-db-worker-subtleties to subtleties and fold in
http-storage-filedata-subtleties, so the name no longer
enumerates topics. Update all mem: references accordingly.

AI-assisted-by: muse-spark-1.3-contributor

* ✨ Add realistic tests for RPC auth override

Cover the transit wire vector and the real wrapped :get-profile
method with two database profiles, proving a session cannot read
another profile by smuggling :app.rpc/profile-id in the body.

AI-assisted-by: muse-spark-1.3-contributor

* ✨ Add e2e test for RPC auth context override

Parametrize rpcPost with contentType, accept and query so e2e
can send hand-written transit bodies without new dependencies.
The new test proves a transit-smuggled :app.rpc/profile-id no
longer overrides the session in get-profile. Also fix the demo
email assertion in auth-flow to the current uuid format.

AI-assisted-by: muse-spark-1.3-contributor
2026-09-22 14:03:02 +02:00
Andrey Antukh
476e25e371 Merge remote-tracking branch 'origin/staging' into develop 2026-09-22 13:44:56 +02:00
Eva Marco
01363be3a8
🐛 Fix text variant changing text content on variant switch (#11815) 2026-09-22 12:59:42 +02:00
Alonso Torres
efb10c82a8
🐛 Fix silent fail in plugin API interactions (#11781) 2026-09-22 12:56:35 +02:00
Alonso Torres
31b73460c3
🐛 Fix problem with viewer url (#11803) 2026-09-22 12:56:01 +02:00
Andy Li
67fb47a06b
🌐 Add translations for: Chinese (Traditional Han script)
Currently translated at 99.3% (2482 of 2497 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hant/
2026-09-22 12:37:46 +02:00
Ingrid Pigueron
832d628b22
🌐 Add translations for: French
Currently translated at 99.6% (2489 of 2497 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2026-09-22 12:37:13 +02:00
Andrey Antukh
b9bb62b497
🐛 Block MCP REPL server in multi-user mode and decouple its host (#11632)
The unauthenticated REPL server started in --multi-user mode whenever DEVENV or REPL_ENABLE was set, unlike developer tools which are already blocked there. Gate its creation behind shouldStartReplServer, mirroring the developer tools policy, so it never starts with --multi-user. Give it a dedicated PENPOT_MCP_REPL_HOST knob defaulting to localhost so it no longer inherits the main server 0.0.0.0 bind in Docker, keep the devenv script on 0.0.0.0, and document and test the new behavior. Closes #11631 AI-assisted-by: muse-spark-1.3-contributor
2026-09-22 12:17:59 +02:00
Andrey Antukh
b5274a4476
🐛 Bind MCP PluginBridge WebSocket to configured host (#11605)
The plugin WebSocket bridge ignored PENPOT_MCP_SERVER_HOST and bound all interfaces, 
exposing unauthenticated task dispatch to the network in single-user mode. Pass mcpServer.host 
into WebSocketServer, mirroring the ReplServer fix, so the bridge binds localhost by default and 
0.0.0.0 only on explicit opt-in. 

Closes #11603. 

AI-assisted-by: muse-spark-1.3-contributor
2026-09-22 11:53:23 +02:00
Shreyash Agare
3c821f1b1b
🐛 Restore the missing handler when aligning a one-handler node (#11789)
* 🐛 Restore the missing handler when aligning a one-handler node

A curve node with one of its handlers removed could not be switched
to aligned or equal: the collapsed handler stayed on the node, or the
line on the other side stayed a line, so nothing happened. Switching
to equal could even collapse the remaining handler.

Such a node now gets a mirrored handler on the other side, turning
that line into a curve when needed, also across the start node of a
closed path. Removing a handler also resets the node to independent.

Closes #11703

AI-assisted-by: claude-opus-5
Signed-off-by: Shreyash Agare <264953665+ShreyashAgare26@users.noreply.github.com>

* 🐛 Add some quality improvements

---------

Signed-off-by: Shreyash Agare <264953665+ShreyashAgare26@users.noreply.github.com>
Co-authored-by: Shreyash Agare <264953665+ShreyashAgare26@users.noreply.github.com>
Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
2026-09-22 11:21:47 +02:00
Andrey Antukh
d9e28fb1a4
🐛 Add authorization checks to WebSocket subscriptions (#11069)
Subscribe-file and subscribe-team handlers now verify the requesting
profile has read permissions on the target resource before creating
a subscription. Pointer-update handler now validates that the message
file-id matches the subscribed file-id before publishing.

Closes #11067

AI-assisted-by: mimo-v2.5-pro
2026-09-22 11:16:14 +02:00
Miguel de Benito Delgado
f0880cf3f9
🐛 Crop cached drop shadows to their filter bounds (#11801)
Snapshot only the scaled shadow bounds instead of the full reusable
filter surface. This avoids retaining viewport-sized textures for each
small shadow while keeping cached pixels valid when the surface is reused.

Use the bounded snapshot for both shape and frame shadow caches. Add
regression tests for rounding, scaling, surface limits, and snapshot reuse.

AI-assisted-by: gpt-6
2026-09-22 11:09:45 +02:00
Andrey Antukh
117c8db0bb Merge remote-tracking branch 'origin/staging' into develop 2026-09-22 10:24:30 +02:00
Eva Marco
683e0aed40
🐛 Fix ambiguation on import modal (#11747) 2026-09-22 10:23:38 +02:00
Andrey Antukh
5c22f5bfb7
⚡ Build the frontend bundle once for all E2E suites (#11792)
* ⚡ Build the frontend bundle once for all E2E suites

Merge tests-integration, tests-composable-suite and tests-plugin-api-suite
into one "CI: E2E" workflow. Each of the three ran its own full
frontend/scripts/build on every PR, so one PR paid the build three times.

The new build-bundle job restores actions/cache key frontend-bundle-<sha>,
runs frontend/scripts/build only on a miss and saves the key before the
job ends. The integration shards, the composable suite and the mocked
Plugin API suite now all need build-bundle and restore the same key with
fail-on-cache-miss, so none of them builds. A workflow re-run of the same
SHA reuses the cached bundle instead of rebuilding it.

Triggers become the union of the previous paths (frontend, common,
render-wasm, plugins): the bundle embeds the built plugins, so a plugins
change runs the whole set. workflow_dispatch keeps running the
integration job only, as before.

Job names are kept identical on purpose: they are the GitHub check
contexts and branch protection may match them by name.

Docs: new mem:frontend/e2e-ci-workflow records the build-once contract,
referenced from mem:frontend/core and mem:frontend/testing; the composable
memory and both suite READMEs are updated.

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

* 🐛 Fix mocked plugin suites crashing without frontend deps

The mocked CI drivers shelled out to frontend/scripts/e2e-server.js,
which imports express from frontend/node_modules. CI jobs install
only plugins/ deps, so the import failed with ERR_MODULE_NOT_FOUND
and the run timed out waiting for localhost:3000.

Serve the prebuilt bundle with a zero-dependency static server
built into each driver (ci/static-server.ts, kept in sync in both
suites) plus node:test coverage for it.

AI-assisted-by: muse-spark-1.3-contributor
2026-09-22 10:23:15 +02:00
Andrey Antukh
d68531b783
⬆️ Update devenv dependencies (#11790)
* ⬆️ Update devenv dependencies

Update Node.js, OpenCode, clj-kondo, Babashka, Pixi, GitHub CLI, uv,
and Serena to their current stable releases.

AI-assisted-by: gpt-5.6-sol

* ⬆️ Update devenv to Java 27

Use Zulu JDK 27 in the development image for compatibility testing.
Update the official checksums for both supported architectures.

AI-assisted-by: gpt-5.6-sol

* 🐳 Replace MinIO with RustFS in devenv

Run RustFS as the development S3 service and wait for its health check.
Install a pinned AWS CLI with checksums and use it to create the bucket
idempotently from each backend entry point.

Keep the old MinIO volume untouched and use a new RustFS volume.

AI-assisted-by: gpt-5.6-sol

* 🐳 Replace MailCatcher with persistent Mailpit

Run Mailpit as the devenv SMTP sink while preserving mailer:1025 and the
localhost:1080 UI.

Store its SQLite inbox in a named volume and wait for the readiness
endpoint before starting runtime containers. Bind the web UI to loopback so
development emails stay local.

AI-assisted-by: gpt-5.6-sol

* ⬆️ Update Node.js to 24.21.0

Align the host NVM version with the Node.js version used by devenv.

AI-assisted-by: gpt-5.6-sol

* ⬆️ Update devenv to PostgreSQL 18.6

Run PostgreSQL 18 with its versioned volume layout and a TCP readiness
check that ignores the temporary initialization server.

Install the matching client, create penpot_nexus, and preserve the old
PostgreSQL 16 volume for rollback or logical migration.

AI-assisted-by: gpt-5.6-sol

* 🐳 Expose RustFS ports in devenv

Publish the RustFS S3 API and management console on localhost port 9000
and 9001.

Keep both bindings on loopback so object storage is not exposed to the local
network.

AI-assisted-by: gpt-5.6-sol

* 🐳 Install standalone pnpm in devenv

Install pnpm 12.5.0 from architecture-specific release archives and
verify their published checksums.

Remove the Corepack setup while allowing pnpm to honor the project
packageManager pins.

AI-assisted-by: gpt-5.6-sol

* 🔥 Remove corepack, use system pnpm everywhere

Corepack is gone from Node 25+, so every `corepack enable` call
fails. pnpm now ships as a system binary (devenv, CI runners and
Docker images install it directly) and auto-downloads the version
pinned in `packageManager` on mismatch.

Scripts, workflows and Dockerfiles call `pnpm` straight away; the
three deploy workflows use a single `pnpm/setup@v2` step; and the
new `scripts/sync-pnpm-version` stamps all 35 `packageManager`
fields from the system pnpm, replacing the `corepack use` sweep.

AI-assisted-by: muse-spark-1.3-contributor

* 🐛 Fix exporter watch missing render-wasm build step

The exporter watch compiled CLJS requiring the generated
src/app/wasm/shared.js, which only render-wasm/build export
produces. Without it shadow-cljs failed with a cryptic missing
./shared.js dependency. Run build:wasm before watching, as
the frontend watch:app and exporter scripts/build already do.

AI-assisted-by: muse-spark-1.3-contributor

* 🔧 Add opencode V2 support and adapt plugins

Register the penpot tools for both opencode V1 (server())
and V2 (setup() with JSON Schema inputs) from a single
dependency-free plugin file, sharing the psql and
paren-repair runners between both paths.

Install the opencode2 binary side-by-side with V1 in the
devenv image and document the dual registration in the
paren-repair and psql memories.

AI-assisted-by: muse-spark-1.3-contributor

* ⬆️ Update pnpm and opencode
2026-09-22 10:22:31 +02:00
Andrey Antukh
e05747b546
✨ Restrict optional RPC ids to user-provided UUIDs (#11777)
* ✨ Restrict optional RPC ids to user-provided UUIDs

Add ::sm/user-provided-uuid, backed by a version and variant
aware regex that only accepts v4, v7 and v8 instances. Use it
for the optional :id of the creation RPC commands so reserved
versions such as v3 are rejected at validation time. Reads
such as get-team keep the lax ::sm/uuid. Cover the predicate
and the schema on both JVM and JS runtimes.

AI-assisted-by: muse-spark-1.3-contributor

* ✨ Cover id version restriction at the RPC boundary

Add backend regression tests proving the seven creation commands
reject reserved-version ids (v3) with :params-validation and
accept v4 ids (plus v7/v8 on create-team) through the real
decode and validate path. Also drop two duplicated assertions
and document the version and variant of every fixture UUID in
user-provided-test.

AI-assisted-by: muse-spark-1.3-contributor
2026-09-22 09:45:50 +02:00
Andrey Antukh
e34c618c3a Merge remote-tracking branch 'origin/staging' into develop 2026-09-22 08:44:45 +02:00
Álvaro Tejero-Cantero
d8fddf9942
♻️ Keep agent content on standard paths, drop client shims (#11719)
* ♻️ Keep agent content on standard paths, drop client shims

Agent guidance stays where the ecosystem reads it: AGENTS.md at the
root and the skills in .agents/skills. Codex scans .agents/skills and
follows symlinks, opencode lists it among its discovery locations, and
Cursor, Zed, Warp, Cline and Amp use the same project path.

Claude Code reads neither, so it needed CLAUDE.md and .claude/skills.
Committing those two paths is not a neutral default. Both are
gitignored, git treats ignored files as expendable, and a checkout
that needs the path deletes whatever a contributor keeps there without
a warning. Neither sparse-checkout nor skip-worktree offers a way out,
because both make the next pull abort. The two links are now created
by the contributor, once per clone, and documented in
.agents/README.md.

AGENTS.local.md and .agents/local/ give every client one personal
layer that composes on top of the shared one, instead of one file per
client inside the repository.

sync-workspace seeds the two links for ws1 and above, the way it
already seeds the gitignored config.js, so the devenv workflow does
not change.

AI-assisted-by: claude-opus-5
Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>

* ♻️ Lean on Claude Code reading AGENTS.md, drop the pointer

Claude Code 2.1.277 reads AGENTS.md through the built-in agents-md
plugin, whose default mode loads it in a project that has no
instruction file of its own. Deleting the committed CLAUDE.md is what
turns that on here, so the pointer a Claude user had to create is no
longer part of the setup, and only the .claude/skills link remains:
the plugin covers instruction files, and skills still load from that
path alone.

The instruction that replaces the pointer is a removal. A CLAUDE.md,
.claude/CLAUDE.md or CLAUDE.local.md anywhere from the root down to
the working directory hands the project back to CLAUDE.md and hides
this repository's AGENTS.md, while .claude/rules/*.md and the
person's own ~/.claude/CLAUDE.md do not count and stay usable for
personal steering. CLAUDE.local.md joins the ignore list, and a hard
rule in AGENTS.md forbids all three.

The docs name the version, say the fallback has not reached Bedrock,
Vertex or Foundry, and say the stable auto-update channel runs about
a week behind the default one, so the symlink is documented as the
stopgap for those cases and as something to drop afterwards.

sync-workspace seeds the skills link alone for the same reason.

AI-assisted-by: claude-opus-5
Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>

* 📎 Remove skills directory mention on AGENTS.md

Signed-off-by: Andrey Antukh <niwi@niwi.nz>

---------

Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-09-22 08:43:25 +02:00
David Barragán Merino
433f877449 🔧 Disable scheduled build for temporary environment 'tokens'
Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-21 19:54:52 +02:00
Eva Marco
e4723cb3a8
🐛 Fix copy paste text with external typography (#11785) 2026-09-21 15:57:01 +02:00