125 Commits
Author SHA1 Message Date
Andrey AntukhandSumit Ridhal eec06a5987 🐛 Allow registration with disabled public registration (#11912)
* 🐛 Allow invitation-based registration when disable-registration is set (#5178)

Per documentation, disable-registration 'disables registration
(still enabled for invitations only)'. Two bugs prevented this:

1. verify_token.clj: when processing an invitation token for a
   non-logged-in user with no member-id, the redirect included
   registration-disabled? in its condition, sending invited users
   to the login page instead of the register page.

2. auth.clj validate-register-attempt!: the registration-disabled
   check fired unconditionally before the invitation-token check,
   rejecting the actual register RPC even with a valid invitation.

Fix: in verify_token.clj remove registration-disabled? from the
redirect condition for new-user invitations. In auth.clj restructure
the check as an if/else: with an invitation token, validate the token
and allow registration; without one, enforce the flag as before.

* 🐛 Allow registration with disabled public registration

Allow valid team invitations to create new profiles when public
registration is disabled, while keeping password login and invitation
validation required.

Add backend regression coverage for flag combinations and verify-token
redirects, frontend route coverage, and configuration documentation.
Closes #5178

AI-assisted-by: space-bunny-free

* 🐛 Revalidate active invitation during registration

Require a live, unexpired team invitation before using the
registration exception, and recheck it before creating a profile.
Reuse the same lookup in invitation token verification.

Add regression tests for canceled and expired invitations, the
registration race, and explicit redirect contracts. Update docs
and backend auth guidance.

AI-assisted-by: Space Bunny Free

* 🐛 Lock and normalize invitation registration checks

Lock active invitation rows during transactional registration and
acceptance so cancellations cannot race with profile or membership
creation.

Normalize invitation emails before comparisons and database lookups.
Add concurrency, email casing, and final flag regression tests.

AI-assisted-by: Space Bunny Free

---------

Co-authored-by: Sumit Ridhal <sridhal@redhat.com>
2026-09-25 14:13:12 +02:00
Andrey Antukh acd146f6f4 🐛 Restore rate-limit headers and add Retry-After on 429 (#11895)
* 🐛 Restore rate-limit headers and add Retry-After

The account-lockout change replaced the header-forwarding 429 handler
with a body-only one, so existing RPC rate-limit responses lost their
x-rate-limit-remaining and x-rate-limit-reset headers. Account lockout
never sent Retry-After either.

Make handle-error :rate-limit preserve ::http/headers and add a
retry-after header when the exception carries a non-nil :ttl in
seconds, keeping the current JSON body. Add focused tests for both the
lockout and the RPC limiter paths.

Document activation, defaults, password/LDAP scope, Redis fail-open
behavior, and the lockout risk, and record the final HTTP contract in
the backend auth memory.

Refs #11397

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

* 🐛 Add Retry-After to RPC 429 and expose headers in CORS

Address review follow-ups on the account-lockout 429 contract:

- The RPC limiter now sets retry-after in its 429 headers (seconds
  until the longest rejecting limit resets), so it matches the
  account-lockout response and the HTTP standard.
- CORS exposes retry-after, x-rate-limit-remaining, and
  x-rate-limit-reset so browser clients can read them.
- Use backticks for Retry-After and account-locked in the docs for
  consistency with nearby sections.

Refs #11397

AI-assisted-by: deepseek-v4.1-flash
2026-09-24 12:19:29 +02:00
Andrey Antukh b3c1aab720 Merge remote-tracking branch 'origin/staging' into develop 2026-09-23 19:19:18 +02:00
Dr. Dominik Jain 6031f6c318 ⬆️ Upgrade MCP SDK to v2, removing SSE support and HTTP session management (#11841)
* ⬆️ Upgrade MCP SDK to v2 and remove HTTP sessions

MCP's per-request protocol removes the need to retain HTTP sessions.
Use the v2 handler to manage each request's transport and lifecycle,
so requests can reach any server instance without session affinity
or the workaround that adopts sessions through private SDK fields.

Keep legacy SSE support and the shared plugin and Redis bridges.
Remove the shared expiry checker, including legacy SSE idle expiry;
SSE connections now remain until disconnection or server shutdown.
Verify stateless requests, token isolation, and legacy compatibility.

Resolves #11827

AI-assisted-by: gpt-6-astra

* 🔥 Remove legacy MCP SSE support

Use Streamable HTTP as the sole MCP client transport so the server no
longer needs a separate SSE connection registry or lifecycle.

Remove /sse and /messages, their nginx routes, and the server-legacy
dependency. Legacy SSE clients must switch to /mcp; older Streamable
HTTP clients remain supported. Document the migration and verify that
the removed endpoints return 404.

Resolves #11846

AI-assisted-by: gpt-6-astra
2026-09-23 15:07:19 +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
Andrey Antukh 117c8db0bb Merge remote-tracking branch 'origin/staging' into develop 2026-09-22 10:24:30 +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 e34c618c3a Merge remote-tracking branch 'origin/staging' into develop 2026-09-22 08:44:45 +02:00
Álvaro Tejero-CanteroandAndrey Antukh 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 b402637fe4 🐳 Allow extending the CSP directives without replacing the policy
Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-18 19:05:37 +02:00
David Barragán Merino 1e8acbb2db 🐳 Cover the inline scripts of the served pages with CSP hashes
The frontend build now emits the sha256 hashes of the inline scripts of every page it writes into resources/public, the image moves them out of the document root, and the entrypoint splices them into the default script-src. This removes one of the two reasons why enforcing mode was not usable.

The hashes are computed on the rendered output rather than on the mustache templates, since the digest covers the exact bytes served between the script tags. All four served pages contribute, not just index.html: challenge.html handles the redirect, render.html is loaded by the exporter in a headless browser, and rasterizer.html is initialised by the frontend itself, so leaving any of them out would have broken those paths under enforcing mode. The storybook previews are excluded because that container does not serve them.

A bundle predating this change yields no hashes and the policy stays as it was, so older bundles keep building.

The three external locations were also passing through the security headers of their upstreams. raw.githubusercontent.com returns its own Content-Security-Policy and both it and fonts.googleapis.com return Strict-Transport-Security. Browsers enforce the intersection of every policy they receive, so the upstream one takes precedence on those responses, and the HSTS one lands on our own host, meaning a deployment that deliberately disables HSTS would get it set anyway by a third party. Hide all three at the proxy.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-18 18:55:19 +02:00
Andrey Antukh b660ea9d53 ✨ Route the app by query string with screen key
Move SPA routing out of the URL fragment into the normal query
string. The screen travels in a reserved `screen` key holding
the route name (`?screen=workspace&team-id=…`); every other
param keeps its name. `rt/nav` and `rt/resolve` keep their
signatures.

This deletes the fragment-mirroring URL surgery, simplifies
link-preview (the server sees everything) and nginx (single
path, no SPA fallback rules needed), and migrates OIDC
redirects, email links, e2e helpers and plugin test utils to
the new format.

Legacy `#/…` URLs translate client-side for one Penpot version
(`legacy-routes`, marked TODO(next-version)); non-SPA paths
are untouched.

AI-assisted-by: muse-spark-1.3-contributor
2026-09-15 17:10:54 +02:00
elhombretecla a91d81c695 🎉 Add link preview metadata for shared links 2026-09-15 17:10:54 +02:00
David Barragán Merino 52723b3851 🐳 Add configurable CSP and HSTS headers to the frontend image
Ship both headers from the image so that every deployment starts from a sensible default instead of each installation deriving its own policy. Report-only mode never blocks a request, so this changes no behaviour for existing deployments, and HSTS stays absent unless PENPOT_PUBLIC_URI declares an https scheme.

The policy can be narrow because the frontend already reverse proxies its own external dependencies, so 'self' covers them. What it must permit beyond that comes from the code: 'wasm-unsafe-eval' for the render engine, 'unsafe-inline' styles for the inline style attributes of the UI, and blob:/data: for thumbnails, exports and fonts.

Closes #11374
Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
AI-assisted-by: Claude
2026-09-15 13:41:51 +02:00
Shreyash 3f1c88fca2 📚 Document MCP and internal resolver environment variables (#11572)
Add the frontend MCP variables PENPOT_MCP_URI and PENPOT_MCP_URI_WS,
the PENPOT_INTERNAL_RESOLVER variable used by nginx to resolve the
internal service names, and the enable-mcp flag to the configuration
guide. All three were used by the frontend docker image but were
missing from the docs.

Closes #11318

AI-assisted-by: claude-opus-5

Signed-off-by: Shreyash Agare <envisiontechdevelopers@gmail.com>
2026-09-09 08:16:04 +02:00
Andrey Antukh f5aad7b1ae Merge remote-tracking branch 'origin/staging' into develop 2026-09-01 12:45:17 +02:00
Andrey Antukh 73d3d63616 ✨ Enable a way to provide custom opencode config on starting devenv 2026-08-31 16:31:24 +02:00
Vaibhav Srivastava ac5c88be66 📚 Fix typo on docs (#11429)
Signed-off-by: Vaibhav Srivastava <vaibhavsri1712@gmail.com>
2026-08-31 14:11:43 +02:00
David Barragán Merino d826c7ac13 📚 Remove architectural constraints related to MCP Server HA 2026-08-18 19:18:06 +02:00
David Barragán Merino ddd32670b3 📚 Remove architectural constraints related to MCP Server HA 2026-08-18 19:15:27 +02:00
Andrey Antukh aa5545c258 Merge remote-tracking branch 'origin/staging' into develop 2026-08-18 14:34:42 +02:00
Andrey Antukh 3f09f161ae Merge remote-tracking branch 'origin/main' into staging 2026-08-18 14:34:22 +02:00
Sebastien MALOT 7ac61e0597 🐛 Fix typo in auto-file-snapshot timeout setting (#10909)
Corrected a typo in the configuration documentation regarding the auto-file-snapshot timeout setting.

Signed-off-by: Sebastien MALOT <sebastien.malot@pm.gouv.fr>
2026-08-17 22:14:01 +02:00
Álvaro Tejero-Cantero 314a2a245f 📚 Fix the devenv backend-flags instructions (#11077)
The section pointed at `docker/devenv/docker-compose.yaml`, which #9906
deleted when it split the devenv compose into `docker-compose.infra.yml`
and `docker-compose.main.yml`. The same page names both replacements in
its architecture section, so only this one was missed.

Setting PENPOT_FLAGS in the container environment would not have worked
anyway: `backend/scripts/_env` expands the inherited value before its own
list, so its flags win. Document the mechanism that does work, the
gitignored `backend/scripts/_env.local` that `start-dev` sources right
after `_env`, and the left-to-right last-wins rule that lets an override
switch off a flag `_env` enables.
2026-08-06 14:14:44 +02:00
Andrey Antukh 32520e66e5 ✨ Allow stopping ws0 independently of other workspaces
Remove the guard in stop-devenv that refused to stop ws0 while any
ws1+ instance was running. Each workspace is now fully independent
and can be started/stopped in any order. Shared infra shuts down
only when no instances remain running.

Updated docs (devenv.md, agentic-devenv.md) and devenv memory to
reflect the new behavior.

AI-assisted-by: mimo-v2.5-pro
2026-08-01 11:29:42 +00:00
Andrey Antukh 4c7863d6c8 📚 Add .penpot format docs and inspector tool (#10674)
* 📚 Add comprehensive documentation for .penpot file format (v3)

Create user-facing documentation for the .penpot binfile format to help
developers and power users understand and inspect the ZIP+JSON structure.

- Add technical specification with complete schema reference for all JSON
  files (manifest, file metadata, pages, shapes, library assets, storage
  objects, plugin data)
- Add user-friendly overview explaining the format structure, inspection
  methods, and version history
- Update export-import-files.njk to clarify current format is not deprecated
  and note that v2 was never released
- Add cross-links between documentation pages
- Include source code references to authoritative malli schemas

AI-assisted-by: qwen3.7-plus

* 📎 Add playwright dependency to the root package.json

* 📚 Add browser based .penpot file inspector to docs

Add a client-side, no-build inspector for .penpot (v3) files. The tool
runs entirely in the browser: JSZip is loaded lazily from a CDN, the
file is never uploaded. It provides a collapsible file tree, syntax-
highlighted JSON viewer with search, image previews, a summary panel
with file/shape/storage counts, shape summary cards with color swatches,
and clickable UUID cross-references with backlinks.

The inspector is added at
docs/technical-guide/developer/data-model/penpot-file-inspector.njk
and linked from the format spec and the user-facing format page.

AI-assisted-by: minimax-m3

* 📚 Use full page width and 2-column layout for inspector

The inspector previously sat inside the docs site's 42rem content
column, which forced a stacked layout and wasted the wide viewport.

- Hide the docs page-navigation sidebar on this page via :has()
- Use a 2-column CSS grid: file tree (280px, sticky) on the left,
  content (flex: 1) on the right
- Restore the directory tree as a permanent left-rail navigation
  (it was removed when the picker was first introduced, then added
  back as a collapsible panel; a sticky rail is a better fit now
  that horizontal space is plentiful)
- Remove the tree toggle button — the tree is always visible
- Collapses to 1 column on viewports below 900px

AI-assisted-by: minimax-m3

* 💄 Indent nested JSON values in inspector

The JSON tree view in the inspector was rendering all properties at
the same indent level, making it hard to see which values were
nested inside objects or arrays. Root-level properties and deeply
nested ones looked identical.

Add padding-left and a subtle vertical guide line to .jchildren so
each nesting level is visually distinct. Bump the toggle column to
1.2em and add a touch of vertical padding to .jrow for breathing
room.

AI-assisted-by: minimax-m3
2026-07-20 14:45:59 +02:00
Andrey Antukh e73aa9e981 ✨ Add PENPOT_INTERNAL_URI to exporter for separate internal and public URI handling (#10630)
Add PENPOT_INTERNAL_URI environment variable to the exporter. This allows
separating the URI used for internal communication (headless browser to
frontend) from the public URI used for resource references in exported SVGs.

Previously, PENPOT_PUBLIC_URI served both purposes, which caused exported
SVGs to contain broken font URLs when the internal Docker address was used.

Changes:
- Add :internal-uri to exporter config schema with fallback to :public-uri
- Add get-internal-uri helper function
- Use internal-uri for browser navigation in SVG/PDF/bitmap renderers
- Post-process SVG output to replace internal URI with public URI
- Use internal-uri for backend API calls in resource handler
- Log both URIs on startup
- Update docker-compose.yaml to use both variables
- Document the new variable in configuration.md

Closes #10627

AI-assisted-by: mimo-v2.5-pro
2026-07-10 10:41:49 +02:00
Dominik JainandMichael Panchenko b9e0421f79 📚 Update and improve devenv documentation
* Fix stale references to `run-devenv-agentic` and `start-devenv`
* Improve clarity in description of workspaces
* Improve section on agentic devenv

Co-authored-by: Michael Panchenko <michael.panchenko@oraios-ai.de>
2026-07-08 11:28:55 +02:00
Andrey Antukhandmimo-v2.5-pro b4532486e3 ✨ Add configurable resource usage limits for imagemagick (#10240)
* 🐳 Add ImageMagick policy.xml resource limits to backend Docker image

Add a restrictive policy.xml to the backend Docker image that caps
ImageMagick resource usage: 256MiB memory, 512MiB map, 128MP area,
30s time limit, 16KP max dimensions. Blocks PS/EPS/PDF/XPS coders
to prevent Ghostscript attack surface.

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>

* ✨ Add timeout support to shell/exec!

Add optional :timeout parameter (in seconds) that uses
Process.waitFor(long, TimeUnit). On timeout, the process is
destroyed forcibly and an :internal/:process-timeout exception
is raised. Stdout/stderr readers handle IOException from closed
streams when the process is killed.

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>

* ♻️ Rename ::wrk/netty-executor to ::wrk/executor with cached pool

Replace DefaultEventExecutorGroup (fixed Netty thread pool) with a
cached thread pool (px/cached-executor) for general async task
offloading. The cached pool creates threads on demand and reuses
idle ones, which is more appropriate for blocking I/O workloads
(shell commands, message bus, rate limiting, etc.).

Changes:
- Rename ::wrk/netty-executor to ::wrk/executor in worker/executor.clj
- Switch implementation from DefaultEventExecutorGroup to px/cached-executor
- Update all ig/ref wiring in main.clj (msgbus, tmp cleaner, climit, rlimit, rpc)
- Remove ::wrk/netty-executor from redis.clj (let lettuce create its own
  eventExecutorGroup instead of sharing a Netty executor)
- Assert executor is present in shell/exec! to prevent silent nil usage
- Remove executor-threads config (no longer needed for cached pool)

The ::wrk/netty-io-executor (NioEventLoopGroup) remains unchanged as it
handles actual non-blocking network I/O for Redis and S3.

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>

* 🔥 Remove im4java dependency and replace with direct ImageMagick CLI calls

- Replace im4java Java library with direct 'magick' CLI calls via shell/exec!
- Add PENPOT_IMAGEMAGICK_* config env vars for resource limits (thread, memory, map, area, disk, time, width, height)
- Use configurable ImageMagick environment with sensible defaults matching policy.xml
- Remove -Dim4java.useV7=true JVM flag from startup scripts
- Remove org.im4java/im4java from deps.edn
- All ImageMagick commands now use shell/exec! with 60s timeout and resource limits

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>

* 💄 Rename imagemagick env functions and optimize config reads

- Rename imagemagick-defaults -> imagemagick-default-env
- Rename imagemagick-env -> get-imagemagick-env
- Optimize to avoid double cf/get calls per config key

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>

* ✨ Add tests for shell/exec! timeout and media processing

- Add shell_test.clj: tests for exec! timeout, env vars, stdin, stderr
- Add media_test.clj: tests for info, generic-thumbnail, profile-thumbnail
- Fix generic-process to prefer explicit format over input mtype
- Fix shell/exec! to use cached executor when system has no executor
- Fix reduce-kv accumulator in set-env (must return penv)

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>

* ♻️ Refactor media/process to take system as first argument

- Change (defmulti process :cmd) -> (defmulti process (fn [_system params] (:cmd params)))
- Change (run params) -> (run system params)
- All process methods now receive [system params]
- Update all callers: rpc/commands/media, profile, auth, fonts
- Revert shell/exec! to require system with executor (no fallback)
- Fix lint warnings and formatting

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>

* 🔥 Remove unused app.svgo namespace

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>

* 🔥 Remove Node.js from backend Docker image

- Delete unused svgo-cli.js script
- Remove Node.js installation from Dockerfile.backend
- Remove svgo-cli.js copy from backend build script

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>

* 🔥 Remove unused process-error multimethod

- Remove process-error multimethod and its default handler
- Simplify media/run to directly call process
- Fix alignment in main.clj

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>

* 📚 Add ImageMagick resource limits configuration to technical guide

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>

---------

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>
2026-06-18 17:52:01 +02:00
Andrey Antukh d44c6250ea Merge remote-tracking branch 'origin/staging' into develop 2026-06-15 13:04:19 +02:00
Justin Lin 9c7af3aeb5 📚 Add WebSocket proxy configuration for MCP in Nginx example (#10152)
The current Nginx example doesn't include the required Websocket settings to correctly proxy /mcp/ws, and leads to WebSocket connection error when trying to connect in a design. Adding this lines fixed the issue.

Signed-off-by: Justin Lin <30039756+lancatlin@users.noreply.github.com>
2026-06-12 08:55:23 +02:00
16dc83616a ✨ Add the ability to launch parallel devenv instances (#9906)
* 🐳 Split devenv compose for parallel workspaces

Move shared services into an infra compose file and keep the main devenv container plus Valkey in a separate compose file driven by defaults.env. Parameterize host-side ports, container names, source path, and runtime env while keeping container-internal ports fixed for same-origin proxying.

Make tmux startup idempotent, add attach-devenv for the live instance, move shared MinIO user setup to infra startup, and let exporter scripts load backend _env.local overrides.

Co-authored-by: Codex <codex@openai.com>

* 🐳 Run parallel devenv instances against shared infra

Add support for running N parallel devenv instances under separate compose
projects sharing Postgres, MinIO, mailer, and LDAP. Each instance has its
own main container, Valkey, source checkout, tmux session, and host port
range offset by 10000 (3449 -> 13449 -> 23449, etc.).

./manage.sh run-devenv-agentic --n-instances N reconciles the running set
to exactly {ws0..ws(N-1)}: missing instances are created (workspace sync
from the live repo via git ls-files + per-instance env-file generation
under docker/devenv/instances/ + detached tmux startup), surplus instances
are stopped highest-first via compose down (never -v), already-running
instances are left untouched. ws0 binds the live repo at PWD; ws1+ are
scratch clones under ~/.penpot/penpot_workspaces/.

Backend workers (enable-backend-worker) are gated on PENPOT_BACKEND_WORKER
in backend/scripts/_env; ws1+ overlays disable them so async-task
notifications stay bound to a single Valkey Pub/Sub instance.

Compose helpers wrap docker compose with env -i so per-instance overlay
--env-file actually overrides defaults.env -- without the strip, the shell
env from sourcing defaults.env at startup would shadow the overlay (Compose
gives shell precedence over --env-file).

Other:
- Drop network aliases (- main, - redis); use container_name for
  cross-container DNS so multiple instances on the shared network don't
  fight over the same DNS name.
- Pin volume names via name: (PENPOT_*_VOLUME) so volumes survive project
  renames; ws0 keeps the pre-existing physical names (penpotdev_*).
- Remove cross-project depends_on from main.yml (postgres/minio-setup now
  live in penpotdev-infra); manage.sh ensure-infra-up docker-waits on the
  minio-setup one-shot.
- Strict arg parsing in run-devenv / run-devenv-agentic; --n-instances 0
  rejected.
- Remove unused Host-matched server block from the Caddyfile.

Memory mem:devenv/core and developer docs updated.

Co-authored-by: Codex <codex@openai.com>

* ✨ Document and stabilise the parallel-workspace CLI; wire AI agents

Improve parallel-workspaces developer CLI,
and add an opt-in layer that lets four AI
coding agents (Claude Code, opencode, VS Code Copilot, OpenAI Codex CLI)
drive a specific workspace through a single launcher command.

Parallel-workspace semantics
----------------------------

each run-devenv-agentic call brings up one wsN;
--ws N (integer; default 0) targets a specific workspace and auto-starts
ws0 first when N>=1 so the worker invariant holds. --sync is forbidden on
ws0 and re-seeds the workspace from the live repo for ws1+. Stop semantics
mirror the start invariant -- ws0 is the last to stop, shared infra stops
with it, --all walks every instance highest-first. The worker policy
section explains why workers run only on ws0 (Postgres FOR UPDATE
SKIP LOCKED is safe across many workers but the cron dedup primitive is
best-effort, and :telemetry / :audit-log-archive are not idempotent).
Per-instance Valkey Pub/Sub isolation, msgbus topology, and the
"async task notifications miss ws1+ tabs" caveat are stated explicitly.

The mem:prod-infra/core memory captures the same external-services and
task-queue / Pub-Sub topology in agent-readable form, and
mem:backend/core and mem:critical-info now cross-link it so backend work
surfaces the horizontal-scaling constraints from the start.

AI coding agent integration
---------------------------

New top-level .devenv/ directory holds committed templates
(templates/{claude-code,opencode,vscode}.json and templates/codex.toml,
each with \${PENPOT_MCP_PORT} and \${SERENA_MCP_PORT} placeholders) plus
committed shared entries (matching shared/* files for Playwright, the
only workspace-independent server we ship today).

./manage.sh start-coding-agent <claude|opencode|vscode|codex> [--ws N]
launches the chosen client against one workspace. It cd's into the
target's directory (the live repo for ws0; workspace-path "wsN" for ws1+)
and refuses to launch unless (a) the binary is on PATH, (b) the
workspace directory exists for ws1+, and (c) the instance is up
(devenv-main-running) -- the MCP servers only exist while the devenv is
running. The agentic-devenv guide is restructured around this Quick
start path, with a per-client table and a Manual configuration fallback
for clients we don't cover.

Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ♻️ Scope the shadow devtools to the dev build

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-03 15:48:25 +02:00
ruizterce e6f5b270de 💄 Fix typos in configuration.md (#9975)
Corrected typos in the configuration documentation.

Signed-off-by: ruizterce <127963868+ruizterce@users.noreply.github.com>
2026-06-02 16:32:34 +02:00
Andrey Antukh 3858993a57 Merge remote-tracking branch 'origin/staging' into develop 2026-05-27 13:37:02 +02:00
Michael Panchenko cec90416c2 ✨ Improve common JS test runner
Add focused common JavaScript test execution with log-level control and a quiet test wrapper matching the frontend workflow.

Update developer docs and testing memories to reflect the common/frontend test split, and document why runner helper extraction is deferred.

Signed-off-by: Codex <codex@openai.com>
2026-05-21 14:20:10 +02:00
Michael Panchenko e252bcf901 ✨ Add --log-level flag to frontend test runner
Lets a caller pin `app.common.logging`'s level for the duration of a
test run via `--log-level <trace|debug|info|warn|error>`. The flag is
off by default, so when absent the runner doesn't touch logger state
and stdout looks exactly as before.

When passed, the runner calls `(l/setup! {:app level})` right before
dispatching to the test block, so production code exercised by tests
emits only at the requested level or higher.

  pnpm run test:quiet -- --focus frontend-tests.logic.groups-test \
                         --log-level warn

Composes with `--focus`; the two flags are independent.

Caveats worth knowing: top-level log calls fired at namespace load
time run before the runner parses CLI options and therefore slip past
this flag; direct `println` / `js/console.log` calls bypass the
logging system entirely and are unaffected.
2026-05-21 14:20:10 +02:00
Michael Panchenko c29f32c7ae ✨ Add quiet variant of frontend test command
Introduces `pnpm run test:quiet` for non-interactive runs (CI, scripted
invocations, agent loops). It runs the same pipeline as `pnpm run test`
— `build:wasm`, then `build:test`, then `node target/tests/test.js` —
but buffers each build step's stdout and stderr and only replays them
when that step exits non-zero. Test-runner output streams through
unchanged, so failures and the summary are never hidden. Short progress
hints (`Building wasm...`, `Building test bundle...`, `Running tests...`)
are written to stderr, leaving stdout to carry only the test results
for clean capture and parsing.

Forwards arguments verbatim, so `pnpm run test:quiet -- --focus ...`
composes with the existing `--focus` flag. The default `pnpm run test`
script and its output are unchanged.

Also documents the new command in the developer guide and updates the
frontend testing memory to recommend it for agent runs.
2026-05-21 14:20:10 +02:00
Michael Panchenko 17041b53a7 ✨ Allow running a single frontend test via --focus
Previously `pnpm run test` always ran the full frontend-tests suite,
which made tight iteration on a single namespace or var painful. The
runner now accepts `--focus <ns>` or `--focus <ns>/<var>` and executes
only the matching tests, preserving each namespace's `:once` and `:each`
fixtures so behavior matches a full-suite run.

  pnpm run test -- --focus frontend-tests.logic.groups-test
  pnpm run test -- --focus frontend-tests.logic.groups-test/some-test

Also updates the developer guide and the testing memory so the flag is
discoverable from both docs and agent context.
2026-05-21 14:20:10 +02:00
Francis Santiago 106b10e971 📚 Clarify self-hosted OIDC configuration for containerized deployments (#9758)
Signed-off-by: Francis Santiago <francis.santiago@kaleidos.net>
2026-05-20 13:05:40 +02:00
Andrey Antukh 52588412c7 Merge remote-tracking branch 'origin/staging' into develop 2026-05-14 11:12:01 +02:00
Andrey Antukh d78074307f Merge remote-tracking branch 'origin/main' into staging 2026-05-14 11:07:42 +02:00
Dominik Jain b952783621 📚 Add documentation page on the agentic DevEnv #9216 2026-05-12 12:49:58 +02:00
Yamila Moreno 328efd4e16 📚 Add notice regarding architectural constraints with MCP Server (#9423) 2026-05-12 10:06:13 +02:00
alonso.torres 5e3e66a99b 📚 Updated docs for MCP development environment 2026-04-29 16:39:19 +02:00
Xaviju e9105f3670 ♻️ Fix linter errors under legacy resources scss (#9035) 2026-04-20 23:58:53 +02:00
wdeveloper16andwdeveloper16 d772632b08 ✨ Allow customising the OIDC login button label (#9026)
* ✨ Allow customising the OIDC login button label (#7027)

* 📚 Add CHANGES entry and docs for PENPOT_OIDC_NAME (#7027)

---------

Co-authored-by: wdeveloper16 <wdeveloer16@protonmail.com>
2026-04-17 16:56:29 +02:00
Yamila Moreno 084ca401fd 📚 Improve recommended settings for self-host (#8846) 2026-03-31 15:11:58 +02:00
Elena Torró c6f8356847 📚 Improve docs on feature flags (#8641) 2026-03-17 11:49:14 +01:00
Yamila Moreno dd856ecf50 ♻️ Deprecate PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE envvar 2026-02-23 13:48:01 +01:00