* 📎 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
* ✨ Add plugin with composable test framework and component tests
The plugin provides a framework for writing composable tests against
the Plugin API, and applies it to systematic end-to-end testing of
component semantics.
The framework's core ideas: a test is written once as a composition of
operations over a starting configuration; choice points among the
operations (optional steps, alternatives) expand the composition into
a full sweep of test variants, so a single case definition yields
broad combinatorial coverage; and the operations drive the real Plugin
API with real change propagation, testing the full production
implementation.
The initial application is a suite of component test cases covering
synchronization, overrides, swap slots and variants — the
TypeScript/e2e continuation of the ClojureScript composable test suite
(frontend_tests.composable_tests). Several cases originate from
reproducing real defects (e.g. #10109 and the swap-slot corruptions).
Tests run from an interactive panel in Penpot: cases are listed with
plain-language descriptions, tests can be run selectively, results
stream in live, and every checkbox carries a stable DOM id — so the
panel can equally be driven programmatically (the basis for running
the suite in CI), as documented in the plugin's README.
Lives at plugins/apps/composable-test-suite as a regular member of the
plugins workspace (init script, start:plugin:composable-test-suite,
shared dev port 4202, covered by build:plugins via the new
./apps/*-test-suite filter).
Related to #10584.
AI-assisted-by: claude-fable-5
* ✨ Run the composable test suite headlessly in CI
Adds a headless run mode for the composable test suite, following the
plugin-api-test-suite's CI architecture, and a workflow that runs it as
a per-PR gate.
An in-sandbox entry (src/ci/headless.ts) runs the suite without the
panel UI — the framework's runner was UI-free by construction, so no
refactoring was needed — and streams each result through console
markers, addressed by the same composite identifiers the panel uses
(e.g. MainEditSyncs-2), with durations and, on failure, the error and
the applied-steps transcript. It is built as a single self-executing
bundle and evaluated directly inside a real Penpot plugin sandbox by
the driver (ci/run-ci.ts), so no plugin dev server or port is involved.
The driver needs no backend and no login: it serves the prebuilt
frontend bundle via the frontend e2e static server and intercepts every
backend RPC with Playwright fixtures. The mocked backend is not a
limitation for this suite — everything it asserts is frontend store
logic executed in memory — which the full run confirms: all 48 tests
behave identically to the interactive panel, including variants and
swap slots, with the single (currently expected) failure of
MainEditSyncs-2 reproducing bug #10109 under the mock.
TEST_FILTER selects tests by identifier substring; CI_TIMEOUT_MS bounds
the run. The mock harness mirrors the frontend e2e harness (see the
provenance note in the driver).
Related to #10584.
AI-assisted-by: claude-fable-5
* 📚 Restructure the composable-tests memory around both suites
Present the composable component tests top-down: the shared framework
principles upfront, then the two implementations — the ClojureScript
suite in the frontend test tree and the TypeScript suite in the plugin,
which tests fully end-to-end with a slightly more elaborate set of
abstractions — and the plugin's headless CI run, pointing to the
plugin's README for operational details. Also records this session's
additions (geometry operations, case N, the CI harness).
AI-assisted-by: claude-fable-5
* 📎 Refine the PR-description conventions in the creating-prs memory
Encourage digestible descriptions: bullet items over prose (grouped by
area with bold lead-ins for larger PRs) and no manual line wraps, since
the rendered markdown adapts to the viewport. Also drop the outdated
'MCP' from the standard Note line.
AI-assisted-by: claude-fable-5
* 🔧 Set Prettier endOfLine to auto in plugins workspace
Prettier defaults to endOfLine "lf", which is incompatible with
checkouts on Windows that use core.autocrlf=true
* 🐛 Fix problems with suite
---------
Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
Move all development tools from tools/ to scripts/ for consistency.
Rename lint/fmt/check-fmt to lint-clj/fmt-clj/check-fmt-clj to clarify
they target Clojure specifically. Remove unused scripts (attach-opencode,
start-opencode, start-opencode-server) and the backport-commit skill.
Update all internal references across .serena/, AGENTS.md, and
CONTRIBUTING.md to point to the new script locations. Simplify
CONTRIBUTING.md by delegating module-specific fmt/lint instructions
to the respective serena memories.
AI-assisted-by: deepseek-v4-flash