* 🐛 Fix geometry sync between mains and rotated component copies
Rotating a copy instance as a whole marked every shape inside it as
touched for geometry, so later geometric changes in the main (e.g. a
resize) were no longer propagated to that copy, while non-geometric
ones (e.g. fills) still were. And on paths where geometry did get
written to a rotated copy (e.g. resetting overrides), the sync engine
compensated only the roots' position delta, so the written values wiped
the copy's rotation back to 0.
Model the instance root's transformation as inherited, overridable
content, asymmetric to position (which remains free per-instance
placement):
- An untouched copy follows the main's transformation verbatim,
including rotation and flips (preserving the BUG #13267 semantics
that rotating a main propagates to its copies).
- Transforming a copy as a whole overrides only its ROOT: check-delta
compares the root's rotation/flips absolutely, but the descendants
relative to their root, so they merely follow and stay untouched.
- When a copy root's geometry is overridden, update-attrs expresses the
main's geometry in the copy's own frame: reposition-shape applies the
roots' relative transformation (rotation/flips) around the dest root
center in addition to the position delta. Geometric changes from the
main then keep propagating to the rotated copy, landing correctly in
its rotated frame instead of destroying its placement.
Covered by the new composable test case
case-n-geometry-sync-with-rotated-instances: an 8-variant sweep over
optional copy rotation, optional main rotation, and one of a fills or
height edit on the main child, asserting the whole model through the
real workspace events (the new rotate operation dispatches
dwt/increase-rotation, whose apply-modifiers step runs the check-delta
classification under test; change-height dispatches
dwt/update-dimensions and implements IPropertyCheck so one-of sweeps
can mix property and geometry edits). Verified by temporarily reverting
the fix: the case then fails with 6 assertion failures and passes again
with the fix restored.
Fixes#10109
AI-assisted-by: claude-fable-5
* 🐛 Fix synchronization problems
---------
Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
Relative operators were accepted in operand position, so "10+*3" silently evaluated to 310 instead of being rejected. Make negation a first-class operand so legitimate negative operands ("10 + -3") keep working.
Fixes#9581
Signed-off-by: Akshit Nassa <akshitnassa412@gmail.com>
Co-authored-by: Akshit Nassa <akshitnassa412@gmail.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
Add a `client-setup` command to the published `@penpot/mcp` package.
When invoked as `penpot-mcp client-setup`, the bin delegates to the `add-mcp`
CLI against the local MCP server URL (from `PENPOT_MCP_SERVER_PORT`, default
4401), run via npx.
Update docs on MCP client configuration.
Recommend calling `add-mcp` directly for now, since the `client-setup` command
only becomes available once a new `@penpot/mcp` release is published to npm.
AI-assisted-by: Claude
Co-authored-by: Michael Panchenko <michael.panchenko@oraios-ai.de>
* 📎 Add postgresql client tool wrapper for devenv
* ♻️ Replace uuid-ossp defaults with gen_random_uuid() and add missing :id on insert
- Switch all DEFAULT uuid_generate_v4() to gen_random_uuid()
(built-in PG 13+, no extension required)
- Add explicit :id (uuid/next) to 4 db/insert! calls that were
relying on the DB default (team-profile-rel, project-profile-rel,
team-project-profile-rel)
- Drop uuid-ossp extension (no longer needed)
- Add missing uuid require to projects.clj and srepl/binfile.clj
AI-assisted-by: deepseek-v4-flash
---------
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
* 🐛 Demote unable-to-retrieve-user-info OIDC error to warning level
401 responses from the OIDC userinfo endpoint (e.g. expired/revoked GitHub
token) are normal auth failures, not server errors. Logging at :error level
triggers the database and Mattermost error reporters unnecessarily.
AI-assisted-by: deepseek-v4-flash
* ✨ Add pure function tests for OIDC auth module
Add tests for: int-in-range?, valid-info?, qualify-prop-key, qualify-props,
provider-has-email-verified?, profile-has-provider-props?, redirect-response,
redirect-with-error, redirect-to-verify-token, and build-redirect-uri.
AI-assisted-by: deepseek-v4-flash
* ✨ Add HTTP-mock tests for fetch-user-info and fetch-access-token
Replace with-redefs with binding (cf/config is ^:dynamic).
Add tests for: fetch-user-info (success, 401, 500, request structure),
fetch-access-token (success, 400 error).
AI-assisted-by: deepseek-v4-flash
* ✨ Add get-info integration tests with partial mocking
Test all branches: token/userinfo/auto info sources, incomplete info,
role checks (satisfied and insufficient), state props merge,
sso-session-id from claims, and sso-provider-id for uuid providers.
AI-assisted-by: deepseek-v4-flash
* ✨ Add callback-handler integration tests with real tokens and session
Tests all main branches: error param, no profile (registration disabled),
profile blocked, provider mismatch, inactive profile, success flow,
and graceful handling of unable-to-retrieve-user-info exception.
Uses real tokens/generate, tokens/verify, and session/inmemory-manager.
AI-assisted-by: deepseek-v4-flash
pnpm occasionally detects an incompatible node_modules directory (e.g.
after a store location or pnpm major version change) and interactively
asks whether to remove and recreate it, blocking the MCP bootstrap in
the devenv tmux pane. Set confirmModulesPurge: false in
mcp/pnpm-workspace.yaml so the purge is auto-confirmed; this file is
included in the npm pack tarball (unlike .npmrc) and applies to all
install invocations from a single place.
AI-assisted-by: claude-fable-5
- Remove conditional build from test scripts (frontend, common)
- Remove test:jvm from common package.json (JVM tests via clojure directly)
- Remove test from backend package.json (JVM tests via clojure directly)
- Unify common/scripts/test-quiet.js with frontend's BUILD_STEPS pattern
- Add execution discipline section to mem:testing (no piping, tee to file)
- Add READ mem:testing FIRST directives to module testing docs
AI-assisted-by: deepseek-v4-flash
The notify steps referenced mattermost/action-mattermost-notify@master, a
mutable branch that runs in CI with access to the MATTERMOST_WEBHOOK_URL
secret. Pinning to the immutable commit of the latest release (v2.1.0,
ae31bb6) keeps the exact reviewed code from executing, per GitHub third-party
action hardening guidance, while staying easy to bump.
Event targets can be DOM text nodes (nodeType 3) which lack the
.closest() method. Add a get-element helper that normalizes text
nodes to their parent Element before calling .closest(), matching
the existing pattern in dom/get-parent-with-data.
Fixes#10640
AI-assisted-by: mimo-v2.5-pro
* ✨ Add systematic component tests via a composable test model
Introduce a framework for systematically testing Penpot component behaviour
(synchronisation/propagation, swaps, variant switches, nesting), plus a first
suite of cases built on it.
A test is expressed as a COMPOSITION OF OPERATIONS over a "situation" (an
in-memory file value plus named role bindings). Operations are reified as data
and composed by two combinators — `in-sequence` (threads the situation) and
`one-of`/`optional` (alternatives, enumerated into concrete variants). So one
written case stands for a whole matrix of variants, and coverage grows by
composition rather than by copying tests. Operations drive the REAL production
change pipeline, and event-operations dispatch the REAL workspace events and
await settlement, so the production watcher's automatic propagation is what is
exercised — the tests reflect genuine app behaviour, not a reimplementation.
Structure (frontend/test/frontend_tests/composable_tests/):
- core — the domain-agnostic engine: situation, the operation and
enumeration protocols, the combinators, and the runners.
- comp/nodes — the component operations (create/instantiate/reset, nesting,
swap, the variant ops, child add/remove/move, change, undo,
library sync).
- comp/setups — component-shaped starting configurations.
- interpreter — runs a case against the real frontend store: sync-ops apply
directly, event-ops dispatch real events and await
settlement (absorbing sync-file's delayed status RPC, which
would otherwise leak an error into subsequent tests).
- comp/sync-test — the cases (B-F, H, I, K, L, M).
This is test-only code with a single consumer — the frontend test suite (the
layer that runs the real app) — so it lives entirely under the frontend test
tree as .cljs, not under app/common.
The framework and its cases are documented in the project memory
frontend/composable-component-tests, added alongside.
Co-authored-by: Claude <noreply@anthropic.com>
* 🐛 Guard WASM mock teardown against an empty snapshot
`teardown-wasm-mocks!` unconditionally restored from the `originals` atom.
When run without a matching setup (double teardown, or `with-wasm-mocks*`
misused around an async test body), the snapshot is empty and every WASM API
function was `set!` to nil — permanently, for the remainder of the test run.
Any later code calling one of them (e.g. a leaked debounced resize-wasm-text
event firing during a subsequent test namespace) then crashed with
"initialized? is not a function".
Make the restore a no-op when there is nothing to restore.
Co-authored-by: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>