Foss main old#17
Closed
aznszn wants to merge 202 commits into
Closed
Conversation
In `preview-next-point`, `st/get-path` was called without extra keys, which returns the full Shape record. That value was then passed directly to `path/next-node` as its `content` argument. `path/next-node` delegates to `impl/path-data`, which only accepts a `PathData` instance, `nil`, or a sequential collection of segments. A Shape record matches none of those cases, so `path-data` threw "unexpected data" every time the user moved the mouse while drawing a path. The fix is to call `(st/get-path state :content)` so that only the `:content` field (a `PathData` instance) is extracted and forwarded to `path/next-node`.
…ssue 🐛 Fix path drawing preview passing shape instead of content to next-node
* 🐛 Add webp export format to plugin types Align plugin API typings with runtime export support by including 'webp' in 'Export.type' and updating the exported formats documentation. Signed-off-by: Marek Hrabe <marekhrabe@me.com> * 📚 Add plugin-types changelog entry for missing webp export format Signed-off-by: Marek Hrabe <marekhrabe@me.com> --------- Signed-off-by: Marek Hrabe <marekhrabe@me.com> Co-authored-by: Andrey Antukh <niwi@niwi.nz>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
…enpot#8929) The backtrace-tokens-tree function used a namespaced keyword :temp/id which clj->js converted to the JS property "temp/id". The sd-token-uuid function then tried to access .id on the sd-token top-level object, which was undefined, causing "Cannot read properties of undefined (reading uuid)". Fix by using the existing token :id instead of generating a temporary one, and read it from sd-token.original (matching sd-token-name pattern).
Add normalize-coord helper function that clamps coordinate values to max-safe-int and min-safe-int bounds when reading segments from PathData binary buffer. Applies normalization to read-segment, impl-walk, impl-reduce, and impl-lookup functions to ensure coordinates remain within safe bounds. Add corresponding test to verify out-of-bounds coordinates are properly clamped when reading PathData. Signed-off-by: Andrey Antukh <niwi@niwi.nz>
The CLJS implementation of PathData's -nth protocol method had swapped arguments in the 3-arity version (with default value). The call (d/in-range? i size) should be (d/in-range? size i) to match the CLJ implementation. With swapped args, valid indices always returned the default value, and invalid indices attempted out-of-bounds buffer reads.
The metadata key was misspelled as :cosnt instead of :const, preventing the compiler from recognizing the Var as a compile-time constant.
- Fix 'conten' typo to 'content' in path.cljc docstring
- Fix 'curvle' typo to 'curve' in shape_to_path.cljc docstring
- Replace confusing XOR-style filter with readable
(contains? #{:line-to :curve-to} ...) in bool.cljc
- Align handler-indices and opposite-index docstrings with
matching API in path.cljc
…enpot#8937) Co-authored-by: Andrey Antukh <niwi@niwi.nz>
Fixes three concrete builder issues in common/files/builder:\n- Use bool type from shape when selecting style source for difference bools\n- Persist :strokes correctly (fix typo :stroks)\n- Validate add-file-media params after assigning default id\n\nAlso adds regression tests in common-tests.files-builder-test and registers them in runner. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: raguirref <ricardoaguirredelafuente@gmail.com>
Remove unrelated local pid file that was accidentally included in previous commit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: raguirref <ricardoaguirredelafuente@gmail.com>
…-media-validation 🐛 Fix builder bool styles and media validation
Refactor use-portal-container to allocate one persistent <div> per logical category (:modal, :popup, :tooltip, :default) instead of creating a new div for every component instance. This keeps the DOM clean with at most four fixed portal containers and eliminates the arbitrary growth of empty <div> elements on document.body while preserving the removeChild race condition fix.
lambdaisland/uri's query-string->map uses :multikeys :duplicates by default: a key that appears once yields a plain string, but the same key repeated yields a vector. cljs.core/parse-long only accepts strings and therefore threw "Expected string, got: object" whenever a URL contained a duplicate 'index' parameter. Add rt/get-query-param to app.main.router. The helper returns the scalar value of a query param key, taking the last element when the value is a sequential (i.e. the key was repeated). Use it at every call site that feeds a query-param value into parse-long, in both app.main.ui (page*) and app.main.data.viewer.
…selected The 'Move to' menu in the dashboard file context menu only filtered out the first selected file's project from the available target list. When multiple files from different projects were selected, the other files' projects still appeared as valid targets, causing a 400 'cant-move-to-same-project' backend error. Now all selected files' project IDs are collected and excluded from the available target projects.
The key :podition was used instead of :position when updating the id-from cell in swap-shapes, silently discarding the position value and leaving the cell's :position as nil after every swap. Signed-off-by: Andrey Antukh <niwi@niwi.nz>
… stops When no gradient stop satisfies (<= offset (:offset %)), d/index-of-pred returns nil. The previous code called (dec nil) in the start binding before the nil check, throwing a NullPointerException/ClassCastException. Guard the start binding with a cond that handles nil before attempting dec. Signed-off-by: Andrey Antukh <niwi@niwi.nz>
get-children-rec passed the original children vector to each recursive call instead of the updated one that already includes the current shape. This caused descendant results to be accumulated from the wrong starting point, losing intermediate shapes. Pass children' (which includes the current shape) into every recursive call. Signed-off-by: Andrey Antukh <niwi@niwi.nz>
font-weight-keys was listed twice in the set/union call for typography-keys, a copy-paste error. The duplicate entry has no functional effect (sets deduplicate), but it is misleading and suggests a missing key such as font-style-keys in its place. Signed-off-by: Andrey Antukh <niwi@niwi.nz>
\`(get "type" shadow)\` always returns nil because the map and key arguments were swapped. The correct call is \`(get shadow "type")\`, which allows the legacy innerShadow detection to work correctly. Update the test expectation accordingly. Signed-off-by: Andrey Antukh <niwi@niwi.nz>
`(mapcat collect-main-shapes children objects)` passes `objects` as a second parallel collection instead of threading it as the second argument to `collect-main-shapes` for each child. Fix by using an anonymous fn: `(mapcat #(collect-main-shapes % objects) children)`. Signed-off-by: Andrey Antukh <niwi@niwi.nz>
* ✨ Add additional logging and validation for image upload
* 🎉 Add chunked upload support for font variants
Extend the font variant upload flow across frontend, backend, and common
to support the standardized chunked upload protocol.
**Backend:**
- Add \`:font-max-file-size\` config default (30 MiB) and schema entry
- Add \`validate-font-size!\` in \`media.clj\` (mirrors
\`validate-media-size!\`, raises \`:font-max-file-size-reached\`)
- Extend \`schema:create-font-variant\` to accept either \`:data\`
(legacy bytes or chunk-vector) or \`:uploads\` (new chunked session
map), with a validator requiring exactly one
- Add \`prepare-font-data-from-uploads\`: assembles each chunked
session via \`cmedia/assemble-chunks\`, validates type+size
- Add \`prepare-font-data-from-legacy\`: normalises legacy byte/chunk
entries, writing to a tempfile (joining via SequenceInputStream),
validates type+size
- Add structured logging ("init"/"end") with \`:size\`, \`:mtypes\`,
and \`:elapsed\` in \`create-font-variant\`
**Frontend:**
- \`upload-blob-chunked\` accepts a per-caller \`:chunk-size\` option
- Add \`font-upload-chunk-size\` (10 MiB) and \`upload-font-variant\`
fn that uploads each mtype as a separate chunked session
- \`on-upload*\` in dashboard fonts now calls \`upload-font-variant\`
instead of issuing \`create-font-variant\` RPC directly
- \`process-upload\` stores raw ArrayBuffer instead of chunking
client-side
**Common:**
- Replace \`"font/opentype"\` with \`"font/woff2"\` in \`font-types\`
**Tests:**
- 25 tests / 224 assertions covering all three upload paths (direct
bytes, legacy chunk-vector, new chunked sessions), size validation,
and media type validation
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
* 📎 Add a script for check the commit format locally
---------
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
* ⬆️ Update dependencies * 📎 Fix playwright dep
* ✨ Remove usage of RELEASE placeholder on deps.edn * 🔧 Add Maven cache to CI --------- Co-authored-by: Yamila Moreno <yamila.moreno@kaleidos.net>
Add escape-html function that escapes HTML special characters and apply it in the comment editor at four dom/set-html! call sites where user-provided text is inserted as innerHTML, preventing stored XSS. Signed-off-by: Andrey Antukh <niwi@niwi.nz>
* feat: Add user in default workspace on login * fix: fixed workspace issue * ✨ Auto join SSO users to provisioned * fix: update backend/src/app/http/auth_request.clj Signed-off-by: Usama Sadiq <usama7274@gmail.com> --------- Co-authored-by: Usama Sadiq <usama.sadiq@arbisoft.com>
Add a shared `schema:font-family` whitelist validator in app.common.types.font that only allows letters, digits, spaces, hyphens, underscores, and dots in font family names. Apply the schema to create-font-variant and update-font RPC endpoints on the backend, and add client-side validation in the dashboard fonts UI. Include unit tests for the schema and integration tests for the RPC handlers. Signed-off-by: Andrey Antukh <niwi@niwi.nz>
…n-minimal 🐛 Re-key session when X-Auth-Request identity differs
# Conflicts: # backend/test/backend_tests/rpc_profile_test.clj
On macOS Docker Desktop, Node's fs.cpSync writes files in the bind mount with a com.docker.grpcfuse.ownership xattr declaring mode 200 even when the host inode is 644. The subsequent `cp -a` (and the original `rsync -avr`) then chmod the destination to 200, which lands on the real host inode and renders the bundle unreadable to anything outside the build container (Linux containers via gRPC-FUSE included). chmod-ing packages/server/dist before the copy rewrites the xattr to 644 while the host mode is still 644, so cp -a propagates the correct mode and the bundle is readable downstream. Behaviour on Linux hosts is unchanged: gRPC-FUSE only runs on macOS Docker Desktop; chmod -R is a no-op when modes are already correct. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…attr fix(mcp-build): normalize dist modes before cp -a (macOS gRPC-FUSE)
* 📚Clarify remote MCP availability in production (penpot#8910) * ⬆️ Update deps on root package.json * 🐛 Fix path drawing preview passing shape instead of content to next-node In `preview-next-point`, `st/get-path` was called without extra keys, which returns the full Shape record. That value was then passed directly to `path/next-node` as its `content` argument. `path/next-node` delegates to `impl/path-data`, which only accepts a `PathData` instance, `nil`, or a sequential collection of segments. A Shape record matches none of those cases, so `path-data` threw "unexpected data" every time the user moved the mouse while drawing a path. The fix is to call `(st/get-path state :content)` so that only the `:content` field (a `PathData` instance) is extracted and forwarded to `path/next-node`. * 🐛 Add webp export format to plugin types (penpot#8870) * 🐛 Add webp export format to plugin types Align plugin API typings with runtime export support by including 'webp' in 'Export.type' and updating the exported formats documentation. Signed-off-by: Marek Hrabe <marekhrabe@me.com> * 📚 Add plugin-types changelog entry for missing webp export format Signed-off-by: Marek Hrabe <marekhrabe@me.com> --------- Signed-off-by: Marek Hrabe <marekhrabe@me.com> Co-authored-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Fix variants corner cases with selrect and points (penpot#8882) Co-authored-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Fix TypeError in sd-token-uuid when resolving tokens interactively (penpot#8929) The backtrace-tokens-tree function used a namespaced keyword :temp/id which clj->js converted to the JS property "temp/id". The sd-token-uuid function then tried to access .id on the sd-token top-level object, which was undefined, causing "Cannot read properties of undefined (reading uuid)". Fix by using the existing token :id instead of generating a temporary one, and read it from sd-token.original (matching sd-token-name pattern). * 🐛 Normalize PathData coordinates to safe integer bounds on read Add normalize-coord helper function that clamps coordinate values to max-safe-int and min-safe-int bounds when reading segments from PathData binary buffer. Applies normalization to read-segment, impl-walk, impl-reduce, and impl-lookup functions to ensure coordinates remain within safe bounds. Add corresponding test to verify out-of-bounds coordinates are properly clamped when reading PathData. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Fix swapped arguments in CLJS PathData -nth with default The CLJS implementation of PathData's -nth protocol method had swapped arguments in the 3-arity version (with default value). The call (d/in-range? i size) should be (d/in-range? size i) to match the CLJ implementation. With swapped args, valid indices always returned the default value, and invalid indices attempted out-of-bounds buffer reads. * 🐛 Fix ^:cosnt typo to ^:const on bool-group-style-properties The metadata key was misspelled as :cosnt instead of :const, preventing the compiler from recognizing the Var as a compile-time constant. * 💄 Fix docstrings and clarify filter expression in path namespaces - Fix 'conten' typo to 'content' in path.cljc docstring - Fix 'curvle' typo to 'curve' in shape_to_path.cljc docstring - Replace confusing XOR-style filter with readable (contains? #{:line-to :curve-to} ...) in bool.cljc - Align handler-indices and opposite-index docstrings with matching API in path.cljc * 🐛 Fix dashboard navigation tabs overlap with content when scrolling (penpot#8937) Co-authored-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Fix highlight on frames after rename (penpot#8938) * 🐛 Fix text editor v1 focus not being handled correctly (penpot#8942) * 📚 Update changelog * ⬆️ Update deps on root package.json * ✨ Fix builder bool and media handling Fixes three concrete builder issues in common/files/builder:\n- Use bool type from shape when selecting style source for difference bools\n- Persist :strokes correctly (fix typo :stroks)\n- Validate add-file-media params after assigning default id\n\nAlso adds regression tests in common-tests.files-builder-test and registers them in runner. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: raguirref <ricardoaguirredelafuente@gmail.com> * 🔥 Remove accidental dev_server.pid Remove unrelated local pid file that was accidentally included in previous commit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: raguirref <ricardoaguirredelafuente@gmail.com> * 📎 Fix formatting issues * ♻️ Use shared singleton containers for React portals (penpot#8957) Refactor use-portal-container to allocate one persistent <div> per logical category (:modal, :popup, :tooltip, :default) instead of creating a new div for every component instance. This keeps the DOM clean with at most four fixed portal containers and eliminates the arbitrary growth of empty <div> elements on document.body while preserving the removeChild race condition fix. * 🔧 Backport ci configuration changes from develop * 🐛 Fix parse-long crash when index query param is duplicated in URL lambdaisland/uri's query-string->map uses :multikeys :duplicates by default: a key that appears once yields a plain string, but the same key repeated yields a vector. cljs.core/parse-long only accepts strings and therefore threw "Expected string, got: object" whenever a URL contained a duplicate 'index' parameter. Add rt/get-query-param to app.main.router. The helper returns the scalar value of a query param key, taking the last element when the value is a sequential (i.e. the key was repeated). Use it at every call site that feeds a query-param value into parse-long, in both app.main.ui (page*) and app.main.data.viewer. * 🐛 Fix move-files allowing same project as target when multiple files selected The 'Move to' menu in the dashboard file context menu only filtered out the first selected file's project from the available target list. When multiple files from different projects were selected, the other files' projects still appeared as valid targets, causing a 400 'cant-move-to-same-project' backend error. Now all selected files' project IDs are collected and excluded from the available target projects. * 🚑 Fix typo :podition in swap-shapes grid cell The key :podition was used instead of :position when updating the id-from cell in swap-shapes, silently discarding the position value and leaving the cell's :position as nil after every swap. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Add better nil handling in interpolate-gradient when offset exceeds stops When no gradient stop satisfies (<= offset (:offset %)), d/index-of-pred returns nil. The previous code called (dec nil) in the start binding before the nil check, throwing a NullPointerException/ClassCastException. Guard the start binding with a cond that handles nil before attempting dec. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Fix stale accumulator in get-children-in-instance recursion get-children-rec passed the original children vector to each recursive call instead of the updated one that already includes the current shape. This caused descendant results to be accumulated from the wrong starting point, losing intermediate shapes. Pass children' (which includes the current shape) into every recursive call. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Remove duplicate font-weight-keys in typography-keys union font-weight-keys was listed twice in the set/union call for typography-keys, a copy-paste error. The duplicate entry has no functional effect (sets deduplicate), but it is misleading and suggests a missing key such as font-style-keys in its place. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Fix reversed `get` args in convert-dtcg-shadow-composite \`(get "type" shadow)\` always returns nil because the map and key arguments were swapped. The correct call is \`(get shadow "type")\`, which allows the legacy innerShadow detection to work correctly. Update the test expectation accordingly. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Fix wrong mapcat call in collect-main-shapes `(mapcat collect-main-shapes children objects)` passes `objects` as a second parallel collection instead of threading it as the second argument to `collect-main-shapes` for each child. Fix by using an anonymous fn: `(mapcat #(collect-main-shapes % objects) children)`. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Fix inside-layout? passing id instead of shape to frame-shape? `(cfh/frame-shape? current-id)` passes a UUID to the single-arity overload of `frame-shape?`, which expects a shape map; it always returns false. Fix by passing `current` (the resolved shape) instead. Update the test to assert the correct behaviour. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Fix ObjectsMap CLJS negative cache keyed on 'key' fn instead of 'k' In the CLJS -lookup implementation, when a key is absent from data the negative cache entry was stored under 'key' (the built-in map-entry key function) rather than the 'k' parameter. As a result every subsequent lookup of any missing key bypassed the cache and repeated the full lookup path, making the negative-cache optimization entirely ineffective. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Fix reversed d/in-range? args in CLJS Fills -nth with default In the ClojureScript Fills deftype, the two-arity -nth implementation called (d/in-range? i size) but the signature is (d/in-range? size i). This meant -nth always fell through to the default value for any valid index when called with an explicit default, since i < size is the condition but the args were swapped. The no-default -nth sibling on line 378 and both CLJ nth impls on lines 286 and 291 had the correct argument order. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Fix wrong extremity point in calculate-extremities for line-to In the :line-to branch of calculate-extremities, move-p (the subpath start point) was being added to the extremities set instead of from-p (the actual previous point). For all line segments beyond the first one in a subpath this produced an incorrect bounding-box start point. The :curve-to branch correctly used from-p; align :line-to to match. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * ✨ Add missing tests for session bug fixes and uniform-spread? Add indexed-access-with-default in fill_test.cljc to cover the two-arity (nth fills i default) form on both valid and out-of-range indices, directly exercising the CLJS Fills -nth path fixed in 593cf125. Add segment-content->selrect-multi-line in path_data_test.cljc to cover content->selrect on a subpath with multiple consecutive line-to commands where move-p diverges from from-p, confirming the bounding box matches both the expected coordinates and the reference implementation; this guards the calculate-extremities fix in bb5a04c7. Add types-uniform-spread? in colors_test.cljc to cover app.common.types.color/uniform-spread?, which had no dedicated tests. Exercises the uniform case (via uniform-spread), the two-stop edge case, wrong-offset detection, and wrong-color detection. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🔥 Remove duplicate gradient helpers from app.common.colors The five functions interpolate-color, offset-spread, uniform-spread?, uniform-spread, and interpolate-gradient duplicated the canonical implementations in app.common.types.color. The copies in colors.cljc also contained two bugs: a division-by-zero in offset-spread when num=1, and a crash on nil idx in interpolate-gradient. All production callers already use app.common.types.color. The duplicate tests that exercised the old copies are removed; their coverage is absorbed into expanded tests under the types-* suite, including a new nil-idx guard test and a single-stop no-crash test. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * ♻️ Move app.common.types.color tests to their own namespace Tests that exercise app.common.types.color were living inside common-tests.colors-test alongside the app.common.colors tests. Move them to common-tests.types.color-test so the test namespace mirrors the source namespace structure, consistent with the rest of the types/ test suite. The [app.common.types.color :as colors] require is removed from colors_test.cljc; the new file is registered in runner.cljc. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Fix spurious argument to dissoc in patch-object The patch-object function was calling (dissoc object key value) when handling nil values. Since dissoc treats each argument after the map as a key to remove, this was also removing nil as a key from the map. The correct call is (dissoc object key). * 🐛 Fix deep-mapm double-applying mfn on leaf entries The deep-mapm function was applying the mapping function twice on leaf entries (non-map, non-vector values): once when destructuring the entry, and again on the already-transformed result in the else branch. Now mfn is applied exactly once per entry. * 🐛 Fix index-of-pred early termination on nil elements The index-of-pred function used (nil? c) to detect end-of-collection, which caused premature termination when the collection contained nil values. Rewrite using (seq coll) / (next s) pattern to correctly distinguish between nil elements and end-of-sequence. * ⚡ Use seq/next idiom in enumerate instead of empty?/rest Replace (empty? items) + (rest items) with (seq items) + (next items) in enumerate. The seq/next pattern is idiomatic Clojure and avoids the overhead of empty? which internally calls seq and then negates. * 🐛 Fix safe-subvec 3-arity evaluating (count v) before nil check The 3-arity of safe-subvec called (count v) in a let binding before checking (some? v). While (count nil) returns 0 in Clojure and does not crash, the nil guard was dead code. Restructure to check (some? v) first with an outer when, then compute size inside the guarded block. * 📚 Fix misleading without-obj docstring The docstring claimed the function removes nil values in addition to the specified object, but the implementation only removes elements equal to the given object. Fix the docstring in both data.cljc and the local copy in files/changes.cljc. * ⚡ Remove redundant map lookups in map-diff The :else branch of diff-attr was calling (get m1 key) and (get m2 key) again, but v1 and v2 were already bound to those exact values. Reuse the existing bindings to avoid the extra lookups. * 📚 Fix typo in namespace docstring ('if' -> 'of') * ✨ Remove redundant str call in format-number format-precision already returns a string, so wrapping its result in an additional (str ...) call was unnecessary. * 🐛 Fix append-class producing leading space for empty class When called with an empty string as the base class, append-class was producing " bar" (with a leading space) because (some? "") returns true. Use (seq class) instead to treat both nil and empty string as absent, avoiding invalid CSS class strings with leading whitespace. * ♻️ Rename shadowed 'fn' parameter to 'pred' in removev The removev function used 'fn' as its predicate parameter name, which shadows clojure.core/fn. Rename to 'pred' for clarity and to follow the naming convention used elsewhere in the namespace. * 🐛 Add missing string? guard to num-string? on JVM The CLJS branch of num-string? checked (string? v) first, but the JVM branch did not. Passing non-string values (nil, keywords, etc.) would rely on exception handling inside parse-double for control flow. Add the string? check for consistency and to avoid using exceptions for normal control flow. * 📚 Fix typos in vec2, zip-all, and map-perm docstrings * 🐛 Fix nan? returning false for ##NaN on JVM Clojure's = uses .equals on doubles, and Double.equals(Double.NaN) returns true, so (not= v v) was always false for NaN. Use Double/isNaN with a number? guard instead. * 🐛 Fix safe-subvec 2-arity rejecting start=0 The guard used (> start 0) instead of (>= start 0), so (safe-subvec v 0) returned nil instead of the full vector. * 🐛 Fix error handling issues (penpot#8962) * 🚑 Fix RangeError from re-entrant error handling in errors.cljs Two complementary changes to prevent 'RangeError: Maximum call stack size exceeded' when an error fires while the potok store error pipeline is still on the call stack: 1. Re-entrancy guard on on-error: a volatile flag (handling-error?) is set true for the duration of each on-error invocation. Any nested call (e.g. from a notification emit that itself throws) is suppressed with a console.error instead of recursing indefinitely. 2. Async notification in flash: the st/emit!(ntf/show ...) call is now wrapped in ts/schedule (setTimeout 0) so the notification event is pushed to the store on the next event-loop tick, outside the error-handler call stack. This matches the pattern already used by the :worker-error, :svg-parser and :comment-error handlers. * 🐛 Add unit tests for app.main.errors Test coverage for the error-handling module: - stale-asset-error?: 6 cases covering keyword-constant and protocol-dispatch mismatch signatures, plus negative cases - exception->error-data: plain JS Error, ex-info with/without :hint - on-error dispatch: map errors routed via ptk/handle-error, JS exceptions wrapped into error-data before dispatch - Re-entrancy guard: verifies that a second on-error call issued from within a handle-error method is suppressed (exactly one handler invocation) --------- Signed-off-by: Andrey Antukh <niwi@niwi.nz> * ✨ Add minor adjustments to the auth events (penpot#9027) * 📎 Update changelog * 📎 Update changelog * 🔧 Add short tag to DocherHub release (penpot#8864) * ⏪ Backport MCP from staging (part 1) * ⬆️ Bump opencode-ai dev dependency 1.4.3 -> 1.14.19 Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🔧 Add main-staging workflow * 🔧 Add main-staging workflow * 🐛 Fix removeChild errors from unmount race conditions (penpot#8927) Guard imperative DOM operations (removeChild, RAF callbacks) against race conditions where React has already unmounted the target nodes. - assets/common.cljs: add dom/child? guard before removeChild in RAF - dynamic_modifiers.cljs: capture RAF IDs and cancel them on cleanup; add null guards for DOM nodes that may no longer exist - hooks.cljs: guard portal container removal with dom/child? check - errors.cljs: extract is-ignorable-exception? to a top-level defn and add NotFoundError/removeChild to ignorable exceptions, since these are caused by browser extensions modifying React-managed DOM - Add unit tests for is-ignorable-exception? predicate Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Fix incorrect handlig of version restore operation (penpot#9041) - Add session ID tracking to RPC layer (backend and frontend) - Send session ID header with RPC requests for request correlation - Rename file-restore to file-restored for consistency - Extract initialize-file function from initialize-workspace flow - Improve file restoration initialization with wait-for-persistence - Extract initialize-version event handler for version restoration - Fix viewport key generation with file version numbers for proper re-renders - Update layout item schema and constraints to use internal sizing state - Add v-sizing state retrieval in layout-size-constraints component - Refactor file-change notifications stream handling with rx/map - Fix team-id lookup in restore-version-from-plugins Improves request traceability across frontend/backend sessions and streamlines the workspace initialization flow for file restoration scenarios. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🎉 Add chunked upload API for large media and binary files Introduce a purpose-agnostic three-step session-based upload API that allows uploading large binary blobs (media files and .penpot imports) without hitting multipart size limits. Backend: - Migration 0147: new `upload_session` table (profile_id, total_chunks, created_at) with indexes on profile_id and created_at. - Three new RPC commands in media.clj: * `create-upload-session` – allocates a session row; enforces `upload-sessions-per-profile` and `upload-chunks-per-session` quota limits (configurable in config.clj, defaults 5 / 20). * `upload-chunk` – stores each slice as a storage object; validates chunk index bounds and profile ownership. * `assemble-file-media-object` – reassembles chunks via the shared `assemble-chunks!` helper and creates the final media object. - `assemble-chunks!` is a public helper in media.clj shared by both `assemble-file-media-object` and `import-binfile`. - `import-binfile` (binfile.clj): accepts an optional `upload-id` param; when provided, materialises the temp file from chunks instead of expecting an inline multipart body, removing the 200 MiB body limit on .penpot imports. Schema updated with an `:and` validator requiring either `:file` or `:upload-id`. - quotes.clj: new `upload-sessions-per-profile` quota check. - Background GC task (`tasks/upload_session_gc.clj`): deletes stalled (never-completed) sessions older than 1 hour; scheduled daily at midnight via the cron system in main.clj. - backend/AGENTS.md: document the background-task wiring pattern. Frontend: - New `app.main.data.uploads` namespace: generic `upload-blob-chunked` helper drives steps 1–2 (create session + upload all chunks with a concurrency cap of 2) and emits `{:session-id uuid}` for callers. - `config.cljs`: expose `upload-chunk-size` (default 25 MiB, overridable via `penpotUploadChunkSize` global). - `workspace/media.cljs`: blobs ≥ chunk-size go through the chunked path (`upload-blob-chunked` → `assemble-file-media-object`); smaller blobs use the existing direct `upload-file-media-object` path. `handle-media-error` simplified; `on-error` callback removed. - `worker/import.cljs`: new `import-blob-via-upload` helper replaces the inline multipart approach for both binfile-v1 and binfile-v3 imports. - `repo.cljs`: `:upload-chunk` derived as a `::multipart-upload`; `form-data?` removed from `import-binfile` (JSON params only). Tests: - Backend (rpc_media_test.clj): happy path, idempotency, permission isolation, invalid media type, missing chunks, session-not-found, chunk-index out-of-range, and quota-limit scenarios. - Frontend (uploads_test.cljs): session creation and chunk-count correctness for `upload-blob-chunked`. - Frontend (workspace_media_test.cljs): direct-upload path for small blobs, chunked path for large blobs, and chunk-count correctness for `process-blobs`. - `helpers/http.cljs`: shared fetch-mock helpers (`install-fetch-mock!`, `make-json-response`, `make-transit-response`, `url->cmd`). Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 📚 Update changelog * ✨ Add 2h min-age threshold to storage/gc_touched task Skip storage objects touched less than 2 hours ago, matching the pattern used by upload-session-gc. Update all affected tests to advance the clock past the threshold using ct/*clock* bindings. * ✨ Add nginx configuration for mcp server * ♻️ Remove worker URI from global templates and compute from public URI - Remove penpotWorkerURI from index.mustache and rasterizer.mustache templates - Remove worker_main entry from the build manifest - Construct worker URI in config.cljs by joining public-uri with worker path - Fix global variable casing for plugins-list-uri and templates-uri - Fix alignment in worker.cljs let bindings * 🐛 Fix exporter renderer URI path construction Apply consistent path construction across bitmap, PDF, and SVG renderers in the exporter. Use path join utilities instead of hardcoding the render.html path, ensuring the path is properly appended to the public URI base path. - bitmap.cljs: Use u/ensure-path-slash and u/join for path - pdf.cljs: Use u/join and ensure-path-slash on base-uri - svg.cljs: Use u/ensure-path-slash and u/join for path * 🐛 Fix nginx configuration for mcp * 🐛 Fix frontend tests * ✨ Allow render entrypoint load alternative config The render entrypoint is used by exporter * 🔧 Add missing public uri handling on nginx entrypoint * 📎 Update mcp types yaml file * 📎 Update version on mcp/ module * 🐛 Fix email validation (penpot#9037) * 🐛 Fix indicate that the mcp is disabled if the mcp key has expired If the mcp key has expired, the switch that indicates the status in the dashboard will appear as disabled, and will show a modal for regenerate the key. It will also appear as disabled in the workspace, not allowing the plugin to connect * ♻️ Derive v-sizing from values instead of passing as prop Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐳 Add PENPOT_PUBLIC_URI to penpot-frontend * 🚑 Fix email blacklisting (penpot#9122) * 📎 Update changelog * 🐛 Fix layer hierarchy to match old and new SCSS (penpot#9126) * ⬆️ Update root repo deps * 📎 Add commit agent for opencode * 🐛 Fix multiple selection on shapes with token applied to stroke-color (penpot#9110) * ✨ Remove the need to navigate to page for deletion operation * 🐛 Fix multiple selection with applied-tokens on stroke-color * 🐛 Fix button position on page header --------- Co-authored-by: Andrey Antukh <niwi@niwi.nz> * 📚 Update AGENTS.md with common github operations * ⬆️ Update devenv dependencies (penpot#9142) * ⬆️ Update devenv dependencies * ✨ Fix formatting issues * 📎 Fix linter issues * 🐛 Prevent invitations to blacklisted domains * 📚 Improve pull request documentation in CONTRIBUTING.md Expand the Pull Requests section with detailed guidance on PR title format, description expectations, branch naming conventions, the review process, and a list of PRs that will not be accepted. Also clarify the 'Discuss Before Building' rule to link to GitHub Issues and Discussions and reference Taiga stories. Update the Table of Contents with nested links for all new subsections. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🔧 Update opencode tooling, agents, and devenv Update agent configurations: change commiter mode to all, rename engineer agent to "Penpot Engineer", and remove obsolete testing agent. Add new read-only planner agent for architecture analysis and planning. Add four new skills: bat-cat (syntax-highlighted cat clone), fd-find (fast file finder), jq-json-processor (JSON processor), and ripgrep (fast text search). Add fd-find and bat packages to devenv Dockerfile. Update .gitignore to exclude opencode package-lock and plans directory. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Fix MCP status is displayed as disabled when setting MCP key without expiration date Fixes #14058 and #14061 in Taiga * 🐛 Fix remove prints * ✨ Add minor compatibility adjustments for audit archive task (penpot#8491) * 📎 Fix fmt issue * 📚 Update changelog * ⏪ Backport transit and plugins hardening compatibility issue From staging * 📚 Update commiter opencode agent * 📚 Update prompt-assistant agent file * ⬆️ Update root repo deps * 📎 Add updated version of github cli to devenv * 🐛 Fix put onboarding modals of top of libraries & templates panel (penpot#9178) * 📚 Update MCP docs for public release (penpot#9184) * 📚 Add 2.15.0 onboarding slides (penpot#9172) * 🎉 Add new slides content * 🎉 Add new slides imgs * 🐛 Fix a typo * 🐛 Fix empty warning on login (penpot#9056) * 📎 Update mcp package.json version * 📎 Update versionon mcp/package.json * ✨ Improve team name validation (penpot#9176) * 🐛 Fix dashboard modal clipping behind sidebar (penpot#9233) Backport from develop commit 510a015. - Fix release notes modal appearing behind the dashboard sidebar (by @RenzoMXD) - Change sidebar z-index from dropdown to panels layer Signed-off-by: Andrey Antukh <niwi@niwi.nz> * ✨ Encourage use of layouts and proper naming in MCP Improve MCP instructions on design creation: * Agents should make use of layouts when appropriate * Agents should name all elements appropriately Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Fix Plugin API token application for JS array of strings Two coupled defects made shape.applyToken(), token.applyToShapes() and token.applyToSelected() silently no-op when invoked from JavaScript with an array of strings (e.g. token.applyToShapes([rect], ["fill"])): 1. token-attr-plugin->token-attr only consulted its alias map when the input was already a keyword; string inputs fell through unchanged, causing downstream token-attr? to return false. 2. The inner schemas used plain [:set ...] which lacks the :decode/json transformer for JS array -> Clojure set coercion. Switching to Penpot's custom [::sm/set ...] lets the standard JSON decoder pipeline handle the conversion automatically. This is a backport of commit 1eac3e2 which fixes GitHub penpot#9162. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🎉 Add backport-commit skill for manual diff-based commit porting Introduce a new OpenCode workflow skill that guides users through backporting commits by applying diffs instead of using cherry-pick. This is useful when cherry-pick is undesirable (e.g. divergent histories, binary conflicts, or partial porting). Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Fix incorrect gh client install on devenv * 🐛 Fix z-index for profile menu (penpot#9257) * 📚 Add WebGL Troubleshooting Guide * 🐛 Fix incorrect text-edition warning when applying tokens (penpot#9355) * 🐛 Fix incorrect invitation token handling on register process (penpot#9380) * 🐛 Fix incorrect invitation token handling on register process - Reject prepare-register-profile when an active profile already exists for the requested email. - Stop embedding an existing profile's :profile-id into the prepared-register JWE. Profile resolution in register-profile is now done exclusively by email lookup, never by a JWE claim. - Add created? guard to the invitation-success branch in register-profile, so existing profiles (active or not) cannot reach session creation via anonymous registration. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * ♻️ Restructure invitation handling inside register-profile Move the invitation-success branch into the created? sub-cond so it sits alongside the other post-creation branches, making the control flow consistent. - Active new profile + matching invitation: mint session and return :invitation-token (frontend redirects to :auth-verify-token). - Not-yet-active new profile + matching invitation: embed the invitation token inside the verify-email JWE and send the verification email. When the user clicks the link, they get logged in and the frontend completes the team-invitation flow. - Extend send-email-verification! with an optional invitation-token parameter propagated into the verify-email JWE claims. - Update the frontend verify-email handler to navigate to :auth-verify-token when the response carries :invitation-token. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Handle email-already-exists error on registration form Add a specific handler for the [:validation :email-already-exists] error code in the registration form's on-error callback. The backend raises this error when an active profile already exists for the requested email, but the frontend was falling through to the generic error message. Now it shows the existing "Email already used" i18n message instead of the generic "Something wrong has happened" toast. * 🐛 Reset submitted state on registration form error The on-error handler in the registration form was not resetting the submitted? state, causing the submit button to remain disabled after any error. The completion callback in rx/subs! only fires on success, not on error. Add (reset! submitted? false) at the beginning of the on-error handler so the form becomes submittable again after any error, allowing the user to fix their input and retry. --------- Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 📚 Add missing changelog entry and document changelog locations Add changelog entry for the fix-incorrect-invitation-token-handling change (PR penpot#9380) under `## 2.15.0 (Unreleased)` > `:bug: Bugs fixed`. Add a `## Changelogs` section to AGENTS.md documenting both changelog locations (main project: `CHANGES.md`, plugins: `plugins/CHANGELOG.md`). Signed-off-by: Andrey Antukh <niwi@niwi.nz> * :bug: Fix incorrect invitation token handling on register process (penpot#9380) * 🐛 Fix incorrect invitation token handling on register process - Reject prepare-register-profile when an active profile already exists for the requested email. - Stop embedding an existing profile's :profile-id into the prepared-register JWE. Profile resolution in register-profile is now done exclusively by email lookup, never by a JWE claim. - Add created? guard to the invitation-success branch in register-profile, so existing profiles (active or not) cannot reach session creation via anonymous registration. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * ♻️ Restructure invitation handling inside register-profile Move the invitation-success branch into the created? sub-cond so it sits alongside the other post-creation branches, making the control flow consistent. - Active new profile + matching invitation: mint session and return :invitation-token (frontend redirects to :auth-verify-token). - Not-yet-active new profile + matching invitation: embed the invitation token inside the verify-email JWE and send the verification email. When the user clicks the link, they get logged in and the frontend completes the team-invitation flow. - Extend send-email-verification! with an optional invitation-token parameter propagated into the verify-email JWE claims. - Update the frontend verify-email handler to navigate to :auth-verify-token when the response carries :invitation-token. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Handle email-already-exists error on registration form Add a specific handler for the [:validation :email-already-exists] error code in the registration form's on-error callback. The backend raises this error when an active profile already exists for the requested email, but the frontend was falling through to the generic error message. Now it shows the existing "Email already used" i18n message instead of the generic "Something wrong has happened" toast. * 🐛 Reset submitted state on registration form error The on-error handler in the registration form was not resetting the submitted? state, causing the submit button to remain disabled after any error. The completion callback in rx/subs! only fires on success, not on error. Add (reset! submitted? false) at the beginning of the on-error handler so the form becomes submittable again after any error, allowing the user to fix their input and retry. --------- Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Fix swapped analytics event names on MCP tab-switch dialog (penpot#9322) * 🐛 Fix MCP "active in another tab" notification not clearing (penpot#9321) * 📚 Update opencode planner agent * 🐛 Bind MCP ReplServer to localhost to prevent unauthenticated RCE The ReplServer Express app was calling `app.listen(port)` with no host argument, causing Node/Express to default to binding on all interfaces (0.0.0.0). Combined with the unauthenticated /execute endpoint, any network peer could POST arbitrary JS and get it run inside the MCP process. Fix: add a `host` parameter (default "localhost") to the ReplServer constructor and pass it to `app.listen`. The call site in PenpotMcpServer now forwards `this.host` (sourced from PENPOT_MCP_SERVER_HOST env var, default "localhost"), so environment- variable overrides continue to work. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Fix keep-alive interval leak in PluginBridge (penpot#9435) The ping interval was stored in a single variable shared across all WebSocket connections, so each new connection overwrote the previous handle and leaked the prior interval. Move the interval onto ClientConnection as a per-connection field, and centralize teardown in a new removeConnection(ws) method used by the close, error and duplicate token rejection paths. Resolves penpot#9430 * 🚑 Use base64 envelope for Uint8Array task results to avoid JSON expansion (penpot#9431) Resolves penpot#9420 (critical memory usage issue in PROD deployment) When the plugin's ExecuteCodeTaskHandler returns a Uint8Array (e.g. from penpotUtils.exportImage), JSON.stringify previously serialized it as an object with numeric string keys, causing ~10x payload expansion and large peak heap usage on the server side. The plugin now wraps a top-level Uint8Array result in a tagged envelope { __type: "base64", data: <base64> }, and ImageContent.byteData decodes this envelope on the server. The legacy numeric-keyed-object path is retained as a fallback for compatibility with older plugin builds. * 📚 Update changelog * 📚 Update changelog * 🐛 Harden outbound HTTP requests against SSRF and restrict assets handlers (penpot#9390) * ⬆️ Update root deps * 🐛 Harden outbound HTTP requests against SSRF and restrict unauthenticated asset access - Add app.util.ssrf URL/host validator that resolves hostnames and blocks loopback, link-local, site-local, cloud metadata, and operator-supplied CIDRs - Add app.media.sanitize image EOF truncator that strips trailing data after PNG IEND, JPEG EOI, GIF trailer, and WebP RIFF markers - Disable HTTP client auto-redirect; add req-with-redirects! helper that revalidates every redirect hop against the SSRF blocklist - Wire SSRF validation and EOF sanitization into media/download-image - Validate webhook URLs and OIDC profile picture URLs against SSRF - Restrict /assets/by-id to require authentication for non-public buckets (profile) while keeping public access for file-media-object, file-object-thumbnail, team-font-variant, and file-data-fragment - Add config knobs: ssrf-protection-enabled, ssrf-allowed-hosts, ssrf-extra-blocked-cidrs Signed-off-by: Andrey Antukh <niwi@niwi.nz> --------- Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 📎 Update changelog * 🐳 Reuse shared Nginx security headers (penpot#9473) Signed-off-by: Francis Santiago <francis.santiago@kaleidos.net> * 📎 Add missing entry on CHANGES.md * ✨ Add nrepl-eval script and skill * 🐛 Fix MCP integrations copy button to match displayed URL (penpot#9239) * ✨ Add plugins and mcp event data (penpot#9228) * ✨ Add plugins and mcp event data * ♻️ Changed data-event ::ev/event to ev/event * 🐛 Fix maximum call stack size exceeded in SSE read-stream (penpot#9484) The recursive `read-items` function in `app.util.sse/read-stream` caused a synchronous stack overflow when reading buffered stream data. Each `rx/mapcat` call chained another recursive invocation on the same call stack without yielding to the event loop. Replace the recursive pattern with an `rx/create`-based async pump that uses Promise `.then()` chaining, keeping the call stack depth constant regardless of stream size. Also add progress reporting with names and IDs during binfile export and import, and bump `eventsource-parser` dependency. Closes penpot#9470 Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 📚 Update changelog * 🔧 Add minor changes to devenv config * 🎉 Add telemetry anonymous event collection (penpot#9483) * 🎉 Add telemetry anonymous event collection Rewrite the audit logging subsystem to support three operating modes and add anonymous telemetry event collection: Modes: - A (audit-log only): events persisted with full context - B (audit-log + telemetry): same as A, plus events are collected for telemetry shipping - C (telemetry-only): events stored anonymously with PII stripped, telemetry flag active, audit-log flag inactive Audit system refactoring (app.loggers.audit): - Replace qualified map keys (::audit/name etc.) with plain keywords - Rename submit! -> submit, insert! -> insert, prepare-event -> prepare-rpc-event - Add submit* as a lower-level public API - Add process-event dispatch function that handles all three modes and webhooks in a single tx-run! - Add :id to event schema (auto-generated if omitted) - Add filter-telemetry-props: anonymises event props per event type. Keeps UUID/boolean/number values; for login/identify events preserves lang, auth-backend, email-domain; for navigate events preserves route, file-id, team-id, page-id; instance-start trigger passes through. - Add filter-telemetry-context: retains only safe context keys. Backend: version, initiator, client-version, client-user-agent. Frontend: browser, os, locale, screen metrics, event-origin. - Timestamps truncated to day precision via ct/truncate for telemetry storage - PII stripped: props emptied, ip-addr zeroed, session-linking and access-token fields removed from context Config (app.config): - Derive :enable-telemetry flag from telemetry-enabled config option Email utilities (app.email): - Add email/clean and email/get-domain helper functions for domain extraction from email addresses Setup (app.setup): - Emit instance-start trigger event at system startup - Simplify handle-instance-id (remove read-only check) RPC layer (app.rpc): - wrap-audit now activates when :telemetry flag is set - Add :request-id to RPC params context for event correlation RPC commands (management, teams_invitations, verify_token, OIDC auth, webhooks): migrate all audit call sites to use the new plain-key API SREPL (app.srepl.main): - Migrate all audit/insert! calls to audit/insert with plain keys Telemetry task (app.tasks.telemetry): - Restructure legacy report into make-legacy-request; distinguish payload type as :telemetry-legacy-report - Add collect-and-send-audit-events: loop fetching up to 10,000 rows per iteration, encodes and sends each page, deletes on success, stops immediately on failure for retry - Add send-event-batch: POSTs fressian+zstd batch (base64 via blob/encode-str) to the telemetry endpoint with instance-id per event - Add gc-telemetry-events: enforces 100,000-row safety cap by dropping oldest rows first - Add delete-sent-events: deletes successfully shipped rows by id Blob utilities (app.util.blob): - Add encode-str/decode-str: combine fressian+zstd encoding with URL- safe base64 for JSON-safe string transport Database: - Add migration 0145: index on audit_log (source, created_at ASC) for efficient telemetry batch collection queries Frontend: - Always initialize event system regardless of :audit-log flag - Defer auth events (signin identify) to after profile is set - Refactor event subsystem for telemetry support Tests (21 test vars, 94 assertions in tasks-telemetry-test): - Cover all code paths: disabled/enabled telemetry, no-events no-op, happy-path batch send and delete, failure retention, payload anonymity, context stripping, timestamp day precision, batch encoding round-trip, multi-page iteration, GC cap enforcement, partial failure handling - blob encode-str/decode-str round-trip tests (14 test vars) - RPC audit integration tests (5 test vars) Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 📎 Add pr feedback changes --------- Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Add missing migrations for audit-log tables * ✨ Improve MCP server logging, adding Loki support (penpot#9425) * ✨ Improve MCP server logging Log only fingerprints of user tokens * ✨ Add Loki transport support to MCP server logger Loki logging is enabled iff PENPOT_LOGGERS_LOKI_URI is non-empty. File logging is now enabled iff PENPOT_MCP_LOG_DIR is set to a non-empty value (previously defaulted to the "logs" directory when unset). GitHub penpot#9415 * 🐛 Skip the ssrf check on internal audit-log archive task * 🐛 Fix unexpected exception on handling webhook events * 🐛 Fix mattermost and database logger related to the audit event change * 📚 Update changelog * 📎 Update version on mcp server * 📚 Add notice regarding architectural constraints with MCP Server (penpot#9423) * 📎 Update changelog with PR info * 🎉 Add chunked upload support for font variants (penpot#9551) * ✨ Add additional logging and validation for image upload * 🎉 Add chunked upload support for font variants Extend the font variant upload flow across frontend, backend, and common to support the standardized chunked upload protocol. **Backend:** - Add \`:font-max-file-size\` config default (30 MiB) and schema entry - Add \`validate-font-size!\` in \`media.clj\` (mirrors \`validate-media-size!\`, raises \`:font-max-file-size-reached\`) - Extend \`schema:create-font-variant\` to accept either \`:data\` (legacy bytes or chunk-vector) or \`:uploads\` (new chunked session map), with a validator requiring exactly one - Add \`prepare-font-data-from-uploads\`: assembles each chunked session via \`cmedia/assemble-chunks\`, validates type+size - Add \`prepare-font-data-from-legacy\`: normalises legacy byte/chunk entries, writing to a tempfile (joining via SequenceInputStream), validates type+size - Add structured logging ("init"/"end") with \`:size\`, \`:mtypes\`, and \`:elapsed\` in \`create-font-variant\` **Frontend:** - \`upload-blob-chunked\` accepts a per-caller \`:chunk-size\` option - Add \`font-upload-chunk-size\` (10 MiB) and \`upload-font-variant\` fn that uploads each mtype as a separate chunked session - \`on-upload*\` in dashboard fonts now calls \`upload-font-variant\` instead of issuing \`create-font-variant\` RPC directly - \`process-upload\` stores raw ArrayBuffer instead of chunking client-side **Common:** - Replace \`"font/opentype"\` with \`"font/woff2"\` in \`font-types\` **Tests:** - 25 tests / 224 assertions covering all three upload paths (direct bytes, legacy chunk-vector, new chunked sessions), size validation, and media type validation Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 📎 Add a script for check the commit format locally --------- Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐳 Add penpot-mcp service to official docker-compose.yml * 🐳 Add mcp server to release workflow * 📚 Update changelog * 🐛 Fix metrics for rpc methods * 🐳 Improve nginx configuration for MCP server (penpot#9565) * 📚 Update changelog * 🐳 Pin minor version in docker-compose.yaml * 🐳 Add enable-mcp to docker-compose as default behaviour * ⬆️ Update dependencies (penpot#9597) * ⬆️ Update dependencies * 📎 Fix playwright dep * 🔧 Add cache to github tests CI worflow. (penpot#9621) * ✨ Remove usage of RELEASE placeholder on deps.edn * 🔧 Add Maven cache to CI --------- Co-authored-by: Yamila Moreno <yamila.moreno@kaleidos.net> * 🐛 Sanitize comment content on rendering (penpot#9605) Add escape-html function that escapes HTML special characters and apply it in the comment editor at four dom/set-html! call sites where user-provided text is inserted as innerHTML, preventing stored XSS. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Prevent CSS injection vulnerability in font family names Add a shared `schema:font-family` whitelist validator in app.common.types.font that only allows letters, digits, spaces, hyphens, underscores, and dots in font family names. Apply the schema to create-font-variant and update-font RPC endpoints on the backend, and add client-side validation in the dashboard fonts UI. Include unit tests for the schema and integration tests for the RPC handlers. Signed-off-by: Andrey Antukh <niwi@niwi.nz> * 🐛 Fix plugins schema validation error (penpot#9632) * 📎 Add gh-issue-from-pr SKILL for opencode * 📎 Add update changelog opencode skill * 📚 Update changelog * fix(mcp-build): normalize dist modes before cp -a (macOS gRPC-FUSE) On macOS Docker Desktop, Node's fs.cpSync writes files in the bind mount with a com.docker.grpcfuse.ownership xattr declaring mode 200 even when the host inode is 644. The subsequent `cp -a` (and the original `rsync -avr`) then chmod the destination to 200, which lands on the real host inode and renders the bundle unreadable to anything outside the build container (Linux containers via gRPC-FUSE included). chmod-ing packages/server/dist before the copy rewrites the xattr to 644 while the host mode is still 644, so cp -a propagates the correct mode and the bundle is readable downstream. Behaviour on Linux hosts is unchanged: gRPC-FUSE only runs on macOS Docker Desktop; chmod -R is a no-op when modes are already correct. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Signed-off-by: Marek Hrabe <marekhrabe@me.com> Signed-off-by: Andrey Antukh <niwi@niwi.nz> Signed-off-by: raguirref <ricardoaguirredelafuente@gmail.com> Signed-off-by: Francis Santiago <francis.santiago@kaleidos.net> Co-authored-by: andrés gonzález <andres.gonzalez79@gmail.com> Co-authored-by: Andrey Antukh <niwi@niwi.nz> Co-authored-by: Elena Torró <elenatorro@gmail.com> Co-authored-by: Marek Hrabe <marekhrabe@me.com> Co-authored-by: Pablo Alba <pablo.alba@kaleidos.net> Co-authored-by: Luis de Dios <luis.dedios@kaleidos.net> Co-authored-by: Eva Marco <eva.marco@kaleidos.net> Co-authored-by: Aitor Moreno <asakon28@gmail.com> Co-authored-by: raguirref <ricardoaguirredelafuente@gmail.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Yamila Moreno <yamila.moreno@kaleidos.net> Co-authored-by: Juan de la Cruz <delacruzgarciajuan@gmail.com> Co-authored-by: Alonso Torres <alonso.torres@kaleidos.net> Co-authored-by: Dexterity <173429049+Dexterity104@users.noreply.github.com> Co-authored-by: Dr. Dominik Jain <dominik.jain@oraios-ai.de> Co-authored-by: Francis Santiago <francis.santiago@kaleidos.net> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
for diff comparison purposes only – do not merge