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
Never pipe test output directly to filters (head, tail, grep).
Always redirect to a file first to prevent hiding test failures.
AI-assisted-by: mimo-v2.5
Move Dockerfile.frontend, .backend, .exporter, .mcp and .storybook
under docker/images/ from ubuntu:26.04 / nginx-unprivileged / a
manual Node tarball install to Docker Hardened Images (Debian 13,
or Alpine for storybook). storybook and mcp get a true non-dev
runtime; frontend, backend and exporter keep the -dev tag as their
final image, since each needs a shell and/or package manager at
container runtime (nginx templating, fontforge/python3, and a
headless-browser stack, respectively).
Move docker/imagemagick/Dockerfile and docker/devenv/Dockerfile from
ubuntu:26.04 to Docker Hardened Images (Debian 13 / trixie).
imagemagick gets a true non-dev runtime with its shared libraries
vendored via ldd; devenv keeps the -dev tag as its final image since
it's an interactive development container, not a production
artifact.
MCP tokens now use a separate JWT issuer claim (`urn:penpot:mcp-token`) instead of `access-token`, preventing them from being validated as API access tokens.
Fixes#10960
AI-assisted-by: qwen3.7-plus
Production crash where @(get bounds id) threw
"No protocol method IDeref.-deref defined for type null"
when a shape ID had no corresponding entry in the bounds map
during layout calculations.
Added defensive nil guards (when-let / when) to all unprotected
bounds dereference sites:
- flex_layout/bounds.cljc: layout-content-points (parent + child)
and layout-content-bounds
- grid_layout/bounds.cljc: layout-content-points and
layout-content-bounds
- min_size_layout.cljc: child-min-width grid branch (3 sites) and
child-min-height grid branch
Added 7 new tests in geom_bounds_layout_nil_test.cljc covering all
nil-bounds edge cases for flex, grid, and min-size layout paths.
Registered in runner.cljc.
Closes#10843
AI-assisted-by: qwen3.7-plus
* 🐛 Fix main menu is covered by the toolbar
* ♻️ Refactor SCSS
* 🐛 Fix adjust z-index of workspace context menu
* ♻️ Refactor SCSS
* 🐛 Fix adjust z-index of tokens context menu
* ♻️ Refactor SCSS
* 🐛 Fix adjust z-index of old context menu
* 📎 PR improvements
* 📎 Update serena documentation about creating-prs workflow
* 🐛 Handle unrecognized JSON escape sequences as malformed-json
When clojure.data.json's read-escaped-char encounters an unrecognized
escape sequence (e.g. a backslash followed by '}', or other case
fall-throughs in the parser) in a JSON request body, it throws a bare
IllegalArgumentException. Previously this fell through to the generic
RuntimeException branch in wrap-parse-request's handle-error, which
unwrapped and recurred without matching, eventually reaching the
internal-error handler and producing HTTP 500 + an error report — even
though the root cause was malformed client input, not a server bug.
The fix converts any IllegalArgumentException raised in the JSON parse
path into a `:validation`/`:malformed-json` error by raising a new
ex-info (which is caught by the top-level error handler in
`app.http/router-handler`). The result is an HTTP 400 response with a
descriptive hint, and no error report is generated. This addresses
~10% of all error reports received.
The new IAE branch is placed before the RuntimeException branch in
the cond (since IllegalArgumentException IS-A RuntimeException) and
uses the throw-style (ex/raise) to match the existing
RequestTooBigException / EOFException branches. A comment above the
handle-error cond documents why raising is intentional and is caught
by the top-level app.http error handler, not by the per-route
wrap-errors middleware.
Test suite changes:
- Extend the existing `DummyRequest` defrecord in
`http_middleware_test.clj` from 2 fields to 12 fields, implementing
every IRequest method, and add a private `make-dummy-request`
constructor that accepts an options map with every key optional and
sensible `:or` defaults. Future fields added to DummyRequest won't
break existing call sites as long as the `:or` defaults are kept in
sync.
- Remove the now-redundant `JsonRequest` defrecord and migrate all 11
`->DummyRequest` call sites to `make-dummy-request`.
- Add 6 new deftest cases:
- parse-request-illegal-argument-exception: malformed JSON body
(containing `\}`) is converted to `:malformed-json`.
- parse-request-request-too-big-exception: RequestTooBigException
is converted to `:request-body-too-large`.
- parse-request-eof-exception: java.io.EOFException is converted
to `:malformed-json`.
- parse-request-runtime-exception-with-cause: a wrapped
RuntimeException recurses on ex-cause and dispatches to the
matching specific branch.
- parse-request-runtime-exception-without-cause: a bare
RuntimeException falls through to errors/handle, returning 500
with :type :server-error :code :unexpected.
- parse-request-non-runtime-throwable: java.io.IOException (a
non-RuntimeException Throwable) is handled by the dedicated
handle-exception method, returning 500 with :code :io-exception.
Together, the new tests cover all 6 branches of wrap-parse-request's
handle-error cond.
Refs #10804.
AI-assisted-by: minimax-m3