Compare commits

...
Author SHA1 Message Date
Andrey Antukh 13b5c96a42 📎 Update changelog 2026-03-24 09:19:58 +01:00
Andrey Antukh b484415a9f 🐛 Fix generic error shown on clipboard permission denial (#8666)
When the browser denies clipboard read permission (NotAllowedError),
the unhandled exception handler was showing a generic 'Something wrong
has happened' toast. This change adds proper error handling for
clipboard permission errors in paste operations and shows a
user-friendly warning message instead.

Changes:
- Add error handling in paste-from-clipboard for NotAllowedError
- Improve error handling in paste-selected-props to detect permission errors
- Mark clipboard NotAllowedError as ignorable in the uncaught error handler
  to prevent duplicate generic error toasts
- Add translation key for clipboard permission denied message

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-23 20:03:14 +01:00
Andrey Antukh 2d616cf9c0 📚 Add better organization for AGENTS.md file (#8675) 2026-03-18 14:59:38 +01:00
Andrey Antukh 1a59017e1c 🐛 Ignore posthog exceptions in unhandled exception handler (#8629)
PostHog recorder throws errors like 'Cannot assign to read only property
'assert' of object' which are unrelated to the application and should be
ignored to prevent noise in error reporting.
2026-03-17 18:41:06 +01:00
Elena Torró c6f8356847 📚 Improve docs on feature flags (#8641) 2026-03-17 11:49:14 +01:00
Andrey Antukh e730e9ee64 🐛 Fix subscribe to undefined stream error in use-stream hook (#8633)
Add a nil guard before subscribing to the stream in the use-stream
hook. When a nil/undefined stream is passed (e.g., from a conditional
expression or timing edge case during React rendering), the subscribe
call on undefined causes a TypeError. The guard ensures we only
subscribe when the stream is defined.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-17 10:06:16 +01:00
Andrey Antukh 0779c9ca61 🐛 Fix TypeError in get-points when content is not PathData (#8634)
The with-cache macro in impl.cljc assumed the target was always a
PathData instance (which has a cache field). When content was a plain
vector, (.-cache content) returned undefined in JS, causing:

  TypeError: Cannot read properties of undefined (reading 'get')

Fix:
- path/get-points (app.common.types.path) is now the canonical safe
  entry point: converts non-PathData content via impl/path-data and
  handles nil safely before delegating to segment/get-points
- segment/get-points remains a low-level function that expects a
  PathData instance (no defensive logic at that level)
- streams.cljs: replace direct call to path.segm/get-points with
  path/get-points so the safe conversion path is always used
- with-cache macro: guards against nil/undefined cache, falling back
  to direct evaluation for non-PathData targets

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-17 09:31:10 +01:00
andrés gonzález efd6d19a12 📚 Remove link to sales form from the Help Center (#8643) 2026-03-16 16:58:08 +01:00
Elena Torró ac69f28a0a Merge pull request #8646 from penpot/elenatorro-include-wasm-share-on-gitignore
🔧 Ignore render_wasm shared.js autogenerated file
2026-03-16 13:04:25 +01:00
Elena Torro ff1ba6b953 🔧 Ignore render_wasm shared.js autogenerated file 2026-03-16 12:09:54 +01:00
Andrey Antukh a528508751 📚 Update AGENTS.md 2026-03-13 12:57:22 +01:00
Andrey Antukh 5d931d1614 Merge remote-tracking branch 'origin/main' into staging 2026-03-13 09:20:30 +01:00
Andrey Antukh 33c5f82c43 🐛 Fix penpot.openPage() to navigate in same tab by default
- Change the default for the newWindow param from true to false, so
  openPage() navigates in the same tab instead of opening a new one
- Accept a UUID string as the page argument in addition to a Page object,
  avoiding the need to call penpot.getPage(uuid) first
- Add validation error when an invalid page argument is passed

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-13 09:19:50 +01:00
Alejandro Alonso ebd17974a6 Merge pull request #8580 from penpot/niwinz-staging-fix-paste-issue
🐛 Fix crash when pasting non-map transit clipboard data
2026-03-13 08:33:19 +01:00
Andrey Antukh eecb51ecc1 🐛 Fix clipboard getType error when no allowed types found (#8609)
When clipboard items have types that don't match the allowed types
list, the filtering results in an empty array. Calling getType with
undefined throws a NotFoundError. This change adds a check for null/undefined
types and filters them from the result.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-13 08:24:36 +01:00
Andrey Antukh e7e6303184 🐛 Make ct/format-inst nil safe (#8612)
Prevent JS TypeError when date is nil in date formatting.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-12 19:55:51 +01:00
Andrey Antukh 25df9f2f83 🐛 Fix DataCloneError in plugin postMessage communication
Fixes a crash where plugins sending messages via 'penpot.ui.sendMessage()'
could fail if their message payload contained non-serializable values like
functions or closures.

The fix adds validation using 'structuredClone()' to catch these messages
early with a helpful error message, and adds a defensive try/catch in the
modal's message handler as a safety net.

Fixes the error: 'Failed to execute postMessage on Window: ... could not
be cloned.'

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-12 15:26:00 +01:00
Andrey Antukh 8d5450391e 🐛 Fix crash when pasting non-map transit clipboard data
Guard against transit-decoded clipboard content that is not a map
before calling assoc, which caused a runtime crash ('No protocol
method IAssociative.-assoc defined for type number').

Also route :copied-props paste data to paste-transit-props instead
of incorrectly sending it to paste-transit-shapes.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-12 15:05:22 +01:00
Alejandro Alonso be9b1158ed Merge pull request #8588 from penpot/niwinz-staging-abort-signal-fix
🐛 Fix unhandled AbortError in HTTP fetch requests
2026-03-12 13:53:18 +01:00
Eva Marco 8f5c38d476 🐛 Fix scroll on colorpicker (#8595) 2026-03-12 13:36:38 +01:00
Andrey Antukh 80d165ed5b 🐛 Fix unhandled AbortError in HTTP fetch requests
Identify and silence "signal is aborted without reason" errors by:
- Providing an explicit reason to AbortController when subscriptions are disposed.
- Updating the global error handler to ignore AbortError exceptions.
- Ensuring unhandled rejections use the ignorable exception filter.

The root cause was RxJS disposal calling .abort() without a reason, combined
with the on-unhandled-rejection handler missing the ignorable error filter.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-12 13:31:58 +01:00
Alejandro Alonso 4b330e7b50 Merge pull request #8596 from penpot/niwinz-staging-fix-max-recursion
🐛 Fix RangeError (stack overflow) in find-component-main
2026-03-12 13:30:16 +01:00
Alejandro Alonso f5cabac5f3 Merge pull request #8583 from penpot/niwinz-staging-ignore-extensions-exceptions
🐛 Ignore browser extension errors in unhandled exception handler
2026-03-12 13:20:11 +01:00
Alejandro Alonso 1487386fbb Merge pull request #8582 from penpot/niwinz-staging-bugfix-path-plain-content
🐛 Fix plain vector leaking into shape :content from shape-to-path
2026-03-12 13:15:14 +01:00
Andrey Antukh b68e400cc1 🐛 Fix crash in select* when options vector is empty (#8578)
Guard get-option fallback with (when (seq options) ...) to avoid
"No item 0 in vector of length 0" when options is an empty vector.
Also guard the selected-option memo in select* to mirror the same
pattern already present in combobox*.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-12 13:06:25 +01:00
Alejandro Alonso 50c27aecc7 Merge pull request #8574 from penpot/niwinz-staging-unmount-fixes
🐛 Fix removeChild crash on portal-on-document* unmount
2026-03-12 13:06:00 +01:00
Alejandro Alonso c254f88367 Merge pull request #8575 from penpot/niwinz-staging-fetch-exception
🐛 Wrap fetch TypeError into proper ex-info with :unable-to-fetch code
2026-03-12 12:48:48 +01:00
Alejandro Alonso 8f7b12dfd8 Merge pull request #8569 from penpot/niwinz-staging-bugfix-2-not-iseqable-exception
🐛 Fix 'not ISeqable' error when entering float values in layout/opacity inputs
2026-03-12 12:37:35 +01:00
Andrey Antukh 82e3a5fa53 🐛 Fix 'not ISeqable' error when entering float values in layout/opacity inputs
Replace int? with number? in on-change handlers for layout item margins,
min/max sizes, and layer opacity. Using int? caused float values like 8.5
to fall into the design token branch, calling (first 8.5) and crashing.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-12 12:37:22 +01:00
Alejandro Alonso 1680be33ef Merge pull request #8568 from penpot/niwinz-staging-bugfix-1-path-get-points
🐛 Fix TypeError when path content is nil in get-points calls
2026-03-12 12:31:58 +01:00
Andrey Antukh 6ee8184821 🐛 Fix error when creating guides without frame (#8598)
* 🐛 Fix error when creating guides without frame

The error 'Cannot read properties of undefined (reading
$cljs$core$IFn$_invoke$arity$0$)' occurred when creating a new
guide. It is probably a race condition because it is not reproducible
from the user point of view.

The cause is mainly because of use incorrect jsx handler :& where :>
should be used. This caused that some props pased with incorrect casing
and the relevant callback props received as nil on the component and
on the use-guide hook.

The fix is simple: use correct jsx handler

Signed-off-by: Andrey Antukh <niwi@niwi.nz>

* 💄 Add cosmetic changes to viewport guides components

---------

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-12 12:23:09 +01:00
Eva Marco c00ef7c128 🐛 Fix unnexpected warning (#8603) 2026-03-12 12:01:28 +01:00
Xaviju 9b3207b06c 🐛 Fix wrong value on property copy on inspect styles (#8605) 2026-03-12 11:45:16 +01:00
Andrey Antukh 11a1ac2a09 🐛 Fix RangeError (stack overflow) in find-component-main
Refactor find-component-main to use an iterative loop/recur pattern instead of direct recursion and added cycle detection for malformed data structures.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-11 16:15:47 +01:00
Alonso Torres 31a4a7f21f 💄 Removed forgotten print (#8594) 2026-03-11 16:04:33 +01:00
Andrey Antukh 7ec9261475 Add improvements to AGENTS.md (#8586) 2026-03-11 15:24:40 +01:00
Andrey Antukh 33da7f384a Merge remote-tracking branch 'origin/staging' 2026-03-11 12:04:28 +01:00
David Barragán Merino e6d15a5ac2 🔧 Disable search indexing of plugin docs for non-production envs 2026-03-10 19:36:52 +01:00
David Barragán Merino e855907b05 🔧 Disable search indexing of plugin docs for non-production envs 2026-03-10 19:36:28 +01:00
Andrey Antukh db9e9f4832 🐛 Ignore browser extension errors in unhandled exception handler
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-10 19:30:01 +01:00
Andrey Antukh 7939cb045b 🐛 Fix plain vector leaking into shape :content from shape-to-path conversions
group-to-path was storing a raw concatenated vector into :content after
flattening children's PathData instances via (map vec). bool-to-path
was storing the plain-vector result of bool/calculate-content directly.
Both now wrap through path.impl/path-data at the assignment site so the
:content invariant (always a PathData instance) is upheld.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-10 19:26:03 +01:00
Andrey Antukh f566a2adfd 🐛 Fix ITransformable error when path content is a plain vector
Coerce content to PathData in transform-content before dispatching
the ITransformable protocol, so shapes carrying a plain vector in
their :content field (legacy data, bool shapes, SVG imports) no
longer crash with 'No protocol method ITransformable.-transform
defined for type object'.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-10 18:06:44 +00:00
Andrey Antukh 31d8b35a2c 📎 Revert small changes related to browser pool on exporter 2026-03-10 18:51:04 +01:00
Andrey Antukh fed01fba73 🐛 Wrap fetch TypeError into proper ex-info with :unable-to-fetch code
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-10 15:59:14 +01:00
Andrey Antukh 4f0bceddae 🐛 Fix stale deferred DOM ops in dashboard navigation
Two related issues that could cause crashes during fast navigation
in the dashboard:

1. grid.cljs: On drag-start, a temporary counter element is appended
   to the file card node for the drag ghost image, then scheduled for
   removal via requestAnimationFrame. If the user navigates away before
   the RAF fires, React unmounts the section and removes the card node
   from the DOM. When the RAF fires, item-el.removeChild(counter-el)
   throws because counter-el is no longer a child. Fixed by guarding
   the removal with dom/child?.

2. sidebar.cljs: Keyboard navigation handlers used ts/schedule-on-idle
   (requestIdleCallback with a 30s timeout) to focus the newly rendered
   section title after navigation. This left a very wide window for the
   callback to fire against a stale DOM after a subsequent navigation.
   Additionally, the idle callbacks were incorrectly passed as arguments
   to st/emit! (which ignores non-event values), making the scheduling
   an accidental side effect. Fixed by replacing all occurrences with
   ts/schedule (setTimeout 0), which is sufficient to defer past the
   current render cycle, and moving the calls outside st/emit!.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-10 14:22:45 +00:00
Andrey Antukh 80b64c440c 🐛 Fix removeChild crash on portal-on-document* unmount
The previous implementation passed document.body directly as the
React portal containerInfo. During unmount, React's commit phase
(commitUnmountFiberChildrenRecursively, case 4) sets the current
container to containerInfo and then calls container.removeChild()
for every DOM node inside the portal tree.

When two concurrent state updates are processed — e.g. navigating
away from a dashboard section while a file-menu portal is open —
React could attempt document.body.removeChild(node) twice for the
same node, the second time throwing:

  NotFoundError: Failed to execute 'removeChild' on 'Node':
  The node to be removed is not a child of this node.

The fix allocates a dedicated <div> container per portal instance
via mf/use-memo. The container is appended to body on mount and
removed in the effect cleanup. React then owns an exclusive
containerInfo and its unmount path never races with another
portal or the modal container (which also targets document.body).

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-10 14:22:34 +00:00
Andrey Antukh 98c1503bca Backport serveral plugin types documentation 2026-03-10 15:05:08 +01:00
Andrey Antukh 9f66220caa 🐛 Fix flex layout container horizontalSizing/verticalSizing via plugin API (#8555)
Setting horizontalSizing/verticalSizing on a FlexLayoutProxy was
dispatching update-layout-child instead of update-layout, so the
frame's auto-sizing (hug content) was never triggered even though
the getter read back the value correctly.

Also restricts accepted values to #{:fix :auto} (matching shape.cljs)
since frames cannot use :fill, and fixes a copy-paste error that
reported :horizontalPadding instead of :horizontalSizing in error messages.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-10 15:01:23 +01:00
Aitor Moreno 3112b0d8cf 🐛 Fix grow options not verifying text-editor/v2 (#8571) 2026-03-10 15:01:23 +01:00
Andrey Antukh ab90500ec8 🐛 Fix download-image to properly handle network errors and non-2xx responses (#8554)
The download-image function in app.media silently succeeded when the
remote image URL was unreachable or returned an error status code,
causing create-file-media-object-from-url to report success with no
actual image stored.

Add exception handling for connection refused, timeouts, and I/O errors
around the HTTP request, and validate the HTTP status code in
parse-and-validate before processing the response body.

Fixes #8499

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-10 15:01:23 +01:00
Andrey Antukh 0f47c30349 Merge branch 'main' into staging 2026-03-10 14:39:16 +01:00
Andrey Antukh 68fbacf8b3 Merge tag '2.14.0-RC2' 2026-03-10 14:38:58 +01:00
Andrey Antukh 7ab5f241da 🐛 Fix TypeError when path content is nil in get-points calls
Use nil-safe path/get-points wrapper (some-> based) instead of
direct path.segment/get-points calls in edition.cljs to prevent
'Cannot read properties of undefined (reading get)' crash.

Add nil-safety test to verify path/get-points returns nil without
throwing when content is nil.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-10 12:21:13 +00:00
Andrey Antukh 32cf95265a 📚 Add GitHub Copilot instructions (#8548) 2026-03-10 13:12:15 +01:00
Pablo Alba 34d29328e6 🐛 Fix bad size on switching a layout with fixed sizing (#8504) 2026-03-09 12:12:03 +01:00
Eva Marco c59cc4dff4 🐛 Fix tooltip position on absolute positioned elements (#8509)
* 🐛 Fix tooltip position on absolute positioned elements

* 🐛 Fix tests
2026-03-09 12:11:39 +01:00
Andrey Antukh 0ceadada35 🐛 Fix invalid data on layout flex dir shape property 2026-03-09 10:09:07 +01:00
Andrey Antukh 77955d7f91 Add several redundant checks for library-id on file rpc methods 2026-03-09 10:01:29 +01:00
Andrey Antukh 151238e518 💄 Add cosmetic change to link-file-to-library rpc method impl 2026-03-09 10:01:29 +01:00
Andrey Antukh 591d63e470 Add better error report on wrong input on logging helpers 2026-03-09 10:01:09 +01:00
andrés gonzález 5a6be141fd 📚 Add info about using math in tokens (#8510) 2026-03-04 14:59:04 +01:00
Eva Marco cc3033735b 🐛 Fix showing warning when no shape is selected (#8515) 2026-03-04 10:58:36 +01:00
Xaviju e1d556f4aa 🐛 Sort tokens by name (#8488) 2026-03-04 10:33:29 +01:00
Andrey Antukh c3f5117757 🐛 Fix unhandled exception on using decimals on stroke row (#8405) 2026-03-04 09:47:14 +01:00
Andrey AntukhandEva Marco 86e851f408 🐛 Fix incorrect version visibility on workspace (#8463)
* 🐛 Add missing order by clause to snapshot query

This fixes the incorrect snapshot visibility when file
has a lot of versions.

*  Reduce allocation on milestone-group* component

* 🐛 Fix milestone group timestamp formatting

* 📎 Update changelog

* 🐛 Fix scroll on history panel

---------

Co-authored-by: Eva Marco <evamarcod@gmail.com>
2026-03-04 09:27:51 +01:00
Andrey Antukh a4351d133b Add minor improvements to error reporting (#8402) 2026-03-04 09:12:19 +01:00
Andrey Antukh b704a7da0e 🐛 Fix inconsistency between plugins api doc and impl for shadows (#8454)
Related to offset-x and offset-y attributes.
2026-03-04 09:09:27 +01:00
Andrey Antukh 478f631df5 🐛 Don't throw exception when picker is closed and image is still uploading (#8453)
*  Add notification tag to media uploading

This avoid hidding error messages once the upload
is finished.

* 🐛 Don't throw exception when picker is closed and image is still uploading
2026-03-04 09:07:15 +01:00
Andrey Antukh 57b9efbcd7 🐛 Fix redo operation on commenting on workspace (#8455) 2026-03-03 09:50:23 +01:00
andrés gonzález db0a8b65ca 📚 Add info about tokens remapping (#8503) 2026-03-03 09:02:31 +01:00
andrés gonzález 7c326e05e4 📚 Fix spanish text at docs (#8502) 2026-03-03 09:02:08 +01:00
andrés gonzález 58e86a545a 📚 Add info about grouping tokens (#8508) 2026-03-03 09:01:48 +01:00
Andrés Moya 31478c6afc 🐛 Fix validation of shadow token with missing keys (#8507) 2026-03-02 16:17:12 +01:00
Juan de la Cruz 7066afa01a 🎉 Add new slides 2.14 content (#8478) 2026-02-26 12:19:15 +01:00
Eva Marco 9345902a62 🐛 Fix cannot apply second token after creation while shape is selected (#8476) 2026-02-26 10:53:25 +01:00
Alonso Torres a4190df073 🐛 Fix problem with flex.appendChild with naturalOrdering on plugins API (#8470) 2026-02-26 10:47:44 +01:00
Eva Marco 47dae090ed 🐛 Add notification to token applied during text edition (#8434) 2026-02-26 10:24:48 +01:00
Andrés Moya c72e9ee1a0 🐛 Convert token values for the plugins 2026-02-25 14:04:20 +01:00
Andrés Moya ba87ea1a44 🔧 Add tokenscript flag and more validations to token values 2026-02-25 14:04:20 +01:00
Andrés Moya 72a855d4ac 🐛 Fix activeSets in themes API 2026-02-25 14:04:20 +01:00
Eva Marco e2377e8fa8 🐛 Fix input width on composite token form 2026-02-25 14:04:20 +01:00
Andrey Antukh b4c279ad7b 💄 Add minor cosmetic refactor on how plugin flags are stored
The main idea behind this, is move all plugin related stuff from
app.main.data.plugins into app.plugins.* and make them more consistent.
Also the intention that put all plugins related state under specific
prefix on the state.
2026-02-25 11:35:03 +01:00
Alonso Torres c972c06142 🐛 Fix problem with export dialog on single board (#8426) 2026-02-24 14:41:35 +01:00
Andrey Antukh d176da8012 Add jfr and jcmd to the backend docker image (#8446) 2026-02-24 00:08:14 +01:00
Andrey Antukh 20862c2da3 🐛 Fix incorrect plugin icon resolution 2026-02-24 00:07:30 +01:00
Andrey Antukh 1b8afccba2 Remove usage of multipart body size config on backend 2026-02-23 14:44:44 +01:00
Yamila Moreno dd856ecf50 ♻️ Deprecate PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE envvar 2026-02-23 13:48:01 +01:00
Andrey Antukh 145198c148 📎 Use proper version tag on frontend index template 2026-02-23 12:17:58 +01:00
alonso.torres eddfc4c4b2 🐛 Fix problem with createText in plugins 2026-02-23 09:35:30 +01:00
alonso.torres e6e34af391 🐛 Show outline on hidden paths 2026-02-23 09:34:50 +01:00
Andrés Moya 3d41dc276e 🐛 Fix resolve tokens with tokenscript when type is font family 2026-02-20 12:41:17 +01:00
alonso.torres cee974a906 🐛 Fix problem with tokens in plugins 2026-02-18 17:20:46 +01:00
Alejandro Alonso a1a7f643ec Merge pull request #8390 from penpot/niwinz-staging-exporter-bundle
🐛 Fix exporter bundle deps issue with pnpm
2026-02-17 18:38:50 +01:00
Andrey Antukh 70013fde74 🐛 Fix exporter bundle deps issue with pnpm 2026-02-17 17:46:09 +01:00
Andrey Antukh 916107ce04 📎 Update pnpm-lock.yaml file on plugins module 2026-02-17 17:42:39 +01:00
Andrey Antukh 8eb5bd3dd8 🔧 Add minor adjustments to mcp build-types script 2026-02-17 17:42:39 +01:00
Andrey Antukh 5718698bff Update mcp api_types.yml file with latest plugins doc updates 2026-02-17 17:42:39 +01:00
Andrey AntukhandLuis de Dios f00b222262 Revert "♻️ Replace some components with DS ones" (#8384)
* Revert "♻️ Replace some components with DS ones"

This reverts commit 6879f54e5d.

* 📎 Restore missing styles

* 📎 Fix tests

---------

Co-authored-by: Luis de Dios <luis.dedios@kaleidos.net>
2026-02-17 16:23:04 +01:00
Andrey Antukh 137febcbab 📎 Clean changelog 2026-02-17 10:00:42 +01:00
Andrey Antukh f98c0bbd16 📎 Update changelog 2026-02-17 09:58:40 +01:00
Luis de Dios 7f3212d5a4 🐛 Fix changelog to remove MCP (#8380) 2026-02-17 09:53:12 +01:00
Andrés Moya 643cd6f61f 🐛 Add resolved value to tokens in plugins API (#8372) 2026-02-17 09:20:04 +01:00
Luis de Dios 166dc05ff2 🐛 Fix incorrect icons in grid view (#8373) 2026-02-16 16:39:39 +01:00
Andrés Moya 619e2387dc 🐛 Fix applied tokens property names 2026-02-16 15:16:14 +01:00
Andrés Moya 813c804d45 🔧 Enhance schema validation of token application 2026-02-16 15:16:14 +01:00
Andrey Antukh 63f0c68977 Merge remote-tracking branch 'origin/main' into staging 2026-02-16 14:35:28 +01:00
Andrey Antukh 1f2a234458 📚 Update changelog 2026-02-16 14:32:28 +01:00
Andrey Antukh b281870c50 📚 Update changelog 2026-02-16 14:27:33 +01:00
Andrey Antukh 3909bc0fc1 Merge remote-tracking branch 'origin/main' into staging 2026-02-16 14:17:46 +01:00
Andrey Antukh b6427ecaac 🐛 Revert yetti upgrade
Because of regression introduced on undertow-core 2.3.19
2026-02-16 14:16:29 +01:00
Andrey Antukh e82319c49e Merge tag '2.13.2' 2026-02-16 14:13:51 +01:00
Andrey Antukh ce63bae92d Add better approach for error handling to obj/reify 2026-02-16 11:07:40 +01:00
Andrey Antukh d1d50138ed Merge remote-tracking branch 'origin/staging-render' into develop 2026-02-16 10:00:46 +01:00
Andrey Antukh c63de58b7f Merge remote-tracking branch 'origin/staging' into staging-render 2026-02-16 10:00:27 +01:00
Andrey Antukh 7301973655 🌐 Rehash and sync translation files 2026-02-16 09:41:49 +01:00
Sebastiaan Pasma e6990e996c 🌐 Add translations for: Dutch
Currently translated at 98.9% (2052 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2026-02-16 09:35:25 +01:00
Alejandro Alonso 82a6e73b0d 🌐 Add translations for: Yoruba
Currently translated at 56.5% (1172 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/yo/
2026-02-16 09:35:24 +01:00
VKing9 a0fc14d4e8 🌐 Add translations for: Hindi
Currently translated at 96.3% (1998 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/
2026-02-16 09:35:24 +01:00
Anonymous a5dc3a4e2b 🌐 Add translations for: Croatian
Currently translated at 76.9% (1596 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hr/
2026-02-16 09:35:24 +01:00
Ņikita K. f60f259937 🌐 Add translations for: Latvian
Currently translated at 90.7% (1883 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/
2026-02-16 09:35:23 +01:00
Anonymous c669bd7327 🌐 Add translations for: Chinese (Simplified Han script)
Currently translated at 86.8% (1801 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2026-02-16 09:35:23 +01:00
Anonymous c33a52ec51 🌐 Add translations for: Basque
Currently translated at 55.5% (1152 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/eu/
2026-02-16 09:35:23 +01:00
Anonymous 9b2e847d99 🌐 Add translations for: Spanish
Currently translated at 97.1% (2015 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es/
2026-02-16 09:35:22 +01:00
Anonymous d1e82c4d40 🌐 Add translations for: Hebrew
Currently translated at 95.6% (1984 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2026-02-16 09:35:22 +01:00
AlexTECPlayz 71ef2294e4 🌐 Add translations for: Romanian
Currently translated at 93.3% (1937 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ro/
2026-02-16 09:35:21 +01:00
Alejandro Alonso 3576c581a3 🌐 Add translations for: Arabic
Currently translated at 54.1% (1123 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/
2026-02-16 09:35:21 +01:00
Anonymous 13be3599b6 🌐 Add translations for: Portuguese (Portugal)
Currently translated at 75.6% (1568 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_PT/
2026-02-16 09:35:20 +01:00
Nicola Bortoletto 7b5e1f0196 🌐 Add translations for: Italian
Currently translated at 98.6% (2047 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2026-02-16 09:35:20 +01:00
Anonymous a483c99480 🌐 Add translations for: Polish
Currently translated at 54.3% (1127 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pl/
2026-02-16 09:35:20 +01:00
Radek Sawicki 60ebbd57c6 🌐 Add translations for: Polish
Currently translated at 54.3% (1127 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pl/
2026-02-16 09:35:19 +01:00
Црнобог e830689987 🌐 Add translations for: Serbian
Currently translated at 65.9% (1368 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/sr/
2026-02-16 09:35:19 +01:00
Amerey.eu c8f9f49793 🌐 Add translations for: Czech
Currently translated at 76.7% (1591 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/cs/
2026-02-16 09:35:19 +01:00
Anonymous f24f872c24 🌐 Add translations for: French
Currently translated at 95.2% (1976 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2026-02-16 09:35:19 +01:00
Renan Mayrinck 4a1a7e6e8f 🌐 Add translations for: Portuguese (Brazil)
Currently translated at 67.0% (1391 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/
2026-02-16 09:35:18 +01:00
Denys Kisil 46ee8402fe 🌐 Add translations for: Ukrainian (ukr_UA)
Currently translated at 87.5% (1815 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/
2026-02-16 09:35:18 +01:00
The_BadUser b6d813dd51 🌐 Add translations for: Russian
Currently translated at 76.1% (1579 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/
2026-02-16 09:35:13 +01:00
Anonymous a9f674389a 🌐 Add translations for: German
Currently translated at 94.4% (1958 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2026-02-16 09:35:13 +01:00
william chen 78bdb13831 🌐 Add translations for: Chinese (Traditional Han script)
Currently translated at 77.0% (1599 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hant/
2026-02-16 09:35:12 +01:00
im424 e548b2d863 🌐 Add translations for: Chinese (Traditional Han script)
Currently translated at 77.0% (1599 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hant/
2026-02-16 09:35:12 +01:00
Alejandro Alonso 7543527fae 🌐 Add translations for: Hausa
Currently translated at 59.6% (1238 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ha/
2026-02-16 09:35:12 +01:00
Anonymous da488373e2 🌐 Add translations for: Turkish
Currently translated at 98.9% (2052 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2026-02-16 09:35:12 +01:00
Henrik Allberg 91e1c8299d 🌐 Add translations for: Swedish
Currently translated at 95.6% (1983 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/sv/
2026-02-16 09:35:11 +01:00
Linerly adc661126a 🌐 Add translations for: Indonesian
Currently translated at 81.6% (1694 of 2074 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/id/
2026-02-16 09:35:11 +01:00
Hosted Weblate 3b07ff2613 🌐 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/
2026-02-16 09:15:59 +01:00
Alexis Morin 3aebff0799 🌐 Add translations for: French (Canada)
Currently translated at 61.2% (1266 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:49 +01:00
Alexis Morin 3b14acc93e 🌐 Add translations for: French (Canada)
Currently translated at 60.2% (1244 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:49 +01:00
Alexis Morin ad2db07123 🌐 Add translations for: French (Canada)
Currently translated at 55.8% (1154 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:49 +01:00
Alexis Morin 068ecc3847 🌐 Add translations for: French (Canada)
Currently translated at 55.4% (1146 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:49 +01:00
Alexis Morin c850fe2d4f 🌐 Add translations for: French (Canada)
Currently translated at 55.0% (1138 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:49 +01:00
Alexis Morin 59acf71255 🌐 Add translations for: French (Canada)
Currently translated at 52.4% (1083 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:49 +01:00
Alexis Morin d7c5e7798c 🌐 Add translations for: French (Canada)
Currently translated at 48.8% (1009 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:49 +01:00
Alexis Morin ee81459d87 🌐 Add translations for: French (Canada)
Currently translated at 44.6% (923 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:49 +01:00
Alexis Morin ea54ff4bd5 🌐 Add translations for: French (Canada)
Currently translated at 43.6% (902 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:49 +01:00
Alexis Morin 353998b989 🌐 Add translations for: French (Canada)
Currently translated at 42.3% (874 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:49 +01:00
Alexis Morin 6b1ae46116 🌐 Add translations for: French (Canada)
Currently translated at 40.8% (844 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:49 +01:00
Sandy S Kuo 727a0adf1f 🌐 Add translations for: Chinese (Traditional Han script)
Currently translated at 77.5% (1603 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hant/
2026-02-16 09:15:49 +01:00
Alexis Morin b99e4e8954 🌐 Add translations for: French (Canada)
Currently translated at 37.5% (775 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:49 +01:00
Alexis Morin e8f893a668 🌐 Add translations for: French (Canada)
Currently translated at 36.6% (758 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:49 +01:00
Yaron Shahrabani 44964bc98b 🌐 Add translations for: Hebrew
Currently translated at 96.2% (1988 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2026-02-16 09:15:49 +01:00
Dogyeong 86f52a15a5 🌐 Add translations for: Korean
Currently translated at 12.1% (250 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ko/
2026-02-16 09:15:49 +01:00
Alexis Morin cc43426bb8 🌐 Add translations for: French (Canada)
Currently translated at 35.8% (740 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:49 +01:00
Dogyeong 5da2865158 🌐 Add translations for: Korean
Currently translated at 11.4% (237 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ko/
2026-02-16 09:15:49 +01:00
Alexis Morin eeb998409d 🌐 Add translations for: French (Canada)
Currently translated at 33.8% (699 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:48 +01:00
Alexis Morin b9ac8f7616 🌐 Add translations for: French (Canada)
Currently translated at 32.2% (667 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:48 +01:00
Nicola Bortoletto 626d6a285d 🌐 Add translations for: Italian
Currently translated at 99.5% (2057 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2026-02-16 09:15:48 +01:00
Alexis Morin 7159d00d75 🌐 Add translations for: French (Canada)
Currently translated at 29.3% (607 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:48 +01:00
Louis Chance 380c5382c7 🌐 Add translations for: French
Currently translated at 95.8% (1980 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2026-02-16 09:15:48 +01:00
Alexis Morin 28c244f8ee 🌐 Add translations for: French (Canada)
Currently translated at 24.1% (499 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:48 +01:00
Stas Haas af149afd08 🌐 Add translations for: German
Currently translated at 95.0% (1963 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2026-02-16 09:15:48 +01:00
Alexis Morin afd8add839 🌐 Add translations for: French (Canada)
Currently translated at 22.6% (467 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:48 +01:00
Andrey Antukh d6aad1d79b 🌐 Add translations for: French
Currently translated at 95.1% (1966 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2026-02-16 09:15:48 +01:00
Nicola Bortoletto 621e5161dc 🌐 Add translations for: Italian
Currently translated at 99.1% (2049 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2026-02-16 09:15:48 +01:00
Alexis Morin 00df74d602 🌐 Add translations for: French (Canada)
Currently translated at 21.7% (449 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:48 +01:00
Alexis Morin d31501ddb6 🌐 Add translations for: French (Canada)
Currently translated at 18.9% (392 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:48 +01:00
Louis Chance b0fda4cb06 🌐 Add translations for: French
Currently translated at 95.1% (1966 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2026-02-16 09:15:48 +01:00
Stephan Paternotte ce80c049cd 🌐 Add translations for: Dutch
Currently translated at 99.8% (2062 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2026-02-16 09:15:48 +01:00
Marius 2873bffff1 🌐 Add translations for: German
Currently translated at 93.7% (1937 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2026-02-16 09:15:48 +01:00
Alexis Morin 56c7ef8e99 🌐 Add translations for: French (Canada)
Currently translated at 18.7% (388 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:48 +01:00
Edgars Andersons 594551c16a 🌐 Add translations for: Latvian
Currently translated at 91.3% (1887 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/
2026-02-16 09:15:47 +01:00
Stephan Paternotte 00c34ecf12 🌐 Add translations for: Dutch
Currently translated at 99.8% (2062 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2026-02-16 09:15:47 +01:00
Alexis Morin 033a1f39fa 🌐 Add translations for: French (Canada)
Currently translated at 17.5% (363 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-02-16 09:15:47 +01:00
Oğuz Ersen aaab3e6b3e 🌐 Add translations for: Turkish
Currently translated at 99.8% (2062 of 2066 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2026-02-16 09:15:47 +01:00
Andrey Antukh 4a7b89a1da Merge pull request #8327 from penpot/niwinz-develop-rlimit-notifications
 Add proper mattermost notifications for rlimit rejects
2026-02-13 17:11:54 +01:00
Elena Torró 51782551cc Merge pull request #8346 from penpot/alotor-fix-problem-text-autogrouw
🐛 Fix problem with autogrow change while editing text
2026-02-13 15:25:35 +01:00
alonso.torres f60a4cd111 🐛 Fix problem with autogrow change while editing text 2026-02-13 14:52:40 +01:00
David Barragán Merino 1349789a7b 🔧 Fix the plugin style documentation build command 2026-02-13 14:20:34 +01:00
David Barragán Merino 8dbb169061 🔧 Fix the plugin style documentation build command 2026-02-13 14:20:01 +01:00
David Barragán Merino cc28bd44f6 🔧 Fix the plugin style documentation build command 2026-02-13 14:18:35 +01:00
Alejandro Alonso d9d4a99e1d 🔧 Migrate variants tests to wasm viewport 2026-02-13 14:13:03 +01:00
David Barragán Merino fe833c9e34 🔧 Disable observability for plugin docs and packages
This reverts commit a4f2641cc9.
2026-02-13 13:55:13 +01:00
Elena Torró 38ad24ea07 Merge pull request #8349 from penpot/alotor-fix-editor-selrect
🐛 Fix problem with text editor outline
2026-02-13 13:25:13 +01:00
Alejandro Alonso 8d225af13a Merge pull request #8351 from penpot/alotor-fix-create-rect-click
🐛 Fix problem when create click
2026-02-13 13:21:27 +01:00
Juanfran 449aa65f8d 🐛 Fix e2e tests for plugins 2026-02-13 13:17:08 +01:00
Andrey Antukh bd7f4dca3a 🐛 Fix rpc methods on plugins e2e tests 2026-02-13 13:17:08 +01:00
Andrey Antukh 1e7bef081a Allow self-signed certs on plugins e2e browser setup 2026-02-13 13:17:08 +01:00
Andrey Antukh 12bc3ac9ed Update default cors headers 2026-02-13 13:17:08 +01:00
Elena Torró 80a3f4cd60 Merge pull request #8350 from penpot/superalex-migrate-tests-to-wasm-viewport-3
🔧 Migrate inspect tab tests to wasm viewport
2026-02-13 13:06:14 +01:00
alonso.torres 3ea0a781f1 🐛 Fix problem when create click 2026-02-13 12:38:33 +01:00
alonso.torres 35abf8a179 🐛 Fix problem with text editor outline 2026-02-13 12:23:05 +01:00
Sagar cfcebf59d5 🐛 Make S3Client and S3Presigner use identical credential resolution (#8316) 2026-02-13 12:21:05 +01:00
Andrey Antukh cf43ac23a1 Merge pull request #8340 from penpot/hiru-fix-plugins-api-tokens
🐛 Fix problems about applying tokens to shapes with plugins
2026-02-13 12:18:29 +01:00
Alejandro Alonso 7e9fb0742d 🔧 Migrate inspect tab tests to wasm viewport 2026-02-13 12:09:31 +01:00
Elena Torró 39b3767203 Merge pull request #8348 from penpot/superalex-fix-non-existent-google-font
🐛 Fix non existent google font
2026-02-13 12:08:18 +01:00
Alejandro Alonso 23333aa3c3 Merge pull request #8347 from penpot/elenatorro-13386-fix-flex-absolute-index
🐛 Fix absolute z-index values on layout children
2026-02-13 12:06:45 +01:00
Alejandro Alonso 684e2b6950 🐛 Fix non existent google font 2026-02-13 11:59:59 +01:00
Elena Torro fd6f70a740 🐛 Fix absolute z-index values on layout children 2026-02-13 11:41:16 +01:00
Elena Torro b892cc9b14 🔧 Refactor shape base props to use transmute 2026-02-13 11:28:18 +01:00
Belén Albeza 50ddf5e628 🔧 Add integration test for bug 13305 2026-02-13 10:57:27 +01:00
Belén Albeza 75a4102637 🐛 Fix resize board to fit (wasm) 2026-02-13 10:57:27 +01:00
David Barragán Merino d7203ef24c 🔧 Fix the plugin bundle build command 2026-02-13 09:39:12 +01:00
David Barragán Merino 61f3e090da 🔧 Fix the plugin bundle build command 2026-02-13 09:38:39 +01:00
David Barragán Merino fda09b02b9 🔧 Fix the plugin bundle build command 2026-02-13 09:37:22 +01:00
Elena Torró 8f478aa6e5 Merge pull request #8325 from penpot/superalex-fix-wasm-forcing-url-param
🐛 Fix forcing wasm via url param
2026-02-12 17:46:50 +01:00
Alejandro Alonso 95e1efa5ff 🐛 Fix forcing wasm via url param 2026-02-12 17:35:02 +01:00
Andrés Moya a23ca6a1cb 🐛 Fix applied tokens reading in shape proxy 2026-02-12 17:14:16 +01:00
Eva Marco d07f568ba2 🐛 Avoid modifying shape by apply negative tokens to border radius (#8336) 2026-02-12 17:01:36 +01:00
Andrés Moya c626634610 🐛 Detect empty font-family 2026-02-12 16:04:23 +01:00
Andrés Moya 11eedd0368 🐛 Patch alternative ways of applying tokens to shapes 2026-02-12 16:01:55 +01:00
Florian Schroedl 375608b44b ⬆️ Update tokenscript interpreter to 0.26.0 and add CSS color schemas
Regenerate schemas.js with preset:cssColors to support CSS color constants.
2026-02-12 14:14:45 +01:00
Elena Torró 97d24b190f Merge pull request #8334 from penpot/superalex-migrate-tests-to-wasm-viewport
🔧 Migrate straightforward tests to user the wasm viewport
2026-02-12 14:03:06 +01:00
Alejandro Alonso 434ac0556a 🔧 Migrate straightforward tests to user the wasm viewport 2026-02-12 13:29:13 +01:00
Andrey Antukh 12e5d8d8c4 Merge remote-tracking branch 'origin/staging-render' into develop 2026-02-12 11:00:56 +01:00
Andrey Antukh 04a3126856 Merge remote-tracking branch 'origin/main' into staging-render 2026-02-12 11:00:38 +01:00
Elena Torró 2f71663470 Merge pull request #8245 from penpot/elenatorro-13047-setup-embedded-text-editor
🔧 Set up embedded editor
2026-02-12 10:05:39 +01:00
Andrey Antukh 43cb313cd7 Merge pull request #8310 from oraios/mcp-tokens
 MCP improvements to enable UC2, design token handling
2026-02-12 09:47:32 +01:00
Elena Torró 0b199c606a Merge pull request #8331 from penpot/ladybenko-add-wasm-config-playwright-helper
🔧 Add helper utils to mock config flags for WasmWorkspacePage (e2e)
2026-02-12 09:45:03 +01:00
Aitor Moreno 54f63c5dc5 ♻️ Refactor minor things 2026-02-12 09:34:21 +01:00
Elena Torro a14c36e996 📚 Add embedded text editor MVP documentation 2026-02-12 09:34:20 +01:00
Elena Torro 2b525f0f48 🔧 Set up embedded editor 2026-02-12 09:34:20 +01:00
Belén Albeza fd6ff04e90 🔧 Add helper utils to mock config flags for WasmWorkspacePage (e2e) 2026-02-12 09:25:08 +01:00
eps-epsiloneridani dbb0aa8ce2 📚 Update recommended-settings.md (#8330)
Got rid of a stray quotation mark

Signed-off-by: eps-epsiloneridani <162043859+eps-epsiloneridani@users.noreply.github.com>
2026-02-12 09:19:10 +01:00
Andrey Antukh 12822833f6 Merge pull request #8301 from eureka928/fix/4513-shift-arrow-color-inputs
🐛 Add Shift/Alt arrow key stepping to color picker inputs
2026-02-12 08:26:05 +01:00
eureka928 307ae374fe ♻️ Unify color picker input handlers by treating alpha as a property
Eliminate duplicated on-change-opacity and on-key-down-opacity handlers
by routing alpha through apply-property-change, and extract shared
stepping logic into on-key-down-step.

Signed-off-by: eureka928 <meobius123@gmail.com>
2026-02-12 08:25:37 +01:00
eureka928 7d7dbd4662 🐛 Add Shift/Alt arrow key stepping to color picker inputs (#4513)
Color picker numeric inputs (R, G, B, H, S, V, Alpha) now support
Shift+Arrow for ×10 steps and Alt+Arrow for ×0.1 steps, matching
the behavior of numeric inputs elsewhere in the application.

Signed-off-by: eureka928 <meobius123@gmail.com>
2026-02-12 08:25:37 +01:00
Alejandro Alonso 139d4ba13c Merge pull request #8328 from penpot/elenatorro-13311-fix-multiple-strokes-blending
🐛 Fix stroke color aliasing when a shape has multiple strokes
2026-02-12 07:05:44 +01:00
Elena Torro 0cb5c16823 🐛 Fix fallback font 2026-02-12 06:43:52 +01:00
Elena Torro 4ed1a544f8 🐛 Fix stroke color aliasing when a shape has multiple strokes 2026-02-12 06:43:52 +01:00
Elena Torró 566ac67fc9 Merge pull request #8324 from penpot/azazeln28-fix-editor-fills
🐛 Fix text editor issues
2026-02-11 16:37:20 +01:00
Juanfran 394d597736 Add enhacements to plugins build mechanism (#8326)
* 🐛 Fix plugin code build

* 🔧 Update editor.defaultFormatter to new Prettier

* 🐛 Fix lint issues in create-palette-plugin

* 🐛 Add missing run in pnpm init script for plugins
2026-02-11 15:28:33 +01:00
Aitor Moreno b2231e520c 📚 Add best practices to text editor README.md 2026-02-11 13:09:56 +01:00
Aitor Moreno e722e17b10 🐛 Fix paragraph styles not being applied 2026-02-11 12:49:20 +01:00
Aitor Moreno 755d720b34 🐛 Fix text editor fills not being updated 2026-02-11 12:29:03 +01:00
Alejandro Alonso d991d59852 Merge pull request #8318 from penpot/elenatorro-13311-fix-multiple-fills-blending
🐛 Fix fill aliasing when a shape has multiple fills
2026-02-11 11:37:43 +01:00
Dominik Jain 7eb9a207f5 Change PenpotUtils.findShapes to search on all pages by default
This matches the behaviour of findShape, more closely aligning with
the LLM's expectations (given the lack of concrete information in
the instructions)
2026-02-11 11:35:10 +01:00
Dominik Jain 8ac17604fd Improve information on component instances
* Add information on detachment
* Add information on remove behaviour in component instances
2026-02-11 11:35:10 +01:00
Elena Torro eede023d6b 🐛 Fix fill aliasing when a shape has multiple fills 2026-02-11 11:21:08 +01:00
Belén Albeza ccd42852b7 🐛 Fix token not being highlighted (wasm) 2026-02-11 11:17:27 +01:00
Alejandro Alonso a2f7ae549e Merge pull request #8312 from penpot/elenatorro-13256-sync-text-selection
🔧 Hide text color from selected text
2026-02-11 11:02:35 +01:00
Alejandro Alonso 6f74d458a8 🐛 Adding lost file for render e2e testing get-file-stroke-styles.json 2026-02-11 10:47:50 +01:00
Alejandro Alonso 8d033de145 Merge pull request #8299 from penpot/elenatorro-13242-review-performance
🔧 Improve layout performance
2026-02-11 10:45:40 +01:00
Dominik Jain f5afcde0de Improve shapeStructure
* Add information on component instance (component id, name; main instance id)
* Improve JSON result order (children should come last)
2026-02-11 10:45:22 +01:00
Dominik Jain b6dfdc23cd Update information on TokenProperty 2026-02-11 10:45:22 +01:00
Dominik Jain a5a084cf0f Update API type information based on current repo state 2026-02-11 10:45:22 +01:00
Dominik Jain 1546025814 Avoid certain <ul> elements with single <li> generating bullets 2026-02-11 10:45:22 +01:00
Dominik Jain 8de510d1c6 🐛 Fix PenpotAPIDocsProcessor not requiring the url parameter
* Add additional constant for the PROD url
* Adapt the debug function to use a URL
* Improve logging
2026-02-11 10:45:10 +01:00
Elena Torró 2e77c09ca5 Merge pull request #8309 from penpot/superalex-fix-stroke-dot-dash-mix
🐛 Fix dot strokes
2026-02-11 10:37:46 +01:00
Elena Torró 47346e478e Merge pull request #8303 from penpot/superalex-fix-stroke-opacity-for-boards
🐛 Fix stroke opacity for boards
2026-02-11 10:05:47 +01:00
Andrey Antukh 89cd4d820c 🔥 Remove mcp from compose 2026-02-11 09:13:24 +01:00
Alejandro Alonso f32c377f17 🐛 Fix stroke opacity for boards 2026-02-11 09:08:03 +01:00
Andrey Antukh 8693623b13 📎 Update SECURITY.md file 2026-02-11 08:11:04 +01:00
Alejandro Alonso 97f01c646d 🎉 Improve multiple emoji E2E test 2026-02-11 07:36:22 +01:00
Alejandro Alonso eea1d3c0a5 🎉 Improve updating canvas background E2E test 2026-02-11 07:19:22 +01:00
Alejandro Alonso 9eef4de87d 🐛 Fix dot/dahs/mixed strokes 2026-02-11 07:08:28 +01:00
Andrey Antukh f4d07a3c36 ⬆️ Update pnpm on frontend and plugins modules 2026-02-10 19:02:32 +01:00
Francis Santiago 15fa6206e2 Merge pull request #8286 from penpot/fc-fix-ipv6-fronted
🎉 Enable IPv6 support for docker images
2026-02-10 16:01:06 +01:00
Francis Santiago 3281819283 🎉 Enable IPv6 support for docker images 2026-02-10 15:52:33 +01:00
Andrés MoyaandAndrey Antukh 41f29767db 🐛 Fix configuration of poc-tokens-plugin (#8314)
* 🐛 Fix configuration of poc-tokens-plugin

* 📎 Add missing changes on poc-tokens-pligin tsconfig

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-02-10 15:18:11 +01:00
Dominik Jain 76289df32c Establish compatibility with new member anchors (h3 instead of a tag) 2026-02-10 14:03:40 +01:00
Andrey Antukh 920fbe34ad 🐛 Fix invalid deps passed to http management routes service 2026-02-10 13:46:29 +01:00
Elena Torro 187d1118c0 🔧 Hide text color from selected text 2026-02-10 13:15:55 +01:00
Dominik Jain a674b5f914 📚 Add instructions on running only the docs server 2026-02-10 12:53:20 +01:00
Dominik Jain 71507fb9b7 ♻️ Adjust ConfigurationLoader to use markdown file instead of yml 2026-02-10 12:35:44 +01:00
Dominik Jain 024aedc3ca ♻️ Convert prompt content to markdown format 2026-02-10 12:35:44 +01:00
Dominik Jain 44657c95df ♻️ Rename prompts.yml -> initial_instructions.md 2026-02-10 12:35:44 +01:00
Dominik Jain d4d5009a3d Improve prompts on token application 2026-02-10 12:35:44 +01:00
Dominik Jain bb4d0322d8 🚧 Temporarily add ts-ignore statements
This shall be reverted once the new API types are published
2026-02-10 12:35:44 +01:00
Dominik Jain 56e369a1c0 Add helper functions for token exploration
Extend PenpotUtils with helper functions for token exploration/discovery
and describe them in the system prompt
2026-02-10 12:35:44 +01:00
Dominik Jain 6b277956b9 Add information on clone() method 2026-02-10 12:35:44 +01:00
Dominik Jain e9a56c9d9f Shorten design token instructions 2026-02-10 12:35:44 +01:00
Dominik Jain 8d90edcc2f Add instructions on design tokens 2026-02-10 12:35:44 +01:00
Dominik Jain 8186f3c87c 📚 Remove misleading information from README
The types build is not part of the bootstrap, and it is not
relevant to regular users (only to developers).

Information on how to apply it is now in types-generator/README.md
2026-02-10 12:35:44 +01:00
Dominik Jain d7282518c4 📚 Improve usage documentation of API type generator script 2026-02-10 12:35:44 +01:00
Dominik Jain 467eb3c333 Update API docs to include token-related types 2026-02-10 12:35:44 +01:00
Dominik Jain d2299f83ec Apply bash in build scripts explicitly (Win compatibility) 2026-02-10 12:35:44 +01:00
Andrey Antukh 11a283916d Merge remote-tracking branch 'origin/staging' into staging-render 2026-02-10 11:58:27 +01:00
Andrey Antukh f08700945a Merge remote-tracking branch 'origin/staging' into develop 2026-02-10 11:58:09 +01:00
Andrey Antukh 59711a1cf8 📎 Update changelog 2026-02-10 11:57:01 +01:00
Aitor Moreno e9b2e9e818 🚑 Hot fix for text editor internal error 2026-02-10 11:10:16 +01:00
Belén Albeza c4aa51bc01 🐛 Fix permanent blur when switching pages 2026-02-10 10:59:47 +01:00
Belén Albeza 1c270ac9c6 Remove leftover println in render code 2026-02-10 10:59:47 +01:00
Andrey Antukh 06e5825c8a 🐛 Add proper input checking to font related RCP method 2026-02-10 10:36:57 +01:00
Andrey Antukh e3dfa69011 Make plugins devserver to be able run inside devenv 2026-02-10 08:29:24 +01:00
Juanfran 96b682aa12 ♻️ Remove Nx and rely on pnpm monorepo features 2026-02-10 08:29:24 +01:00
Juanfran 45d04942cc Add example ui storybook 2026-02-10 08:29:24 +01:00
Juanfran 07055b53d1 ⬆️ Update plugins dependencies 2026-02-10 08:29:24 +01:00
Andrey Antukh d30387eb77 Backport docker images changes from develop 2026-02-09 19:21:30 +01:00
Andrey Antukh 33fd672c21 Backport MCP related changes from develop (#8306) 2026-02-09 18:00:43 +01:00
Andrey Antukh dd7038bdad 📎 Fix fmt issue on frontend code 2026-02-09 17:38:40 +01:00
Andrey Antukh 5ec345162a Add mcp plugin into the frontend bundle 2026-02-09 17:38:40 +01:00
Andrey Antukh 0027e9031a Make mcp env vars to use the same convention as penpot 2026-02-09 17:38:40 +01:00
Pablo Alba 5d3ccbc8b4 Add managed profiles endpoint to nitrate api (#8292) 2026-02-09 15:52:18 +01:00
Andrés Moya 1a1c351466 🐛 Fix dependency 2026-02-09 15:06:39 +01:00
Andrés MoyaandAndrey Antukh 5b5f22a8c6 🎉 Add tokens to Penpot Plugins API (#7756)
* 🎉 Add tokens to plugins API documentation

And add poc plugin example

* 📚 Document better the tokens value in plugins API

* 🔧 Refactor token validation schemas

* 🔧 Use automatic validation in token proxies

* 🔧 Use schemas to validate token creation

* 🔧 Use multi schema for token value

* 🔧 Use schema in token api methods

* 🐛 Fix review comments

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-02-09 14:18:31 +01:00
Andrey Antukh ac1c3ff184 Merge branch 'staging-render' into develop 2026-02-09 14:14:02 +01:00
Andrey Antukh 43cd92c76d Merge remote-tracking branch 'origin/staging' into staging-render 2026-02-09 14:12:55 +01:00
Elena Torró cf2b40a097 Merge pull request #8302 from penpot/azazeln28-issue-13124-text-not-restored-undoing
🐛 Fix text not restored on ctrl+z
2026-02-09 13:41:43 +01:00
Aitor Moreno b72959544c 🐛 Fix text not restored on ctrl+z 2026-02-09 13:29:31 +01:00
Andrey Antukh a7b2e98b8e ⬆️ Use latest imagemagick version on docker images 2026-02-09 13:19:26 +01:00
Andrey Antukh d979894872 Add libxml2 dep to imagemagick dockerfile 2026-02-09 12:27:44 +01:00
Alejandro Alonso 3d20fc508d 🐛 Fix image magick info call (#8300) 2026-02-09 12:26:42 +01:00
Elena Torro 969666b39b 🔧 Simplify view interaction log message
Remove zoom_changed from log output as it's no longer needed
for debugging after the tile optimization changes.
2026-02-09 11:44:50 +01:00
Yamila Moreno d953222eb4 🔧 Add CI for MCP server (#8293) 2026-02-09 11:25:24 +01:00
Elena Torró b3faa985ce Merge pull request #8291 from penpot/superalex-fix-dashboard-navigation
🐛 Fix dashboard navigation from workspace
2026-02-09 09:59:11 +01:00
Elena Torro a8322215dd 🔧 Optimize pan/zoom tile handling
- Add incremental tile update that preserves cache during pan
- Only invalidate tile cache when zoom changes
- Force visible tiles to render synchronously (no yielding)
- Increase interest area threshold from 2 to 3 tiles
2026-02-09 09:38:01 +01:00
Elena Torro e1ce97a2b4 🔧 Prioritize visible tiles over interest-area tiles
Partition pending tiles into 4 groups by visibility and cache status.
Visible tiles are processed first to eliminate empty squares during
pan/zoom. Cached tiles within each group are processed before uncached.
2026-02-09 09:38:01 +01:00
Elena Torro 2ccd2a6679 🔧 Use HashSet for grid layout children lookup
HashSet provides O(1) contains() vs Vec's O(n), improving
child lookup performance in grid cell data creation.
2026-02-09 09:38:01 +01:00
Elena Torro 2d9a2e0d50 🔧 Use swap_remove in flex layout distribution
swap_remove is O(1) vs remove's O(n) when order doesn't matter.
These loops iterate backwards, so swap_remove is safe.
2026-02-09 09:38:01 +01:00
Elena Torro 216d400262 🔧 Prevent duplicate layout calculations
Use HashSet for layout_reflows to avoid processing the same
layout multiple times. Also use std::mem::take instead of
creating a new Vec on each iteration.
2026-02-09 09:37:58 +01:00
Elena Torro c87ffdcd30 🔧 Add forward children iterator for flex layout
Avoid Vec allocation + reverse for reversed flex layouts.
The new children_ids_iter_forward returns children in original order,
eliminating the need to collect and reverse.
2026-02-09 09:35:04 +01:00
Elena Torro 8ef6600cdc 🔧 Return HashSet from update_shape_tiles
Avoid final collect() allocation by returning HashSet directly.
Callers already use extend() which works with both types.
2026-02-09 09:35:04 +01:00
Elena Torro a3764b9713 🔧 Avoid clone in rebuild_touched_tiles
Use std::mem::take instead of clone to avoid HashSet allocation.
The set was cleared anyway by clean_touched(), so take() is safe.
2026-02-09 09:35:03 +01:00
Alejandro Alonso e5cdb5b163 Merge pull request #8290 from penpot/alotor-fix-alt-duplicate
🐛 Fix problem with alt+move for duplicate shapes
2026-02-09 06:33:13 +01:00
David Barragán Merino a4f2641cc9 🔧 Enable observability for plugin docs and packages 2026-02-06 18:01:11 +01:00
Alejandro Alonso a164a1bab3 🐛 Fix dashboard navigation from workspace 2026-02-06 12:58:56 +01:00
alonso.torres a0cbb392af 🐛 Fix problem with alt+move for duplicate shapes 2026-02-06 12:20:43 +01:00
Alejandro Alonso ccfee34e76 Merge pull request #8289 from penpot/niwinz-staging-exporter-fix
🐛 Fix issue with pdf render on exporter
2026-02-06 11:40:18 +01:00
Andrey Antukh 989eb12139 🔥 Remove merge conflict from plugins api ns 2026-02-06 11:38:36 +01:00
Eva Marco a5e36dbb3d 🐛 Fix broken attribute on numeric input (#8250)
* 🐛 Fix broken attribute on numeric input

* 🐛 Fix tooltip position
2026-02-06 11:32:16 +01:00
Alejandro Alonso 8acd031ab2 Merge remote-tracking branch 'origin/staging-render' into develop 2026-02-06 11:23:50 +01:00
Andrey Antukh 6f3f2f9a71 🐛 Fix issue with pdf render on exporter
When paired with release build penpot app
2026-02-06 11:19:56 +01:00
Elena Torro a7c1de6478 🐛 Fix lazy load intersection on dragging at the beginning 2026-02-06 10:59:05 +01:00
Elena Torro 184487f568 🐛 Fix lazy load intersection on dragging at the beginning 2026-02-06 10:53:11 +01:00
Andrey Antukh c00d512193 Add the concept of version to plugins
And make mcp plugin version 2
2026-02-06 09:42:59 +01:00
alonso.torres af5dbf2fbc 🐛 Set objects modified instead of modif-tree 2026-02-06 09:34:58 +01:00
Alejandro Alonso 7c7e32d85f 🐛 Fix grid lines 2026-02-06 09:34:58 +01:00
Andrey Antukh 2ccb33ba89 📎 Add missing for-update for the migration 145 2026-02-05 18:12:11 +01:00
Andrey Antukh ee88ee63a2 Add data migration for fix plugins data on profiles 2026-02-05 18:08:28 +01:00
alonso.torres fd3d549f9c Batch text layout updates 2026-02-05 17:29:43 +01:00
alonso.torres 53c2acb3e6 🐛 Fix several problems with layouts and texts 2026-02-05 17:29:43 +01:00
Belén Albeza 8a72eb64c3 Add integration test for 13267 2026-02-05 16:37:21 +01:00
alonso.torres 1d45ca7019 🐛 Fix problem propagating geometry changes to instances 2026-02-05 16:37:21 +01:00
Eva Marco f961f9a123 🐛 Fix several bugs (#8267)
* ♻️ Remove rename warning

* 🐛 Fix opacity value
2026-02-05 11:34:14 +01:00
Eva Marco dda3377596 🐛 Allow detach broken token from input (#8242)
* 🐛 Allow detach broken token from input

* 🐛 Fix multiselection on multiple token applied

* ♻️ Remove detach-token new fn
2026-02-05 11:28:47 +01:00
Andrey Antukh 17935443df Move all tokenscript related adaptations to a separared package 2026-02-05 09:45:55 +01:00
Florian Schroedl 150d57b1eb Add tokenscript MVP 2026-02-05 09:45:55 +01:00
Alejandro Alonso ad5e8ccdb3 🐛 Fix pdf sizing issue on export (#8274) 2026-02-05 09:23:14 +01:00
Andrey Antukh 490619119e 🐛 Use correct listen address for mcp server 2026-02-04 18:57:53 +01:00
Belén Albeza 834b513562 🔧 Fix typo in workspace spec (#8272) 2026-02-04 17:05:49 +01:00
Andrey Antukh 1656fefdc9 Merge remote-tracking branch 'origin/staging-render' into develop 2026-02-04 16:23:46 +01:00
Andrey Antukh 7f318bb110 Merge remote-tracking branch 'origin/staging' into staging-render 2026-02-04 16:22:13 +01:00
Andrey Antukh 44c7d3fbd6 Backport .github workflows from develop 2026-02-04 16:21:19 +01:00
Andrey Antukh 3d50aa6cb2 ⬆️ Update imagemagick version 2026-02-04 16:21:19 +01:00
Andrey Antukh 06afd94a74 ⬆️ Update backend dependencies (mainly bugfixes) 2026-02-04 16:21:19 +01:00
Andrey Antukh e7d9dca55e ⬆️ Update jdk and node on devenv and other images 2026-02-04 16:21:19 +01:00
Andrey Antukh c14ccc18b8 Import mcp from develop 2026-02-04 16:21:19 +01:00
Andrey Antukh ca4d00df69 🐛 Fix latest error report related migration 2026-02-04 15:36:07 +01:00
Andrey Antukh 9667477d6b 🐛 Add missing dep for rpc routes on backend 2026-02-04 15:26:02 +01:00
Alejandro Alonso 485005477e 🐛 Fix WasmWorkspacePage import 2026-02-04 14:02:38 +01:00
Alejandro Alonso 86ca260ea2 Merge remote-tracking branch 'origin/staging-render' into develop 2026-02-04 13:50:13 +01:00
Andrey Antukh d80ba1856a Add several improvements to frontend error reporting
*  Add major improvement on error handling

*  Add the ability to store frontend reports

* 📎 Add PR feedback changes
2026-02-04 12:45:38 +01:00
Alejandro Alonso ebb7d01bc9 🐛 Fix entering decimal values in dimension fields causes internal server error (#8263) 2026-02-04 12:44:19 +01:00
Andrey Antukh a1bfb2781e 📎 Update mcp readme 2026-02-04 12:22:36 +01:00
Andrey Antukh 08e8787568 Add mcp types generator build script 2026-02-04 12:22:36 +01:00
Andrey Antukh da55653844 Add integration tests for mcp 2026-02-04 12:22:36 +01:00
Andrey Antukh 11f2323057 Add mcp to default compose template 2026-02-04 12:22:36 +01:00
Andrey Antukh ae0f5e2bb9 🐛 Fix subpath support on plugins 2026-02-04 12:22:36 +01:00
Andrey Antukh 1fff1f9506 Add mcp dockerfile 2026-02-04 12:22:36 +01:00
Andrey Antukh 61d7dd3167 Update devenv with mcp required dependencies 2026-02-04 12:22:36 +01:00
Dominik Jain 880b9b61c4 🎉 Integrate mcp repository
Original repository: https://github.com/penpot/penpot-mcp
Imported commit: fcfa67e908fc54e23a3a3543dee432472dc90c5d
2026-02-04 12:22:36 +01:00
Yamila Moreno 307dae9f61 💄 Remove access logs for /readyz 2026-02-04 10:54:07 +01:00
Xaviju 0f0ad4f161 🐛 Remove path from state when removing tokens (#8252)
* 🐛 Remove path from state when removing tokens

* ♻️ Improve path edition legibility

* ♻️ Fix path delete on change set
2026-02-04 10:15:46 +01:00
Alejandro Alonso 24c8fc484f 🐛 Fix Internal Error when adding a new text layer and trying to go back to Dashboard without saving 2026-02-04 10:01:10 +01:00
Eva Marco d6831e9b48 ♻️ Restore warning on name change in generic form (#8260) 2026-02-03 14:08:35 +01:00
Pablo Alba 138df7c958 🐛 Fix remove fill affects different element than selected (#8233) 2026-02-03 13:17:54 +01:00
alonso.torres ef2bdf86d8 Add event to create shape in plugins 2026-02-03 13:09:58 +01:00
alonso.torres 512a31d375 Add naturalChildOrdering flag to Plugin's API 2026-02-03 13:09:58 +01:00
Alejandro Alonso bc16b8ddc3 Merge pull request #8198 from penpot/ladybenko-13176-playwright-wasm
🔧 Migrate workspace tests to user the wasm viewport
2026-02-03 13:00:10 +01:00
Alejandro Alonso b07c98faa5 Merge pull request #8259 from penpot/superalex-improve-shadow-rendering
🎉 Improving shadow rendering performance
2026-02-03 12:59:49 +01:00
Alejandro Alonso 25aff100cf 🎉 Add shadows playground for render wasm 2026-02-03 12:44:43 +01:00
Alejandro Alonso 5be887f10b 🎉 Improve plain shape calculation 2026-02-03 12:44:43 +01:00
Alejandro Alonso f7403935c8 🎉 Improve shadows rendering performance 2026-02-03 12:33:05 +01:00
Andrey Antukh 7d09d930fe 📚 Update changelog 2026-02-03 11:13:46 +01:00
Belén Albeza 79be3ab7df 🔧 Fix text editor flaky tests 2026-02-03 10:39:38 +01:00
Andrey Antukh 717a048b73 📎 Add fmt fix on frontend 2026-02-03 09:37:19 +01:00
Andrey Antukh cbd90ff970 📎 Comment problematic code on frontend 2026-02-03 09:31:26 +01:00
Andrey Antukh c99fac000a Merge remote-tracking branch 'origin/staging-render' into develop 2026-02-03 09:30:16 +01:00
andrés gonzález 79e5d2f4cd 📚 Change link to post at SH guide (#8247) 2026-02-03 08:27:17 +01:00
Andrey Antukh 1325584e1a Merge remote-tracking branch 'origin/staging' into staging-render 2026-02-03 08:24:04 +01:00
Andrey Antukh 0d9b7ca696 Merge tag '2.13.0-RC11' 2026-02-03 08:23:27 +01:00
Andrey Antukh d215a5c402 Merge tag '2.13.0-RC10' 2026-02-03 08:22:50 +01:00
Belén Albeza 629649aca6 🔧 Fix config playwright syntax 2026-02-02 16:25:16 +01:00
Belén Albeza cc326f23cf 🔧 Adjust timeout of websocket readiness (playwright) 2026-02-02 16:16:59 +01:00
Belén Albeza 2c4efc6b53 🔧 Fix onboarding test 2026-02-02 16:16:58 +01:00
Belén Albeza 4d5c874b91 🔧 Fix typography token test 2026-02-02 16:16:58 +01:00
Belén Albeza e3b97638b4 🔧 Fix broken / flaky tests 2026-02-02 16:16:58 +01:00
Belén Albeza daedc660b9 🔧 Migrate workspace tests to user the wasm viewport 2026-02-02 16:16:58 +01:00
Elena Torró 7681231d8f Merge pull request #8246 from penpot/azazeln28-test-more-text-editor
🔧 Add more Text Editor v2 tests
2026-02-02 15:09:18 +01:00
Aitor Moreno 07b9ef0fd6 🔧 Add more v2 text editor tests 2026-02-02 09:35:28 +01:00
Alejandro Alonso 2ae68d5752 Merge pull request #8244 from penpot/alotor-fix-modifiers-propagation
🐛 Fix problem with modifiers propagation
2026-01-29 17:34:36 +01:00
alonso.torres 913672e5c5 🐛 Fix problem with modifiers propagation 2026-01-29 17:15:01 +01:00
Xaviju 91671afb7a 🐛 Update switch checked state on change prop (#8235) 2026-01-29 13:13:50 +01:00
Eva Marco 838194f9e5 ♻️ Refactor code to reduce duplicates (#8213) 2026-01-29 12:28:05 +01:00
Alejandro Alonso 8c25fb00ac 🐛 Fix auto width/height texts on variant swithching 2026-01-29 12:25:38 +01:00
Alejandro Alonso 6a84215911 🐛 Fix stroke weight visually different with different levels of zoom 2026-01-29 12:18:26 +01:00
Xaviju 68cf2ecc57 🐛 Break long token names in remapping modal (#8241) 2026-01-29 11:04:36 +01:00
Xaviju 3e4f70f37b 🐛 Bulk remove tokens with a single undo action (#8208) 2026-01-29 10:58:16 +01:00
Andrey Antukh f65292a13c 📎 Mark as skip two text editor v2 tests (flaky) 2026-01-29 10:43:29 +01:00
Andrey Antukh 94722fdec2 Ensure .hidePopover fn exist before call 2026-01-29 10:43:29 +01:00
Andrey Antukh 28509e0418 Ensure .stopPropagation fn exists before calling
Also for .stopImmediatePropagation and .preventDefault on
the event instances.
2026-01-29 10:43:29 +01:00
Eva Marco 9569fa2bcb 🐛 Fix error when creating a token with an invalid name (#8216) 2026-01-29 10:41:52 +01:00
Eva Marco 852b31c3a0 🐛 Fix allow spaces on token description (#8234) 2026-01-29 10:40:32 +01:00
Eva Marco b8fdbd1ef8 🐛 Fix opacity value when token is broken (#8239) 2026-01-29 10:27:25 +01:00
Andrés Moya 84b3f5d7c6 🐛 Fix import of shadow tokens 2026-01-29 10:25:22 +01:00
Andrey Antukh 32454f5959 Merge remote-tracking branch 'origin/staging-render' into develop 2026-01-29 10:23:46 +01:00
Andrey Antukh b881e36875 Merge remote-tracking branch 'origin/staging' into staging-render 2026-01-29 10:23:31 +01:00
Pablo Alba 0bb74ed722 🐛 Fix viewer can update library (#8231) 2026-01-28 20:48:52 +01:00
Andrey Antukh b40e775a70 Add minor improvements to performance events (#8217)
*  Move devtools perf logging helpers to util.perf ns

* 💄 Move flag check to the entry point instead of initialize event

* ♻️ Make performance events consistent with other events
2026-01-28 20:47:14 +01:00
Eva Marco 2b4e315744 ♻️ Replace layout item numeric inputs. (#8163)
*  Replace opacity numeric input

*  Add test

* ♻️ Replace margin inputs

* 🎉 Add test
2026-01-28 14:30:18 +01:00
Pablo Alba 4ca82821c1 🐛 Fix shared keys init should be by keywords (2) (#8230) 2026-01-28 13:41:37 +01:00
David Barragán Merino 76bd31fe7d 🔧 Fix CORS error 2026-01-28 13:40:45 +01:00
David Barragán Merino cc81e56d82 🔧 Fix CORS error 2026-01-28 13:40:26 +01:00
David Barragán Merino a90f672a5e 🔧 Fix CORS error 2026-01-28 13:30:08 +01:00
Aitor Moreno 2b00e4eec9 Merge pull request #8207 from penpot/alotor-wasm-disable-thumbnail-generation
🐛 Disable thumbnails render in wasm
2026-01-28 13:28:07 +01:00
Aitor Moreno 3b86d7c1b1 🐛 Fix initializing rasterizer 2026-01-28 12:59:16 +01:00
alonso.torres 3cb716ec30 🐛 Disable thumbnails render in wasm 2026-01-28 12:59:16 +01:00
Andrey Antukh a9e2fc8d94 Backport linter fixes and config from develop 2026-01-28 12:58:54 +01:00
Pablo Alba f76598f638 🐛 Fix shared keys init should be by keywords (#8228) 2026-01-28 12:56:04 +01:00
Andrey Antukh 17ffd9a5d0 Backport linter fixes and config from develop 2026-01-28 12:54:18 +01:00
Xaviju eacc033567 🐛 Fix long token names overflow remap modal (#8224) 2026-01-28 12:44:07 +01:00
Andrey Antukh 71c349479f Merge pull request #8196 from penpot/niwinz-develop-management-auth-changes
♻️ Make several improvements to management API authentication
2026-01-28 10:52:26 +01:00
David Barragán Merino 77bbf30ae4 🔧 Fix file name 2026-01-27 21:16:44 +01:00
David Barragán Merino 693b52bf45 📚 Fix links related to penpot plugins 2026-01-27 21:16:44 +01:00
David Barragán Merino 0f51b23ce7 🔧 Deploy plugin styles documentation 2026-01-27 21:16:44 +01:00
David Barragán Merino ec61aa6b6d 🔧 Add custom domain 2026-01-27 21:16:41 +01:00
David Barragán Merino 18aca16f98 🔧 Fix file name 2026-01-27 21:12:32 +01:00
David Barragán Merino c6465e27e3 📚 Fix links related to penpot plugins 2026-01-27 21:12:32 +01:00
David Barragán Merino 1834a18263 🔧 Deploy plugin styles documentation 2026-01-27 21:12:32 +01:00
David Barragán Merino d220d07875 🔧 Add custom domain 2026-01-27 21:12:32 +01:00
David Barragán Merino fda31624c1 🔧 Fix file name 2026-01-27 21:04:25 +01:00
David Barragán Merino 7f640569bd 📚 Fix links related to penpot plugins 2026-01-27 20:59:54 +01:00
David Barragán Merino 91f1323802 🔧 Deploy plugin styles documentation 2026-01-27 20:59:54 +01:00
David Barragán Merino dbd4a2366f 🔧 Add custom domain 2026-01-27 20:59:54 +01:00
Pablo Alba cbb6d098a7 🐛 Fix boolean operators in menu for boards (#8177) 2026-01-27 17:58:07 +01:00
Andrey Antukh b6f5000d1c ⬆️ Update pnpm 2026-01-27 17:57:07 +01:00
Andrey Antukh 0527124f2f Merge remote-tracking branch 'origin/staging-render' into develop 2026-01-27 17:56:03 +01:00
Andrey Antukh faf91ac70d Merge remote-tracking branch 'origin/staging' into staging-render 2026-01-27 17:53:16 +01:00
Eva Marco 9ca76c745f 🐛 Fix app freeze on token name change (#8214) 2026-01-27 17:31:50 +01:00
Andrey Antukh 89935e2174 Make nitrate module loading conditional to flag
This removes the flag checking on each rpc method
2026-01-27 15:16:36 +01:00
Andrey Antukh 7f27e0326d Reuse basic team and profile schemas on nitrate 2026-01-27 15:14:32 +01:00
Andrey Antukh 9c539dfb2f 🔥 Remove subscriptions related management module 2026-01-27 15:14:32 +01:00
Andrey Antukh 50a4cf8b99 📎 Adapt nitrate module to auth changes 2026-01-27 15:14:32 +01:00
Andrey Antukh f5996a7235 ♻️ Make several improvements to management API authentication 2026-01-27 15:14:32 +01:00
Andrey Antukh e8fd4698c9 🔧 Update caddy configuration 2026-01-27 15:10:53 +01:00
Andrey Antukh 0ab126748f 💄 Add format rule for code comments (#8211)
* 💄 Add format rule for code comments

* ⬆️ Update linter and formatter on devenv
2026-01-27 15:07:18 +01:00
Yamila Moreno 71a5ab9913 🔧 Delete unused workflow 2026-01-27 13:47:05 +01:00
Andrey Antukh 61969f3eb5 Improve unhandled exception handling 2026-01-27 13:46:51 +01:00
Andrey Antukh bd2ef8057e Add helper for proper print js exceptions 2026-01-27 13:46:51 +01:00
Elena Torró 9808b6ca57 Merge pull request #8205 from penpot/superalex-improve-huge-shapes-render
🎉 Improving huge shapes render
2026-01-27 13:08:25 +01:00
Eva Marco 2523096fdd 🐛 Fix css rule (#8206) 2026-01-27 12:30:14 +01:00
Aitor Moreno de41cb5488 🐛 Fix add/remove fills to text nodes 2026-01-27 12:17:10 +01:00
Xaviju 8e63c4e3e8 ♻️ Review remap interface and interaction (#8168)
* ♻️ Review remap interface and interaction
* ♻️ Fix remapping feature tests
2026-01-27 11:18:34 +01:00
Alejandro Alonso b40ccaf030 🎉 Improve zoom actions for huge shapes 2026-01-27 11:11:38 +01:00
Alejandro Alonso 7d3ac38749 🎉 Improve huge shapes rendering 2026-01-27 11:11:38 +01:00
d5abc52dac 🎉 Add first integration with nitrate (#7803)
* 🐛 Display missing selected tokens set info (#8098)

* 🐛 Display missing selected tokens set info

*  Add integration tests to verify current active set

* 🎉 Integration with nitrate platform

* 🐛 Fix nitrate get-teams returns deleted teams

*  Add nitrate to tmux devenv

*  Add retry and validation to nitrate module

*  Add photoUrl to profile on nitrate authenticate

*  Move nitrate url to an env variable

* ♻️ Change Nitrate organization-id schema to text

* ♻️ Cleanup unused imports

* 🔧 Add control-center to nginx

*  Add create org link

* 🔧 Fix nginx entrypoint

* 🐛 Fix control-center proxy pass

* 🎉 Add nitrate licence check

* Revert " Add nitrate to tmux devenv"

This reverts commit dc6f6c458995dac55cab7be365ced0972760a058.

*  Add feature flag check

* 🐛 Rename licences for licenses

*  MR changes

*  MR changes 2

* 📎 Add the ability to have local config on start backend

* 📎 Add FIXME comment

---------

Co-authored-by: Xaviju <xavier.julian@kaleidos.net>
Co-authored-by: Juanfran <juanfran.ag@gmail.com>
Co-authored-by: Yamila Moreno <yamila.moreno@kaleidos.net>
Co-authored-by: Marina López <marina.lopez.yap@gmail.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-01-27 10:04:53 +01:00
Elena Torro 8d1bc6c50c 🐛 Fix flex layout sorting on reverse order with no z-index 2026-01-27 09:34:36 +01:00
Andrey Antukh 3112b240a0 📎 Add missing entry on changelog 2026-01-27 09:28:41 +01:00
Andrey Antukh 56fd66b91a 🐛 Fix several issues related to path edition (#8187)
*  Improve save-path-content event consistency

Mainly removing possible race conditions from the event
implementation.

*  Ensure path content snapshot on start-path-edit event

*  Reuse already available shape-id on split-segments
2026-01-27 09:27:42 +01:00
Andrey Antukh 3b96eb5476 🐛 Fix incorrect handling of schema expression on obj/reify 2026-01-27 09:20:33 +01:00
Elena Torro 2a7c24f6fd 🐛 Fix shape operations on sidebar when using interaction observer 2026-01-27 09:03:41 +01:00
Andrey Antukh 7a842ce36a Restore back node based http server for e2e tests 2026-01-27 08:55:17 +01:00
Andrey Antukh ea25c5db99 🔥 Remove unused svg from frontend resources 2026-01-27 08:55:17 +01:00
Alejandro Alonso 947aa22dee Merge pull request #8173 from penpot/elenatorro-improve-surface-performance
🔧 Improve surface rendering performance
2026-01-27 07:21:23 +01:00
Alejandro Alonso ce1796eb02 Merge pull request #8199 from penpot/elenatorro-13192-fix-layer-items-operations
🐛 Fix shape operations on sidebar when using interaction observer
2026-01-27 07:14:55 +01:00
Andrey Antukh 6f0685ba8e 🐛 Fix several issues related to path edition (#8187)
*  Improve save-path-content event consistency

Mainly removing possible race conditions from the event
implementation.

*  Ensure path content snapshot on start-path-edit event

*  Reuse already available shape-id on split-segments
2026-01-26 22:48:57 +01:00
Andrey Antukh abc1773f65 Merge tag '2.13.0-RC9' 2026-01-26 18:12:16 +01:00
David Barragán Merino 93f5e74bb0 🔧 Run all the jobs if the workflow is launched manually 2026-01-26 17:14:15 +01:00
David Barragán Merino 1ce0b60e3d 🔧 Run all the jobs if the workflow is launched manually 2026-01-26 17:13:54 +01:00
David Barragán Merino d433fd25c1 🔧 Run all the jobs if the workflow is launched manually 2026-01-26 17:12:58 +01:00
Elena Torro bb0e9b47cb 🐛 Fix shape operations on sidebar when using interaction observer 2026-01-26 16:14:18 +01:00
Elena Torro 5209a8b423 🔧 Improve surface rendering performance 2026-01-26 16:10:22 +01:00
Aitor Moreno f4f4f5bbb5 🐛 Fix multiple issues and tests 2026-01-26 14:14:06 +01:00
David Barragán Merino 38179ba11e 🔧 Enable secret inheritance 2026-01-26 14:01:22 +01:00
David Barragán Merino ef80901400 🔧 Enable secret inheritance 2026-01-26 14:00:55 +01:00
David Barragán Merino c5f03d711a 🔧 Enable secret inheritance 2026-01-26 14:00:09 +01:00
David Barragán Merino 719a95246a 🔧 Define deploy plugin packages workflows 2026-01-26 13:48:33 +01:00
David Barragán Merino e590cd852d 🔧 Rename wrangle to wrangler 2026-01-26 13:48:33 +01:00
David Barragán Merino a9741073e5 🔧 Add deploy plugin packages workflow placeholder and wrangle config files 2026-01-26 13:48:33 +01:00
David Barragán Merino 5306bed548 🔧 Define deploy plugin packages workflows 2026-01-26 13:47:57 +01:00
David Barragán Merino 92a319ddd1 🔧 Rename wrangle to wrangler 2026-01-26 13:47:57 +01:00
David Barragán Merino 68a6d4c9a8 🔧 Add deploy plugin packages workflow placeholder and wrangle config files 2026-01-26 13:47:57 +01:00
David Barragán Merino 72cc5ee349 🔧 Define deploy plugin packages workflows 2026-01-26 13:23:46 +01:00
Eva Marco 804695b48b ♻️ Replace stroke width numeric inputs (#8137)
*  Replace opacity numeric input

*  Add test

* ♻️ Replace stroke width numeric input

* 🎉 Add tests
2026-01-26 12:50:28 +01:00
Elena Torró 20c8fbf314 🔧 Make render-wasm visual regression tests use gpu (#8189) 2026-01-26 11:53:29 +01:00
Andrey Antukh e02536f8d4 Merge branch 'staging-render' into develop 2026-01-26 11:02:50 +01:00
Andrey Antukh 3eeaaab17e Merge branch 'staging' into staging-render 2026-01-26 11:02:26 +01:00
Andrey Antukh f07495ae95 🐛 Fix incorrect handling of numeric values layout padding and gap
Fixes https://github.com/penpot/penpot/issues/8113
2026-01-26 11:01:25 +01:00
Andrey Antukh 23d5fc7408 🐛 Prevent exception on open-new-window when no window is returned
Fixes https://github.com/penpot/penpot/issues/7787
2026-01-26 11:01:25 +01:00
Andrey Antukh 8632b18eec 🐛 Avoid json decoder liner limit exception by chunking
Happens only when we send large binary data serialized with transit
(mainly used for upload fonts data).
2026-01-26 11:01:25 +01:00
Andrey Antukh 33e650242c Add slugify to the filename on assets exportation
Fixes https://github.com/penpot/penpot/issues/8017
2026-01-26 11:01:25 +01:00
Alejandro Alonso 3dc9e28230 Merge pull request #8155 from penpot/elenatorro-13089-improve-page-load-render
🔧 Improve render UX on first load
2026-01-26 10:40:44 +01:00
Andrey Antukh e03ad25118 🔧 Backport CI workflow from develop 2026-01-26 10:18:24 +01:00
Andrey Antukh d9c56da705 ⬆️ Update playwright on frontend module 2026-01-26 10:08:53 +01:00
Andrey Antukh 75248aec4e 🔧 Add missing container on tests.yml 2026-01-26 09:52:47 +01:00
David Barragán Merino f0d9429775 🔧 Rename wrangle to wrangler 2026-01-26 09:37:33 +01:00
Eva Marco 62ecf48bdb 🐛 Fix glich when applying padding (#8099)
*  Replace opacity numeric input

*  Add test

* 🐛 Fix glich when applying padding
2026-01-26 08:40:32 +01:00
Alejandro Alonso 18de7f1db6 Merge pull request #8041 from penpot/niwinz-subpath-support
 Add several adjustments for make penpot run on subpath
2026-01-26 07:34:24 +01:00
Alejandro Alonso 2b2941bd25 Merge pull request #8049 from penpot/niwinz-virtual-clock-by-user
 Make the virtual clock by profile and not global
2026-01-26 07:22:08 +01:00
Alejandro Alonso f2d561eff7 Merge pull request #8063 from penpot/niwinz-develop-auditlog-logging
 Add the ability to log using logging subsystem the audit events
2026-01-26 07:17:13 +01:00
Alejandro Alonso 418b65a287 Merge pull request #8143 from penpot/niwinz-develop-bugfix-2
🐛 Fix incorrect handling of numeric values layout padding and gap
2026-01-26 07:11:09 +01:00
Alejandro Alonso d4e7810eba Merge pull request #8135 from penpot/niwinz-develop-chunking-on-font-upload
🐛 Avoid json decoder line limit exception by chunking
2026-01-26 07:08:23 +01:00
Andrey Antukh 1d1d32ad39 🐛 Avoid json decoder liner limit exception by chunking
Happens only when we send large binary data serialized with transit
(mainly used for upload fonts data).
2026-01-26 06:58:17 +01:00
Alejandro Alonso fb08dc65c8 Merge pull request #8133 from penpot/niwinz-develop-asset-export-whitespaces
 Add slugify to the filename on assets exportation
2026-01-26 06:55:52 +01:00
Alejandro Alonso 927ac93fa7 Merge pull request #8141 from penpot/niwinz-develop-bugfix-1
🐛 Prevent exception on open-new-window when no window is returned
2026-01-26 06:32:02 +01:00
Andrey Antukh e546a7c614 🐛 Prevent exception on open-new-window when no window is returned
Fixes https://github.com/penpot/penpot/issues/7787
2026-01-26 06:31:40 +01:00
David Barragán Merino 058c20c2e2 🔧 Add deploy plugin packages workflow placeholder and wrangle config files 2026-01-23 20:39:39 +01:00
David Barragán Merino 599656c31e 🔧 Fix a typo in an interpolation 2026-01-23 19:52:47 +01:00
David Barragán Merino 5d7e6afd76 🔧 Fix a typo in an interpolation 2026-01-23 19:52:20 +01:00
Elena Torró 68a77e9cc8 Merge pull request #8179 from penpot/superalex-adding-performance-logs-flag
🎉 Adding performance logs flag
2026-01-23 14:06:57 +01:00
Alejandro Alonso e3148ea20e 🎉 Adding performance logs flag 2026-01-23 13:34:19 +01:00
Elena Torró 5da9bbea62 Merge pull request #8174 from penpot/superalex-fix-blur-events-text-editor-v2
🐛 Fix blur events for text editor v2 in firefox
2026-01-23 13:08:01 +01:00
Alonso Torres 15d369493b 🐛 Fix problem with z-index modal in dashboard (#8178) 2026-01-23 12:48:01 +01:00
Andrey Antukh 5016b2a7bf Merge remote-tracking branch 'origin/staging-render' into develop 2026-01-23 11:18:33 +01:00
Andrey Antukh 089d1667b6 Merge remote-tracking branch 'origin/staging' into staging-render 2026-01-23 11:08:07 +01:00
Alejandro Alonso 4ad5282063 🐛 Fix blur events for text editor v2 in firefox 2026-01-23 10:58:54 +01:00
Elena Torró d0e79c94b4 Merge pull request #8162 from penpot/superalex-fix-auto-height
🐛 Fix text boxes with auto-height don't update height when resized by dragging side handles
2026-01-23 10:57:54 +01:00
Eva Marco 9c9b672e3e 🐛 Fix spanish translations on import export token modal (#8172) 2026-01-23 10:05:20 +01:00
Andrey Antukh 43ae213659 Replace login illustration svg with a correct bitmap (#8170) 2026-01-23 09:59:29 +01:00
Eva Marco 5146221513 🐛 Fix allow negative spread values on shadow token creation (#8167)
* 🐛 Fix allow negative spread values on shadow token creation

* 🎉 Add test
2026-01-23 09:50:36 +01:00
Eva Marco e53f335204 🐛 Fix unhandled error on tokens modal (#8165) 2026-01-23 09:35:53 +01:00
Alejandro Alonso d112c0a33b 🐛 Fix text boxes with auto-height don't update height when resized by dragging side handles 2026-01-23 09:05:20 +01:00
Elena Torró 7b86518afa Merge pull request #8171 from penpot/ladybenko-13152-fix-blur
🐛 Fix blur when clicking on same page
2026-01-22 17:42:39 +01:00
Elena Torró 9991901ed8 Merge pull request #8161 from penpot/superalex-fix-editing-text-doesnt-update-layer-name
🐛 Bug: Editing the text inside a text object doesn’t update the text layer name.
2026-01-22 17:40:32 +01:00
Belén Albeza 3d0c6ad421 Blur board titles and outlines when switching pages 2026-01-22 16:00:24 +01:00
Andrey Antukh dc973dac36 🔧 Enable link workspace packages on plugins 2026-01-22 13:55:41 +01:00
Andrey Antukh 4467827218 ⬆️ Update react dependency on frontend 2026-01-22 13:55:41 +01:00
Andrey Antukh 6470db8d5f Remove mention of yarn on several files 2026-01-22 13:55:41 +01:00
Andrey Antukh dc44156b53 🐛 Fix text editor wasm playground 2026-01-22 13:55:41 +01:00
Andrey Antukh f0e53d70ae 🎉 Migrate to PNPM render-wasm module 2026-01-22 13:55:41 +01:00
Andrey Antukh ef73a263b2 🎉 Migrate to PNPM docs module 2026-01-22 13:55:41 +01:00
Andrey Antukh 9b1e007a49 Replace e2e node server with caddy
Which is already available in the devenv runtime image
2026-01-22 13:55:41 +01:00
Andrey Antukh ea8632e56a 🐛 Fix frontend test-components scripts 2026-01-22 13:55:41 +01:00
Andrey Antukh 2d00e64ede Fix e2e scripts related to pnpm change 2026-01-22 13:55:41 +01:00
Andrey Antukh 1246250198 🎉 Migrate to PNPM frontend module 2026-01-22 13:55:41 +01:00
Andrey Antukh 34f2943dcd 🎉 Migrate to PNPM library module 2026-01-22 13:55:41 +01:00
Andrey Antukh 3fb78116b8 🎉 Migrate to PNPM backend module 2026-01-22 13:55:41 +01:00
Andrey Antukh 072e415b9e 🎉 Migrate to PNPM common module 2026-01-22 13:55:41 +01:00
Andrey Antukh 67a904824c 🎉 Migrate to PNPM exporter module 2026-01-22 13:55:41 +01:00
Belén Albeza 835ea97be7 🐛 Fix blur applied when clicking in the active page 2026-01-22 13:27:05 +01:00
Andrey Antukh 68184209be Add the ability to make json/->clj non-recursive 2026-01-22 13:07:05 +01:00
Andrey Antukh d2295862b4 Add the ability to customize decoder and schema on obj/reify 2026-01-22 13:07:05 +01:00
Andrey Antukh 23cbf33d1b Merge remote-tracking branch 'origin/staging' into develop 2026-01-22 12:34:49 +01:00
David Barragán Merino 16f22a7b5c 🔧 Fixes to the API documentation deployer 2026-01-22 12:10:27 +01:00
David Barragán Merino a1460115e8 🔧 Deploy penpot api documentation 2026-01-22 12:10:27 +01:00
David Barragán Merino 2574ad3315 🔧 Fixes to the API documentation deployer 2026-01-22 12:09:38 +01:00
David Barragán Merino e6b5364a84 🔧 Deploy penpot api documentation 2026-01-22 12:09:38 +01:00
David Barragán Merino b4ff0ccf3a 🔧 Fixes to the API documentation deployer 2026-01-22 12:08:09 +01:00
Andrey Antukh 6c6666a39a Merge pull request #8158 from penpot/ladybenko-13058-hide-avatar
🐛 Do not hide active users avatar when there are 3 of them
2026-01-22 12:02:53 +01:00
Elena Torro f94c9cdb02 🐛 Fix objects sorting for thumbnail generation 2026-01-22 09:29:33 +01:00
Elena Torro 8637c46ba1 🐛 Fix empty pool state 2026-01-22 08:52:26 +01:00
Elena Torro 5d7d23a2c7 🔧 Keep clear cached canvas 2026-01-22 08:51:58 +01:00
Alejandro Alonso a1a3966d7b 🐛 Editing the text inside a text object doesn’t update the text layer name 2026-01-22 08:24:13 +01:00
David Barragán Merino c1335961b4 🔧 Deploy penpot api documentation 2026-01-21 18:52:07 +01:00
Alonso Torres 656f81f89f ⬆️ Update plugins to 1.4.2 (#8157) 2026-01-21 17:36:58 +01:00
Belén Albeza eaf64b6e16 ♻️ Make the CSS of presence widgets to adhere to our guidelines 2026-01-21 17:33:18 +01:00
Belén Albeza 560a0d09d5 🐛 Fix hiding avatar when we have 3 active users 2026-01-21 16:01:20 +01:00
Elena Torro aab1d97c4c 🔧 Clean up and use proper imports 2026-01-21 16:01:06 +01:00
Elena Torro 499aac31a4 🔧 Improve tile invalidation to prevent visual flickering
When tiles are invalidated (during shape updates or page loading), the old tile
content is now kept visible until new content is rendered to replace it. This
provides a smoother visual experience during updates.
2026-01-21 15:42:52 +01:00
Alonso Torres 01a4ffeb8b ⬆️ Updated plugins release to 1.4.0 (#8148) 2026-01-21 15:41:00 +01:00
Elena Torro 962d7839a2 🔧 Add progressive rendering support for improved page load experience
When loading large pages with many shapes, the UI now remains responsive by
processing shapes in chunks (100 shapes at a time) and yielding to the browser
between chunks. Preview renders are triggered at 25%, 50%, and 75% progress to
give users visual feedback during loading.
2026-01-21 14:55:53 +01:00
Elena Torro 83387701a0 🔧 Add batched shape base properties serialization for improved WASM performance 2026-01-21 14:55:07 +01:00
Elena Torro 5775fa61ba 🔧 Refactor ShapesPool to use index-based storage instead of unsafe lifetime references
Replace `HashMap<&'a Uuid, ...>` with `HashMap<usize, ...>` for all auxiliary maps
(modifiers, structure, scale_content, modified_shape_cache)
2026-01-21 14:53:56 +01:00
Andrey Antukh b70eb768e0 Merge remote-tracking branch 'origin/staging' into develop 2026-01-21 13:51:41 +01:00
Andrey Antukh b8c70be9a2 Make frontend build and watch process more resilent to errors 2026-01-21 13:44:35 +01:00
Andrey Antukh 525adcfcbe Add wasm build on watch app script (devenv) 2026-01-21 13:44:35 +01:00
Eva Marco 7cce4c6532 🐛 Fix unhandled exception tokens creation dialog (#8136) 2026-01-21 13:09:22 +01:00
Alejandro Alonso a3fdd8b691 Merge pull request #8147 from penpot/niwinz-staging-file-menu-issue
 Use correct team-id on file-menu on dashboard
2026-01-21 12:54:28 +01:00
Andrey Antukh b6a9579c98 Use correct team-id on file-menu on dashboard
Before the changes on this commit, the team object is used for
retrieve the id, where we already have team-id. Additionally, the
team object resolution is async operation and is not available on
the first render which causes strange issues on automated flows
(playwright) where an option is clicked when the async flow is
still pending and we have no team object loaded.
2026-01-21 12:23:44 +01:00
Belén Albeza 5b1766835f 🐛 Fix broken selection on duplicated shapes on new pages 2026-01-21 10:32:13 +01:00
Andrey Antukh 4397ede5c1 Merge branch 'staging-render' into develop 2026-01-21 10:18:15 +01:00
Andrey Antukh ff25df0457 Merge remote-tracking branch 'origin/staging' into staging-render 2026-01-21 10:17:22 +01:00
Andrey Antukh e0910db99e 🐛 Fix incorrect handling of numeric values layout padding and gap
Fixes https://github.com/penpot/penpot/issues/8113
2026-01-21 09:38:44 +01:00
Eva Marco 8c7fd0af4b 🐛 Fix shadow reference validation (#8132) 2026-01-21 09:17:03 +01:00
Andrey Antukh cf46051f56 🔥 Remove .traivis.yml file from the repository 2026-01-20 19:40:31 +01:00
Luis de Dios 079b3fbfad ♻️ Extract and create panel title component (#8090) 2026-01-20 18:56:25 +01:00
Andrey Antukh 299f628951 Merge pull request #8123 from penpot/GlobalStar117-fix/token-validation-crash
🐛 Fix Penpot crash when setting some name in Design tokens
2026-01-20 18:53:05 +01:00
David Barragán Merino 32d0fe6463 🔧 Use selfhosted runner 01 to generate the bundle 2026-01-20 18:09:18 +01:00
Andrey Antukh 6393330ee1 Merge remote-tracking branch 'origin/staging' 2026-01-20 16:25:10 +01:00
Andrey Antukh 8252bc485e 📚 Fix oidc callback related documentation issue 2026-01-20 16:24:12 +01:00
Andrey Antukh cecd3d4a90 📎 Update changelog 2026-01-20 16:00:57 +01:00
Eva Marco 1c2c0987f5 🐛 Fix schema validation for references from other sets 2026-01-20 15:51:43 +01:00
Globalstar117 0418147e74 🐛 Add error handler to token form validation to prevent crash
When creating a token with a name that conflicts with existing
hierarchical token names (e.g., 'accent-color' when 'accent-color.blue.dark'
exists), the validation throws an error via rx/throw. However, the
rx/subs! subscriber in generic_form.cljs had no error handler, causing
an unhandled exception that resulted in an 'Internal Error' crash.

This fix adds an error handler that:
1. Catches validation errors from the reactive stream
2. Uses humanize-errors to convert them to user-friendly messages
3. Displays the error in the form's extra-errors field

Before: Crash with 'Internal Error' dialog
After: Form shows validation error message

Fixes #8110

---
This is a Gittensor contribution.
gittensor:user:GlobalStar117
2026-01-20 15:51:25 +01:00
Andrey Antukh 9e0ba4429a Add slugify to the filename on assets exportation
Fixes https://github.com/penpot/penpot/issues/8017
2026-01-20 15:27:24 +01:00
Alonso Torres 47775a9e2c Merge pull request #8134 from penpot/alotor-fix-plugins-export
🐛 Fix problem with export in plugins
2026-01-20 15:03:04 +01:00
Eva Marco 7499a5bca6 ♻️ Replace opacity input (#8072)
*  Replace opacity numeric input

*  Add test
2026-01-20 14:30:35 +01:00
Xaviju 6cd5bc76d7 💄 Replace current themes switch with DS switch (#8131) 2026-01-20 14:17:25 +01:00
Andrey Antukh 8191d04114 Use non-legacy config example on docker compose file 2026-01-20 13:25:55 +01:00
David Barragán Merino bbe6ee2e19 🔧 Define a different temporary config file for each execution 2026-01-20 12:59:56 +01:00
David Barragán Merino fb6d8309b6 🔧 Prevent error 429 downloading docker images from dockerhub 2026-01-20 12:59:56 +01:00
Alejandro Alonso b7c2d9a079 Merge pull request #8130 from penpot/superalex-improve-zoom-pan-performance-7
🐛 Fix some tiles disappear after fast zoom and pan
2026-01-20 12:56:02 +01:00
Alejandro Alonso aeb34a6f64 Merge pull request #8109 from penpot/superalex-fix-text-selrect-calculation
🐛 Render wasm typography token issues
2026-01-20 12:54:45 +01:00
Alejandro Alonso 6fa0c3af0c 🐛 Fix some tiles disappear after fast zoom and pan 2026-01-20 12:40:01 +01:00
Alejandro Alonso 260b9fb040 🐛 Fix texts with auto size updated via tokens with render wasm
activated
2026-01-20 12:39:17 +01:00
Alejandro Alonso 884954f4ff 🐛 Fix text selrect calculation 2026-01-20 12:37:57 +01:00
Andrey Antukh 88f0f75174 Merge pull request #8129 from penpot/niwinz-staging-bugfix-1
 Several improvements for build process
2026-01-20 12:26:55 +01:00
Andrey Antukh 1ffa956251 Include timestamp on version tag 2026-01-20 12:26:39 +01:00
Andrey Antukh 31054099ff Use pseudo-names on release builds of frontend (#8105) 2026-01-20 12:26:39 +01:00
Andrey Antukh 689467bcf9 📎 Update changelog 2026-01-20 12:25:43 +01:00
Andrey Antukh 7724450037 Merge remote-tracking branch 'origin/staging-render' into develop 2026-01-20 12:18:04 +01:00
Xaviju 368fa954ce Remove tokens tree node (#8042) 2026-01-20 11:00:13 +01:00
Eva Marco 983487d73c 🐛 Fix shadow token reference validation (#8128) 2026-01-20 10:56:27 +01:00
Andrey Antukh 6fd0f5377c Merge remote-tracking branch 'origin/staging' into staging-render 2026-01-20 10:08:58 +01:00
Elena Torró eb54bc485e Merge pull request #8120 from penpot/alotor-fix-flex-layout
🐛 Fix problems with layout
2026-01-20 10:00:24 +01:00
Elena Torró 12c24a36b4 Merge pull request #8122 from penpot/fix-thumbnail-generation
🐛 Fix problem with thumbnail generation
2026-01-20 09:59:34 +01:00
Alejandro Alonso 324d54ad28 🐛 Fix set all rounded corners to 0 2026-01-20 09:34:06 +01:00
alonso.torres f42ff27f3d 🐛 Fix problem with bools 2026-01-19 17:05:04 +01:00
Andrey Antukh 0ecb2bc838 Merge remote-tracking branch 'origin/staging' into develop 2026-01-19 13:42:05 +01:00
Andrey Antukh 3ecf509f3b 🔧 Add missing gif files handling on defaut nginx config 2026-01-19 13:32:01 +01:00
alonso.torres 2c1cc89f53 🐛 Fix problem with thumbnail generation 2026-01-19 12:54:15 +01:00
alonso.torres 498b0b30fe 🐛 Fix problems with layout 2026-01-19 12:17:58 +01:00
Elena Torró 89f40dcda2 🔧 Move WebGL context error message to 'errors' namespace (#8117) 2026-01-19 11:24:19 +01:00
Andrey Antukh e92f3fb3cb Use pseudo-names on release builds of frontend (#8105) 2026-01-19 11:23:35 +01:00
Andrey Antukh 5193cfd56e :paerclip: Update changelog 2026-01-19 11:15:39 +01:00
Dalai FelintoandDalai Felinto 7f395b2642 🐛 Fix import tokens dialog default option (#8051)
This was intended to be changed on 13fcf3a9bb. However only the menu
order changed, not the default option.

Signed-off-by: Dalai Felinto <dalai@blender.org>
Co-authored-by: Dalai Felinto <dalai@blender.org>
2026-01-19 11:14:34 +01:00
Dalai FelintoandDalai Felinto 813d5d8e69 🐛 Fix import tokens dialog default option (#8051)
This was intended to be changed on 13fcf3a9bb. However only the menu
order changed, not the default option.

Signed-off-by: Dalai Felinto <dalai@blender.org>
Co-authored-by: Dalai Felinto <dalai@blender.org>
2026-01-19 11:13:55 +01:00
Andrey Antukh 84f1ff092d Merge remote-tracking branch 'origin/staging' into develop 2026-01-19 11:10:37 +01:00
Juan de la Cruz 2a62bd2586 🎉 Add 2.13 release slides (#8116) 2026-01-19 11:09:58 +01:00
Elena Torró ccac7bd510 Merge pull request #8108 from penpot/ladybenko-13022-blur-page
🎉 Apply blur effect when switching pages
2026-01-19 11:04:31 +01:00
Andrey Antukh f2b082b93e Merge branch 'staging-render' into develop 2026-01-19 10:54:37 +01:00
Andrey Antukh d73197625d Merge remote-tracking branch 'origin/staging' into staging-render 2026-01-19 10:43:43 +01:00
Andrey Antukh 1f41bef4a9 Add several adjustments for make penpot run on subpath 2026-01-18 10:12:23 +01:00
Marina López fdf5bb250b 🐛 Fix profile not updating after deleting a team member (#8075) 2026-01-18 10:08:18 +01:00
Yamila Moreno 786736fadd Improve default nginx config (#8104) 2026-01-18 10:07:44 +01:00
Pablo Alba 1ff6e00398 🐛 Fix error message on components doesn't close automatically (#8081) 2026-01-18 10:07:11 +01:00
David Barragán Merino 25455523ad 🔧 Revert use of selfhosted runner 02 to generate the bundle 2026-01-16 17:51:02 +01:00
David Barragán Merino 8dfeb21978 🔧 Use selfhosted runner 02 to generate the bundle and the docker images 2026-01-16 17:44:15 +01:00
Belén Albeza 43d1d127dc 🎉 Apply blur effect to previous canvas pixels while setting wasm objects 2026-01-16 13:04:59 +01:00
Belén Albeza 8bd3ef717c 🎉 Apply blur to canvas when switching pages 2026-01-16 13:04:59 +01:00
Elena Torro 53bc647783 🔧 Fix shape selection from canvas to sidebar 2026-01-16 13:02:25 +01:00
Alejandro Alonso ad2833bb7a Merge pull request #8107 from penpot/elenatorro-13077-improve-shape-selection-performance
🔧 Fix shape selection from canvas to sidebar
2026-01-16 13:01:58 +01:00
Elena Torro 21911e898f 🔧 Fix shape selection from canvas to sidebar 2026-01-16 12:45:45 +01:00
Andrey Antukh 538073debf Merge remote-tracking branch 'origin/staging' into develop 2026-01-16 10:38:20 +01:00
Andrey Antukh 1ae7515189 🐛 Set correct defaults for storage configuration on compose (#8060)
* 🔥 Remove mention to legacy and never released admin on compose

* 🐛 Set correct defaults for storage configuration on compose
2026-01-16 08:11:34 +01:00
andrés gonzález 8c401f5346 📚 Add Files and Projects section to user guide (#8095) 2026-01-15 17:01:05 +01:00
Elena Torró 6029f9bb51 Merge pull request #8089 from penpot/superalex-improve-zoom-pan-performance-5
🎉 Performance improvements
2026-01-15 16:46:07 +01:00
Elena Torro e0fd8bac81 🔧 Optimize sidebar performance for deeply nested shapes
- Batch hover highlights using RAF to avoid long tasks from rapid events
- Run parent expansion asynchronously to not block selection
- Lazy-load children in layer items using IntersectionObserver
- Clarify expand-all-parents logic with explicit bindings
2026-01-15 13:41:54 +01:00
Elena Torro 34737ddfc9 🔧 Always lookup over a set 2026-01-15 13:41:10 +01:00
Elena Torro a8dfd19338 🔧 Add performance debugging logs 2026-01-15 13:40:58 +01:00
Elena Torro e33e8a8c3b 🔧 Lookup page objects only when value changes 2026-01-15 13:40:53 +01:00
Alejandro Alonso 214b0efa02 Merge pull request #8074 from penpot/elenatorro-13017-improve-sidebar-performance
🔧 Optimize sidebar performance for deeply nested shapes
2026-01-15 13:28:14 +01:00
David Barragán Merino 661436ecae 🔧 Use selfhosted runner 02 2026-01-15 13:26:49 +01:00
Alejandro Alonso c411aefc6c 🐛 Fix rotated shapes extrect calculation 2026-01-15 12:53:21 +01:00
Alejandro Alonso 311e124658 🎉 Reduce extrect work in tile traversal
Avoid repeated extrect calculations and simplify root ordering per tile.
2026-01-15 12:53:21 +01:00
Alejandro Alonso afc914f486 🎉 Render simple shapes directly on Current
Bypass intermediate surfaces for simple shapes without effects.
2026-01-15 12:53:21 +01:00
Pablo Alba 0d5fe6e527 🐛 Fix wrong register image 2026-01-15 11:25:12 +01:00
Pablo Alba e7230d9da4 🐛 Fix wrong image in the onboarding invitation block 2026-01-15 11:25:12 +01:00
Elena Torro 5054f6bc38 🔧 Optimize sidebar performance for deeply nested shapes
- Batch hover highlights using RAF to avoid long tasks from rapid events
- Run parent expansion asynchronously to not block selection
- Lazy-load children in layer items using IntersectionObserver
- Clarify expand-all-parents logic with explicit bindings
2026-01-15 09:15:32 +01:00
andrés gonzález b72de2dc8f 📚 Add shadow token documentation (#8082) 2026-01-15 09:09:08 +01:00
Alejandro Alonso 84f750da0d 🎉 Skip heavy effects in fast mode
Avoid blur and shadow passes for text and shapes when FAST_MODE is enabled.
2026-01-15 08:45:21 +01:00
Xaviju 38396ba299 ♻️ Divide token integrations tests into multiple files (#8076) 2026-01-14 16:12:36 +01:00
Pablo Alba b1997a83b3 🐛 Fix prototype connections lost when switching between variants 2026-01-14 15:30:54 +01:00
Elena Torro 68f5671eab 🔧 Always lookup over a set 2026-01-14 13:49:32 +01:00
Elena Torro 92976143bb 🔧 Add performance debugging logs 2026-01-14 13:49:32 +01:00
Eva Marco dd2d03e6a0 ♻️ Replace border radius inputs (#7953)
*  Replace border radius numeric input

*  Add border radius token inputs on multiple selection
2026-01-14 12:45:40 +01:00
Elena Torro fd675e0194 🔧 Lookup page objects only when value changes 2026-01-14 12:15:01 +01:00
Elena Torro a3119bef5e 🔧 Show message and button to reload the page when WebGL context is lost 2026-01-14 11:10:03 +01:00
Alejandro Alonso c60d74df62 🐛 Fix nested frames border clipping 2026-01-14 11:10:03 +01:00
Alejandro Alonso d593e299e3 🐛 Fix mask erros on save/restore optimizations 2026-01-14 11:10:03 +01:00
Alejandro Alonso 4a8e02987f 🐛 Fix mask erros on save/restore optimizations 2026-01-14 11:10:03 +01:00
Alejandro Alonso ee766e85a0 🎉 Wasm render dirty surfaces 2026-01-14 11:10:03 +01:00
Alejandro Alonso 35e3b7f19a 🎉 Root ids refactor 2026-01-14 11:10:03 +01:00
Alejandro Alonso 1810df232b 🎉 Ignore frames and groups when they have no visual extra information 2026-01-14 11:10:03 +01:00
Alejandro Alonso 3e99ad036c 🎉 Avoid unnecesary saves and restores 2026-01-14 11:10:03 +01:00
Alejandro Alonso 042a3a4080 🐛 Fix wasm playgrounds 2026-01-14 11:10:03 +01:00
Belén Albeza f0687fd1f7 🎉 Make workspace loader to wait for first render 2026-01-14 11:10:03 +01:00
Aitor Moreno 2c9159288f 🐛 Fix previous styles lost when changing selected text 2026-01-14 11:10:01 +01:00
Alejandro Alonso 51635770ce Merge pull request #8065 from penpot/eva-fix-lost-translation
🐛 Fix translation on rename token
2026-01-14 09:44:41 +01:00
Eva Marco 18a4e63da0 🐛 Fix translation on rename token 2026-01-13 15:57:09 +01:00
Andrey Antukh c98373658e Revert "🔧 Use selfhosted runners (#8057)"
This reverts commit 01e42b0458.
2026-01-13 15:45:28 +01:00
Andrey Antukh 2e400768b7 Add the ability to log using logging subsystem the audit events 2026-01-13 14:22:18 +01:00
David Barragán Merino 01e42b0458 🔧 Use selfhosted runners (#8057) 2026-01-13 14:20:33 +01:00
Madalena Melo 7529673812 📎 Create an issue template for reporting bugs on the new render engine (#8059)
This template will be part of the open beta test for the new render, so that users can report issues for us to review.
2026-01-13 13:27:27 +01:00
Andrey Antukh d2dad35d7a Merge branch 'staging' into develop 2026-01-13 10:36:22 +01:00
Andrey Antukh d71f811dbe Update help center build script 2026-01-13 10:35:15 +01:00
Eva Marco f7b5266304 🐛 Fix sticky-buttons-on-layers (#7962) 2026-01-12 14:28:19 +01:00
Eva Marco 09c23256b7 🐛 Fix multiselection with tokens on stroke color (#7977) 2026-01-12 12:54:24 +01:00
Andrey Antukh 1ae1c0460e 🐛 Fix translation related to dashboard deleted page (#8056)
* 🐛 Fix translation related to dashboard deleted page

*  Rehash and validate translation files
2026-01-12 12:42:51 +01:00
Andrey Antukh 291c7349db Merge remote-tracking branch 'origin/staging' into develop 2026-01-12 11:53:02 +01:00
Andrey Antukh 1beb3b86aa 🐛 Add missing msgid_plural on several translations 2026-01-12 11:52:07 +01:00
Andrey Antukh b605a3b53d Merge branch 'staging' into develop 2026-01-12 11:44:51 +01:00
Andrey Antukh fe20bdd00e 🐛 Fix multiple selection options on dashboard deleted page (#8055)
* 🐛 Fix multiple selection options on dashboard deleted page

* 📎 Fix translations
2026-01-12 11:41:37 +01:00
María Valderrama 5420897b92 🐛 Fix empty state message in trash page (#8045) 2026-01-12 11:17:01 +01:00
Andrey Antukh e430a4c9f3 Revert " Backport translations from develop"
This reverts commit ec6d72bd91.
2026-01-12 10:40:17 +01:00
Andrey Antukh ec6d72bd91 Backport translations from develop 2026-01-12 09:39:33 +01:00
Nicola Bortoletto cc2dab2756 🌐 Add translations for: Italian
Currently translated at 98.4% (2032 of 2065 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2026-01-12 09:36:34 +01:00
Alexis Morin d0c0664338 🌐 Add translations for: French (Canada)
Currently translated at 16.5% (342 of 2065 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-12 09:36:34 +01:00
Alexis Morin 2240f25143 🌐 Add translations for: French (Canada)
Currently translated at 15.3% (317 of 2065 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-12 09:36:34 +01:00
Oğuz Ersen 93a5ec2f5d 🌐 Add translations for: Turkish
Currently translated at 99.8% (2061 of 2065 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2026-01-12 09:36:33 +01:00
Alexis Morin d6784771a8 🌐 Add translations for: French (Canada)
Currently translated at 13.5% (279 of 2065 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-12 09:36:33 +01:00
Edgars Andersons 930c814ded 🌐 Add translations for: Latvian
Currently translated at 91.1% (1883 of 2065 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/
2026-01-12 09:36:33 +01:00
Stephan Paternotte 1a5a69bca2 🌐 Add translations for: Dutch
Currently translated at 99.8% (2061 of 2065 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2026-01-12 09:36:33 +01:00
VKing9 9ad323a220 🌐 Add translations for: Hindi
Currently translated at 96.9% (2001 of 2065 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/
2026-01-12 09:36:33 +01:00
Andrey Antukh bcaf76d055 Make the virtual clock by profile and not global
And make it affect only RPC/HTTP requests and not worker
tasks.
2026-01-09 12:58:44 +01:00
Eva Marco 6ec451b46d 🐛 Fix resolved value on line height (#8047) 2026-01-09 12:54:24 +01:00
Andrés Moya 5fa4368d70 🔧 Refactor integration test to be cleaner (#8044) 2026-01-09 12:52:50 +01:00
Elena Torró b8efd2518d 🐛 Fix invite members UI modal (#8032) 2026-01-09 11:12:15 +01:00
Andrey Antukh 2b836f10cb 🐛 Do not show deleted files on search (#8036)
* 🐛 Do not show deleted files on search

* 💄 Add cosmetic changes to dashboard deleted files page
2026-01-09 11:11:29 +01:00
Andrés Moya 7b2271ec38 🐛 Fix remapping of tokens with the same name and update tests (#8043) 2026-01-09 10:53:19 +01:00
Xaviju 2240d93069 Save unfolded tokens path (#7949) 2026-01-09 09:56:18 +01:00
Edgars Andersons 3f4506284b 🌐 Add translations for: Latvian
Currently translated at 91.3% (1869 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/
2026-01-08 18:06:51 +01:00
Valentina Chapellu af1dfd91aa 🌐 Add translations for: Italian
Currently translated at 97.0% (1984 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2026-01-08 18:06:51 +01:00
Mikel Larreategi 24feebd73b 🌐 Add translations for: Basque
Currently translated at 56.4% (1155 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/eu/
2026-01-08 18:06:51 +01:00
Aryiu 33e5a9a538 🌐 Add translations for: Catalan
Currently translated at 52.2% (1068 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ca/
2026-01-08 18:06:50 +01:00
Linerly 9c69b07a62 🌐 Add translations for: Indonesian
Currently translated at 82.9% (1697 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/id/
2026-01-08 18:06:50 +01:00
Црнобог 56f5be4f37 🌐 Add translations for: Serbian
Currently translated at 67.0% (1371 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/sr/
2026-01-08 18:06:50 +01:00
ascarida 8a70204d41 🌐 Add translations for: Galician
Currently translated at 18.0% (370 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/gl/
2026-01-08 18:06:50 +01:00
Henrik Allberg 57a27f7e7f 🌐 Add translations for: Swedish
Currently translated at 97.1% (1986 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/sv/
2026-01-08 18:06:50 +01:00
Eranot 3b0b2a78d6 🌐 Add translations for: Portuguese (Brazil)
Currently translated at 68.1% (1394 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/
2026-01-08 18:06:50 +01:00
Alejandro Alonso 10bf4610df 🌐 Add translations for: Hausa
Currently translated at 60.6% (1241 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ha/
2026-01-08 18:06:50 +01:00
Andy Li 77e8414aea 🌐 Add translations for: Chinese (Traditional Han script)
Currently translated at 78.1% (1599 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hant/
2026-01-08 18:06:50 +01:00
bingling_sama 20ecf3b066 🌐 Add translations for: Chinese (Simplified Han script)
Currently translated at 88.2% (1804 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2026-01-08 18:06:50 +01:00
Amerey.eu 49b1032973 🌐 Add translations for: Czech
Currently translated at 77.9% (1594 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/cs/
2026-01-08 18:06:49 +01:00
Radek Sawicki 5ba7dd8c56 🌐 Add translations for: Polish
Currently translated at 55.2% (1130 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pl/
2026-01-08 18:06:49 +01:00
Ingrid Pigueron 38b5125186 🌐 Add translations for: French
Currently translated at 94.5% (1934 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2026-01-08 18:06:49 +01:00
Vint Prox 6677ae83d4 🌐 Add translations for: Russian
Currently translated at 77.3% (1582 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/
2026-01-08 18:06:49 +01:00
Marius 0737c055f0 🌐 Add translations for: German
Currently translated at 93.2% (1906 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2026-01-08 18:06:49 +01:00
Dário 4b88748fe3 🌐 Add translations for: Portuguese (Portugal)
Currently translated at 76.8% (1571 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_PT/
2026-01-08 18:06:49 +01:00
Denys Kisil 92107e5b1e 🌐 Add translations for: Ukrainian (ukr_UA)
Currently translated at 88.8% (1818 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/
2026-01-08 18:06:49 +01:00
Shuaib Zahda ebc0e3a23c 🌐 Add translations for: Arabic
Currently translated at 55.0% (1126 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/
2026-01-08 18:06:49 +01:00
VKing9 ebe4f2da50 🌐 Add translations for: Hindi
Currently translated at 97.1% (1986 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/
2026-01-08 18:06:48 +01:00
Vincas Dundzys a07c1d6eaa 🌐 Add translations for: Lithuanian
Currently translated at 5.7% (118 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lt/
2026-01-08 18:06:48 +01:00
Ahmad HosseinBor 613bfda955 🌐 Add translations for: Persian
Currently translated at 38.2% (782 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2026-01-08 18:06:48 +01:00
AlexTECPlayz f7ef6618e5 🌐 Add translations for: Romanian
Currently translated at 94.8% (1940 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ro/
2026-01-08 18:06:48 +01:00
Sebastiaan Pasma fe334d9cbe 🌐 Add translations for: Dutch
Currently translated at 97.1% (1986 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2026-01-08 18:06:48 +01:00
Revenant 268b883c73 🌐 Add translations for: Malay
Currently translated at 32.8% (672 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ms/
2026-01-08 18:06:48 +01:00
Zvonimir Juranko f6a4effa29 🌐 Add translations for: Croatian
Currently translated at 78.1% (1599 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hr/
2026-01-08 18:06:48 +01:00
Yessenia Villarte Vaca ced848077e 🌐 Add translations for: Spanish (Latin America)
Currently translated at 6.4% (131 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es_419/
2026-01-08 18:06:48 +01:00
Alexis Morin 7d9d318539 🌐 Add translations for: French (Canada)
Currently translated at 12.5% (257 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-08 18:06:48 +01:00
Oğuz Ersen 9781fceadb 🌐 Add translations for: Turkish
Currently translated at 97.1% (1986 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2026-01-08 18:06:48 +01:00
Yaron Shahrabani 3178bd9a27 🌐 Add translations for: Hebrew
Currently translated at 97.0% (1984 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2026-01-08 18:06:47 +01:00
Andrey Antukh e5d677f449 🌐 Validate and rehash translation files 2026-01-08 18:05:51 +01:00
Andrey Antukh 6bf928893c Merge pull request #8000 from penpot/luis-radio-buttons-ds
♻️ Replace some components with DS ones
2026-01-08 18:04:20 +01:00
Andrey Antukh 1ae0f3fc87 Merge pull request #8037 from penpot/niwinz-staging-project-name-fix
🐛 Fix long project name visual problem on dashboard
2026-01-08 17:41:37 +01:00
Andrés Moya 53dd90aa24 🔥 Remove unused css (#8039) 2026-01-08 16:37:27 +01:00
Eva Marco e13c203b8d ♻️ Refactor scss file 2026-01-08 16:35:56 +01:00
Andrey Antukh 9fd0f6a8f3 📎 Fix integration tests 2026-01-08 16:02:52 +01:00
Andrey Antukh 638c3356d3 📎 Use correct casing on translation strings 2026-01-08 14:58:17 +01:00
Luis de Dios 6879f54e5d ♻️ Replace some components with DS ones 2026-01-08 14:52:25 +01:00
Andrey Antukh a71baa5a78 🌐 Rehash and validate translation files 2026-01-08 14:46:18 +01:00
Andrey Antukh 8e4a89bd1c Merge branch 'staging' into develop 2026-01-08 14:43:43 +01:00
Andrey Antukh 90efb665b5 Add several additional renames for make translation string consistent 2026-01-08 14:37:58 +01:00
Pablo Alba 47ee490158 🐛 Fix typos on download modal 2026-01-08 14:37:58 +01:00
Andrey Antukh f0f89599bc 🌐 Backport translations from develop 2026-01-08 14:08:02 +01:00
Hosted Weblate 7aad9da285 🌐 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/
2026-01-08 14:04:56 +01:00
Alexis Morin ab57a4ae52 🌐 Add translations for: French (Canada)
Currently translated at 12.9% (259 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-08 14:04:48 +01:00
Alexis Morin 266ee29bb9 🌐 Add translations for: French (Canada)
Currently translated at 9.2% (184 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-08 14:04:48 +01:00
Alexis Morin 69ca86bb6c 🌐 Add translations for: French (Canada)
Currently translated at 7.3% (147 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-08 14:04:48 +01:00
Alexis Morin ee14a845fc 🌐 Add translations for: French (Canada)
Currently translated at 3.1% (62 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-08 14:04:48 +01:00
Yaron Shahrabani 73639f5d16 🌐 Add translations for: Hebrew
Currently translated at 99.7% (1992 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2026-01-08 14:04:48 +01:00
Yaron Shahrabani 9bd106b2bc 🌐 Add translations for: Hebrew
Currently translated at 99.4% (1986 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2026-01-08 14:04:47 +01:00
Alexis Morin 59c75afc7b 🌐 Add translations for: French (Canada)
Currently translated at 1.0% (21 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-08 14:04:47 +01:00
Nicola Bortoletto bbc81586e3 🌐 Add translations for: Italian
Currently translated at 99.7% (1992 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2026-01-08 14:04:47 +01:00
Anton Palmqvist c9c30eab75 🌐 Add translations for: Swedish
Currently translated at 99.8% (1994 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/sv/
2026-01-08 14:04:47 +01:00
Alexis Morin 86ba9280db 🌐 Add translations for: French (Canada)
Currently translated at 0.3% (6 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-08 14:04:47 +01:00
Vin 5800cc4bb2 🌐 Add translations for: Russian
Currently translated at 79.2% (1583 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/
2026-01-08 14:04:47 +01:00
andy aa29a34c4c 🌐 Added translation for: French (Canada) 2026-01-08 14:04:47 +01:00
Edgars Andersons 3276129cc7 🌐 Add translations for: Latvian
Currently translated at 93.9% (1876 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/
2026-01-08 14:04:47 +01:00
VKing9 67a96de475 🌐 Add translations for: Hindi
Currently translated at 100.0% (1997 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/
2026-01-08 14:04:47 +01:00
Stephan Paternotte 48785b4846 🌐 Add translations for: Dutch
Currently translated at 99.8% (1994 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2026-01-08 14:04:46 +01:00
Oğuz Ersen 3f0573f95d 🌐 Add translations for: Turkish
Currently translated at 99.8% (1994 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2026-01-08 14:04:46 +01:00
Andrey Antukh d94a2a8881 Merge branch 'staging-render' into develop 2026-01-08 13:59:01 +01:00
Andrey Antukh 1c237a0968 Merge branch 'staging' into staging-render 2026-01-08 13:58:48 +01:00
Andrey Antukh b0dc7d6ffb 🔧 Change default jmx port on deps.edn 2026-01-08 13:56:22 +01:00
Elena Torró b7eaeffa88 Merge pull request #8024 from penpot/azazeln28-issue-12835-fix-previous-styles-lost
🐛 Fix previous styles lost when changing selected text
2026-01-08 13:49:06 +01:00
Andrey Antukh 722fcc1f82 Merge remote-tracking branch 'origin/staging' into develop 2026-01-08 13:48:21 +01:00
Andrey Antukh b7cd315872 🐛 Fix wasm-playground on devenv 2026-01-08 13:48:09 +01:00
Andrés MoyaandAkshay Gupta 2ad42cfd9b Add ability to remap tokens when renamed ones are referenced by other child tokens (#8035)
* 🎉 Add ability to remap tokens when renamed ones are referenced by other child tokens

Signed-off-by: Akshay Gupta <gravity.akshay@gmail.com>

* 🐛 Fix remap skipping tokens with same name in different sets

* 📚 Update CHANGES.md

* 🔧 Fix css styles

---------

Signed-off-by: Akshay Gupta <gravity.akshay@gmail.com>
Co-authored-by: Akshay Gupta <gravity.akshay@gmail.com>
2026-01-08 13:42:06 +01:00
Eva Marco 743d4e5c8d 🐛 Fix error on shadow token creation (#8029) 2026-01-08 13:26:01 +01:00
Andrey Antukh 97e4f4c424 🐛 Fix long project name visual problem on dashboard 2026-01-08 11:40:55 +01:00
Belén Albeza fb9560c315 🐛 Fix guides dropdown width (#8031)
* 🐛 Fix width of guides column dropdown

* ♻️ Remove deprecated tokens in css

* 🔧 Update changelog
2026-01-08 10:47:11 +01:00
Andrey Antukh 795f65632a 🐛 Fix wasm-playground on devenv 2026-01-08 10:42:37 +01:00
Alejandro Alonso d53c090900 Merge pull request #8028 from penpot/elenatorro-12956-fix-text-color-tokens
🐛 Fix missing text color token from selected shapes in selected colors list
2026-01-07 16:49:41 +01:00
Elena Torro 621e030095 🐛 Fix missing text color token from selected shapes in selected colors list 2026-01-07 16:41:25 +01:00
Alejandro Alonso 157e4aa2d0 Merge pull request #8025 from penpot/elenatorro-12951-fix-inner-text-shadow-token
🐛 Fix inner shadow selector on shadow token
2026-01-07 16:37:19 +01:00
Elena Torro 7cd2308f3b 🐛 Fix inner shadow selector on shadow token 2026-01-07 16:36:51 +01:00
Alejandro Alonso c315a15b48 Merge pull request #8026 from penpot/elenatorro-12997-fix-clojure-on-css-box-shadow
🐛 Fix CSS generated box-shadow property
2026-01-07 16:32:12 +01:00
Elena Torro 8a3e6d026e 🐛 Fix CSS generated box-shadow property 2026-01-07 16:28:05 +01:00
Florian Schrödl 0dd062d011 🐛 Fix line-height throwing for int (#7927) 2026-01-07 16:13:10 +01:00
Alejandro Alonso bfbb546699 Merge pull request #8027 from penpot/superalex-fix-colors-assets-from-shared-libraries
🐛 Fix color assets from shared libraries
2026-01-07 14:16:57 +01:00
Alejandro Alonso 083e77e9c5 🐛 Fix color assets from shared libraries 2026-01-07 14:02:28 +01:00
Aitor Moreno 7819e6c440 🐛 Fix previous styles lost when changing selected text 2026-01-07 12:41:39 +01:00
Andrey Antukh 952f622ce9 🔧 Add 'Reapply` prefix to valid commit checker prefixes 2026-01-07 11:56:38 +01:00
Andrey Antukh a6c6f97f47 Reapply "💄 Group tokens by name path (#7775)"
This reverts commit eff572d3bb.
2026-01-07 11:55:56 +01:00
Andrey Antukh 88424eb54a Merge branch 'staging' into develop 2026-01-07 11:55:40 +01:00
Alejandro Alonso 919f78daeb Merge pull request #7965 from penpot/eva-fix-styles-on-viewer
🐛 Fix inspect tab styles on viewer
2026-01-07 11:54:33 +01:00
Eva Marco b5c30f8c41 🐛 Fix inspect tab styles on viewer 2026-01-07 11:41:49 +01:00
Alejandro Alonso 60aa426753 Merge pull request #8022 from penpot/alotor-fix-drag-handlers
🐛 Fix problem with dragging handlers
2026-01-07 11:36:26 +01:00
Alejandro Alonso 86f7d6b26b Sanitizing error values (#8020) 2026-01-07 11:23:19 +01:00
Andrey Antukh 36732a4bd3 Make the devenv runtine initialization yarn independent (#8023) 2026-01-07 11:21:58 +01:00
Andrey Antukh eff572d3bb Revert "💄 Group tokens by name path (#7775)"
This reverts commit 0956b66281.
2026-01-07 11:20:44 +01:00
alonso.torres d470d96833 🐛 Fix problem with dragging handlers 2026-01-07 11:00:02 +01:00
Aitor Moreno cab70773d2 Merge pull request #7667 from penpot/azazeln28-doc-add-more-info-text-editor-v2-readme
📚 Add more info about text editor v2
2026-01-07 09:40:06 +01:00
Alejandro Alonso de9a21121a Merge remote-tracking branch 'origin/staging' into develop 2026-01-05 13:22:14 +01:00
Alejandro Alonso 32ca42a093 Merge remote-tracking branch 'origin/staging-render' into staging 2026-01-05 13:21:58 +01:00
Alejandro Alonso 523a97a4ec Merge pull request #8016 from penpot/alotor-fix-refresh-thumbnails
🐛 Fix problem with thumbnail regeneration
2026-01-05 13:21:34 +01:00
Alejandro Alonso 260f6861a3 Merge pull request #8015 from penpot/alotor-fix-grid-component-auto-sizing
🐛 Fix problem with grid layout components and auto sizing
2026-01-05 13:18:59 +01:00
alonso.torres edd53b419a 🐛 Fix problem with thumbnail regeneration 2026-01-05 13:09:40 +01:00
Alejandro Alonso cea10308b7 Merge remote-tracking branch 'origin/staging' into develop 2026-01-05 11:52:15 +01:00
alonso.torres 078a3d5a5c 🐛 Fix problem with grid layout components and auto sizing 2026-01-05 10:54:36 +01:00
Alejandro Alonso c4e57427ac Merge branch 'staging-render' into staging 2026-01-05 10:30:06 +01:00
David Barragán Merino 5223c9c881 🔧 Fix a typo in an interpolation 2026-01-05 09:13:14 +01:00
Alejandro Alonso be62fa10c4 📎 Bump new version on changelog 2026-01-05 08:42:57 +01:00
Alejandro Alonso 7a6405481c Merge remote-tracking branch 'origin/develop' into staging 2026-01-05 08:37:19 +01:00
Alejandro Alonso 218f34380a Merge pull request #8012 from penpot/azazeln28-refactor-minor-changes
♻️ Minor naming changes and event handling
2026-01-02 14:16:55 +01:00
Alejandro Alonso 47aaa2b5fa Merge pull request #8011 from penpot/alotor-fix-trash-bar
🐛 Fix problems with trash bar in dashboard
2026-01-02 13:46:53 +01:00
Aitor Moreno 6c6b3db87e ♻️ Minor naming changes and event handling 2026-01-02 13:41:48 +01:00
Alejandro Alonso 6eb32cfb79 Merge pull request #8008 from penpot/alotor-fix-path-editor
🐛 Fix problem with path editor and right click
2026-01-02 13:29:44 +01:00
alonso.torres dbba3496af 🐛 Fix problems with trash bar in dashboard 2026-01-02 12:00:16 +01:00
alonso.torres 55752d361f 🐛 Fix problem with path editor and right click 2026-01-02 10:37:52 +01:00
Alejandro Alonso fe94ee4526 Merge pull request #8009 from penpot/alotor-fix-style-font-input
🐛 Fix problem with style in fonts input
2026-01-02 10:23:02 +01:00
Aitor Moreno e39f292499 📚 Add more info about text editor v2 2026-01-02 10:13:34 +01:00
Andrey Antukh 5c71c57dd9 Merge tag '2.12.1' 2025-12-30 15:37:30 +01:00
Andrey Antukh 52b8560b70 Merge branch 'staging-render' into develop 2025-12-30 15:30:56 +01:00
Andrey Antukh 75860afe57 Merge remote-tracking branch 'origin/staging' into staging-render 2025-12-30 15:29:58 +01:00
Andrey Antukh 824ca1bbca 🔧 Make devenv init yarn indpendent 2025-12-30 15:28:19 +01:00
Andrey Antukh 5b6f9c1741 📎 Disable legacy cache on plugins nx config 2025-12-30 14:56:15 +01:00
Andrey Antukh 19853b832b 📚 Update documentation 2025-12-30 14:56:15 +01:00
Andrey Antukh d20c011db2 Migrate plugins to pnpm 2025-12-30 14:56:15 +01:00
Andrey Antukh 9431ae6858 📎 Update docs 2025-12-30 14:56:15 +01:00
Andrey Antukh 96356c1b89 ⬆️ Update storybook and fix compatibility issues 2025-12-30 14:56:15 +01:00
Andrey Antukh b7b68eeb47 🔥 Remove npx prefix on package.json scripts 2025-12-30 14:56:15 +01:00
Andrey Antukh 9bbeb657f8 🔧 Add plugins runtime ci job 2025-12-30 14:56:15 +01:00
Andrey Antukh ec1af4ad96 🎉 Import penpot-plugins repository
As commit 819a549e4928d2b1fa98e52bee82d59aec0f70d8
2025-12-30 14:56:15 +01:00
alonso.torres 23e7116b24 🐛 Fix problem with style in fonts input 2025-12-30 14:28:10 +01:00
Alejandro Alonso 48e3f35bb3 🐛 Fix setting a portion of text as bold or underline messes things up 2025-12-30 11:34:24 +01:00
Andrey Antukh 6b794c9d12 Merge branch 'staging' into staging-render 2025-12-30 11:13:15 +01:00
Yamila Moreno d3ee50daf5 🔧 Add ci for branch staging-render 2025-12-30 11:13:00 +01:00
Yamila Moreno 22a36d59d8 🔧 Add ci for branch staging-render 2025-12-30 10:57:51 +01:00
Alejandro Alonso a948e49e51 🐛 Fix using cache on first zoom after pan 2025-12-30 10:03:24 +01:00
Alejandro Alonso d635f5a8dc 🐛 Detecting situations where WebGL context is lost or no WebGL support 2025-12-30 10:03:24 +01:00
Alejandro Alonso ab3a3ef43b 🎉 Resize cache only when required 2025-12-30 10:03:24 +01:00
Alejandro Alonso 9c21fd3359 🐛 Fix resize cache memory leak 2025-12-30 10:03:24 +01:00
Andrey Antukh 7b5817f407 ♻️ Make several adjustments to the dashboard deleted page (#7999)
* ♻️ Make several sustantial adjustments to the dashboard deleted page

* 📎 Add PR feedback changes
2025-12-30 09:52:29 +01:00
Yamila Moreno e3405eacca 🔧 Improve mattermost notification 2025-12-29 19:06:26 +01:00
Alejandro Alonso 44b70cf1d4 Merge pull request #7998 from penpot/alotor-fix-problem-with-create-grid
🐛 Fix problem creating grid from elements
2025-12-29 14:31:15 +01:00
Alejandro Alonso a8bd74b392 Merge pull request #8001 from penpot/alotor-fix-gfonts-references
🐛 Fix problem with some fonts
2025-12-29 14:25:54 +01:00
alonso.torres 3d3e3582d6 🐛 Fix problem with some fonts 2025-12-29 12:35:19 +01:00
Andrey Antukh de052b5161 📎 Update changelog 2025-12-29 11:10:04 +01:00
Andrey Antukh e01654ba43 Merge branch 'staging-render' into develop 2025-12-29 10:43:00 +01:00
Andrey Antukh 6ebd48b94c Merge branch 'staging' into staging-render 2025-12-29 10:41:08 +01:00
Andrey Antukh 8a3b33797f 🐛 Fix error handling on password change form
Fixes https://github.com/penpot/penpot/issues/7978
2025-12-29 10:27:27 +01:00
Andrey Antukh 13fd20f76f Backport form error management improvements from develop 2025-12-29 10:27:27 +01:00
alonso.torres 417cd80564 🐛 Fix problem creating grid from elements 2025-12-23 14:49:21 +01:00
Alejandro Alonso a57011ec7b Merge remote-tracking branch 'origin/staging' into staging-render 2025-12-23 13:35:27 +01:00
Andrey Antukh 69c880d00e 🐛 Fix importmap usage on firefox 2025-12-23 13:10:58 +01:00
Andrey Antukh 9eebc467ef Preload default translations 2025-12-23 13:10:58 +01:00
Andrey Antukh b77712ce73 Move frontend/vendor to frontend/packages 2025-12-23 13:10:58 +01:00
Andrey Antukh 3d3e81f314 Replace tubax with more modern tooling 2025-12-23 13:10:58 +01:00
Andrey Antukh fe6441bb24 Replace hightlight.js internal bundle with direct npm use 2025-12-23 13:10:58 +01:00
Andrey Antukh e15f0baf30 Replace direct draft-js usage with internal module 2025-12-23 13:10:58 +01:00
Andrey Antukh c040cbb784 🔥 Remove old gulp related dependencies 2025-12-23 13:10:58 +01:00
Andrey Antukh 7f674b78a9 📎 Move all deps to dev-dependencies on frontend package.json
All they only needed for build process.
2025-12-23 13:10:58 +01:00
Andrey Antukh 099b78affd 📎 Update frontend yarn.lock file 2025-12-23 13:10:58 +01:00
Andrey Antukh 78cc3f0aa4 📎 Add immutable dependency to vendor/draft-js 2025-12-23 13:10:58 +01:00
Andrey Antukh 76f5f12808 ⬆️ Update dependencies on exporter 2025-12-23 13:10:58 +01:00
Alejandro Alonso cb325282ec Merge pull request #7994 from penpot/alotor-fix-font-style
🐛 Fix problem when changing colors with multiple fonts
2025-12-23 07:34:41 +01:00
Andrey Antukh 01ecde3bfa Add the ability to add relations on penpot sdk (#7987)
*  Add the ability to add relations on penpot sdk

* 📎 Remove debug console log
2025-12-22 20:55:31 +01:00
Andrey Antukh 047483a70a 🐛 Fix deleted files thumbnails generation 2025-12-22 20:20:43 +01:00
Alonso Torres 4000ec8762 🐛 Fix problem resizing auto size layouts (#7995) 2025-12-22 20:17:11 +01:00
Andrey Antukh 8cb2f27de8 ♻️ Move file permissions to binfile common ns 2025-12-22 20:16:41 +01:00
Andrey Antukh 0433336fc9 📎 Use correct criterium version on frontend deps 2025-12-22 20:16:41 +01:00
Andrey Antukh ce234fbeda Allow get thumbnails for deleted files 2025-12-22 20:16:41 +01:00
Andrey Antukh fc4d31eed7 Add minor efficiency improvements to deleted dashboard page 2025-12-22 20:16:41 +01:00
María Valderrama c670aac339 🎉 Added deleted files to dashboard 2025-12-22 20:16:41 +01:00
Andrés Moya 1d3fb5434f Enable shadow tokens by default (#7996) 2025-12-22 18:17:29 +01:00
Andrey Antukh f478399ae0 Merge remote-tracking branch 'origin/staging-render' into develop 2025-12-22 17:28:18 +01:00
Andrey Antukh 6a1854f180 Merge remote-tracking branch 'origin/staging' into staging-render 2025-12-22 17:28:01 +01:00
Andrés Moya 0858e297e5 🎉 Add composite tokens to plugins API (#7992) 2025-12-22 17:14:54 +01:00
alonso.torres bd580ab159 🐛 Fix problem when changing colors with multiple fonts 2025-12-22 17:14:37 +01:00
Alejandro Alonso 5780a43fe0 🐛 Fix object added in different page (#7988) 2025-12-22 16:59:47 +01:00
Alejandro Alonso 737eceda3a 🐛 Fix unmasking shapes (#7989) 2025-12-22 16:59:04 +01:00
Alonso Torres 923c3c2dbd 🐛 Fix font weight token (#7991) 2025-12-22 16:58:26 +01:00
Alejandro Alonso a14b4561e7 🐛 Fix comment bubbles (#7990) 2025-12-22 16:57:45 +01:00
Andrey Antukh bb5568e15a 🎉 Enable hindi translations on the application 2025-12-22 16:57:00 +01:00
Pablo Alba 5cbcec3db6 🐛 Fix "maximum call stack size exceeded" crash on variant 2025-12-22 16:57:00 +01:00
Anonymous 105e1fe86c 🌐 Add translations for: Spanish
Currently translated at 97.1% (1940 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es/
2025-12-22 16:34:43 +01:00
Yaron Shahrabani 3e0a916883 🌐 Add translations for: Hebrew
Currently translated at 99.3% (1985 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2025-12-22 16:34:42 +01:00
Ahmad HosseinBor 4f80238bc2 🌐 Add translations for: Persian
Currently translated at 39.2% (783 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2025-12-22 16:34:42 +01:00
Alejandro Alonso 5156cc5d9a 🌐 Add translations for: Yoruba
Currently translated at 58.8% (1176 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/yo/
2025-12-22 16:34:42 +01:00
Yessenia Villarte Vaca 42c46b6cfc 🌐 Add translations for: Spanish (Latin America)
Currently translated at 6.5% (131 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es_419/
2025-12-22 16:34:41 +01:00
VKing9 8b3c40b35e 🌐 Add translations for: Hindi
Currently translated at 99.6% (1991 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/
2025-12-22 16:34:41 +01:00
Andy Li d3996e5fb1 🌐 Add translations for: Chinese (Traditional Han script)
Currently translated at 80.1% (1601 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hant/
2025-12-22 16:34:40 +01:00
Anonymous 0c42bca866 🌐 Add translations for: German
Currently translated at 95.5% (1908 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2025-12-22 16:34:40 +01:00
Marius e5685c1f1c 🌐 Add translations for: German
Currently translated at 95.5% (1908 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2025-12-22 16:34:40 +01:00
Anonymous 2784209bde 🌐 Add translations for: Turkish
Currently translated at 99.5% (1989 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2025-12-22 16:34:40 +01:00
Alejandro Alonso 024f460e99 🌐 Add translations for: Igbo
Currently translated at 25.6% (512 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ig/
2025-12-22 16:34:39 +01:00
Anonymous 1d9b76b62a 🌐 Add translations for: Romanian
Currently translated at 97.2% (1942 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ro/
2025-12-22 16:34:39 +01:00
Shuaib Zahda 7e17a75b7d 🌐 Add translations for: Arabic
Currently translated at 56.4% (1127 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/
2025-12-22 16:34:39 +01:00
Anonymous ca093d6fae 🌐 Add translations for: French
Currently translated at 96.8% (1934 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2025-12-22 16:34:38 +01:00
Alexandre Pawlak 0f0b7562b5 🌐 Add translations for: French
Currently translated at 96.8% (1934 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2025-12-22 16:34:38 +01:00
Anonymous 9cdc694697 🌐 Add translations for: Chinese (Simplified Han script)
Currently translated at 90.3% (1804 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2025-12-22 16:34:37 +01:00
Dário b972a4033b 🌐 Add translations for: Chinese (Simplified Han script)
Currently translated at 90.3% (1804 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2025-12-22 16:34:37 +01:00
Anonymous cbe9f4da51 🌐 Add translations for: Swedish
Currently translated at 99.3% (1985 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/sv/
2025-12-22 16:34:37 +01:00
Anonymous c583bde9e3 🌐 Add translations for: Russian
Currently translated at 76.8% (1534 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/
2025-12-22 16:34:37 +01:00
Vint Prox 3911ebdc4e 🌐 Add translations for: Russian
Currently translated at 76.8% (1534 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/
2025-12-22 16:34:36 +01:00
Anonymous 3e3b18667b 🌐 Add translations for: Portuguese (Brazil)
Currently translated at 69.9% (1396 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/
2025-12-22 16:34:36 +01:00
Anonymous ed81c9b8df 🌐 Add translations for: Dutch
Currently translated at 99.5% (1988 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2025-12-22 16:34:36 +01:00
Sebastiaan Pasma fbdf98d29c 🌐 Add translations for: Dutch
Currently translated at 99.5% (1988 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2025-12-22 16:34:36 +01:00
Anonymous e603825a55 🌐 Add translations for: Italian
Currently translated at 99.5% (1988 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2025-12-22 16:34:35 +01:00
Valentina Chapellu 1d724783e6 🌐 Add translations for: Italian
Currently translated at 99.5% (1988 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2025-12-22 16:34:35 +01:00
Hosted Weblate e0abe7dcb5 🌐 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/
2025-12-22 16:33:05 +01:00
Andrey Antukh 5c1bbf5be8 Merge remote-tracking branch 'weblate/develop' into develop 2025-12-22 16:32:30 +01:00
Pablo Alba bbb0d58190 🐛 Fix "maximum call stack size exceeded" crash on variant 2025-12-22 16:27:10 +01:00
Andrey Antukh 88dcf9d1fe 🐛 Mark rpc calls as authenticated when shared key is used (#7901) 2025-12-22 12:18:36 +01:00
Alejandro Alonso fe44c14bac Merge pull request #7982 from penpot/niwinz-staging-import-bucket
🐛 Prefill storage object bucket if it comes nil on import binfile
2025-12-22 12:17:16 +01:00
Belén Albeza 20061067ad 🐛 Fix text editor not getting focus back after font variant change 2025-12-22 11:18:25 +01:00
Andrey Antukh 336173645e 🐛 Fix regression on export shape on plungins API 2025-12-22 10:41:42 +01:00
Andrey Antukh 2acf15958b Merge branch 'staging-render' into develop 2025-12-22 09:24:04 +01:00
Andrey Antukh 08267de242 Merge remote-tracking branch 'origin/staging' into staging-render 2025-12-22 09:23:48 +01:00
Pablo Alba 35fb376a78 Add proxypass to caddyfile on devenv (#7985) 2025-12-22 09:21:22 +01:00
Dalai FelintoandDalai Felinto 13fcf3a9bb 💄 Set import Tokens default option to be Single JSON value (#7918)
This patches makes the default Tokens importing option to match the
current default Tokens exporting option (single JSON value). This way it
is more obvious and quick to export the tokens from a file and import
in new one,

---

While testing our design system we are often re-exporting and
re-importing the Tokens to the files using the design system components.

I'm aware that this may be addressed in the future so the Tokens are
brought in together with the library. Meanwhile (and even in the future)
I think it is sensible to have a symmetry between the export and import
defeault options.

Co-authored-by: Dalai Felinto <dalai@blender.org>
2025-12-19 10:44:05 +01:00
Andrey Antukh 83bb4bf221 🐛 Prefill storage object bucket if it comes nil on import binfile 2025-12-19 09:32:51 +01:00
Henrik Steffens dba6ae2820 🌐 Add translations for: German
Currently translated at 95.8% (1911 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2025-12-17 13:00:25 +01:00
Marius ada101c236 🌐 Add translations for: German
Currently translated at 95.8% (1911 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2025-12-17 13:00:24 +01:00
Marius ea48fb5825 🌐 Add translations for: German
Currently translated at 90.4% (1804 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2025-12-16 11:00:25 +01:00
Alejandro Alonso 15ed25ca79 Merge pull request #7966 from penpot/niwinz-staging-abrreviate
🐛 Fix incorrect string truncation with abbreviate template filter
2025-12-12 13:53:33 +01:00
Andrey Antukh 9aa387a473 🐛 Fix incorrect string truncation with abbreviate template filter 2025-12-12 13:50:46 +01:00
Alejandro Alonso 67ba91b4b9 Merge pull request #7971 from penpot/niwinz-staging-bugfix-6
🐛 Fix tokens-lib encoding when value is nilable
2025-12-12 13:46:06 +01:00
Alejandro Alonso f67f1a6a0e Merge pull request #7972 from penpot/niwinz-staging-bugfix-7
🐛 Fix exception on assinging gradient to shadow on multiple selection
2025-12-12 13:42:39 +01:00
Alejandro Alonso 82d3e2024e Merge pull request #7973 from penpot/niwinz-staging-worker-scheduler
🐛 Fix incorrect redis connection error handling
2025-12-12 13:23:49 +01:00
Alejandro Alonso 4bd846c16d Merge pull request #7969 from penpot/niwinz-staging-fix-ratelimit
🐛 Fix issue on reading rlimit config
2025-12-12 13:22:53 +01:00
alonso.torres 8fde6b28ed 🐛 Fix problems with alignments and margins 2025-12-12 13:21:04 +01:00
alonso.torres 63325ec796 🐛 Fix problem with flex fill size distribution 2025-12-12 13:21:04 +01:00
alonso.torres 84415476d0 🐛 Fix problem with reflow layout 2025-12-12 13:21:04 +01:00
Andrey Antukh 94f95ca6b8 🐛 Fix incorrect redis connection error handling 2025-12-12 12:33:38 +01:00
Andrey Antukh 33c786498d Merge remote-tracking branch 'origin/staging-render' into develop 2025-12-12 12:19:49 +01:00
Andrey Antukh 5abc1aafb4 Merge tag '2.12.0-RC3' 2025-12-12 12:19:29 +01:00
Andrey Antukh 1f886b1f88 Merge remote-tracking branch 'origin/staging' into develop 2025-12-12 12:16:41 +01:00
Aitor Moreno 5a922c6bd6 Merge pull request #7960 from penpot/superalex-fix-too-many-active-webgl-contexts
🐛 Fix too many active WEBGL contexts
2025-12-12 12:03:46 +01:00
Andrey Antukh 507bf7445b 🐛 Fix tokens-lib encoding when value is nilable 2025-12-12 11:42:15 +01:00
Andrey Antukh 81b72c5acd 🐛 Fix exception on assinging gradient to shadow on multiple selection 2025-12-12 11:24:53 +01:00
Alejandro Alonso 1388865cfc 🐛 Fix too many active WEBGL contexts 2025-12-12 11:16:47 +01:00
Andrey Antukh 1738847694 Merge remote-tracking branch 'origin/staging' into staging-render 2025-12-12 10:20:21 +01:00
Aitor Moreno ca1c3c799d Merge pull request #7968 from penpot/alotor-fix-border-radius
🐛 Fix problem with border radius to path
2025-12-12 10:18:07 +01:00
Andrey Antukh 974495e08f Reduce log level for profile picture download error
Because it is not blocking operation and does not provents user
to proceed.
2025-12-12 08:17:13 +01:00
Andrey Antukh 2ed39e43c3 🐛 Fix issue on reading rlimit config 2025-12-11 23:50:01 +01:00
alonso.torres ce5006ae84 🐛 Fix problem with border radius to path 2025-12-11 22:40:44 +01:00
Eva Marco 50dbe6ab12 🐛 Fix horizontal scroll on layer panel (#7956) 2025-12-11 21:34:18 +01:00
Belén Albeza 0a7a65af5d ♻️ Make SerializableResult to depend on From traits 2025-12-11 16:00:03 +01:00
alonso.torres ea4d0e1238 Calculate position data in wasm 2025-12-11 16:00:03 +01:00
Elena Torro b705cf953a 🐛 Set layout data from set-object 2025-12-11 14:52:32 +01:00
Alejandro Alonso 90ce1f56e7 Merge pull request #7958 from penpot/superalex-fix-svg-extract-ids
🐛 Fix svg extract ids
2025-12-11 14:02:05 +01:00
Alejandro Alonso ab0438cc6f 🐛 Fix svg extract ids 2025-12-11 13:47:00 +01:00
Aitor Moreno c6aa9cc4b7 Merge pull request #7950 from penpot/ladybenko-12851-fix-text-selection
🐛 Fix text selection when editor regains focus
2025-12-11 13:45:29 +01:00
Andrey Antukh 5779adef33 Merge remote-tracking branch 'origin/staging' into staging-render 2025-12-11 13:30:59 +01:00
Andrey Antukh 2f46cbc0d4 Make render wasm import on worker http cache aware 2025-12-11 13:27:20 +01:00
Elena Torró ebf1758958 Merge pull request #7935 from penpot/superalex-improve-svg-import
🎉 Improve svg import
2025-12-11 13:21:29 +01:00
Elena Torró e94c56bfa7 Merge pull request #7954 from penpot/azazeln28-fix-font-weight-mixed-value
🐛 Fix font weight mixed value
2025-12-11 12:43:53 +01:00
Andrey Antukh 53be6f996b 🐛 Fix issues on build processs related to render-wasm 2025-12-11 12:41:19 +01:00
Alejandro Alonso 89d9591011 🎉 Improve svg import 2025-12-11 12:02:34 +01:00
Andrey Antukh 5a260294a1 🔧 Update build-tag.yml github workflow 2025-12-11 12:00:42 +01:00
Andrey Antukh 3becfcd723 🔧 Update build-tag.yml github workflow 2025-12-11 11:59:16 +01:00
Andrey Antukh 3f6e44316e 🐛 Add missing node depes install on render-wasm 2025-12-11 11:51:47 +01:00
Aitor Moreno 5501a2815f 🐛 Fix font-variant-id mixed value 2025-12-11 11:32:27 +01:00
Eva Marco 77ef8e6fe6 🐛 Fix scroll on move library modal (#7952) 2025-12-11 10:46:54 +01:00
Alejandro Alonso 1066438b02 Merge pull request #7922 from penpot/elenatorro-12855-improve-pan-rendering
🔧 Improve pan rendering
2025-12-10 15:58:59 +01:00
Alejandro Alonso 3b23a3ad19 Merge pull request #7947 from penpot/elenatorro-12880-fix-variant-ui
🔧 Support variants interactivity on the new render's UI
2025-12-10 15:27:48 +01:00
Andrey Antukh 7396f4bfb6 Merge remote-tracking branch 'origin/staging' into develop 2025-12-10 15:17:50 +01:00
Alejandro Alonso 916b7709dc Update Pencil Penpot Design System System template in carousel (#7948) 2025-12-10 15:09:28 +01:00
Belén Albeza 5cf51f3d26 🐛 Fix text selection not being restore if it was only 1 word 2025-12-10 15:05:13 +01:00
Belén Albeza 25acad5154 🔧 Add formatting rules to the TextEditor 2025-12-10 15:04:34 +01:00
Elena Torro 0a212b6291 🔧 Support variants interactivity on the new render's UI 2025-12-10 14:39:59 +01:00
Eva Marco 443e41fea4 🐛 Fix multiple selection with color tokens (#7941) 2025-12-10 14:36:08 +01:00
Alejandro Alonso c7c9b04095 Merge pull request #7944 from penpot/niwinz-staging-exporter-fix
🐛 Fix incorrect resource lifetime handling on exporter
2025-12-10 14:35:20 +01:00
Eva Marco c61a0c0332 📚 Add line to changelog (#7945) 2025-12-10 13:58:18 +01:00
Andrey Antukh eb1eeb4750 Merge remote-tracking branch 'origin/staging-render' into niwinz-develop-merge 2025-12-10 13:53:15 +01:00
Andrey Antukh a78477592b Merge remote-tracking branch 'origin/staging' into staging-render 2025-12-10 13:36:23 +01:00
Eva Marco 8707ff6511 🎉 Add spanish translation 2025-12-10 13:12:30 +01:00
Florian Schroedl 3d8a251741 🐛 Disallow font-family referencing composite token 2025-12-10 13:12:30 +01:00
Andrey Antukh 34e84ee3c8 🐛 Fix incorrect resource lifetime handling on exporter 2025-12-10 13:02:31 +01:00
Xaviju 0956b66281 💄 Group tokens by name path (#7775)
* 💄 Group tokens by name path
2025-12-10 12:34:19 +01:00
Luis de Dios 007b3f11f9 🐛 Fix pass new icons to radio buttons (#7939) 2025-12-10 12:28:27 +01:00
Alejandro Alonso e8201402a7 Merge pull request #7938 from penpot/niwinz-staging-bugfix-5
🐛 Fix several issues
2025-12-10 12:05:42 +01:00
Aitor Moreno 8a22477b96 Merge pull request #7932 from penpot/niwinz-staging-worker-wasm-load
🐛 Fix WASM loading strategy on worker
2025-12-10 11:47:31 +01:00
Elena Torro a661b2564f 🐛 Fix default case on vertical align 2025-12-10 10:59:27 +01:00
Elena Torro 2c3732f3f4 🔧 Fix line height calculation 2025-12-10 10:59:27 +01:00
Andrey Antukh e16645227b Merge branch 'staging-render' into develop 2025-12-10 10:10:44 +01:00
Andrey Antukh 45665a3c21 Merge remote-tracking branch 'origin/staging' into staging-render 2025-12-10 10:09:58 +01:00
Alejandro Alonso 3e684ea54f ⬆️ Update svgo dependency on frontend (#7936) 2025-12-10 10:07:02 +01:00
Eva Marco 179e6a195d 🎉 Add test for token creation (#7915) 2025-12-10 09:56:21 +01:00
Andrey Antukh 98039f13d8 🐛 Fix main toolbar z-index 2025-12-10 09:47:40 +01:00
Alejandro Alonso 40c27591f6 🐛 Fix svg import (#7925) 2025-12-10 08:36:54 +01:00
Andrey Antukh 91d20a46d1 💄 Add cosmetic changes to exports assets progress component 2025-12-10 08:23:05 +01:00
Andrey Antukh 50bead7c56 🐛 Fix react warning on having p inside p on assets export progress 2025-12-10 08:22:41 +01:00
Andrey Antukh b75b999903 📎 Fix devenv jvm warning 2025-12-10 08:22:05 +01:00
Andrey Antukh 810f1721c8 🐛 Fix recursion render on subscription modal 2025-12-10 07:54:52 +01:00
Stephan Paternotte b45bdd723f 🌐 Add translations for: Dutch
Currently translated at 99.8% (1991 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2025-12-09 22:00:21 +00:00
Ingrid Pigueron 8696044620 🌐 Add translations for: French
Currently translated at 97.1% (1937 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2025-12-09 22:00:19 +00:00
Andrey Antukh 8a8f360c7f Merge remote-tracking branch 'origin/staging' into develop 2025-12-09 19:53:38 +01:00
Andrey Antukh a4646373cf ♻️ Refactor wasm loading strategy on worker 2025-12-09 19:41:19 +01:00
Andrey Antukh f111cbb2a4 Add better cache config on devenv nginx 2025-12-09 19:38:30 +01:00
Luis de Dios e35fc85c3d 🎉 Create new empty-state component (#7903) 2025-12-09 16:48:12 +01:00
Aitor Moreno a614207f7e 🐛 Fix exporter failing with HTTPS 2025-12-09 16:08:20 +01:00
Elena Torro 81bc1bb0af 🔧 Log performance when building using profile-macros 2025-12-09 15:25:13 +01:00
Yamila Moreno 1798461d21 🐳 Add override for assets (#7926) 2025-12-09 14:55:21 +01:00
Luis de Dios 6ce3249c6d 🐛 Fix color format does not switch in the view mode (#7923)
* 🐛 Fix color format does not switch in the inspect mode of the view mode

* ♻️ Update components
2025-12-09 14:38:15 +01:00
Yamila Moreno dde0fddd6f 🐳 Add missing override to Dockerfile.frontend (#7920) 2025-12-09 12:08:46 +01:00
Aitor Moreno 7d36bc4025 Merge pull request #7907 from penpot/alotor-fix-export-text
🐛 Fix problem when exporting texts
2025-12-09 11:28:47 +01:00
Elena Torro b8feb6374d 🔧 Rebuild indices on zoom change, not pan 2025-12-09 11:26:03 +01:00
Elena Torro 0889df8e08 🔧 Skip slow operations on fast render 2025-12-09 11:26:03 +01:00
Andrey Antukh 4637aced8c Add support auto decoding and validation syntax for obj/reify 2025-12-09 11:13:06 +01:00
Andrey Antukh 9dfe5b0865 🐛 Fix inconsistencies on using obj/reify on plugins 2025-12-09 11:13:06 +01:00
Andrey Antukh 33bcc9544a Update frontend repl script 2025-12-09 11:13:06 +01:00
Andrey Antukh babd481b7f Make sm/coercer lazy 2025-12-09 11:13:06 +01:00
Andrey Antukh a9733c792d Make check-fn completly lazy 2025-12-09 11:13:06 +01:00
Belén Albeza 7be8ac3fd7 🐛 Fix internal error while importing a library 2025-12-09 11:10:32 +01:00
Pablo Alba b0351be724 🐛 Fix switch variants with paths 2025-12-09 11:08:55 +01:00
Elena Torro 9216d965ef 🔧 Update rendering settings to smooth render 2025-12-09 10:43:33 +01:00
Andrey Antukh d04fdb5fbd Make the dist bundle use consistent and cache-aware uris (#7911) 2025-12-09 08:05:28 +01:00
Ingrid Pigueron 4f3ca6422c 🌐 Add translations for: French
Currently translated at 96.8% (1931 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2025-12-08 19:40:14 +01:00
Nicola Bortoletto 1c03457fda 🌐 Add translations for: Italian
Currently translated at 99.8% (1991 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2025-12-06 07:00:19 +00:00
Eva Marco 81e0e4f222 ♻️ Replace token form files (#7896)
* ♻️ Replace shadow form

* ♻️ Rename files and components

* ♻️ Replace offsetx and offsety names

* ♻️ Replace form file for new form component using new form system

* ♻️ Rename files and props
2025-12-05 17:04:07 +01:00
Andrey Antukh b8392b3731 🐛 Fix regression on sending team invitations (#7912) 2025-12-05 12:36:06 +01:00
Andrey Antukh 935728aa39 🔧 Backport build-tag github workflow from develop 2025-12-05 10:26:01 +01:00
Andrey Antukh 77dba477ca 🔧 Backport build-tag github workflow from develop 2025-12-05 10:25:03 +01:00
Eva Marco b6598d1f07 🐛 Fix scrollbar on color modal (#7906) 2025-12-05 09:55:41 +01:00
Yamila Moreno f13b3c8737 🔧 Fix bug in Github Actions (#7908) 2025-12-04 20:24:33 +01:00
alonso.torres 520e979363 🐛 Fix problem when exporting texts 2025-12-04 17:32:54 +01:00
Yamila Moreno a0f8559ffc 🔧 Add ci/cd for nitrate-module (#7905) 2025-12-04 16:02:29 +01:00
Xaviju bf1dc21c75 💄 Hide themes & sets panels when none active (#7902) 2025-12-04 14:11:57 +01:00
Alejandro Alonso 46c20a993f Merge pull request #7904 from penpot/niwinz-staging-fix-invitation-resend
🐛 Fix exception on resending invitation
2025-12-04 11:56:07 +01:00
Andrey Antukh 0e0106f69a 🐛 Add correct assertion on create-invitation fn 2025-12-04 11:38:32 +01:00
Andrey Antukh 19bb69cc60 Improve invalid schema error report 2025-12-04 11:38:16 +01:00
Alejandro Alonso 504eb70988 Merge pull request #7885 from penpot/niwinz-staging-bugfix-2
🐛 Make workspace palette reposition on left sidebar collapse
2025-12-04 11:19:20 +01:00
Andrey Antukh a38f425dd3 Merge remote-tracking branch 'origin/staging' into staging-render 2025-12-04 11:06:48 +01:00
Xaviju 75a2331edf 💄 Set low-emphasis color for both light/dark modes (#7884) 2025-12-04 11:04:07 +01:00
VKing9 74d4b9b045 🌐 Add translations for: Hindi
Currently translated at 100.0% (1994 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/
2025-12-04 11:00:32 +01:00
Alejandro Alonso c2b4c9907d Merge pull request #7886 from penpot/niwinz-staging-bugfix-3
🐛 Fix casing on a translation of export files modal option
2025-12-04 10:59:51 +01:00
Alejandro Alonso bd5bbcae26 Merge pull request #7894 from penpot/niwinz-staging-bugfix-4
🐛 Fix incorrect interaction betwen hower and scroll on assets sidebar
2025-12-04 10:58:54 +01:00
Andrey Antukh 84273508ad 🐛 Fix incorrect interaction betwen hower and scroll on assets sidebar 2025-12-04 10:56:29 +01:00
Andrey Antukh 9245ba6bc2 💄 Adapt component style for assets-local-library on sidebar assets 2025-12-04 10:55:57 +01:00
Andrey Antukh 4be046406d Pass direct args instead of a vector to toggle-values on sidebar assets 2025-12-04 10:55:57 +01:00
Alejandro Alonso 84c747cd31 Merge pull request #7883 from penpot/niwinz-staging-bugfix
🐛 Fix exception on paste text on comments input
2025-12-04 10:32:07 +01:00
Alejandro Alonso 0036a9a0cd Merge pull request #7865 from penpot/niwinz-staging-audit
 Add minor improvements to the audit module
2025-12-04 10:04:00 +01:00
Alejandro Alonso 2105c3a68c Merge pull request #7866 from penpot/niwinz-staging-fix-emails
🐛 Change internal ordering on how email parts are assembled
2025-12-04 09:56:22 +01:00
Belén Albeza 38efa88460 🐛 Fix unpublish library modal not scrolling file list (#7892)
* 🐛 Fix unpublish library modal not scrolling when the linked files list is too long

* 💄 Remove deprecated tokens in unpublish library modal

* 🔧 Update CHANGELOG
2025-12-03 22:41:20 +01:00
Pablo Alba 6e254c2cf4 🐛 Fix change of library on swap (#7898) 2025-12-03 22:40:23 +01:00
Andrey Antukh 416980f063 🐛 Fix issue on render template on dist bundle (#7899) 2025-12-03 20:48:02 +01:00
Andrey Antukh f76710296c Merge remote-tracking branch 'origin/staging' into develop 2025-12-03 18:52:28 +01:00
Andrey Antukh 6251fa6b22 🐛 Close other open context menus on open a context menu (#7895) 2025-12-03 18:50:00 +01:00
alonso.torres aedd8cc11e 🐛 Fix problem when renaming variants in plugins 2025-12-03 17:42:17 +01:00
Andrey Antukh d1379c55f6 Make i18n translation files load on demand 2025-12-03 16:44:37 +01:00
Andrey Antukh b125c7b5a3 Merge remote-tracking branch 'origin/staging' into develop 2025-12-03 13:55:01 +01:00
Andrey Antukh 496d37795b Adapt docker images nginx config template to latest changes (#7891) 2025-12-03 13:45:18 +01:00
Alonso Torres 2f0853f5cc 🐛 Fix problem with variant plugins api (#7890) 2025-12-03 13:27:32 +01:00
Juan de la CruzandAndrey Antukh 648e660bcf 🎉 Add new content and images for the slides of 2.12 (#7874)
* 🎉 Add new slide's content

* 🎉 Add new slides images

* 📎 Fix clj fmt

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2025-12-03 13:26:55 +01:00
Andrey Antukh 9f6899007a Merge remote-tracking branch 'origin/staging' into develop 2025-12-03 13:10:30 +01:00
Slava "nerfur" Voronzoff bee2f70bfa 🐛 Add missing amd64 option on arch detection code on mange.sh script
Signed-off-by: Slava "nerfur" Voronzoff <nerfur@gmail.com>
2025-12-03 13:09:49 +01:00
Pablo Alba 00f8eac8fa 🐛 Fix can't delete unsaved variant prop (#7878) 2025-12-03 13:03:17 +01:00
Dalai FelintoandDalai Felinto df7caacb45 🐛 Fix crash in token grid view due to tooltip validation (#7887)
The color tokens in grid view have a tooltip which is a map.
This is done so the frontend can render:

```
Name: foo
Resolved value: #000000
```

However the validation scheme for tooltips was only accepting functions
and strings.

---

How to reproduce the original (unreported) crash:
* Create a color token
* Create a shape, add a fill
* Pick a color, chose the Token options
* Click on the Grid View

Crash: `{:hint "invalid props on component tooltip*\n\n  -> 'content'
    should be a string\n"}`

Signed-off-by: Dalai Felinto <dalai@blender.org>
Co-authored-by: Dalai Felinto <dalai@blender.org>
2025-12-03 13:01:36 +01:00
Marina López 641df77834 🐛 Fix wrong board size presets in Android (#7888) 2025-12-03 12:52:47 +01:00
Marina López 49bbdfb257 🐛 Fix U and E icon displayed in project list (#7875)
* 🐛 Fix U and E icon displayed in project lis

* 🐛 Fix U and E icon displayed in project list
2025-12-03 12:50:51 +01:00
Aitor Moreno 4e84deca44 Merge pull request #7879 from penpot/elenatorro-12797-fix-update-spans
🐛 Fix paragraph with text spans with multiple styles
2025-12-03 11:30:17 +01:00
Aitor Moreno 0d21e52068 🐛 Fix applyStylesTo entire selection 2025-12-03 11:07:33 +01:00
alonso.torres 1b29e9a50f 🐛 Fix race condition with fix fonts patch 2025-12-03 10:39:05 +01:00
Andrey Antukh 94af978be8 🐛 Fix casing on a translation of export files modal option 2025-12-03 10:22:45 +01:00
Andrey Antukh feababe2a8 🐛 Make workspace palette reposition on left sidebar collapse 2025-12-03 09:56:14 +01:00
Andrey Antukh 5ef06685fc 💄 Add cosmetic improvements to workspace palette component 2025-12-03 09:38:23 +01:00
Elena Torro 9f567c3bf4 🐛 Fix italic variant 2025-12-03 08:59:25 +01:00
Elena Torro 1ba15e5d10 🐛 Do not merge fill styles 2025-12-03 08:55:11 +01:00
Andrey Antukh 57fcec5afc 🐛 Make from-synthetic-clipboard-event function return always a stream
Causes an execption on steam processing when it returns nil
2025-12-03 08:32:38 +01:00
Andrey Antukh 58f82da61e 🐛 Fix exception on paste text on comments input 2025-12-03 08:20:58 +01:00
Andrey Antukh a28c5b61ca 💄 Adapt viewport paste code codestyle
And remove some not necessary constructions
2025-12-03 08:09:13 +01:00
Yaron Shahrabani 60df56caa3 🌐 Add translations for: Hebrew
Currently translated at 99.6% (1988 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2025-12-02 21:00:32 +00:00
Andrey Antukh 53aad7bc15 Merge remote-tracking branch 'origin/staging' into develop 2025-12-02 17:43:34 +01:00
Andrey Antukh 9123d199b7 🐛 Fix scripts/fmt 2025-12-02 17:43:21 +01:00
alonso.torres 37e45a8bbf 🐛 Fix race condition with text and type 2025-12-02 17:28:20 +01:00
alonso.torres 3471d40f46 🐛 Fix problem with boolean shapes updates 2025-12-02 17:28:20 +01:00
Elena Torro c6b64a8e39 🐛 Fix selectAll on mixed span styles 2025-12-02 16:50:48 +01:00
Elena Torro 511e80c948 🐛 Fix merge fill styles when there are multiple fills 2025-12-02 16:50:04 +01:00
Elena Torró f5a640d104 Merge pull request #7876 from penpot/ladybenko-12805-slow-loading
🐛 Fix viewport not being fully drawn on first load until a mouse …
2025-12-02 15:31:43 +01:00
Belén Albeza 3ae7c514e4 🐛 Fix viewport not being fully drawn on first load until a mouse hover 2025-12-02 15:06:28 +01:00
Andrey Antukh 57297741f5 Merge remote-tracking branch 'origin/staging' into develop 2025-12-02 13:28:50 +01:00
Andrey Antukh eeaf28bb25 📎 Disable caddy logging 2025-12-02 13:27:09 +01:00
Dalai Felinto d63d692d34 🐛 Fix mask issues with component swap #7675
The logic to swap a component would delete the swapped out component
first before bringing in the new one.

In the process of doing so, the sanitization code would unmask the
group, now orphan of its mask shape component, when it was the first
element of the group.

The fix  was to pass an optional argument to the generate-delete-shapes
function to ignore mask in special cases like this.

Signed-off-by: Dalai Felinto <dalai@blender.org>
2025-12-02 12:31:44 +01:00
alonso.torres fad9ed1c48 🐛 Fix problem with reordering layers 2025-12-02 12:27:00 +01:00
alonso.torres 0caaefefea 🐛 Fix outline with single click text creation 2025-12-02 11:08:58 +01:00
Elena Torro b179aa79b1 🐛 Fix create empty text on click regression 2025-12-02 11:08:58 +01:00
Andrey Antukh 6b8091bb90 Make devenv https and http2 capable (#7871)
Making it more similar on how it runs on production
environments and improves large amount of files loading
thanks to http2.
2025-12-02 10:49:37 +01:00
Andrey Antukh fe72d0af82 Add self-signed cert to caddy (#7872) 2025-12-02 10:45:26 +01:00
Aitor Moreno 405ddb60d8 🐛 Fix letter spacing applied to paragraph 2025-12-02 10:45:19 +01:00
Madalena MeloandAndres Gonzalez bba02473d5 📚 Update subtitles in the new user guide cards (#7823)
Co-authored-by: Andres Gonzalez <andres.gonzalez79@gmail.com>
2025-12-02 09:21:05 +01:00
Elena Torró 95c0d42d5b Merge pull request #7868 from penpot/alotor-fix-flex-tools
🐛 Fix visual feedback on padding/margin/gaps modified
2025-12-01 17:51:44 +01:00
alonso.torres 721b337511 🐛 Fix visual feedback on padding/margin/gaps modified 2025-12-01 16:31:15 +01:00
Elena Torró 359379be09 Merge pull request #7867 from penpot/azazeln28-add-text-editor-v2-tests-to-staging
 Add text editor v2 integration tests
2025-12-01 16:11:25 +01:00
Aitor Moreno 876d5783cf Add text editor v2 integration tests 2025-12-01 15:56:52 +01:00
Elena Torro 786f73767b 🔧 Normalize font attributes to support old formats 2025-12-01 14:59:24 +01:00
Andrey Antukh 95b7784a42 🐛 Change internal ordering on how email parts are assembled
This fixes the html email rendering on gmail. Other clients (like proton,
emailcatcher) properly renders html independently of the order of parts
on the multipart email structure but gmail requires that html should be
the last one.
2025-12-01 14:27:21 +01:00
Andrey Antukh 4690f740b9 Add minor improvements to the audit module 2025-12-01 13:57:55 +01:00
Andrey Antukh 4282cdcd2c Merge remote-tracking branch 'origin/staging' into staging-render 2025-12-01 10:11:06 +01:00
Alejandro Alonso e889413f26 🐛 Fix nested shadows clipping 2025-12-01 09:22:23 +01:00
Elena Torró 115273b478 Merge pull request #7852 from penpot/alotor-flex-issues
🐛 Fix flex problems in new render
2025-11-28 14:10:42 +01:00
Elena Torró fdddd3284a Merge pull request #7859 from penpot/ladybenko-12801-fix-mismatched-fonts
🐛 Fix mismatch between fonts for rendered and selected text when no fallback fonts apply
2025-11-28 14:10:17 +01:00
Belén Albeza 51385a04a0 🐛 Fix mismatch between fonts for rendered and selected text when no fallback fonts apply 2025-11-28 13:54:17 +01:00
Belén Albeza f96ed8ccd6 Fix playwright tests 2025-11-28 13:25:13 +01:00
Belén Albeza bda5de5c1b 🔧 Update google fonts list 2025-11-28 13:25:13 +01:00
alonso.torres 59f3b4db4c 🐛 Fix problem with auto-size and element margins 2025-11-28 12:12:19 +01:00
alonso.torres 7ee03ad911 🐛 Fix problem with grid layout editor 2025-11-28 12:12:09 +01:00
alonso.torres 130b8c8214 🐛 Fix problems with flex layout in new render 2025-11-28 10:49:55 +01:00
alonso.torres 0198d41757 🐛 Fix crash when cleanup 2025-11-28 10:44:54 +01:00
alonso.torres 567a955151 🐛 Fix problem with change gap/margin/padding 2025-11-28 10:44:38 +01:00
Nicola Bortoletto 34da754357 🌐 Add translations for: Italian
Currently translated at 98.9% (1973 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2025-11-28 05:00:28 +00:00
Oğuz Ersen 39eafae251 🌐 Add translations for: Turkish
Currently translated at 99.8% (1991 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2025-11-25 16:51:25 +00:00
Edgars Andersons e1e09b7f96 🌐 Add translations for: Latvian
Currently translated at 94.0% (1876 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/
2025-11-25 16:51:24 +00:00
Stephan Paternotte 3b39980f2f 🌐 Add translations for: Dutch
Currently translated at 99.8% (1991 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2025-11-25 16:51:23 +00:00
Anton Palmqvist 223b12d2c7 🌐 Add translations for: Swedish
Currently translated at 99.6% (1987 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/sv/
2025-11-25 16:51:21 +00:00
1352 changed files with 191243 additions and 78092 deletions

No files matched your search

+9
View File
@@ -45,6 +45,15 @@
:potok/reify-type
{:level :error}
:redundant-primitive-coercion
{:level :off}
:unused-excluded-var
{:level :off}
:unresolved-excluded-var
{:level :off}
:missing-protocol-method
{:level :off}
+5
View File
@@ -2,6 +2,11 @@
:remove-multiple-non-indenting-spaces? false
:remove-surrounding-whitespace? true
:remove-consecutive-blank-lines? false
:indent-line-comments? true
:parallel? true
:align-form-columns? false
;; :align-map-columns? false
;; :align-single-column-lines? false
:extra-indents {rumext.v2/fnc [[:inner 0]]
cljs.test/async [[:inner 0]]
promesa.exec/thread [[:inner 0]]
@@ -0,0 +1,38 @@
---
name: New Render Bug Report
about: Create a report about the bugs you have found in the new render
title: ''
labels: new render
assignees: claragvinola
---
**Describe the bug**
A clear and concise description of what the bug is.
**Steps to Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots or screen recordings**
If applicable, add screenshots or screen recording to help illustrate your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
+1 -1
View File
@@ -40,7 +40,7 @@ on:
jobs:
build-bundle:
name: Build and Upload Penpot Bundle
runs-on: ubuntu-24.04
runs-on: penpot-runner-01
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+6 -1
View File
@@ -7,9 +7,14 @@ jobs:
build-and-push:
name: Build and push DevEnv Docker image
environment: release-admins
runs-on: ubuntu-24.04
runs-on: penpot-runner-02
steps:
- name: Set common environment variables
run: |
# Each job execution will use its own docker configuration.
echo "DOCKER_CONFIG=${{ runner.temp }}/.docker-${{ github.run_id }}-${{ github.job }}" >> $GITHUB_ENV
- name: Checkout code
uses: actions/checkout@v4
+32 -1
View File
@@ -19,9 +19,14 @@ on:
jobs:
build-and-push:
name: Build and Push Penpot Docker Images
runs-on: ubuntu-24.04-arm
runs-on: penpot-runner-02
steps:
- name: Set common environment variables
run: |
# Each job execution will use its own docker configuration.
echo "DOCKER_CONFIG=${{ runner.temp }}/.docker-${{ github.run_id }}-${{ github.job }}" >> $GITHUB_ENV
- name: Checkout code
uses: actions/checkout@v4
with:
@@ -54,6 +59,7 @@ jobs:
mv penpot/frontend bundle-frontend
mv penpot/exporter bundle-exporter
mv penpot/storybook bundle-storybook
mv penpot/mcp bundle-mcp
popd
- name: Set up Docker Buildx
@@ -66,6 +72,15 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
# To avoid the “429 Too Many Requests” error when downloading
# images from DockerHub for unregistered users.
# https://docs.docker.com/docker-hub/usage/
- name: Login to DockerHub Registry
uses: docker/login-action@v3
with:
username: ${{ secrets.PUB_DOCKER_USERNAME }}
password: ${{ secrets.PUB_DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
@@ -75,6 +90,7 @@ jobs:
backend
exporter
storybook
mcp
labels: |
bundle_version=${{ steps.bundles.outputs.bundle_version }}
@@ -138,6 +154,21 @@ jobs:
cache-from: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache
cache-to: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache,mode=max
- name: Build and push MCP Docker image
uses: docker/build-push-action@v6
env:
DOCKER_IMAGE: 'mcp'
BUNDLE_PATH: './bundle-mcp'
with:
context: ./docker/images/
file: ./docker/images/Dockerfile.mcp
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:${{ steps.vars.outputs.gh_ref }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache
cache-to: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache,mode=max
- name: Notify Mattermost
if: failure()
uses: mattermost/action-mattermost-notify@master
@@ -0,0 +1,14 @@
name: _STAGING RENDER
on:
schedule:
- cron: '36 5-20 * * 1-5'
jobs:
build-bundle:
uses: ./.github/workflows/build-bundle.yml
secrets: inherit
with:
gh_ref: "staging-render"
build_wasm: "yes"
build_storybook: "yes"
+3 -2
View File
@@ -11,7 +11,7 @@ jobs:
secrets: inherit
with:
gh_ref: ${{ github.ref_name }}
build_wasm: "no"
build_wasm: "yes"
build_storybook: "yes"
build-docker:
@@ -23,6 +23,7 @@ jobs:
notify:
name: Notifications
runs-on: ubuntu-24.04
needs: build-docker
steps:
@@ -32,7 +33,7 @@ jobs:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
MATTERMOST_CHANNEL: bot-alerts-cicd
TEXT: |
🐳 *[PENPOT] Docker image available.*
🐳 *[PENPOT] Docker image available: ${{ github.ref_name }}*
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
@infra
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
- name: Check Commit Type
uses: gsactions/commit-message-checker@v2
with:
pattern: '^(((:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle|rewind|construction_worker):)\s[A-Z].*[^.])|(Merge|Revert).+[^.])$'
pattern: '^(((:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle|rewind|construction_worker):)\s[A-Z].*[^.])|(Merge|Revert|Reapply).+[^.])$'
flags: 'gm'
error: 'Commit should match CONTRIBUTING.md guideline'
checkAllCommitMessages: 'true' # optional: this checks all commits associated with a pull request
@@ -0,0 +1,142 @@
name: Plugins/api-doc deployer
on:
push:
branches:
- develop
- staging
- main
paths:
- 'plugins/libs/plugin-types/index.d.ts'
- 'plugins/libs/plugin-types/REAME.md'
- 'plugins/tools/typedoc.css'
- 'plugins/CHANGELOG.md'
- 'plugins/wrangler-penpot-plugins-api-doc.toml'
workflow_dispatch:
inputs:
gh_ref:
description: 'Name of the branch'
type: choice
required: true
default: 'develop'
options:
- develop
- staging
- main
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Extract some useful variables
id: vars
run: |
echo "gh_ref=${{ inputs.gh_ref || github.ref_name }}" >> $GITHUB_OUTPUT
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ steps.vars.outputs.gh_ref }}
# START: Setup Node and PNPM enabling cache
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: Enable PNPM
working-directory: ./plugins
shell: bash
run: |
corepack enable;
corepack install;
- name: Get pnpm store path
id: pnpm-store
working-directory: ./plugins
shell: bash
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
- name: Cache pnpm store
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-${{ hashFiles('plugins/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
# END: Setup Node and PNPM enabling cache
- name: Install deps
working-directory: ./plugins
shell: bash
run: |
pnpm install --no-frozen-lockfile;
pnpm add -D -w wrangler@latest;
- name: Build docs
working-directory: plugins
shell: bash
run: pnpm run build:doc
- name: Select Worker name
run: |
REF="${{ steps.vars.outputs.gh_ref }}"
case "$REF" in
main)
echo "WORKER_NAME=penpot-plugins-api-doc-pro" >> $GITHUB_ENV
echo "WORKER_URI=doc.plugins.penpot.app" >> $GITHUB_ENV ;;
staging)
echo "WORKER_NAME=penpot-plugins-api-doc-pre" >> $GITHUB_ENV
echo "WORKER_URI=doc.plugins.penpot.dev" >> $GITHUB_ENV ;;
develop)
echo "WORKER_NAME=penpot-plugins-api-doc-hourly" >> $GITHUB_ENV
echo "WORKER_URI=doc.plugins.hourly.penpot.dev" >> $GITHUB_ENV ;;
*) echo "Unsupported branch ${REF}" && exit 1 ;;
esac
- name: Set the custom url
working-directory: plugins
shell: bash
run: |
sed -i "s/WORKER_URI/${{ env.WORKER_URI }}/g" wrangler-penpot-plugins-api-doc.toml
- name: Add noindex header and robots.txt files for non-production environments
if: ${{ steps.vars.outputs.gh_ref != 'main' }}
working-directory: plugins
shell: bash
run: |
ASSETS_DIR="dist/doc"
cat > "${ASSETS_DIR}/_headers" << 'EOF'
/*
X-Robots-Tag: noindex, nofollow
EOF
cat > "${ASSETS_DIR}/robots.txt" << 'EOF'
User-agent: *
Disallow: /
EOF
- name: Deploy to Cloudflare Workers
uses: cloudflare/wrangler-action@v3
with:
workingDirectory: plugins
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --config wrangler-penpot-plugins-api-doc.toml --name ${{ env.WORKER_NAME }}
- name: Notify Mattermost
if: failure()
uses: mattermost/action-mattermost-notify@master
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
MATTERMOST_CHANNEL: bot-alerts-cicd
TEXT: |
❌ 🧩📚 *[PENPOT PLUGINS] Error deploying API documentation.*
📄 Triggered from ref: `${{ inputs.gh_ref }}`
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
@infra
@@ -0,0 +1,127 @@
name: Plugins/package deployer
on:
# Deploy package from manual action
workflow_dispatch:
inputs:
gh_ref:
description: 'Name of the branch'
type: choice
required: true
default: 'develop'
options:
- develop
- staging
- main
plugin_name:
description: 'Pluging name (like plugins/apps/<plugin_name>-plugin)'
type: string
required: true
workflow_call:
inputs:
gh_ref:
description: 'Name of the branch'
type: string
required: true
default: 'develop'
plugin_name:
description: 'Publig name (from plugins/apps/<plugin_name>-plugin)'
type: string
required: true
permissions:
contents: read
jobs:
deploy:
runs-on: penpot-runner-01
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ inputs.gh_ref }}
# START: Setup Node and PNPM enabling cache
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: Enable PNPM
working-directory: ./plugins
shell: bash
run: |
corepack enable;
corepack install;
- name: Get pnpm store path
id: pnpm-store
working-directory: ./plugins
shell: bash
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
- name: Cache pnpm store
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-${{ hashFiles('plugins/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
# END: Setup Node and PNPM enabling cache
- name: Install deps
working-directory: ./plugins
shell: bash
run: |
pnpm install --no-frozen-lockfile;
pnpm add -D -w wrangler@latest;
- name: "Build package for ${{ inputs.plugin_name }}-plugin"
working-directory: plugins
shell: bash
run: pnpm --filter ${{ inputs.plugin_name }}-plugin build
- name: Select Worker name
run: |
REF="${{ inputs.gh_ref }}"
case "$REF" in
main)
echo "WORKER_NAME=${{ inputs.plugin_name }}-plugin-pro" >> $GITHUB_ENV
echo "WORKER_URI=${{ inputs.plugin_name }}.plugins.penpot.app" >> $GITHUB_ENV ;;
staging)
echo "WORKER_NAME=${{ inputs.plugin_name }}-plugin-pre" >> $GITHUB_ENV
echo "WORKER_URI=${{ inputs.plugin_name }}.plugins.penpot.dev" >> $GITHUB_ENV ;;
develop)
echo "WORKER_NAME=${{ inputs.plugin_name }}-plugin-hourly" >> $GITHUB_ENV
echo "WORKER_URI=${{ inputs.plugin_name }}.plugins.hourly.penpot.dev" >> $GITHUB_ENV ;;
*) echo "Unsupported branch ${REF}" && exit 1 ;;
esac
- name: Set the custom url
working-directory: plugins
shell: bash
run: |
sed -i "s/WORKER_URI/${{ env.WORKER_URI }}/g" apps/${{ inputs.plugin_name }}-plugin/wrangler.toml
- name: Deploy to Cloudflare Workers
uses: cloudflare/wrangler-action@v3
with:
workingDirectory: plugins
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --config apps/${{ inputs.plugin_name }}-plugin/wrangler.toml --name ${{ env.WORKER_NAME }}
- name: Notify Mattermost
if: failure()
uses: mattermost/action-mattermost-notify@master
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
MATTERMOST_CHANNEL: bot-alerts-cicd
TEXT: |
❌ 🧩📦 *[PENPOT PLUGINS] Error deploying ${{ env.WORKER_NAME }}.*
📄 Triggered from ref: `${{ inputs.gh_ref }}`
Plugin name: `${{ inputs.plugin_name }}-plugin`
Cloudflare worker name: `${{ env.WORKER_NAME }}`
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
@infra
@@ -0,0 +1,143 @@
name: Plugins/packages deployer
on:
push:
branches:
- develop
- staging
- main
paths:
- 'plugins/apps/*-plugin/**'
- 'libs/plugins-styles/**'
workflow_dispatch:
inputs:
gh_ref:
description: 'Name of the branch'
type: choice
required: true
default: 'develop'
options:
- develop
- staging
- main
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
colors_to_tokens: ${{ steps.filter.outputs.colors_to_tokens }}
create_palette: ${{ steps.filter.outputs.create_palette }}
lorem_ipsum: ${{ steps.filter.outputs.lorem_ipsum }}
rename_layers: ${{ steps.filter.outputs.rename_layers }}
contrast: ${{ steps.filter.outputs.contrast }}
icons: ${{ steps.filter.outputs.icons }}
poc_state: ${{ steps.filter.outputs.poc_state }}
table: ${{ steps.filter.outputs.table }}
# [For new plugins]
# Add more outputs here
steps:
- uses: actions/checkout@v4
- id: filter
uses: dorny/paths-filter@v3
with:
filters: |
colors_to_tokens:
- 'plugins/apps/colors-to-tokens-plugin/**'
- 'libs/plugins-styles/**'
contrast:
- 'plugins/apps/contrast-plugin/**'
- 'libs/plugins-styles/**'
create_palette:
- 'plugins/apps/create-palette-plugin/**'
- 'libs/plugins-styles/**'
icons:
- 'plugins/apps/icons-plugin/**'
- 'libs/plugins-styles/**'
lorem_ipsum:
- 'plugins/apps/lorem-ipsum-plugin/**'
- 'libs/plugins-styles/**'
rename_layers:
- 'plugins/apps/rename-layers-plugin/**'
- 'libs/plugins-styles/**'
table:
- 'plugins/apps/table-plugin/**'
- 'libs/plugins-styles/**'
# [For new plugins]
# Add more plugin filters here
# another_plugin:
# - 'plugins/apps/another-plugin/**'
# - 'libs/plugins-styles/**'
colors-to-tokens-plugin:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.colors_to_tokens == 'true'
uses: ./.github/workflows/plugins-deploy-package.yml
secrets: inherit
with:
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
plugin_name: colors-to-tokens
contrast-plugin:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.contrast == 'true'
uses: ./.github/workflows/plugins-deploy-package.yml
secrets: inherit
with:
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
plugin_name: contrast
create-palette-plugin:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.create_palette == 'true'
uses: ./.github/workflows/plugins-deploy-package.yml
secrets: inherit
with:
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
plugin_name: create-palette
icons-plugin:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.icons == 'true'
uses: ./.github/workflows/plugins-deploy-package.yml
secrets: inherit
with:
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
plugin_name: icons
lorem-ipsum-plugin:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.lorem_ipsum == 'true'
uses: ./.github/workflows/plugins-deploy-package.yml
secrets: inherit
with:
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
plugin_name: lorem-ipsum
rename-layers-plugin:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.rename_layers == 'true'
uses: ./.github/workflows/plugins-deploy-package.yml
secrets: inherit
with:
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
plugin_name: rename-layers
table-plugin:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.table == 'true'
uses: ./.github/workflows/plugins-deploy-package.yml
secrets: inherit
with:
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
plugin_name: table
# [For new plugins]
# Add more jobs for other plugins below, following the same pattern
# another-plugin:
# needs: detect-changes
# if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.another_plugin == 'true'
# uses: ./.github/workflows/plugins-deploy-package.yml
# secrets: inherit
# with:
# gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
# plugin_name: another
@@ -0,0 +1,140 @@
name: Plugins/styles-doc deployer
on:
push:
branches:
- develop
- staging
- main
paths:
- 'plugins/apps/example-styles/**'
- 'plugins/libs/plugins-styles/**'
- 'plugins/wrangler-penpot-plugins-styles-doc.toml'
workflow_dispatch:
inputs:
gh_ref:
description: 'Name of the branch'
type: choice
required: true
default: 'develop'
options:
- develop
- staging
- main
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Extract some useful variables
id: vars
run: |
echo "gh_ref=${{ inputs.gh_ref || github.ref_name }}" >> $GITHUB_OUTPUT
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ steps.vars.outputs.gh_ref }}
# START: Setup Node and PNPM enabling cache
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: Enable PNPM
working-directory: ./plugins
shell: bash
run: |
corepack enable;
corepack install;
- name: Get pnpm store path
id: pnpm-store
working-directory: ./plugins
shell: bash
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
- name: Cache pnpm store
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-${{ hashFiles('plugins/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
# END: Setup Node and PNPM enabling cache
- name: Install deps
working-directory: ./plugins
shell: bash
run: |
pnpm install --no-frozen-lockfile;
pnpm add -D -w wrangler@latest;
- name: Build styles
working-directory: plugins
shell: bash
run: pnpm run build:styles-example
- name: Select Worker name
run: |
REF="${{ steps.vars.outputs.gh_ref }}"
case "$REF" in
main)
echo "WORKER_NAME=penpot-plugins-styles-doc-pro" >> $GITHUB_ENV
echo "WORKER_URI=styles-doc.plugins.penpot.app" >> $GITHUB_ENV ;;
staging)
echo "WORKER_NAME=penpot-plugins-styles-doc-pre" >> $GITHUB_ENV
echo "WORKER_URI=styles-doc.plugins.penpot.dev" >> $GITHUB_ENV ;;
develop)
echo "WORKER_NAME=penpot-plugins-styles-doc-hourly" >> $GITHUB_ENV
echo "WORKER_URI=styles-doc.plugins.hourly.penpot.dev" >> $GITHUB_ENV ;;
*) echo "Unsupported branch ${REF}" && exit 1 ;;
esac
- name: Set the custom url
working-directory: plugins
shell: bash
run: |
sed -i "s/WORKER_URI/${{ env.WORKER_URI }}/g" wrangler-penpot-plugins-styles-doc.toml
- name: Add noindex header and robots.txt files for non-production environments
if: ${{ steps.vars.outputs.gh_ref != 'main' }}
working-directory: plugins
shell: bash
run: |
ASSETS_DIR="dist/apps/example-styles"
cat > "${ASSETS_DIR}/_headers" << 'EOF'
/*
X-Robots-Tag: noindex, nofollow
EOF
cat > "${ASSETS_DIR}/robots.txt" << 'EOF'
User-agent: *
Disallow: /
EOF
- name: Deploy to Cloudflare Workers
uses: cloudflare/wrangler-action@v3
with:
workingDirectory: plugins
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --config wrangler-penpot-plugins-styles-doc.toml --name ${{ env.WORKER_NAME }}
- name: Notify Mattermost
if: failure()
uses: mattermost/action-mattermost-notify@master
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
MATTERMOST_CHANNEL: bot-alerts-cicd
TEXT: |
❌ 🧩💅 *[PENPOT PLUGINS] Error deploying Styles documentation.*
📄 Triggered from ref: `${{ inputs.gh_ref }}`
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
@infra
+45
View File
@@ -0,0 +1,45 @@
name: "MCP CI"
on:
pull_request:
branches:
- develop
- staging
- main
types:
- opened
- synchronize
paths:
- 'mcp/**'
push:
branches:
- develop
- staging
- main
paths:
- 'mcp/**'
jobs:
test:
name: "Test"
runs-on: penpot-runner-02
container: penpotapp/devenv:latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup
working-directory: ./mcp
run: ./scripts/setup
- name: Check
working-directory: ./mcp
run: |
pnpm run fmt:check;
pnpm -r run build;
pnpm -r run types:check;
+112 -19
View File
@@ -21,39 +21,130 @@ concurrency:
jobs:
lint:
name: "Linter"
runs-on: ubuntu-24.04
runs-on: penpot-runner-02
container: penpotapp/devenv:latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Check clojure code format
- name: Lint Common
working-directory: ./common
run: |
./scripts/lint
corepack enable;
corepack install;
pnpm install;
pnpm run check-fmt:clj
pnpm run check-fmt:js
pnpm run lint:clj
- name: Lint Frontend
working-directory: ./frontend
run: |
corepack enable;
corepack install;
pnpm install;
pnpm run check-fmt:js
pnpm run check-fmt:clj
pnpm run check-fmt:scss
pnpm run lint:clj
pnpm run lint:js
pnpm run lint:scss
- name: Lint Backend
working-directory: ./backend
run: |
corepack enable;
corepack install;
pnpm install;
pnpm run check-fmt
pnpm run lint
- name: Lint Exporter
working-directory: ./exporter
run: |
corepack enable;
corepack install;
pnpm install;
pnpm run check-fmt
pnpm run lint
- name: Lint Library
working-directory: ./library
run: |
corepack enable;
corepack install;
pnpm install;
pnpm run check-fmt
pnpm run lint
test-common:
name: "Common Tests"
runs-on: ubuntu-24.04
runs-on: penpot-runner-02
container: penpotapp/devenv:latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Run tests on JVM
working-directory: ./common
run: |
clojure -M:dev:test
- name: Run tests on NODE
- name: Run tests
working-directory: ./common
run: |
./scripts/test
test-plugins:
name: Plugins Runtime Linter & Tests
runs-on: penpot-runner-02
container: penpotapp/devenv:latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
id: setup-node
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: Install deps
working-directory: ./plugins
shell: bash
run: |
corepack enable;
corepack install;
pnpm install;
- name: Run Lint
working-directory: ./plugins
run: pnpm run lint
- name: Run Format Check
working-directory: ./plugins
run: pnpm run format:check
- name: Run Test
working-directory: ./plugins
run: pnpm run test
- name: Build runtime
working-directory: ./plugins
run: pnpm run build:runtime
- name: Build doc
working-directory: ./plugins
run: pnpm run build:doc
- name: Build plugins
working-directory: ./plugins
run: pnpm run build:plugins
- name: Build styles
working-directory: ./plugins
run: pnpm run build:styles-example
test-frontend:
name: "Frontend Tests"
runs-on: ubuntu-24.04
runs-on: penpot-runner-02
container: penpotapp/devenv:latest
steps:
@@ -67,12 +158,14 @@ jobs:
- name: Component Tests
working-directory: ./frontend
env:
VITEST_BROWSER_TIMEOUT: 120000
run: |
./scripts/test-components
test-render-wasm:
name: "Render WASM Tests"
runs-on: ubuntu-24.04
runs-on: penpot-runner-02
container: penpotapp/devenv:latest
steps:
@@ -96,7 +189,7 @@ jobs:
test-backend:
name: "Backend Tests"
runs-on: ubuntu-24.04
runs-on: penpot-runner-02
container: penpotapp/devenv:latest
services:
@@ -135,7 +228,7 @@ jobs:
test-library:
name: "Library Tests"
runs-on: ubuntu-24.04
runs-on: penpot-runner-02
container: penpotapp/devenv:latest
steps:
@@ -149,7 +242,7 @@ jobs:
build-integration:
name: "Build Integration Bundle"
runs-on: ubuntu-24.04
runs-on: penpot-runner-02
container: penpotapp/devenv:latest
steps:
@@ -170,7 +263,7 @@ jobs:
test-integration-1:
name: "Integration Tests 1/4"
runs-on: ubuntu-24.04
runs-on: penpot-runner-02
container: penpotapp/devenv:latest
needs: build-integration
@@ -200,7 +293,7 @@ jobs:
test-integration-2:
name: "Integration Tests 2/4"
runs-on: ubuntu-24.04
runs-on: penpot-runner-02
container: penpotapp/devenv:latest
needs: build-integration
@@ -230,7 +323,7 @@ jobs:
test-integration-3:
name: "Integration Tests 3/4"
runs-on: ubuntu-24.04
runs-on: penpot-runner-02
container: penpotapp/devenv:latest
needs: build-integration
@@ -260,7 +353,7 @@ jobs:
test-integration-4:
name: "Integration Tests 4/4"
runs-on: ubuntu-24.04
runs-on: penpot-runner-02
container: penpotapp/devenv:latest
needs: build-integration
+13 -14
View File
@@ -1,10 +1,4 @@
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
*-init.clj
*.css.json
*.jar
@@ -19,7 +13,6 @@
.nyc_output
.rebel_readline_history
.repl
.shadow-cljs
/*.jpg
/*.md
/*.png
@@ -33,6 +26,8 @@
/notes
/playground/
/backend/*.md
!/backend/AGENTS.md
/backend/.shadow-cljs
/backend/*.sql
/backend/*.txt
/backend/assets/
@@ -43,27 +38,32 @@
/backend/resources/public/media
/backend/target/
/backend/experiments
/backend/scripts/_env.local
/bundle*
/cd.md
/clj-profiler/
/common/coverage
/common/target
/deploy
/common/.shadow-cljs
/docker/images/bundle*
/exporter/target
/exporter/.shadow-cljs
/frontend/.storybook/preview-body.html
/frontend/.storybook/preview-head.html
/frontend/playwright-report/
/frontend/text-editor/src/wasm/
/frontend/dist/
/frontend/npm-debug.log
/frontend/out/
/frontend/package-lock.json
/frontend/resources/fonts/experiments
/frontend/resources/public/*
/frontend/src/app/render_wasm/api/shared.js
/frontend/storybook-static/
/frontend/target/
/frontend/test-results/
/frontend/.shadow-cljs
/other/
/scripts/
/telemetry/
/nexus/
/tmp/
/vendor/**/target
/vendor/svgclean/bundle*.js
@@ -71,13 +71,12 @@
/library/target/
/library/*.zip
/external
clj-profiler/
node_modules
/penpot-nitrate
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/render-wasm/target/
/**/node_modules
/**/.yarn/*
/.pnpm-store
-105
View File
@@ -1,105 +0,0 @@
image:
file: docker/gitpod/Dockerfile
ports:
# nginx
- port: 3449
onOpen: open-preview
# frontend nREPL
- port: 3447
onOpen: ignore
visibility: private
# frontend shadow server
- port: 3448
onOpen: ignore
visibility: private
# backend
- port: 6060
onOpen: ignore
# exporter shadow server
- port: 9630
onOpen: ignore
visibility: private
# exporter http server
- port: 6061
onOpen: ignore
# mailhog web interface
- port: 8025
onOpen: ignore
# mailhog postfix
- port: 1025
onOpen: ignore
# postgres
- port: 5432
onOpen: ignore
# redis
- port: 6379
onOpen: ignore
# openldap
- port: 389
onOpen: ignore
tasks:
# https://github.com/gitpod-io/gitpod/issues/666#issuecomment-534347856
- name: gulp
command: >
cd $GITPOD_REPO_ROOT/frontend/;
yarn && gp sync-done 'frontend-yarn';
npx gulp --theme=${PENPOT_THEME} watch
- name: frontend shadow watch
command: >
cd $GITPOD_REPO_ROOT/frontend/;
gp sync-await 'frontend-yarn';
npx shadow-cljs watch main
- init: gp await-port 5432 && psql -f $GITPOD_REPO_ROOT/docker/gitpod/files/postgresql_init.sql
name: backend
command: >
cd $GITPOD_REPO_ROOT/backend/;
./scripts/start-dev
- name: exporter shadow watch
command:
cd $GITPOD_REPO_ROOT/exporter/;
gp sync-await 'frontend-yarn';
yarn && npx shadow-cljs watch main
- name: exporter web server
command: >
cd $GITPOD_REPO_ROOT/exporter/;
./scripts/wait-and-start.sh
- name: signed terminal
before: >
[[ ! -z ${GNUGPG} ]] &&
cd ~ &&
rm -rf .gnupg &&
echo ${GNUGPG} | base64 -d | tar --no-same-owner -xzvf -
init: >
[[ ! -z ${GNUGPG_KEY} ]] &&
git config --global commit.gpgsign true &&
git config --global user.signingkey ${GNUGPG_KEY}
command: cd $GITPOD_REPO_ROOT
- name: redis
command: redis-server
- before: go get github.com/mailhog/MailHog
name: mailhog
command: MailHog
- name: Nginx
command: >
nginx &&
multitail /var/log/nginx/access.log -I /var/log/nginx/error.log
+1 -1
View File
@@ -1 +1 @@
v22.19.0
v22.22.0
-40
View File
@@ -1,40 +0,0 @@
dist: xenial
language: generic
sudo: required
cache:
directories:
- $HOME/.m2
services:
- docker
branches:
only:
- master
- develop
install:
- curl -O https://download.clojure.org/install/linux-install-1.10.1.447.sh
- chmod +x linux-install-1.10.1.447.sh
- sudo ./linux-install-1.10.1.447.sh
before_script:
- env | sort
script:
- ./manage.sh build-devenv
- ./manage.sh run-frontend-tests
- ./manage.sh run-backend-tests
- ./manage.sh build-images
- ./manage.sh run
after_script:
- docker images
notifications:
email: false
env:
- NODE_VERSION=10.16.0
-11
View File
@@ -1,11 +0,0 @@
enableGlobalCache: true
enableImmutableCache: false
enableImmutableInstalls: false
enableTelemetry: false
httpTimeout: 600000
nodeLinker: node-modules
+139
View File
@@ -0,0 +1,139 @@
# IA Agent guide for Penpot monorepo
This document provides comprehensive context and guidelines for AI
agents working on this repository.
CRITICAL: When you encounter a file reference (e.g.,
@rules/general.md), use your Read tool to load it on a need-to-know
basis. They're relevant to the SPECIFIC task at hand.
## STOP - DO NOT PROCEED WITHOUT COMPLETING THESE STEPS
Before responding to ANY user request, you MUST:
1. **READ** the CONTRIBUTING.md file
2. **READ** this file and has special focus on your ROLE.
## ROLE: SENIOR SOFTWARE ENGINEER
You are a high-autonomy Senior Software Engineer. You have full
permission to navigate the codebase, modify files, and execute
commands to fulfill your tasks. Your goal is to solve complex
technical tasks with high precision, focusing on maintainability and
performance.
### OPERATIONAL GUIDELINES
1. Always begin by analyzing this document and understand the
architecture and read the additional context from AGENTS.md of the
affected modules.
2. Before writing code, describe your plan. If the task is complex,
break it down into atomic steps.
3. Be concise and autonomous as possible in your task.
4. Commit only if it explicitly asked, and use the CONTRIBUTING.md
document to understand the commit format guidelines.
5. Do not touch unrelated modules if not proceed or not explicitly
asked (per example you probably do not need to touch and read
docker/ directory unless the task explicitly requires it)
6. When searching code, always use `ripgrep` (rg) instead of grep if
available, as it respects `.gitignore` by default.
## ARCHITECTURE OVERVIEW
Penpot is a full-stack design tool composed of several distinct
components separated in modules and subdirectories:
| Component | Language | Role | IA Agent CONTEXT |
|-----------|----------|------|----------------
| `frontend/` | ClojureScript + SCSS | Single-page React app (design editor) | @frontend/AGENTS.md |
| `backend/` | Clojure (JVM) | HTTP/RPC server, PostgreSQL, Redis | @backend/AGENTS.md |
| `common/` | Cljc (shared Clojure/ClojureScript) | Data types, geometry, schemas, utilities | @common/AGENTS.md |
| `exporter/` | ClojureScript (Node.js) | Headless Playwright-based export (SVG/PDF) | @exporter/AGENTS.md |
| `render-wasm/` | Rust → WebAssembly | High-performance canvas renderer using Skia | @render-wasm/AGENTS.md |
| `mcp/` | TypeScript | Model Context Protocol integration | @mcp/AGENTS.md |
| `plugins/` | TypeScript | Plugin runtime and example plugins | @plugins/AGENTS.md |
Several of the mentionend submodules are internall managed with `pnpm` workspaces.
## COMMIT FORMAT
We have very precise rules on how our git commit messages must be
formatted.
The commit message format is:
```
<type> <subject>
[body]
[footer]
```
Where type is:
- :bug: `:bug:` a commit that fixes a bug
- :sparkles: `:sparkles:` a commit that adds an improvement
- :tada: `:tada:` a commit with a new feature
- :recycle: `:recycle:` a commit that introduces a refactor
- :lipstick: `:lipstick:` a commit with cosmetic changes
- :ambulance: `:ambulance:` a commit that fixes a critical bug
- :books: `:books:` a commit that improves or adds documentation
- :construction: `:construction:` a WIP commit
- :boom: `:boom:` a commit with breaking changes
- :wrench: `:wrench:` a commit for config updates
- :zap: `:zap:` a commit with performance improvements
- :whale: `:whale:` a commit for Docker-related stuff
- :paperclip: `:paperclip:` a commit with other non-relevant changes
- :arrow_up: `:arrow_up:` a commit with dependency updates
- :arrow_down: `:arrow_down:` a commit with dependency downgrades
- :fire: `:fire:` a commit that removes files or code
- :globe_with_meridians: `:globe_with_meridians:` a commit that adds or updates
translations
The commit should contain a sign-off at the end of the patch/commit
description body. It can be automatically added by adding the `-s`
parameter to `git commit`.
This is an example of what the line should look like:
```
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
```
Please, use your real name (sorry, no pseudonyms or anonymous
contributions are allowed).
CRITICAL: The commit Signed-off-by is mandatory and should match the commit author.
Each commit should have:
- A concise subject using the imperative mood.
- The subject should capitalize the first letter, omit the period
at the end, and be no longer than 65 characters.
- A blank line between the subject line and the body.
- An entry in the CHANGES.md file if applicable, referencing the
GitHub or Taiga issue/user story using these same rules.
Examples of good commit messages:
- `:bug: Fix unexpected error on launching modal`
- `:bug: Set proper error message on generic error`
- `:sparkles: Enable new modal for profile`
- `:zap: Improve performance of dashboard navigation`
- `:wrench: Update default backend configuration`
- `:books: Add more documentation for authentication process`
- `:ambulance: Fix critical bug on user registration process`
- `:tada: Add new approach for user registration`
More info:
- https://gist.github.com/parmentf/035de27d6ed1dce0b36a
- https://gist.github.com/rxaviers/7360908
+106 -6
View File
@@ -1,22 +1,116 @@
# CHANGELOG
## 2.13.0 (Unreleased)
## 2.14.0
### :boom: Breaking changes & Deprecations
### :rocket: Epics and highlights
- Deprecate `PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE` in favour of `PENPOT_HTTP_SERVER_MAX_BODY_SIZE`.
### :sparkles: New features & Enhancements
- Access to design tokens in Penpot Plugins [Taiga #8990](https://tree.taiga.io/project/penpot/us/8990)
- Remap references when renaming tokens [Taiga #10202](https://tree.taiga.io/project/penpot/us/10202)
- Tokens panel nested path view [Taiga #9966](https://tree.taiga.io/project/penpot/us/9966)
- Improve usability of lock and hide buttons in the layer panel. [Taiga #12916](https://tree.taiga.io/project/penpot/issue/12916)
- Optimize sidebar performance for deeply nested shapes [Taiga #13017](https://tree.taiga.io/project/penpot/task/13017)
- Remove tokens path node and bulk remove tokens [Taiga #13007](https://tree.taiga.io/project/penpot/us/13007)
- Replace themes management modal radio buttons for switches [Taiga #9215](https://tree.taiga.io/project/penpot/us/9215)
### :bug: Bugs fixed
- Remove whitespaces from asset export filename [Github #8133](https://github.com/penpot/penpot/pull/8133)
- Fix prototype connections lost when switching between variants [Taiga #12812](https://tree.taiga.io/project/penpot/issue/12812)
- Fix wrong image in the onboarding invitation block [Taiga #13040](https://tree.taiga.io/project/penpot/issue/13040)
- Fix wrong register image [Taiga #12955](https://tree.taiga.io/project/penpot/task/12955)
- Fix error message on components doesn't close automatically [Taiga #12012](https://tree.taiga.io/project/penpot/issue/12012)
- Fix incorrect handling of input values on layout gap and padding inputs [Github #8113](https://github.com/penpot/penpot/issues/8113)
- Fix incorrect default option on tokens import dialog [Github #8051](https://github.com/penpot/penpot/pull/8051)
- Fix unhandled exception tokens creation dialog [Github #8110](https://github.com/penpot/penpot/issues/8110)
- Fix displaying a hidden user avatar when there is only one more [Taiga #13058](https://tree.taiga.io/project/penpot/issue/13058)
- Fix unhandled exception on open-new-window helper [Github #7787](https://github.com/penpot/penpot/issues/7787)
- Fix exception on uploading large fonts [Github #8135](https://github.com/penpot/penpot/pull/8135)
- Fix boolean operators in menu for boards [Taiga #13174](https://tree.taiga.io/project/penpot/issue/13174)
- Fix viewer can update library [Taiga #13186](https://tree.taiga.io/project/penpot/issue/13186)
- Fix remove fill affects different element than selected [Taiga #13128](https://tree.taiga.io/project/penpot/issue/13128)
- Fix cannot apply second token after creation while shape is selected [Taiga #13513](https://tree.taiga.io/project/penpot/issue/13513)
- Fix error activating a set with invalid shadow token applied [Taiga #13528](https://tree.taiga.io/project/penpot/issue/13528)
- Fix component "broken" after variant switch [Taiga #12984](https://tree.taiga.io/project/penpot/issue/12984)
- Fix incorrect query for file versions [Github #8463](https://github.com/penpot/penpot/pull/8463)
- Fix warning when clicking on number token pills [Taiga #13661](https://tree.taiga.io/project/penpot/issue/13661)
- Fix 'not ISeqable' error when entering float values in layout item and opacity inputs [Github #8569](https://github.com/penpot/penpot/pull/8569)
- Fix crash in select component when options vector is empty [Github #8578](https://github.com/penpot/penpot/pull/8578)
- Fix scroll on colorpicker [Taiga #13623](https://tree.taiga.io/project/penpot/issue/13623)
- Fix crash when pasting non-map transit clipboard data [Github #8580](https://github.com/penpot/penpot/pull/8580)
- Fix `penpot.openPage()` plugin API not navigating in the same tab; change default to same-tab navigation and allow passing a UUID string instead of a Page object [Github #8520](https://github.com/penpot/penpot/issues/8520)
## 2.13.3
### :bug: Bugs fixed
- Revert yetti (http server) update, because that caused a regression on multipart uploads
## 2.13.2
### :bug: Bugs fixed
- Fix modifying shapes by apply negative tokens to border radius [Taiga #13317](https://tree.taiga.io/project/penpot/issue/13317)
- Fix arbitrary file read security issue on create-font-variant rpc method (https://github.com/penpot/penpot/security/advisories/GHSA-xp3f-g8rq-9px2)
## 2.13.1
### :bug: Bugs fixed
- Fix PDF Exporter outputs empty page when board has A4 format [Taiga #13181](https://tree.taiga.io/project/penpot/issue/13181)
## 2.13.0
### :heart: Community contributions (Thank you!)
- Fix mask issues with component swap (by @dfelinto) [Github #7675](https://github.com/penpot/penpot/issues/7675)
### :sparkles: New features & Enhancements
- Add new Box Shadow Tokens [Taiga #10201](https://tree.taiga.io/project/penpot/us/10201)
- Make i18n translation files load on-demand [Taiga #11474](https://tree.taiga.io/project/penpot/us/11474)
- Add deleted files to dashboard [Taiga #8149](https://tree.taiga.io/project/penpot/us/8149)
### :bug: Bugs fixed
- Fix problem when drag+duplicate a full grid [Taiga #12565](https://tree.taiga.io/project/penpot/issue/12565)
- Fix problem when pasting elements in reverse flex layout [Taiga #12460](https://tree.taiga.io/project/penpot/issue/12460)
- Fix wrong board size presets in Android [Taiga #12339](https://tree.taiga.io/project/penpot/issue/12339)
- Fix problem with grid layout components and auto sizing [Github #7797](https://github.com/penpot/penpot/issues/7797)
- Fix some alignments on inspect tab [Taiga #12915](https://tree.taiga.io/project/penpot/issue/12915)
- Fix problem with text editor maintaining previous styles [Taiga #12835](https://tree.taiga.io/project/penpot/issue/12835)
- Fix color assets from shared libraries not appearing as assets in Selected colors panel [Taiga #12957](https://tree.taiga.io/project/penpot/issue/12957)
- Fix CSS generated box-shadow property [Taiga #12997](https://tree.taiga.io/project/penpot/issue/12997)
- Fix inner shadow selector on shadow token [Taiga #12951](https://tree.taiga.io/project/penpot/issue/12951)
- Fix missing text color token from selected shapes in selected colors list [Taiga #12956](https://tree.taiga.io/project/penpot/issue/12956)
- Fix dropdown option width in Guides columns dropdown [Taiga #12959](https://tree.taiga.io/project/penpot/issue/12959)
- Fix typos on download modal [Taiga #12865](https://tree.taiga.io/project/penpot/issue/12865)
- Fix problem with text editor maintaining previous styles [Taiga #12835](https://tree.taiga.io/project/penpot/issue/12835)
- Fix unhandled exception tokens creation dialog [Github #8110](https://github.com/penpot/penpot/issues/8110)
- Fix allow negative spread values on shadow token creation [Taiga #13167](https://tree.taiga.io/project/penpot/issue/13167)
- Fix spanish translations on import export token modal [Taiga #13171](https://tree.taiga.io/project/penpot/issue/13171)
- Remove whitespaces from asset export filename [Github #8133](https://github.com/penpot/penpot/pull/8133)
- Fix exception on uploading large fonts [Github #8135](https://github.com/penpot/penpot/pull/8135)
- Fix unhandled exception on open-new-window helper [Github #7787](https://github.com/penpot/penpot/issues/7787)
- Fix incorrect handling of input values on layout gap and padding inputs [Github #8113](https://github.com/penpot/penpot/issues/8113)
- Fix several race conditions on path editor [Github #8187](https://github.com/penpot/penpot/pull/8187)
- Fix app freeze when introducing an error on a very long token name [Taiga #13214](https://tree.taiga.io/project/penpot/issue/13214)
- Fix import a file with shadow tokens [Taiga #13229](https://tree.taiga.io/project/penpot/issue/13229)
- Fix allow spaces on token description [Taiga #13184](https://tree.taiga.io/project/penpot/issue/13184)
- Fix error when creating a token with an invalid name [Taiga #13219](https://tree.taiga.io/project/penpot/issue/13219)
## 2.12.1
## 2.12.0 (Unreleased)
### :bug: Bugs fixed
- Fix setting a portion of text as bold or underline messes things up [Github #7980](https://github.com/penpot/penpot/issues/7980)
- Fix problem with style in fonts input [Taiga #12935](https://tree.taiga.io/project/penpot/issue/12935)
- Fix problem with path editor and right click [Github #7917](https://github.com/penpot/penpot/issues/7917)
## 2.12.0
### :boom: Breaking changes & Deprecations
@@ -27,7 +121,6 @@ The backend RPC API URLS are changed from `/api/rpc/command/<name>` to
compatibility; however, if you are a user of this API, it is strongly
recommended that you adapt your code to use the new PATH.
#### Updated SSO Callback URL
The OAuth / Single Sign-On (SSO) callback endpoint has changed to
@@ -60,7 +153,6 @@ This update standardizes all authentication flows under the single URL
and makis it more modular, enabling the ability to configure SSO auth
provider dinamically.
#### Changes on default docker compose
We have updated the `docker/images/docker-compose.yaml` with a small
@@ -77,6 +169,8 @@ example. It's still usable as before, we just removed the example.
### :heart: Community contributions (Thank you!)
- Ensure consistent snap behavior across all zoom levels [Github #7774](https://github.com/penpot/penpot/pull/7774) by [@Tokytome](https://github.com/Tokytome)
- Fix crash in token grid view due to tooltip validation (by @dfelinto) [Github #7887](https://github.com/penpot/penpot/pull/7887)
- Enable Hindi translations on the application
### :sparkles: New features & Enhancements
@@ -104,6 +198,13 @@ example. It's still usable as before, we just removed the example.
- Fix input confirmation behavior is not uniform [Taiga #12294](https://tree.taiga.io/project/penpot/issue/12294)
- Fix copy/pasting application/transit+json [Taiga #12721](https://tree.taiga.io/project/penpot/issue/12721)
- Fix problem with plugins content attribute [Plugins #209](https://github.com/penpot/penpot-plugins/issues/209)
- Fix U and E icon displayed in project list [Taiga #12806](https://tree.taiga.io/project/penpot/issue/12806)
- Fix unpublish library modal not scrolling a long file list [Taiga #12285](https://tree.taiga.io/project/penpot/issue/12285)
- Fix incorrect interaction betwen hower and scroll on assets sidebar [Taiga #12389](https://tree.taiga.io/project/penpot/issue/12389)
- Fix switch variants with paths [Taiga #12841](https://tree.taiga.io/project/penpot/issue/12841)
- Fix referencing typography tokens on font-family tokens [Taiga #12492](https://tree.taiga.io/project/penpot/issue/12492)
- Fix horizontal scroll on layer panel [Taiga #12843](https://tree.taiga.io/project/penpot/issue/12843)
- Fix unicode handling on email template abbreviation filter [Github #7966](https://github.com/penpot/penpot/pull/7966)
## 2.11.1
@@ -115,7 +216,6 @@ example. It's still usable as before, we just removed the example.
- Deprecated configuration variables with the prefix `PENPOT_ASSETS_*`, and will be
removed in future versions:
- The `PENPOT_ASSETS_STORAGE_BACKEND` becomes `PENPOT_OBJECTS_STORAGE_BACKEND` and its
values passes from (`assets-fs` or `assets-s3`) to (`fs` or `s3`)
- The `PENPOT_STORAGE_ASSETS_FS_DIRECTORY` becomes `PENPOT_OBJECTS_STORAGE_FS_DIRECTORY`
+6 -8
View File
@@ -120,17 +120,12 @@ them on your system, you can run them with:
```bash
# Check formatting
yarn fmt:clj:check
./scripts/fmt
# Check and fix formatting
yarn fmt:clj
# Run the linter
yarn lint:clj
# Lint
./scripts/lint
```
There are more choices in `package.json`.
Ideally, you should run these commands as git pre-commit hooks. A convenient way
of defining them is to use [Husky](https://typicode.github.io/husky/#/).
@@ -211,3 +206,6 @@ Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Please, use your real name (sorry, no pseudonyms or anonymous
contributions are allowed).
The commit Signed-off-by is mandatory and should match the commit author.
+27 -1
View File
@@ -2,4 +2,30 @@
## Reporting a Vulnerability
Please report security issues to `support@penpot.app`
We take the security of this project seriously. If you have discovered
a security vulnerability, please do **not** open a public issue.
Please report vulnerabilities via email to: **[support@penpot.app]**
### What to include:
* A brief description of the vulnerability.
* Steps to reproduce the issue.
* Potential impact if exploited.
We appreciate your patience and your commitment to **responsible disclosure**.
---
## Security Contributors
We are incredibly grateful to the following individuals and
organizations for their help in keeping this project safe.
* **Ali Maharramli** for identifying critical path traversal vulnerability
> **Note:** This list is a work in progress. If you have contributed
> to the security of this project and would like to be recognized (or
> prefer to remain anonymous), please let us know.
-7
View File
@@ -1,7 +0,0 @@
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
+147
View File
@@ -0,0 +1,147 @@
# Penpot Backend Agent Instructions
Clojure backend (RPC) service running on the JVM.
Uses Integrant for dependency injection, PostgreSQL for storage, and
Redis for messaging/caching.
## General Guidelines
This is a golden rule for backend development standards. To ensure consistency
across the Penpot JVM stack, all contributions must adhere to these criteria:
### 1. Testing & Validation
* **Coverage:** If code is added or modified in `src/`, corresponding
tests in `test/backend_tests/` must be added or updated.
* **Execution:**
* **Isolated:** Run `clojure -M:dev:test --focus backend-tests.my-ns-test` for the specific task.
* **Regression:** Run `clojure -M:dev:test` for ensure the suite passes without regressions in related functional areas.
### 2. Code Quality & Formatting
* **Linting:** All code must pass `clj-kondo` checks (run `pnpm run lint:clj`)
* **Formatting:** All the code must pass the formatting check (run `pnpm run
check-fmt`). Use the `pnpm run fmt` fix the formatting issues. Avoid "dirty"
diffs caused by unrelated whitespace changes.
* **Type Hinting:** Use explicit JVM type hints (e.g., `^String`, `^long`) in
performance-critical paths to avoid reflection overhead.
## Code Conventions
### Namespace Overview
The source is located under `src` directory and this is a general overview of
namespaces structure:
- `app.rpc.commands.*` RPC command implementations (`auth`, `files`, `teams`, etc.)
- `app.http.*` HTTP routes and middleware
- `app.db.*` Database layer
- `app.tasks.*` Background job tasks
- `app.main` Integrant system setup and entrypoint
- `app.loggers` Internal loggers (auditlog, mattermost, etc) (do not be confused with `app.common.loggin`)
### RPC
The PRC methods are implement in a some kind of multimethod structure using
`app.util.serivices` namespace. The main RPC methods are collected under
`app.rpc.commands` namespace and exposed under `/api/rpc/command/<cmd-name>`.
The RPC method accepts POST and GET requests indistinctly and uses `Accept`
header for negotiate the response encoding (which can be transit, the defaut or
plain json). It also accepts transit (defaut) or json as input, which should be
indicated using `Content-Type` header.
The main convention is: use `get-` prefix on RPC name when we want READ
operation.
Example of RPC method definition:
```clojure
(sv/defmethod ::my-command
{::rpc/auth true ;; requires auth
::doc/added "1.18"
::sm/params [:map ...] ;; malli input schema
::sm/result [:map ...]} ;; malli output schema
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
;; return a plain map or throw
{:id (uuid/next)})
```
Look under `src/app/rpc/commands/*.clj` to see more examples.
### Tests
Test namespaces match `.*-test$` under `test/`. Config is in `tests.edn`.
### Integrant System
The `src/app/main.clj` declares the system map. Each key is a component; values
are config maps with `::ig/ref` for dependencies. Components implement
`ig/init-key` / `ig/halt-key!`.
### Database Access
`app.db` wraps next.jdbc. Queries use a SQL builder that auto-converts kebab-case ↔ snake_case.
```clojure
;; Query helpers
(db/get cfg-or-pool :table {:id id}) ; fetch one row (throws if missing)
(db/get* cfg-or-pool :table {:id id}) ; fetch one row (returns nil)
(db/query cfg-or-pool :table {:team-id team-id}) ; fetch multiple rows
(db/insert! cfg-or-pool :table {:name "x" :team-id id}) ; insert
(db/update! cfg-or-pool :table {:name "y"} {:id id}) ; update
(db/delete! cfg-or-pool :table {:id id}) ; delete
;; Run multiple statements/queries on single connection
(db/run! cfg (fn [{:keys [::db/conn]}]
(db/insert! conn :table row1)
(db/insert! conn :table row2))
;; Transactions
(db/tx-run! cfg (fn [{:keys [::db/conn]}]
(db/insert! conn :table row)))
```
Almost all methods on `app.db` namespace accepts `pool`, `conn` or
`cfg` as params.
Migrations live in `src/app/migrations/` as numbered SQL files. They run automatically on startup.
### Error Handling
The exception helpers are defined on Common module, and are available under
`app.commin.exceptions` namespace.
Example of raising an exception:
```clojure
(ex/raise :type :not-found
:code :object-not-found
:hint "File does not exist"
:file-id id)
```
Common types: `:not-found`, `:validation`, `:authorization`, `:conflict`, `:internal`.
### Performance Macros (`app.common.data.macros`)
Always prefer these macros over their `clojure.core` equivalents — they compile to faster JavaScript:
```clojure
(dm/select-keys m [:a :b]) ;; ~6x faster than core/select-keys
(dm/get-in obj [:a :b :c]) ;; faster than core/get-in
(dm/str "a" "b" "c") ;; string concatenation
```
### Configuration
`src/app/config.clj` reads `PENPOT_*` environment variables, validated with
Malli. Access anywhere via `(cf/get :smtp-host)`. Feature flags: `(cf/flags
:enable-smtp)`.
+8 -8
View File
@@ -3,7 +3,7 @@
:deps
{penpot/common {:local/root "../common"}
org.clojure/clojure {:mvn/version "1.12.2"}
org.clojure/clojure {:mvn/version "1.12.4"}
org.clojure/tools.namespace {:mvn/version "1.5.0"}
com.github.luben/zstd-jni {:mvn/version "1.5.7-4"}
@@ -28,8 +28,8 @@
com.google.guava/guava {:mvn/version "33.4.8-jre"}
funcool/yetti
{:git/tag "v11.8"
:git/sha "1d1b33f"
{:git/tag "v11.9"
:git/sha "5fad7a9"
:git/url "https://github.com/funcool/yetti.git"
:exclusions [org.slf4j/slf4j-api]}
@@ -39,7 +39,7 @@
metosin/reitit-core {:mvn/version "0.9.1"}
nrepl/nrepl {:mvn/version "1.4.0"}
org.postgresql/postgresql {:mvn/version "42.7.7"}
org.postgresql/postgresql {:mvn/version "42.7.9"}
org.xerial/sqlite-jdbc {:mvn/version "3.50.3.0"}
com.zaxxer/HikariCP {:mvn/version "7.0.2"}
@@ -49,7 +49,7 @@
buddy/buddy-hashers {:mvn/version "2.0.167"}
buddy/buddy-sign {:mvn/version "3.6.1-359"}
com.github.ben-manes.caffeine/caffeine {:mvn/version "3.2.2"}
com.github.ben-manes.caffeine/caffeine {:mvn/version "3.2.3"}
org.jsoup/jsoup {:mvn/version "1.21.2"}
org.im4java/im4java
@@ -66,7 +66,7 @@
;; Pretty Print specs
pretty-spec/pretty-spec {:mvn/version "0.1.4"}
software.amazon.awssdk/s3 {:mvn/version "2.33.10"}}
software.amazon.awssdk/s3 {:mvn/version "2.41.21"}}
:paths ["src" "resources" "target/classes"]
:aliases
@@ -97,8 +97,8 @@
:jmx-remote
{:jvm-opts ["-Dcom.sun.management.jmxremote"
"-Dcom.sun.management.jmxremote.port=9090"
"-Dcom.sun.management.jmxremote.rmi.port=9090"
"-Dcom.sun.management.jmxremote.port=9000"
"-Dcom.sun.management.jmxremote.rmi.port=9000"
"-Dcom.sun.management.jmxremote.local.only=false"
"-Dcom.sun.management.jmxremote.authenticate=false"
"-Dcom.sun.management.jmxremote.ssl=false"
+5 -4
View File
@@ -4,7 +4,7 @@
"license": "MPL-2.0",
"author": "Kaleidos INC",
"private": true,
"packageManager": "yarn@4.9.2+sha512.1fc009bc09d13cfd0e19efa44cbfc2b9cf6ca61482725eb35bbc5e257e093ebf4130db6dfe15d604ff4b79efd8e1e8e99b25fa7d0a6197c9f9826358d4d65c3c",
"packageManager": "pnpm@10.26.2+sha512.0e308ff2005fc7410366f154f625f6631ab2b16b1d2e70238444dd6ae9d630a8482d92a451144debc492416896ed16f7b114a86ec68b8404b2443869e68ffda6",
"repository": {
"type": "git",
"url": "https://github.com/penpot/penpot"
@@ -19,8 +19,9 @@
"ws": "^8.17.0"
},
"scripts": {
"fmt:clj:check": "cljfmt check --parallel=false src/ test/",
"fmt:clj": "cljfmt fix --parallel=true src/ test/",
"lint:clj": "clj-kondo --parallel --lint src/"
"lint": "clj-kondo --parallel --lint ../common/src src/",
"check-fmt": "cljfmt check --parallel=true src/ test/",
"fmt": "cljfmt fix --parallel=true src/ test/",
"test": "clojure -M:dev:test"
}
}
+306
View File
@@ -0,0 +1,306 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
luxon:
specifier: ^3.4.4
version: 3.7.2
sax:
specifier: ^1.4.1
version: 1.4.3
devDependencies:
nodemon:
specifier: ^3.1.2
version: 3.1.11
source-map-support:
specifier: ^0.5.21
version: 0.5.21
ws:
specifier: ^8.17.0
version: 8.18.3
packages:
anymatch@3.1.3:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
brace-expansion@1.1.12:
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
has-flag@3.0.0:
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
engines: {node: '>=4'}
ignore-by-default@1.0.1:
resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
luxon@3.7.2:
resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==}
engines: {node: '>=12'}
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
nodemon@3.1.11:
resolution: {integrity: sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==}
engines: {node: '>=10'}
hasBin: true
normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
pstree.remy@1.1.8:
resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
sax@1.4.3:
resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==}
semver@7.7.3:
resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
engines: {node: '>=10'}
hasBin: true
simple-update-notifier@2.0.0:
resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
engines: {node: '>=10'}
source-map-support@0.5.21:
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
source-map@0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
supports-color@5.5.0:
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
engines: {node: '>=4'}
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
touch@3.1.1:
resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==}
hasBin: true
undefsafe@2.0.5:
resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==}
ws@8.18.3:
resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
snapshots:
anymatch@3.1.3:
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.1
balanced-match@1.0.2: {}
binary-extensions@2.3.0: {}
brace-expansion@1.1.12:
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
braces@3.0.3:
dependencies:
fill-range: 7.1.1
buffer-from@1.1.2: {}
chokidar@3.6.0:
dependencies:
anymatch: 3.1.3
braces: 3.0.3
glob-parent: 5.1.2
is-binary-path: 2.1.0
is-glob: 4.0.3
normalize-path: 3.0.0
readdirp: 3.6.0
optionalDependencies:
fsevents: 2.3.3
concat-map@0.0.1: {}
debug@4.4.3(supports-color@5.5.0):
dependencies:
ms: 2.1.3
optionalDependencies:
supports-color: 5.5.0
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
fsevents@2.3.3:
optional: true
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
has-flag@3.0.0: {}
ignore-by-default@1.0.1: {}
is-binary-path@2.1.0:
dependencies:
binary-extensions: 2.3.0
is-extglob@2.1.1: {}
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
is-number@7.0.0: {}
luxon@3.7.2: {}
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.12
ms@2.1.3: {}
nodemon@3.1.11:
dependencies:
chokidar: 3.6.0
debug: 4.4.3(supports-color@5.5.0)
ignore-by-default: 1.0.1
minimatch: 3.1.2
pstree.remy: 1.1.8
semver: 7.7.3
simple-update-notifier: 2.0.0
supports-color: 5.5.0
touch: 3.1.1
undefsafe: 2.0.5
normalize-path@3.0.0: {}
picomatch@2.3.1: {}
pstree.remy@1.1.8: {}
readdirp@3.6.0:
dependencies:
picomatch: 2.3.1
sax@1.4.3: {}
semver@7.7.3: {}
simple-update-notifier@2.0.0:
dependencies:
semver: 7.7.3
source-map-support@0.5.21:
dependencies:
buffer-from: 1.1.2
source-map: 0.6.1
source-map@0.6.1: {}
supports-color@5.5.0:
dependencies:
has-flag: 3.0.0
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
touch@3.1.1: {}
undefsafe@2.0.5: {}
ws@8.18.3: {}
View File
Whitespace-only changes.
@@ -240,4 +240,4 @@
</div>
</body>
</html>
</html>
+1 -1
View File
@@ -3,7 +3,7 @@
:file-uri "https://github.com/penpot/penpot-files/raw/refs/heads/main/Tokens%20starter%20kit.penpot"}
{:id "penpot-design-system"
:name "Penpot Design System | Pencil"
:file-uri "https://github.com/penpot/penpot-files/raw/refs/heads/main/penpot-app.penpot"}
:file-uri "https://github.com/penpot/penpot-files/raw/refs/heads/main/Pencil-Penpot-Design-System.penpot"}
{:id "wireframing-kit"
:name "Wireframe library"
:file-uri "https://github.com/penpot/penpot-files/raw/refs/heads/main/Wireframing%20kit%20v1.1.penpot"}
@@ -5,7 +5,6 @@
<meta name="robots" content="noindex,nofollow">
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono">
<style>
{% include "app/templates/styles.css" %}
</style>
+93 -79
View File
@@ -12,43 +12,22 @@ Debug Main Page
</nav>
<main class="dashboard">
<section class="widget">
<fieldset>
<legend>Error reports</legend>
<desc><a href="/dbg/error">CLICK HERE TO SEE THE ERROR REPORTS</a> </desc>
</fieldset>
<fieldset>
<legend>Profile Management</legend>
<form method="post" action="/dbg/actions/resend-email-verification">
<div class="row">
<input type="email" name="email" placeholder="example@example.com" value="" />
</div>
<div class="row">
<label for="force-verify">Are you sure?</label>
<input id="force-verify" type="checkbox" name="force" />
<br />
<small>
This is a just a security double check for prevent non intentional submits.
</small>
</div>
<div class="row">
<input type="submit" name="resend" value="Resend Verification" />
<input type="submit" name="verify" value="Verify" />
</div>
<div class="row">
<input type="submit" class="danger" name="block" value="Block" />
<input type="submit" class="danger" name="unblock" value="Unblock" />
</div>
</form>
<legend>CURRENT PROFILE</legend>
<desc>
<p>
Name: <b>{{profile.fullname}}</b> <br />
Email: <b>{{profile.email}}</b>
</p>
</desc>
</fieldset>
<fieldset>
<legend>VIRTUAL CLOCK</legend>
<desc>
<p><b>IMPORTANT:</b> The virtual clock is profile based and only affects the currently logged-in profile.</p>
<p>
CURRENT CLOCK: <b>{{current-clock}}</b>
<br />
@@ -81,8 +60,93 @@ Debug Main Page
</form>
</fieldset>
<fieldset>
<legend>ERROR REPORTS</legend>
<desc><a href="/dbg/error">CLICK HERE TO SEE THE ERROR REPORTS</a> </desc>
</fieldset>
</section>
<section class="widget">
<fieldset>
<legend>Profile Management</legend>
<form method="post" action="/dbg/actions/resend-email-verification">
<div class="row">
<input type="email" name="email" placeholder="example@example.com" value="" />
</div>
<div class="row">
<label for="force-verify">Are you sure?</label>
<input id="force-verify" type="checkbox" name="force" />
<br />
<small>
This is a just a security double check for prevent non intentional submits.
</small>
</div>
<div class="row">
<input type="submit" name="resend" value="Resend Verification" />
<input type="submit" name="verify" value="Verify" />
</div>
<div class="row">
<input type="submit" class="danger" name="block" value="Block" />
<input type="submit" class="danger" name="unblock" value="Unblock" />
</div>
</form>
</fieldset>
<fieldset>
<legend>Feature Flags for Team</legend>
<desc>Add a feature flag to a team</desc>
<form method="post" action="/dbg/actions/handle-team-features">
<div class="row">
<input type="text" style="width:300px" name="team-id" placeholder="team-id" />
</div>
<div class="row">
<select type="text" style="width:100px" name="feature">
{% for feature in supported-features %}
<option value="{{feature}}">{{feature}}</option>
{% endfor %}
</select>
</div>
<div class="row">
<select style="width:100px" name="action">
<option value="">Action...</option>
<option value="show">Show</option>
<option value="enable">Enable</option>
<option value="disable">Disable</option>
</select>
</div>
<div class="row">
<label for="check-feature">Skip feature check</label>
<input id="check-feature" type="checkbox" name="skip-check" />
<br />
<small>
Do not check if the feature is supported
</small>
</div>
<div class="row">
<label for="force-version">Are you sure?</label>
<input id="force-version" type="checkbox" name="force" />
<br />
<small>
This is a just a security double check for prevent non intentional submits.
</small>
</div>
<div class="row">
<input type="submit" value="Submit" />
</div>
</form>
</fieldset>
</section>
<section class="widget">
<fieldset>
@@ -173,55 +237,5 @@ Debug Main Page
</form>
</fieldset>
</section>
<section class="widget">
<fieldset>
<legend>Feature Flags for Team</legend>
<desc>Add a feature flag to a team</desc>
<form method="post" action="/dbg/actions/handle-team-features">
<div class="row">
<input type="text" style="width:300px" name="team-id" placeholder="team-id" />
</div>
<div class="row">
<select type="text" style="width:100px" name="feature">
{% for feature in supported-features %}
<option value="{{feature}}">{{feature}}</option>
{% endfor %}
</select>
</div>
<div class="row">
<select style="width:100px" name="action">
<option value="">Action...</option>
<option value="show">Show</option>
<option value="enable">Enable</option>
<option value="disable">Disable</option>
</select>
</div>
<div class="row">
<label for="check-feature">Skip feature check</label>
<input id="check-feature" type="checkbox" name="skip-check" />
<br />
<small>
Do not check if the feature is supported
</small>
</div>
<div class="row">
<label for="force-version">Are you sure?</label>
<input id="force-version" type="checkbox" name="force" />
<br />
<small>
This is a just a security double check for prevent non intentional submits.
</small>
</div>
<div class="row">
<input type="submit" value="Submit" />
</div>
</form>
</fieldset>
</section>
</main>
{% endblock %}
+22 -19
View File
@@ -5,23 +5,26 @@ penpot - error list
{% endblock %}
{% block content %}
<nav>
<div class="title">
<h1>Error reports (last 200)
<a href="/dbg">[GO BACK]</a>
</h1>
</div>
</nav>
<main class="horizontal-list">
<ul>
{% for item in items %}
<li>
<a class="date" href="/dbg/error/{{item.id}}">{{item.created-at}}</a>
<a class="hint" href="/dbg/error/{{item.id}}">
<span class="title">{{item.hint|abbreviate:150}}</span>
</a>
</li>
{% endfor %}
</ul>
</main>
<nav>
<div class="title">
<a href="/dbg"> [BACK]</a>
<h1>Error reports (last 300)</h1>
<a class="{% if version = 3 %}strong{% endif %}" href="?version=3">[BACKEND ERRORS]</a>
<a class="{% if version = 4 %}strong{% endif %}" href="?version=4">[FRONTEND ERRORS]</a>
<a class="{% if version = 5 %}strong{% endif %}" href="?version=5">[RLIMIT REPORTS]</a>
</div>
</nav>
<main class="horizontal-list">
<ul>
{% for item in items %}
<li>
<a class="date" href="/dbg/error/{{item.id}}">{{item.created-at}}</a>
<a class="hint" href="/dbg/error/{{item.id}}">
<span class="title">{{item.hint|abbreviate:150}}</span>
</a>
</li>
{% endfor %}
</ul>
</main>
{% endblock %}
@@ -6,7 +6,7 @@ Report: {{hint|abbreviate:150}} - {{id}} - Penpot Error Report (v3)
{% block content %}
<nav>
<div>[<a href="/dbg/error">⮜</a>]</div>
<div>[<a href="/dbg/error?version={{version}}">⮜</a>]</div>
<div>[<a href="#head">head</a>]</div>
<div>[<a href="#props">props</a>]</div>
<div>[<a href="#context">context</a>]</div>
@@ -0,0 +1,46 @@
{% extends "app/templates/base.tmpl" %}
{% block title %}
Report: {{hint|abbreviate:150}} - {{id}} - Penpot Error Report (v4)
{% endblock %}
{% block content %}
<nav>
<div>[<a href="/dbg/error?version={{version}}">⮜</a>]</div>
<div>[<a href="#head">head</a>]</div>
<div>[<a href="#context">context</a>]</div>
{% if report %}
<div>[<a href="#report">report</a>]</div>
{% endif %}
</nav>
<main>
<div class="table">
<div class="table-row multiline">
<div id="head" class="table-key">HEAD</div>
<div class="table-val">
<h1><span class="not-important">Hint:</span> <br/> {{hint}}</h1>
<h2><span class="not-important">Reported at:</span> <br/> {{created-at}}</h2>
<h2><span class="not-important">Origin:</span> <br/> {{origin}}</h2>
<h2><span class="not-important">HREF:</span> <br/> {{href}}</h2>
</div>
</div>
<div class="table-row multiline">
<div id="context" class="table-key">CONTEXT: </div>
<div class="table-val">
<pre>{{context}}</pre>
</div>
</div>
{% if report %}
<div class="table-row multiline">
<div id="report" class="table-key">REPORT:</div>
<div class="table-val">
<pre>{{report}}</pre>
</div>
</div>
{% endif %}
</div>
</main>
{% endblock %}
@@ -0,0 +1,40 @@
{% extends "app/templates/base.tmpl" %}
{% block title %}
Report: {{hint|abbreviate:150}} - {{id}} - Penpot Rate Limit Report
{% endblock %}
{% block content %}
<nav>
<div>[<a href="/dbg/error?version={{version}}">⮜</a>]</div>
<div>[<a href="#head">head</a>]</div>
<div>[<a href="#context">context</a>]</div>
<div>[<a href="#result">result</a>]</div>
</nav>
<main>
<div class="table">
<div class="table-row multiline">
<div id="head" class="table-key">HEAD:</div>
<div class="table-val">
<h1><span class="not-important">Hint:</span> <br/> {{hint}}</h1>
<h2><span class="not-important">Reported at:</span> <br/> {{created-at}}</h2>
<h2><span class="not-important">Report ID:</span> <br/> {{id}}</h2>
</div>
</div>
<div class="table-row multiline">
<div id="context" class="table-key">CONTEXT: </div>
<div class="table-val">
<pre>{{context}}</pre>
</div>
</div>
<div class="table-row multiline">
<div id="result" class="table-key">RESULT: </div>
<div class="table-val">
<pre>{{result}}</pre>
</div>
</div>
</div>
</main>
{% endblock %}
+19 -3
View File
@@ -1,5 +1,5 @@
* {
font-family: "JetBrains Mono", monospace;
font-family: monospace;
font-size: 12px;
}
@@ -36,6 +36,10 @@ small {
color: #888;
}
.strong {
font-weight: 900;
}
.not-important {
color: #888;
font-weight: 200;
@@ -57,14 +61,26 @@ nav {
nav > .title {
display: flex;
justify-content: center;
width: 100%;
}
nav > .title > a {
color: black;
text-decoration: none;
}
nav > .title > a.strong {
text-decoration: underline;
}
nav > .title > h1 {
padding: 0px;
margin: 0px;
font-size: 11px;
display: block;
}
nav > .title > * {
padding: 0px 6px;
}
nav > div {
+2 -2
View File
@@ -3,9 +3,9 @@
{:default
[[:default :window "200000/h"]]
;; #{:command/get-teams}
;; #{:main/get-teams}
;; [[:burst :bucket "5/5/5s"]]
;; #{:command/get-profile}
;; #{:main/get-profile}
;; [[:burst :bucket "60/60/1m"]]
}
+10 -1
View File
@@ -1,8 +1,14 @@
#!/usr/bin/env bash
export PENPOT_MANAGEMENT_API_KEY=super-secret-management-api-key
export PENPOT_NITRATE_SHARED_KEY=super-secret-nitrate-api-key
export PENPOT_EXPORTER_SHARED_KEY=super-secret-exporter-api-key
export PENPOT_SECRET_KEY=super-secret-devenv-key
# DEPRECATED: only used for subscriptions
export PENPOT_MANAGEMENT_API_KEY=super-secret-management-api-key
export PENPOT_HOST=devenv
export PENPOT_PUBLIC_URI=https://localhost:3449
export PENPOT_FLAGS="\
$PENPOT_FLAGS \
@@ -12,6 +18,7 @@ export PENPOT_FLAGS="\
disable-login-with-google \
disable-login-with-github \
disable-login-with-gitlab \
disable-telemetry \
enable-backend-worker \
enable-backend-asserts \
disable-feature-fdata-pointer-map \
@@ -54,6 +61,8 @@ export PENPOT_OBJECTS_STORAGE_BACKEND=s3
export PENPOT_OBJECTS_STORAGE_S3_ENDPOINT=http://minio:9000
export PENPOT_OBJECTS_STORAGE_S3_BUCKET=penpot
export PENPOT_NITRATE_BACKEND_URI=http://localhost:3000/control-center
export JAVA_OPTS="\
-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
-Djdk.attach.allowAttachSelf \
+4
View File
@@ -3,6 +3,10 @@
SCRIPT_DIR=$(dirname $0);
source $SCRIPT_DIR/_env;
if [ -f $SCRIPT_DIR/_env.local ]; then
source $SCRIPT_DIR/_env.local;
fi
# Initialize MINIO config
setup_minio;
+5
View File
@@ -3,6 +3,11 @@
SCRIPT_DIR=$(dirname $0);
source $SCRIPT_DIR/_env;
if [ -f $SCRIPT_DIR/_env.local ]; then
source $SCRIPT_DIR/_env.local;
fi
export OPTIONS="-A:dev"
entrypoint=${1:-app.main};
+4
View File
@@ -3,6 +3,10 @@
SCRIPT_DIR=$(dirname $0);
source $SCRIPT_DIR/_env;
if [ -f $SCRIPT_DIR/_env.local ]; then
source $SCRIPT_DIR/_env.local;
fi
# Initialize MINIO config
setup_minio;
+7 -18
View File
@@ -36,17 +36,6 @@
[integrant.core :as ig]
[yetti.response :as-alias yres]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HELPERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn obfuscate-string
[s]
(if (< (count s) 10)
(apply str (take (count s) (repeat "*")))
(str (subs s 0 5)
(apply str (take (- (count s) 5) (repeat "*"))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; OIDC PROVIDER (GENERIC)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -177,7 +166,7 @@
(l/inf :hint "provider initialized"
:provider (:id provider)
:client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider)))
:client-secret (d/obfuscate-string (:client-secret provider)))
provider)
(catch Throwable cause
@@ -222,7 +211,7 @@
(l/inf :hint "provider initialized"
:provider (:id provider)
:client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider)))
:client-secret (d/obfuscate-string (:client-secret provider)))
provider)
(catch Throwable cause
@@ -299,7 +288,7 @@
(l/inf :hint "provider initialized"
:provider (:id provider)
:client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider)))
:client-secret (d/obfuscate-string (:client-secret provider)))
provider)
(catch Throwable cause
@@ -341,7 +330,7 @@
:provider "gitlab"
:base-uri (:base-uri provider)
:client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider)))
:client-secret (d/obfuscate-string (:client-secret provider)))
provider)
(catch Throwable cause
(ex/raise :type ::internal
@@ -361,7 +350,7 @@
(l/inf :hint "provider initialized"
:provider (:id provider)
:client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider)))
:client-secret (d/obfuscate-string (:client-secret provider)))
provider)
(catch Throwable cause
@@ -459,7 +448,7 @@
(l/trc :hint "fetch access token"
:provider (:id provider)
:client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider))
:client-secret (d/obfuscate-string (:client-secret provider))
:grant-type (:grant_type params)
:redirect-uri (:redirect_uri params))
@@ -512,7 +501,7 @@
[cfg provider tdata]
(l/trc :hint "fetch user info"
:uri (:user-uri provider)
:token (obfuscate-string (:token/access tdata)))
:token (d/obfuscate-string (:token/access tdata)))
(let [params {:uri (:user-uri provider)
:headers {"Authorization" (str (:token/type tdata) " " (:token/access tdata))}
+75
View File
@@ -331,6 +331,81 @@
(set/difference cfeat/backend-only-features))
#{}))))
(defn check-file-exists
[cfg id & {:keys [include-deleted?]
:or {include-deleted? false}
:as options}]
(db/get-with-sql cfg [sql:get-minimal-file id]
{:db/remove-deleted (not include-deleted?)}))
(def ^:private sql:file-permissions
"select fpr.is_owner,
fpr.is_admin,
fpr.can_edit
from file_profile_rel as fpr
inner join file as f on (f.id = fpr.file_id)
where fpr.file_id = ?
and fpr.profile_id = ?
union all
select tpr.is_owner,
tpr.is_admin,
tpr.can_edit
from team_profile_rel as tpr
inner join project as p on (p.team_id = tpr.team_id)
inner join file as f on (p.id = f.project_id)
where f.id = ?
and tpr.profile_id = ?
union all
select ppr.is_owner,
ppr.is_admin,
ppr.can_edit
from project_profile_rel as ppr
inner join file as f on (f.project_id = ppr.project_id)
where f.id = ?
and ppr.profile_id = ?")
(defn- get-file-permissions*
[conn profile-id file-id]
(when (and profile-id file-id)
(db/exec! conn [sql:file-permissions
file-id profile-id
file-id profile-id
file-id profile-id])))
(defn get-file-permissions
([conn profile-id file-id]
(let [rows (get-file-permissions* conn profile-id file-id)
is-owner (boolean (some :is-owner rows))
is-admin (boolean (some :is-admin rows))
can-edit (boolean (some :can-edit rows))]
(when (seq rows)
{:type :membership
:is-owner is-owner
:is-admin (or is-owner is-admin)
:can-edit (or is-owner is-admin can-edit)
:can-read true
:is-logged (some? profile-id)})))
([conn profile-id file-id share-id]
(let [perms (get-file-permissions conn profile-id file-id)
ldata (some-> (db/get* conn :share-link {:id share-id :file-id file-id})
(dissoc :flags)
(update :pages db/decode-pgarray #{}))]
;; NOTE: in a future when share-link becomes more powerful and
;; will allow us specify which parts of the app is available, we
;; will probably need to tweak this function in order to expose
;; this flags to the frontend.
(cond
(some? perms) perms
(some? ldata) {:type :share-link
:can-read true
:pages (:pages ldata)
:is-logged (some? profile-id)
:who-comment (:who-comment ldata)
:who-inspect (:who-inspect ldata)}))))
(defn get-project
[cfg project-id]
(db/get cfg :project {:id project-id}))
+5 -7
View File
@@ -821,9 +821,10 @@
entries (keep (match-storage-entry-fn) entries)]
(doseq [{:keys [id entry]} entries]
(let [object (->> (read-entry input entry)
(decode-storage-object)
(validate-storage-object))
(let [object (-> (read-entry input entry)
(decode-storage-object)
(update :bucket d/nilv sto/default-bucket)
(validate-storage-object))
ext (cmedia/mtype->extension (:content-type object))
path (str "objects/" id ext)
@@ -872,11 +873,8 @@
(import-storage-objects cfg)
(let [files (get manifest :files)
result (reduce (fn [result {:keys [id] :as file}]
result (reduce (fn [result file]
(let [name' (get file :name)
name' (if (map? name)
(get name id)
name')
file (assoc file :name name')]
(conj result (import-file cfg file))))
[]
+4 -1
View File
@@ -98,10 +98,11 @@
[:http-server-port {:optional true} ::sm/int]
[:http-server-host {:optional true} :string]
[:http-server-max-body-size {:optional true} ::sm/int]
[:http-server-max-multipart-body-size {:optional true} ::sm/int]
[:http-server-io-threads {:optional true} ::sm/int]
[:http-server-max-worker-threads {:optional true} ::sm/int]
[:exporter-shared-key {:optional true} :string]
[:nitrate-shared-key {:optional true} :string]
[:management-api-key {:optional true} :string]
[:telemetry-uri {:optional true} :string]
@@ -225,6 +226,8 @@
[:netty-io-threads {:optional true} ::sm/int]
[:executor-threads {:optional true} ::sm/int]
[:nitrate-backend-uri {:optional true} ::sm/uri]
;; DEPRECATED
[:assets-storage-backend {:optional true} :keyword]
[:storage-assets-fs-directory {:optional true} :string]
+5 -7
View File
@@ -106,17 +106,17 @@
(let [content-part (MimeBodyPart.)
alternative-mpart (MimeMultipart. "alternative")]
(when-let [content (get body "text/plain")]
(let [text-part (MimeBodyPart.)]
(.setText text-part ^String content ^String charset)
(.addBodyPart alternative-mpart text-part)))
(when-let [content (get body "text/html")]
(let [html-part (MimeBodyPart.)]
(.setContent html-part ^String content
(str "text/html; charset=" charset))
(.addBodyPart alternative-mpart html-part)))
(when-let [content (get body "text/plain")]
(let [text-part (MimeBodyPart.)]
(.setText text-part ^String content ^String charset)
(.addBodyPart alternative-mpart text-part)))
(.setContent content-part alternative-mpart)
(.addBodyPart mixed-mpart content-part))
@@ -124,8 +124,6 @@
(throw (IllegalArgumentException. "invalid email body provided")))
(doseq [[name content] attachments]
(prn "attachment" name)
(let [attachment-part (MimeBodyPart.)]
(.setFileName attachment-part ^String name)
(.setContent attachment-part ^String content (str "text/plain; charset=" charset))
+2 -2
View File
@@ -138,6 +138,7 @@
c.deleted_at
FROM snapshots1 AS c
WHERE c.file_id = ?
ORDER BY c.created_at DESC
), snapshots3 AS (
(SELECT * FROM snapshots2
WHERE created_by = 'system'
@@ -150,8 +151,7 @@
AND deleted_at IS NULL
LIMIT 500)
)
SELECT * FROM snapshots3
ORDER BY created_at DESC"))
SELECT * FROM snapshots3;"))
(defn get-visible-snapshots
"Return a list of snapshots fecheable from the API, it has a limited
+3 -4
View File
@@ -42,8 +42,8 @@
(def default-params
{::port 6060
::host "0.0.0.0"
::max-body-size 31457280 ; default 30 MiB
::max-multipart-body-size 367001600}) ; default 350 MiB
::max-body-size 367001600 ; default 350 MiB
})
(defmethod ig/expand-key ::server
[k v]
@@ -56,7 +56,6 @@
[::io-threads {:optional true} ::sm/int]
[::max-worker-threads {:optional true} ::sm/int]
[::max-body-size {:optional true} ::sm/int]
[::max-multipart-body-size {:optional true} ::sm/int]
[::router {:optional true} [:fn r/router?]]
[::handler {:optional true} ::sm/fn]])
@@ -79,7 +78,7 @@
{:http/port port
:http/host host
:http/max-body-size (::max-body-size cfg)
:http/max-multipart-body-size (::max-multipart-body-size cfg)
:http/max-multipart-body-size (::max-body-size cfg)
:xnio/direct-buffers false
:xnio/io-threads (::io-threads cfg)
:xnio/max-worker-threads (::max-worker-threads cfg)
+1 -1
View File
@@ -30,7 +30,7 @@
(defn- get-file-media-object
[pool id]
(db/get pool :file-media-object {:id id}))
(db/get pool :file-media-object {:id id} {::db/remove-deleted false}))
(defn- serve-object-from-s3
[{:keys [::sto/storage] :as cfg} obj]
+39 -16
View File
@@ -49,13 +49,16 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn index-handler
[_cfg _request]
(let [{:keys [clock offset]} @clock/current]
[cfg request]
(let [profile-id (::session/profile-id request)
offset (clock/get-offset profile-id)
profile (profile/get-profile cfg profile-id)]
{::yres/status 200
::yres/headers {"content-type" "text/html"}
::yres/body (-> (io/resource "app/templates/debug.tmpl")
(tmpl/render {:version (:full cf/version)
:current-clock (str clock)
:profile profile
:current-clock ct/*clock*
:current-offset (if offset
(ct/format-duration offset)
"NO OFFSET")
@@ -229,13 +232,30 @@
(-> (io/resource "app/templates/error-report.v3.tmpl")
(tmpl/render (-> content
(assoc :id id)
(assoc :version 3)
(assoc :created-at (ct/format-inst created-at :rfc1123))))))
(render-template-v4 [{:keys [content id created-at]}]
(-> (io/resource "app/templates/error-report.v4.tmpl")
(tmpl/render (-> content
(assoc :id id)
(assoc :version 4)
(assoc :created-at (ct/format-inst created-at :rfc1123))))))
(render-template-v5 [{:keys [content id created-at]}]
(-> (io/resource "app/templates/error-report.v5.tmpl")
(tmpl/render (-> content
(assoc :id id)
(assoc :version 5)
(assoc :created-at (ct/format-inst created-at :rfc1123))))))]
(if-let [report (get-report request)]
(let [result (case (:version report)
1 (render-template-v1 report)
2 (render-template-v2 report)
3 (render-template-v3 report))]
3 (render-template-v3 report)
4 (render-template-v4 report)
5 (render-template-v5 report))]
{::yres/status 200
::yres/body result
::yres/headers {"content-type" "text/html; charset=utf-8"
@@ -243,20 +263,22 @@
{::yres/status 404
::yres/body "not found"})))
(def sql:error-reports
(def ^:private sql:error-reports
"SELECT id, created_at,
content->>'~:hint' AS hint
FROM server_error_report
WHERE version = ?
ORDER BY created_at DESC
LIMIT 200")
LIMIT 300")
(defn error-list-handler
[{:keys [::db/pool]} _request]
(let [items (->> (db/exec! pool [sql:error-reports])
(map #(update % :created-at ct/format-inst :rfc1123)))]
(defn- error-list-handler
[{:keys [::db/pool]} {:keys [params]}]
(let [version (or (some-> (get params :version) parse-long) 3)
items (->> (db/exec! pool [sql:error-reports version])
(map #(update % :created-at ct/format-inst :rfc1123)))]
{::yres/status 200
::yres/body (-> (io/resource "app/templates/error-list.tmpl")
(tmpl/render {:items items}))
(tmpl/render {:items items :version version}))
::yres/headers {"content-type" "text/html; charset=utf-8"
"x-robots-tag" "noindex"}}))
@@ -447,15 +469,16 @@
(defn- set-virtual-clock
[_ {:keys [params] :as request}]
(let [offset (some-> params :offset str/trim not-empty ct/duration)
reset? (contains? params :reset)]
(let [offset (some-> params :offset str/trim not-empty ct/duration)
profile-id (::session/profile-id request)
reset? (contains? params :reset)]
(if (= "production" (cf/get :tenant))
{::yres/status 501
::yres/body "OPERATION NOT ALLOWED"}
(do
(if (or reset? (zero? (inst-ms offset)))
(clock/set-offset! nil)
(clock/set-offset! offset))
(clock/assign-offset profile-id nil)
(clock/assign-offset profile-id offset))
{::yres/status 302
::yres/headers {"location" "/dbg"}}))))
@@ -495,7 +518,7 @@
(defn authorized?
[pool {:keys [::session/profile-id]}]
(or (= "devenv" (cf/get :host))
(or (and (= "devenv" (cf/get :host)) profile-id)
(let [profile (ex/ignoring (profile/get-profile pool profile-id))
admins (or (cf/get :admins) #{})]
(contains? admins (:email profile)))))
+1 -1
View File
@@ -32,7 +32,7 @@
(assoc :request/ip-addr (inet/parse-request request))
(assoc :request/profile-id (get claims :uid))
(assoc :request/auth-data auth)
(assoc :version/frontend (or (yreq/get-header request "x-frontend-version") "unknown")))))
(assoc :frontend/version (or (yreq/get-header request "x-frontend-version") "unknown")))))
(defmulti handle-error
(fn [cause _ _]
+31 -19
View File
@@ -13,13 +13,13 @@
[app.common.time :as ct]
[app.config :as cf]
[app.db :as db]
[app.http.middleware :as mw]
[app.main :as-alias main]
[app.rpc.commands.profile :as cmd.profile]
[app.setup :as-alias setup]
[app.tokens :as tokens]
[app.worker :as-alias wrk]
[integrant.core :as ig]
[yetti.request :as yreq]
[yetti.response :as-alias yres]))
;; ---- ROUTES
@@ -49,28 +49,40 @@
(fn [cfg request]
(db/tx-run! cfg handler request)))))})
(def ^:private shared-key-auth
{:name ::shared-key-auth
:compile
(fn [_ _]
(fn [handler key]
(if key
(fn [request]
(if-let [key' (yreq/get-header request "x-shared-key")]
(if (= key key')
(handler request)
{::yres/status 403})
{::yres/status 403}))
(fn [_ _]
{::yres/status 403}))))})
(defmethod ig/init-key ::routes
[_ {:keys [::setup/props] :as cfg}]
[_ cfg]
(let [management-key (or (cf/get :management-api-key)
(get props :management-key))]
["" {:middleware [[shared-key-auth (cf/get :management-api-key)]
[default-system cfg]
[transaction]]}
["/authenticate"
{:handler authenticate
:allowed-methods #{:post}}]
["" {:middleware [[mw/shared-key-auth management-key]
[default-system cfg]
[transaction]]}
["/authenticate"
{:handler authenticate
:allowed-methods #{:post}}]
["/get-customer"
{:handler get-customer
:transaction true
:allowed-methods #{:post}}]
["/get-customer"
{:handler get-customer
:transaction true
:allowed-methods #{:post}}]
["/update-customer"
{:handler update-customer
:allowed-methods #{:post}
:transaction true}]]))
["/update-customer"
{:handler update-customer
:allowed-methods #{:post}
:transaction true}]])
;; ---- HELPERS
+17 -14
View File
@@ -16,7 +16,6 @@
[app.http.errors :as errors]
[app.tokens :as tokens]
[app.util.pointer-map :as pmap]
[buddy.core.codecs :as bc]
[cuerdas.core :as str]
[yetti.adapter :as yt]
[yetti.middleware :as ymw]
@@ -214,14 +213,14 @@
(assoc "access-control-allow-origin" origin)
(assoc "access-control-allow-methods" "GET,POST,DELETE,OPTIONS,PUT,HEAD,PATCH")
(assoc "access-control-allow-credentials" "true")
(assoc "access-control-expose-headers" "x-requested-with, content-type, cookie")
(assoc "access-control-allow-headers" "x-frontend-version, content-type, accept, x-requested-width")))
(assoc "access-control-expose-headers" "content-type, set-cookie")
(assoc "access-control-allow-headers" "x-frontend-version, x-client, x-requested-width, content-type, accept, cookie")))
(defn wrap-cors
[handler]
(fn [request]
(let [response (if (= (yreq/method request) :options)
{::yres/status 200}
{::yres/status 204}
(handler request))
origin (yreq/get-header request "origin")]
(update response ::yres/headers with-cors-headers origin))))
@@ -301,16 +300,20 @@
:compile (constantly wrap-auth)})
(defn- wrap-shared-key-auth
[handler shared-key]
(if shared-key
(let [shared-key (if (string? shared-key)
shared-key
(bc/bytes->b64-str shared-key true))]
(fn [request]
(let [key (yreq/get-header request "x-shared-key")]
(if (= key shared-key)
(handler request)
{::yres/status 403}))))
[handler keys]
(if (seq keys)
(fn [request]
(if-let [[key-id key] (some-> (yreq/get-header request "x-shared-key")
(str/split #"\s+" 2))]
(let [key-id (-> key-id str/lower keyword)]
(if (and (string? key)
(contains? keys key-id)
(= key (get keys key-id)))
(-> request
(assoc ::http/auth-key-id key-id)
(handler))
{::yres/status 403}))
{::yres/status 403}))
(fn [_ _]
{::yres/status 403})))
+15 -10
View File
@@ -20,6 +20,7 @@
[app.http.session.tasks :as-alias tasks]
[app.main :as-alias main]
[app.setup :as-alias setup]
[app.setup.clock :as clock]
[app.tokens :as tokens]
[integrant.core :as ig]
[yetti.request :as yreq]
@@ -229,18 +230,22 @@
(let [{:keys [type token claims metadata]} (get request ::http/auth-data)]
(cond
(= type :cookie)
(let [session (case (:ver metadata)
;; BACKWARD COMPATIBILITY WITH OLD TOKENS
0 (read-session manager token)
1 (some->> (:sid claims) (read-session manager))
nil)
(let [session
(case (:ver metadata)
;; BACKWARD COMPATIBILITY WITH OLD TOKENS
0 (read-session manager token)
1 (some->> (:sid claims) (read-session manager))
nil)
request (cond-> request
(some? session)
(-> (assoc ::profile-id (:profile-id session))
(assoc ::session session)))
request
(cond-> request
(some? session)
(-> (assoc ::profile-id (:profile-id session))
(assoc ::session session)))
response (handler request)]
response
(binding [ct/*clock* (clock/get-clock (:profile-id session))]
(handler request))]
(if (and session (renew-session? session))
(let [session (->> session
-1
View File
@@ -6,7 +6,6 @@
(ns app.http.sse
"SSE (server sent events) helpers"
(:refer-clojure :exclude [tap])
(:require
[app.common.data :as d]
[app.common.logging :as l]
+83 -43
View File
@@ -9,6 +9,7 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.json :as json]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.time :as ct]
@@ -79,18 +80,6 @@
(remove #(contains? reserved-props (key %))))
props))
(defn event-from-rpc-params
"Create a base event skeleton with pre-filled some important
data that can be extracted from RPC params object"
[params]
(let [context {:external-session-id (::rpc/external-session-id params)
:external-event-origin (::rpc/external-event-origin params)
:triggered-by (::rpc/handler-name params)}]
{::type "action"
::profile-id (::rpc/profile-id params)
::ip-addr (::rpc/ip-addr params)
::context (d/without-nils context)}))
(defn get-external-session-id
[request]
(when-let [session-id (yreq/get-header request "x-external-session-id")]
@@ -99,13 +88,24 @@
(str/blank? session-id))
session-id)))
(defn- get-external-event-origin
(defn- get-client-event-origin
[request]
(when-let [origin (yreq/get-header request "x-event-origin")]
(when-not (or (> (count origin) 256)
(= origin "null")
(when-not (or (= origin "null")
(str/blank? origin))
origin)))
(str/prune origin 200))))
(defn get-client-user-agent
[request]
(when-let [user-agent (yreq/get-header request "user-agent")]
(str/prune user-agent 500)))
(defn- get-client-version
[request]
(when-let [origin (yreq/get-header request "x-frontend-version")]
(when-not (or (= origin "null")
(str/blank? origin))
(str/prune origin 100))))
;; --- SPECS
@@ -113,6 +113,8 @@
;; COLLECTOR API
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare ^:private prepare-context-from-request)
;; Defines a service that collects the audit/activity log using
;; internal database. Later this audit log can be transferred to
;; an external storage and data cleared.
@@ -126,6 +128,8 @@
[::props {:optional true} [:map-of :keyword :any]]
[::context {:optional true} [:map-of :keyword :any]]
[::tracked-at {:optional true} ::ct/inst]
[::created-at {:optional true} ::ct/inst]
[::source {:optional true} ::sm/text]
[::webhooks/event? {:optional true} ::sm/boolean]
[::webhooks/batch-timeout {:optional true} ::ct/duration]
[::webhooks/batch-key {:optional true}
@@ -134,6 +138,9 @@
(def ^:private check-event
(sm/check-fn schema:event))
(def valid-event?
(sm/validator schema:event))
(defn prepare-event
[cfg mdata params result]
(let [resultm (meta result)
@@ -144,28 +151,22 @@
uuid/zero)
props (-> (or (::replace-props resultm)
(-> params
(merge (::props resultm))
(dissoc :profile-id)
(dissoc :type)))
(merge params (::props resultm)))
(clean-props))
token-id (::actoken/id request)
context (-> (::context resultm)
(assoc :external-session-id
(get-external-session-id request))
(assoc :external-event-origin
(get-external-event-origin request))
(assoc :access-token-id (some-> token-id str))
(d/without-nils))
ip-addr (inet/parse-request request)]
context (merge (::context resultm)
(prepare-context-from-request request))
ip-addr (inet/parse-request request)
module (get cfg ::rpc/module)]
{::type (or (::type resultm)
(::rpc/type cfg))
::name (or (::name resultm)
(::sv/name mdata))
(let [sname (::sv/name mdata)]
(if (not= module "main")
(str module "-" sname)
sname)))
::profile-id profile-id
::ip-addr ip-addr
::props props
@@ -189,6 +190,38 @@
(::webhooks/event? resultm)
false)}))
(defn- prepare-context-from-request
"Prepare backend event context from request"
[request]
(let [client-event-origin (get-client-event-origin request)
client-version (get-client-version request)
client-user-agent (get-client-user-agent request)
session-id (get-external-session-id request)
key-id (::http/auth-key-id request)
token-id (::actoken/id request)
token-type (::actoken/type request)]
(d/without-nils
{:external-session-id session-id
:initiator (or key-id "app")
:access-token-id (some-> token-id str)
:access-token-type (some-> token-type str)
:client-event-origin client-event-origin
:client-user-agent client-user-agent
:client-version client-version
:version (:full cf/version)})))
(defn event-from-rpc-params
"Create a base event skeleton with pre-filled some important
data that can be extracted from RPC params object"
[params]
(let [context (some-> params meta ::http/request prepare-context-from-request)
event {::type "action"
::profile-id (or (::rpc/profile-id params) uuid/zero)
::ip-addr (::rpc/ip-addr params)}]
(cond-> event
(some? context)
(assoc ::context context))))
(defn- event->params
[event]
(let [params {:id (uuid/next)
@@ -205,7 +238,7 @@
(some? tnow)
(assoc :tracked-at tnow))))
(defn- append-audit-entry!
(defn- append-audit-entry
[cfg params]
(let [params (-> params
(update :props db/tjson)
@@ -215,17 +248,26 @@
(defn- handle-event!
[cfg event]
(let [params (event->params event)
tnow (ct/now)]
(let [tnow (ct/now)
params (-> (event->params event)
(assoc :created-at tnow)
(update :tracked-at #(or % tnow)))]
(when (contains? cf/flags :audit-log-logger)
(l/log! ::l/logger "app.audit"
::l/level :info
:profile-id (str (::profile-id event))
:ip-addr (str (::ip-addr event))
:type (::type event)
:name (::name event)
:props (json/encode (::props event) :key-fn json/write-camel-key)
:context (json/encode (::context event) :key-fn json/write-camel-key)))
(when (contains? cf/flags :audit-log)
;; NOTE: this operation may cause primary key conflicts on inserts
;; because of the timestamp precission (two concurrent requests), in
;; this case we just retry the operation.
(let [params (-> params
(assoc :created-at tnow)
(update :tracked-at #(or % tnow)))]
(append-audit-entry! cfg params)))
(append-audit-entry cfg params))
(when (and (or (contains? cf/flags :telemetry)
(cf/get :telemetry-enabled))
@@ -236,11 +278,9 @@
;;
;; NOTE: this is only executed when general audit log is disabled
(let [params (-> params
(assoc :created-at tnow)
(update :tracked-at #(or % tnow))
(assoc :props {})
(assoc :context {}))]
(append-audit-entry! cfg params)))
(append-audit-entry cfg params)))
(when (and (contains? cf/flags :webhooks)
(::webhooks/event? event))
@@ -294,4 +334,4 @@
params (-> (event->params event)
(assoc :created-at tnow)
(update :tracked-at #(or % tnow)))]
(append-audit-entry! cfg params)))))))
(append-audit-entry cfg params)))))))
+169 -65
View File
@@ -14,6 +14,8 @@
[app.common.schema :as sm]
[app.config :as cf]
[app.db :as db]
[app.loggers.audit :as audit]
[app.rpc.rlimit :as-alias rlimit]
[clojure.spec.alpha :as s]
[integrant.core :as ig]
[promesa.exec :as px]
@@ -28,69 +30,145 @@
(defonce enabled (atom true))
(defn- persist-on-database!
[pool id report]
[pool id version report]
(when-not (db/read-only? pool)
(db/insert! pool :server-error-report
{:id id
:version 3
:version version
:content (db/tjson report)})))
(defn record->report
(defn- concurrent-exception?
[cause]
(or (instance? java.util.concurrent.CompletionException cause)
(instance? java.util.concurrent.ExecutionException cause)))
(defn- log-record->report
[{:keys [::l/context ::l/message ::l/props ::l/logger ::l/level ::l/cause] :as record}]
(assert (l/valid-record? record) "expectd valid log record")
(if (or (instance? java.util.concurrent.CompletionException cause)
(instance? java.util.concurrent.ExecutionException cause))
(-> record
(assoc ::trace (ex/format-throwable cause :data? true :explain? false :header? false :summary? false))
(assoc ::l/cause (ex-cause cause))
(record->report))
(let [data (if (concurrent-exception? cause)
(ex-data (ex-cause cause))
(ex-data cause))
(let [data (ex-data cause)
ctx (-> context
(assoc :tenant (cf/get :tenant))
(assoc :host (cf/get :host))
(assoc :public-uri (str (cf/get :public-uri)))
(assoc :logger/name logger)
(assoc :logger/level level)
(dissoc :request/params :value :params :data))]
ctx (-> context
(assoc :service/tenant (cf/get :tenant))
(assoc :service/host (cf/get :host))
(assoc :service/public-uri (str (cf/get :public-uri)))
(assoc :backend/version (:full cf/version))
(assoc :logger/name logger)
(assoc :logger/level level)
(dissoc :request/params :value :params :data))]
(merge
{:context (-> (into (sorted-map) ctx)
(pp/pprint-str :length 50))
:props (pp/pprint-str props :length 50)
:hint (or (when-let [message (ex-message cause)]
(if-let [props-hint (:hint props)]
(str props-hint ": " message)
message))
@message)
:trace (or (::trace record)
(some-> cause (ex/format-throwable :data? true :explain? false :header? false :summary? false)))}
(merge
{:context (-> (into (sorted-map) ctx)
(pp/pprint-str :length 50))
:props (pp/pprint-str props :length 50)
:hint (or (when-let [message (ex-message cause)]
(if-let [props-hint (:hint props)]
(str props-hint ": " message)
message))
@message)
:trace (or (::trace record)
(some-> cause (ex/format-throwable :data? true :explain? false :header? false :summary? false)))}
(when-let [params (or (:request/params context) (:params context))]
{:params (pp/pprint-str params :length 20 :level 20)})
(when-let [params (or (:request/params context) (:params context))]
{:params (pp/pprint-str params :length 20 :level 20)})
(when-let [value (:value context)]
{:value (pp/pprint-str value :length 30 :level 13)})
(when-let [value (:value context)]
{:value (pp/pprint-str value :length 30 :level 13)})
(when-let [data (some-> data (dissoc ::s/problems ::s/value ::s/spec ::sm/explain :hint))]
{:data (pp/pprint-str data :length 30 :level 13)})
(when-let [data (some-> data (dissoc ::s/problems ::s/value ::s/spec ::sm/explain :hint))]
{:data (pp/pprint-str data :length 30 :level 13)})
(when-let [explain (ex/explain data :length 30 :level 13)]
{:explain explain})))))
(when-let [explain (ex/explain data :length 30 :level 13)]
{:explain explain}))))
(defn error-record?
[{:keys [::l/level]}]
(= :error level))
(defn- handle-event
(defn- handle-log-record
"Convert the log record into a report object and persist it on the database"
[{:keys [::db/pool]} {:keys [::l/id] :as record}]
(try
(let [uri (cf/get :public-uri)
report (-> record record->report d/without-nils)]
(l/debug :hint "registering error on database" :id id
:uri (str uri "/dbg/error/" id))
report (-> record log-record->report d/without-nils)]
(l/dbg :hint "registering error on database"
:id (str id)
:src "logging"
:uri (str uri "/dbg/error/" id))
(persist-on-database! pool id 3 report))
(catch Throwable cause
(l/warn :hint "unexpected exception on database error logger" :cause cause))))
(persist-on-database! pool id report))
(defn- audit-event->report
[{:keys [::audit/context ::audit/props ::audit/ip-addr] :as record}]
(let [context
(reduce-kv (fn [context k v]
(let [k' (keyword "frontend" (name k))]
(-> context
(dissoc k)
(assoc k' v))))
context
context)
context
(-> context
(assoc :backend/tenant (cf/get :tenant))
(assoc :backend/host (cf/get :host))
(assoc :backend/public-uri (str (cf/get :public-uri)))
(assoc :backend/version (:full cf/version))
(assoc :frontend/ip-addr ip-addr))]
{:context (-> (into (sorted-map) context)
(pp/pprint-str :length 50))
:origin (::audit/name record)
:href (get props :href)
:hint (get props :hint)
:report (get props :report)}))
(defn- handle-audit-event
"Convert the log record into a report object and persist it on the database"
[{:keys [::db/pool]} {:keys [::audit/id] :as event}]
(try
(let [uri (cf/get :public-uri)
report (-> event audit-event->report d/without-nils)]
(l/dbg :hint "registering error on database"
:id (str id)
:src "audit-log"
:uri (str uri "/dbg/error/" id))
(persist-on-database! pool id 4 report))
(catch Throwable cause
(l/warn :hint "unexpected exception on database error logger" :cause cause))))
(defn- rlimit-event->report
[event]
(let [context
(-> {}
(assoc :rlimit/uid (::rlimit/uid event))
(assoc :rlimit/method (::rlimit/method event))
(assoc :backend/tenant (cf/get :tenant))
(assoc :backend/host (cf/get :host))
(assoc :backend/public-uri (str (cf/get :public-uri)))
(assoc :backend/version (:full cf/version)))
result
(->> (::rlimit/results event)
(mapv (fn [result]
(-> (into (sorted-map) result)
(dissoc ::rlimit/method)))))]
{:hint (str "Rate Limit Rejection: " (::rlimit/method event) " for " (::rlimit/uid event))
:context (-> (into (sorted-map) context)
(pp/pprint-str :length 50))
:result (pp/pprint-str result :length 50)}))
(defn- handle-rlimit-event
"Convert the log record into a report object and persist it on the database"
[{:keys [::db/pool]} {:keys [::rlimit/id] :as event}]
(try
(let [uri (cf/get :public-uri)
report (-> event rlimit-event->report d/without-nils)]
(l/dbg :hint "registering rate limit rejection"
:id (str id)
:src "rlimit"
:uri (str uri "/dbg/error/" id))
(persist-on-database! pool id 5 report))
(catch Throwable cause
(l/warn :hint "unexpected exception on database error logger" :cause cause))))
@@ -100,26 +178,52 @@
(defmethod ig/init-key ::reporter
[_ cfg]
(let [input (sp/chan :buf (sp/sliding-buffer 64)
:xf (filter error-record?))]
(add-watch l/log-record ::reporter #(sp/put! input %4))
(let [input (sp/chan :buf (sp/sliding-buffer 256))
thread (px/thread
{:name "penpot/reporter/database"}
(l/info :hint "initializing database error persistence")
(try
(loop []
(when-let [item (sp/take! input)]
(cond
(::l/id item)
(handle-log-record cfg item)
(px/thread {:name "penpot/database-reporter"}
(l/info :hint "initializing database error persistence")
(try
(loop []
(when-let [record (sp/take! input)]
(handle-event cfg record)
(recur)))
(catch InterruptedException _
(l/debug :hint "reporter interrupted"))
(catch Throwable cause
(l/error :hint "unexpected error" :cause cause))
(finally
(sp/close! input)
(remove-watch l/log-record ::reporter)
(l/info :hint "reporter terminated"))))))
(::audit/id item)
(handle-audit-event cfg item)
(::rlimit/id item)
(handle-rlimit-event cfg item)
:else
(l/warn :hint "received unexpected item" :item item))
(recur)))
(catch InterruptedException _
(l/debug :hint "reporter interrupted"))
(catch Throwable cause
(l/error :hint "unexpected error" :cause cause))
(finally
(l/info :hint "reporter terminated"))))]
(add-watch l/log-record ::reporter
(fn [_ _ _ record]
(when (= :error (::l/level record))
(sp/put! input record))))
{::input input
::thread thread}))
(defmethod ig/halt-key! ::reporter
[_ thread]
(some-> thread px/interrupt!))
[_ {:keys [::input ::thread]}]
(remove-watch l/log-record ::reporter)
(sp/close! input)
(px/interrupt! thread))
(defn emit
"Emit an event/report into the database reporter"
[cfg event]
(when-let [{:keys [::input]} (get cfg ::reporter)]
(sp/put! input event)))
+131 -51
View File
@@ -9,9 +9,12 @@
(:require
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.pprint :as pp]
[app.common.uri :as u]
[app.config :as cf]
[app.http.client :as http]
[app.loggers.database :as ldb]
[app.loggers.audit :as audit]
[app.rpc.rlimit :as-alias rlimit]
[app.util.json :as json]
[integrant.core :as ig]
[promesa.exec :as px]
@@ -20,24 +23,34 @@
(defonce enabled (atom true))
(defn- send-mattermost-notification!
[cfg {:keys [id public-uri] :as report}]
[cfg {:keys [id] :as report}]
(let [type (get report :type)
text (str "#" type " | " (get report :hint) "\n"
(when id
(str (u/join (cf/get :public-uri) "/dbg/error/" id) " "))
(let [text (str "Exception: " public-uri "/dbg/error/" id " "
(when-let [pid (:profile-id report)]
(str "(pid: #uuid-" pid ")"))
(if (uuid? pid)
(str "(pid: #uuid-" pid ")")
(str "(pid: #ip-" pid ")")))
"\n"
"- host: #" (:host report) "\n"
"- tenant: #" (:tenant report) "\n"
"- logger: #" (:logger report) "\n"
"- request-path: `" (:request-path report) "`\n"
"- frontend-version: `" (:frontend-version report) "`\n"
"- backend-version: `" (:backend-version report) "`\n"
"- origin: #" (:origin report) "\n"
(when-let [href (get report :href)]
(str "- href: `" href "`\n"))
(when-let [version (get report :frontend-version)]
(str "- frontend-version: `" version "`\n"))
(when-let [version (get report :backend-version)]
(str "- backend-version: `" version "`\n"))
"\n"
"```\n"
"Trace:\n"
(:trace report)
"```")
(when-let [info (:info report)]
(str "```\n" info "```"))
(when-let [trace (:trace report)]
(str "```\n"
"Trace:\n"
trace
"```")))
resp (http/req! cfg
{:uri (cf/get :error-report-webhook)
@@ -50,28 +63,70 @@
(l/warn :hint "error on sending data"
:response (pr-str resp)))))
(defn record->report
[{:keys [::l/context ::l/id ::l/cause] :as record}]
(defn- log-record->report
[{:keys [::l/context ::l/id ::l/cause ::l/message] :as record}]
(assert (l/valid-record? record) "expectd valid log record")
(let [public-uri (cf/get :public-uri)]
{:id id
:type "exception"
:origin "logging"
:hint (or (some-> cause ex-message) @message)
:tenant (cf/get :tenant)
:host (cf/get :host)
:backend-version (:full cf/version)
:frontend-version (:frontend/version context)
:profile-id (:request/profile-id context)
:href (-> public-uri
(assoc :path (:request/path context))
(str))
:trace (ex/format-throwable cause :detail? false :header? false)}))
(defn- audit-event->report
[{:keys [::audit/context ::audit/props ::audit/id] :as event}]
{:id id
:type "exception"
:origin "audit-log"
:hint (get props :hint)
:tenant (cf/get :tenant)
:host (cf/get :host)
:public-uri (cf/get :public-uri)
:backend-version (or (:version/backend context) (:full cf/version))
:frontend-version (:version/frontend context)
:profile-id (:request/profile-id context)
:request-path (:request/path context)
:logger (::l/logger record)
:trace (ex/format-throwable cause :detail? false :header? false)})
:backend-version (:full cf/version)
:frontend-version (:version context)
:profile-id (:audit/profile-id event)
:href (get props :href)})
(defn handle-event
[cfg record]
(when @enabled
(try
(let [report (record->report record)]
(send-mattermost-notification! cfg report))
(catch Throwable cause
(l/warn :hint "unhandled error" :cause cause)))))
(defn- rlimit-event->report
[event]
{:id (::rlimit/id event)
:type "notification"
:origin "rlimit"
:hint (str "rlimit reject of "
(::rlimit/method event)
" for "
(::rlimit/uid event))
:tenant (cf/get :tenant)
:host (cf/get :host)
:backend-version (:full cf/version)
:profile-id (::rlimit/profile-id event)
:info (with-out-str
(println "Rejected by:")
(println "------------")
(println "Method: " (::rlimit/method event))
(println "Limit Name: " (::rlimit/name event))
(println "Limit Strategy:" (::rlimit/strategy event))
(println)
(println "Results & Config:")
(println "-----------------")
(doseq [result (::rlimit/results event)]
(pp/pprint (into (sorted-map) result))))})
(defn- handle-event
[cfg event event->report]
(try
(let [report (event->report event)]
(send-mattermost-notification! cfg report))
(catch Throwable cause
(l/warn :hint "unhandled error" :cause cause))))
(defmethod ig/assert-key ::reporter
[_ params]
@@ -80,27 +135,52 @@
(defmethod ig/init-key ::reporter
[_ cfg]
(when-let [uri (cf/get :error-report-webhook)]
(px/thread
{:name "penpot/mattermost-reporter"
:virtual true}
(l/info :hint "initializing error reporter" :uri uri)
(let [input (sp/chan :buf (sp/sliding-buffer 128)
:xf (filter ldb/error-record?))]
(add-watch l/log-record ::reporter #(sp/put! input %4))
(try
(loop []
(when-let [msg (sp/take! input)]
(handle-event cfg msg)
(recur)))
(catch InterruptedException _
(l/debug :hint "reporter interrupted"))
(catch Throwable cause
(l/error :hint "unexpected error" :cause cause))
(finally
(sp/close! input)
(remove-watch l/log-record ::reporter)
(l/info :hint "reporter terminated")))))))
(let [input (sp/chan :buf (sp/sliding-buffer 256))
thread (px/thread
{:name "penpot/reporter/mattermost"}
(l/info :hint "initializing error reporter" :uri uri)
(try
(loop []
(when-let [item (sp/take! input)]
(when @enabled
(cond
(::l/id item)
(handle-event cfg item log-record->report)
(::audit/id item)
(handle-event cfg item audit-event->report)
(::rlimit/id item)
(handle-event cfg item rlimit-event->report)
:else
(l/warn :hint "received unexpected item" :item item)))
(recur)))
(catch InterruptedException _
(l/debug :hint "reporter interrupted"))
(catch Throwable cause
(l/error :hint "unexpected error" :cause cause))
(finally
(l/info :hint "reporter terminated"))))]
(add-watch l/log-record ::reporter
(fn [_ _ _ record]
(when (= :error (::l/level record))
(sp/put! input record))))
{::input input
::thread thread})))
(defmethod ig/halt-key! ::reporter
[_ thread]
[_ {:keys [::input ::thread]}]
(remove-watch l/log-record ::reporter)
(some-> input sp/close!)
(some-> thread px/interrupt!))
(defn emit
"Emit an event/report into the mattermost reporter"
[cfg event]
(when-let [{:keys [::input]} (get cfg ::reporter)]
(sp/put! input event)))
+29 -6
View File
@@ -226,11 +226,10 @@
::http/server
{::http/port (cf/get :http-server-port)
::http/host (cf/get :http-server-host)
::http/router (ig/ref ::http/router)
::http/io-threads (cf/get :http-server-io-threads)
::http/max-worker-threads (cf/get :http-server-max-worker-threads)
::http/max-body-size (cf/get :http-server-max-body-size)
::http/max-multipart-body-size (cf/get :http-server-max-multipart-body-size)
::http/router (ig/ref ::http/router)
::mtx/metrics (ig/ref ::mtx/metrics)}
::ldap/provider
@@ -317,12 +316,19 @@
::climit/enabled (contains? cf/flags :rpc-climit)}
:app.rpc/rlimit
{::wrk/executor (ig/ref ::wrk/netty-executor)}
{::wrk/executor (ig/ref ::wrk/netty-executor)
:app.loggers.mattermost/reporter
(ig/ref :app.loggers.mattermost/reporter)
:app.loggers.database/reporter
(ig/ref :app.loggers.database/reporter)}
:app.rpc/methods
{::http.client/client (ig/ref ::http.client/client)
::db/pool (ig/ref ::db/pool)
::rds/pool (ig/ref ::rds/pool)
:app.nitrate/client (ig/ref :app.nitrate/client)
::wrk/executor (ig/ref ::wrk/netty-executor)
::session/manager (ig/ref ::session/manager)
::ldap/provider (ig/ref ::ldap/provider)
@@ -337,7 +343,17 @@
::setup/props (ig/ref ::setup/props)
::email/blacklist (ig/ref ::email/blacklist)
::email/whitelist (ig/ref ::email/whitelist)}
::email/whitelist (ig/ref ::email/whitelist)
:app.loggers.database/reporter
(ig/ref :app.loggers.database/reporter)
:app.loggers.mattermost/reporter
(ig/ref :app.loggers.mattermost/reporter)}
:app.nitrate/client
{::http.client/client (ig/ref ::http.client/client)
::setup/shared-keys (ig/ref ::setup/shared-keys)}
:app.rpc/management-methods
{::http.client/client (ig/ref ::http.client/client)
@@ -348,17 +364,19 @@
::sto/storage (ig/ref ::sto/storage)
::mtx/metrics (ig/ref ::mtx/metrics)
::mbus/msgbus (ig/ref ::mbus/msgbus)
:app.nitrate/client (ig/ref :app.nitrate/client)
::rds/client (ig/ref ::rds/client)
::setup/props (ig/ref ::setup/props)}
::rpc/routes
{::rpc/methods (ig/ref :app.rpc/methods)
{::rpc/methods (ig/ref :app.rpc/methods)
::rpc/management-methods (ig/ref :app.rpc/management-methods)
;; FIXME: revisit if db/pool is necessary here
::db/pool (ig/ref ::db/pool)
::session/manager (ig/ref ::session/manager)
::setup/props (ig/ref ::setup/props)}
::setup/props (ig/ref ::setup/props)
::setup/shared-keys (ig/ref ::setup/shared-keys)}
::wrk/registry
{::mtx/metrics (ig/ref ::mtx/metrics)
@@ -446,6 +464,11 @@
;; module requires the migrations to run before initialize.
::migrations (ig/ref :app.migrations/migrations)}
::setup/shared-keys
{::setup/props (ig/ref ::setup/props)
:nitrate (cf/get :nitrate-shared-key)
:exporter (cf/get :exporter-shared-key)}
::setup/clock
{}
+67 -28
View File
@@ -35,8 +35,7 @@
javax.xml.parsers.SAXParserFactory
org.apache.commons.io.IOUtils
org.im4java.core.ConvertCmd
org.im4java.core.IMOperation
org.im4java.core.Info))
org.im4java.core.IMOperation))
(def default-max-file-size
(* 1024 1024 10)) ; 10 MiB
@@ -224,17 +223,18 @@
;; If we are processing an animated gif we use the first frame with -scene 0
(let [dim-result (sh/sh "identify" "-format" "%w %h\n" path)
orient-result (sh/sh "identify" "-format" "%[EXIF:Orientation]\n" path)]
(if (and (= 0 (:exit dim-result))
(= 0 (:exit orient-result)))
(when (= 0 (:exit dim-result))
(let [[w h] (-> (:out dim-result)
str/trim
(clojure.string/split #"\s+")
(->> (mapv #(Integer/parseInt %))))
orientation (-> orient-result :out str/trim)]
(case orientation
("6" "8") {:width h :height w} ; Rotated 90 or 270 degrees
{:width w :height h})) ; Normal or unknown orientation
nil)))
orientation-exit (:exit orient-result)
orientation (-> orient-result :out str/trim)]
(if (= 0 orientation-exit)
(case orientation
("6" "8") {:width h :height w} ; Rotated 90 or 270 degrees
{:width w :height h}) ; Normal or unknown orientation
{:width w :height h}))))) ; If orientation can't be read, use dimensions as-is
(defmethod process :info
[{:keys [input] :as params}]
@@ -247,26 +247,37 @@
:hint "uploaded svg does not provides dimensions"))
(merge input info {:ts (ct/now) :size (fs/size path)}))
(let [instance (Info. (str path))
mtype' (.getProperty instance "Mime type")]
(let [path-str (str path)
identify-res (sh/sh "identify" "-format" "image/%[magick]\n" path-str)
;; identify prints one line per frame (animated GIFs, etc.); we take the first one
mtype' (if (zero? (:exit identify-res))
(-> identify-res
:out
str/trim
(str/split #"\s+" 2)
first
str/lower)
(ex/raise :type :validation
:code :invalid-image
:hint "invalid image"))
{:keys [width height]}
(or (get-dimensions-with-orientation path-str)
(do
(l/warn "Failed to read image dimensions with orientation" {:path path})
(ex/raise :type :validation
:code :invalid-image
:hint "invalid image")))]
(when (and (string? mtype)
(not= mtype mtype'))
(not= (str/lower mtype) mtype'))
(ex/raise :type :validation
:code :media-type-mismatch
:hint (str "Seems like you are uploading a file whose content does not match the extension."
"Expected: " mtype ". Got: " mtype')))
(let [{:keys [width height]}
(or (get-dimensions-with-orientation (str path))
(do
(l/warn "Failed to read image dimensions with orientation; falling back to im4java"
{:path path})
{:width (.getPageWidth instance)
:height (.getPageHeight instance)}))]
(assoc input
:width width
:height height
:size (fs/size path)
:ts (ct/now)))))))
(assoc input
:width width
:height height
:size (fs/size path)
:ts (ct/now))))))
(defmethod process-error org.im4java.core.InfoException
[error]
@@ -282,12 +293,17 @@
(defn download-image
"Download an image from the provided URI and return the media input object"
[{:keys [::http/client]} uri]
(letfn [(parse-and-validate [{:keys [headers] :as response}]
(letfn [(parse-and-validate [{:keys [status headers] :as response}]
(let [size (some-> (get headers "content-length") d/parse-integer)
mtype (get headers "content-type")
format (cm/mtype->format mtype)
max-size (cf/get :media-max-file-size default-max-file-size)]
(when-not (<= 200 status 299)
(ex/raise :type :validation
:code :unable-to-download-image
:hint (str/ffmt "unable to download image from '%': unexpected status code %" uri status)))
(when-not size
(ex/raise :type :validation
:code :unknown-size
@@ -307,9 +323,32 @@
{:size size :mtype mtype :format format}))]
(let [{:keys [body] :as response} (http/req! client
{:method :get :uri uri}
{:response-type :input-stream})
(let [{:keys [body] :as response}
(try
(http/req! client
{:method :get :uri uri}
{:response-type :input-stream})
(catch java.net.ConnectException cause
(ex/raise :type :validation
:code :unable-to-download-image
:hint (str/ffmt "unable to download image from '%': connection refused or host unreachable" uri)
:cause cause))
(catch java.net.http.HttpConnectTimeoutException cause
(ex/raise :type :validation
:code :unable-to-download-image
:hint (str/ffmt "unable to download image from '%': connection timeout" uri)
:cause cause))
(catch java.net.http.HttpTimeoutException cause
(ex/raise :type :validation
:code :unable-to-download-image
:hint (str/ffmt "unable to download image from '%': request timeout" uri)
:cause cause))
(catch java.io.IOException cause
(ex/raise :type :validation
:code :unable-to-download-image
:hint (str/ffmt "unable to download image from '%': I/O error" uri)
:cause cause)))
{:keys [size mtype]} (parse-and-validate response)
path (tmp/tempfile :prefix "penpot.media.download.")
written (io/write* path body :size size)]
+9 -1
View File
@@ -10,6 +10,7 @@
[app.common.logging :as l]
[app.db :as db]
[app.migrations.clj.migration-0023 :as mg0023]
[app.migrations.clj.migration-0145 :as mg0145]
[app.util.migrations :as mg]
[integrant.core :as ig]))
@@ -456,7 +457,14 @@
:fn (mg/resource "app/migrations/sql/0142-add-sso-provider-table.sql")}
{:name "0143-http-session-v2-table"
:fn (mg/resource "app/migrations/sql/0143-add-http-session-v2-table.sql")}])
:fn (mg/resource "app/migrations/sql/0143-add-http-session-v2-table.sql")}
{:name "0144-mod-server-error-report-table"
:fn (mg/resource "app/migrations/sql/0144-mod-server-error-report-table.sql")}
{:name "0145-fix-plugins-uri-on-profile"
:fn mg0145/migrate}])
(defn apply-migrations!
[pool name migrations]
@@ -0,0 +1,83 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.migrations.clj.migration-0145
"Migrate plugins references on profiles"
(:require
[app.common.data :as d]
[app.common.logging :as l]
[app.db :as db]
[cuerdas.core :as str]))
(def ^:private replacements
{"https://colors-to-tokens-plugin.pages.dev"
"https://colors-to-tokens.plugins.penpot.app"
"https://contrast-penpot-plugin.pages.dev"
"https://contrast.plugins.penpot.app"
"https://create-palette-penpot-plugin.pages.dev"
"https://create-palette.plugins.penpot.app"
"https://icons-penpot-plugin.pages.dev"
"https://icons.plugins.penpot.app"
"https://lorem-ipsum-penpot-plugin.pages.dev"
"https://lorem-ipsum.plugins.penpot.app"
"https://rename-layers-penpot-plugin.pages.dev"
"https://rename-layers.plugins.penpot.app"
"https://table-penpot-plugin.pages.dev"
"https://table.plugins.penpot.app"})
(defn- fix-url
[url]
(reduce-kv (fn [url prefix replacement]
(if (str/starts-with? url prefix)
(reduced (str replacement (subs url (count prefix))))
url))
url
replacements))
(defn- fix-manifest
[manifest]
(-> manifest
(d/update-when :url fix-url)
(d/update-when :host fix-url)))
(defn- fix-plugins-data
[props]
(d/update-in-when props [:plugins :data]
(fn [data]
(reduce-kv (fn [data id manifest]
(let [manifest' (fix-manifest manifest)]
(if (= manifest manifest')
data
(assoc data id manifest'))))
data
data))))
(def ^:private sql:get-profiles
"SELECT id, props FROM profile
WHERE props ?? '~:plugins'
ORDER BY created_at
FOR UPDATE")
(defn migrate
[conn]
(->> (db/plan conn [sql:get-profiles])
(run! (fn [{:keys [id props]}]
(when-let [props (some-> props db/decode-transit-pgobject)]
(let [props' (fix-plugins-data props)]
(when (not= props props')
(l/inf :hint "fixing plugins data on profile props" :profile-id (str id))
(db/update! conn :profile
{:props (db/tjson props')}
{:id id}
{::db/return-keys false}))))))))
@@ -0,0 +1,11 @@
ALTER TABLE server_error_report DROP CONSTRAINT server_error_report_pkey;
DELETE FROM server_error_report a
USING server_error_report b
WHERE a.id = b.id
AND a.ctid < b.ctid;
ALTER TABLE server_error_report ADD PRIMARY KEY (id);
CREATE INDEX server_error_report__version__idx
ON server_error_report ( version );
+130
View File
@@ -0,0 +1,130 @@
(ns app.nitrate
"Module that make calls to the external nitrate aplication"
(:require
[app.common.logging :as l]
[app.common.schema :as sm]
[app.config :as cf]
[app.http.client :as http]
[app.rpc :as-alias rpc]
[app.setup :as-alias setup]
[app.util.json :as json]
[clojure.core :as c]
[integrant.core :as ig]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HELPERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- request-builder
[cfg method uri shared-key profile-id]
(fn []
(http/req! cfg {:method method
:headers {"content-type" "application/json"
"accept" "application/json"
"x-shared-key" shared-key
"x-profile-id" (str profile-id)}
:uri uri
:version :http1.1})))
(defn- with-retries
[handler max-retries]
(fn []
(loop [attempt 1]
(let [result (try
(handler)
(catch Exception e
(if (< attempt max-retries)
::retry
(do
;; TODO Error handling
(l/error :hint "request fail after multiple retries" :cause e)
nil))))]
(if (= result ::retry)
(recur (inc attempt))
result)))))
(defn- with-validate [handler uri schema]
(fn []
(let [coercer-http (sm/coercer schema
:type :validation
:hint (str "invalid data received calling " uri))]
(try
(coercer-http (-> (handler) :body json/decode))
(catch Exception e
;; TODO Error handling
(l/error :hint "error validating json response" :cause e)
nil)))))
(defn- request-to-nitrate
[cfg method uri schema {:keys [::rpc/profile-id] :as params}]
(let [shared-key (-> cfg ::setup/shared-keys :nitrate)
full-http-call (-> (request-builder cfg method uri shared-key profile-id)
(with-retries 3)
(with-validate uri schema))]
(full-http-call)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; API
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn call
[cfg method params]
(when (contains? cf/flags :nitrate)
(let [client (get cfg ::client)
method (get client method)]
(method params))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^:private schema:organization
[:map
[:id ::sm/text]
[:name ::sm/text]])
(def ^:private schema:user
[:map
[:valid ::sm/boolean]])
(defn- get-team-org
[cfg {:keys [team-id] :as params}]
(let [baseuri (cf/get :nitrate-backend-uri)]
(request-to-nitrate cfg :get (str baseuri "/api/teams/" (str team-id)) schema:organization params)))
(defn- is-valid-user
[cfg {:keys [profile-id] :as params}]
(let [baseuri (cf/get :nitrate-backend-uri)]
(request-to-nitrate cfg :get (str baseuri "/api/users/" (str profile-id)) schema:user params)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INITIALIZATION
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod ig/init-key ::client
[_ cfg]
(when (contains? cf/flags :nitrate)
{:get-team-org (partial get-team-org cfg)
:is-valid-user (partial is-valid-user cfg)}))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; UTILS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn add-nitrate-licence-to-profile
[cfg profile]
(try
(let [nitrate-licence (call cfg :is-valid-user {:profile-id (:id profile)})]
(assoc profile :nitrate-licence (:valid nitrate-licence)))
(catch Throwable cause
(l/error :hint "failed to get nitrate licence"
:profile-id (:id profile)
:cause cause)
profile)))
(defn add-org-to-team
[cfg team params]
(let [params (assoc (or params {}) :team-id (:id team))
org (call cfg :get-team-org params)]
(assoc team :organization-id (:id org) :organization-name (:name org))))
+25 -21
View File
@@ -14,6 +14,7 @@
[app.common.spec :as us]
[app.common.time :as ct]
[app.common.uri :as u]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.http :as-alias http]
@@ -89,10 +90,14 @@
[methods]
(let [methods (update-vals methods peek)]
(fn [{:keys [params path-params method] :as request}]
(let [handler-name (:type path-params)
(let [handler-name (:method-name path-params)
etag (yreq/get-header request "if-none-match")
key-id (get request ::http/auth-key-id)
profile-id (or (::session/profile-id request)
(::actoken/profile-id request))
(::actoken/profile-id request)
(if key-id uuid/zero nil))
ip-addr (inet/parse-request request)
data (-> params
@@ -222,8 +227,8 @@
(wrap-authentication cfg $ mdata)))
(defn- process-method
[cfg module wrap-fn [f mdata]]
(l/trc :hint "add method" :module module :name (::sv/name mdata))
[cfg wrap-fn [f mdata]]
(l/trc :hint "add method" :module (::module cfg) :type (::type cfg) :name (::sv/name mdata))
(let [f (wrap-fn cfg f mdata)
k (keyword (::sv/name mdata))]
[k [mdata (partial f cfg)]]))
@@ -234,7 +239,7 @@
(defn- resolve-methods
[cfg]
(let [cfg (assoc cfg ::type "command" ::metrics-id :rpc-command-timing)]
(let [cfg (assoc cfg ::module "main" ::type "command" ::metrics-id :rpc-main-timing)]
(->> (sv/scan-ns
'app.rpc.commands.access-token
'app.rpc.commands.audit
@@ -261,7 +266,7 @@
'app.rpc.commands.verify-token
'app.rpc.commands.viewer
'app.rpc.commands.webhooks)
(map (partial process-method cfg "rpc" wrap))
(map (partial process-method cfg wrap))
(into {}))))
(def ^:private schema:methods-params
@@ -293,11 +298,13 @@
(defn- resolve-management-methods
[cfg]
(let [cfg (assoc cfg ::type "management" ::metrics-id :rpc-management-timing)]
(->> (sv/scan-ns
'app.rpc.management.subscription
'app.rpc.management.exporter)
(map (partial process-method cfg "management" wrap-management))
(let [cfg (assoc cfg ::module "management" ::type "command" ::metrics-id :rpc-management-timing)
mods (cond->> (list 'app.rpc.management.exporter)
(contains? cf/flags :nitrate)
(cons 'app.rpc.management.nitrate))]
(->> (apply sv/scan-ns mods)
(map (partial process-method cfg wrap-management))
(into {}))))
(def ^:private schema:management-methods-params
@@ -340,23 +347,20 @@
(defmethod ig/assert-key ::routes
[_ params]
(assert (map? (::setup/shared-keys params)))
(assert (db/pool? (::db/pool params)) "expect valid database pool")
(assert (some? (::setup/props params)))
(assert (session/manager? (::session/manager params)) "expect valid session manager")
(assert (valid-methods? (::methods params)) "expect valid methods map")
(assert (valid-methods? (::management-methods params)) "expect valid methods map"))
(defmethod ig/init-key ::routes
[_ {:keys [::methods ::management-methods ::setup/props] :as cfg}]
(let [public-uri (cf/get :public-uri)
management-key (or (cf/get :management-api-key)
(get props :management-key))]
[_ {:keys [::methods ::management-methods ::setup/shared-keys] :as cfg}]
(let [public-uri (cf/get :public-uri)]
["/api"
["/management"
["/methods/:type"
{:middleware [[mw/shared-key-auth management-key]
["/methods/:method-name"
{:middleware [[mw/shared-key-auth shared-keys]
[session/authz cfg]]
:handler (make-rpc-handler management-methods)}]
@@ -366,7 +370,7 @@
:description "MANAGEMENT API")]
["/main"
["/methods/:type"
["/methods/:method-name"
{:middleware [[mw/cors]
[sec/client-header-check]
[session/authz cfg]
@@ -384,7 +388,7 @@
["/openapi" {:handler (redirect (u/join public-uri "/api/main/doc/openapi"))}]
["/openapi.join" {:handler (redirect (u/join public-uri "/api/main/doc/openapi.json"))}]
["/rpc/command/:type"
["/rpc/command/:method-name"
{:middleware [[mw/cors]
[sec/client-header-check]
[session/authz cfg]
+65 -35
View File
@@ -16,6 +16,8 @@
[app.db :as db]
[app.http :as-alias http]
[app.loggers.audit :as-alias audit]
[app.loggers.database :as loggers.db]
[app.loggers.mattermost :as loggers.mm]
[app.rpc :as-alias rpc]
[app.rpc.climit :as-alias climit]
[app.rpc.doc :as-alias doc]
@@ -36,52 +38,79 @@
:context])
(defn- event->row [event]
[(uuid/next)
(:name event)
(:source event)
(:type event)
(:timestamp event)
(:created-at event)
(:profile-id event)
(db/inet (:ip-addr event))
(db/tjson (:props event))
(db/tjson (d/without-nils (:context event)))])
[(::audit/id event)
(::audit/name event)
(::audit/source event)
(::audit/type event)
(::audit/tracked-at event)
(::audit/created-at event)
(::audit/profile-id event)
(db/inet (::audit/ip-addr event))
(db/tjson (::audit/props event))
(db/tjson (d/without-nils (::audit/context event)))])
(defn- adjust-timestamp
[{:keys [timestamp created-at] :as event}]
(let [margin (inst-ms (ct/diff timestamp created-at))]
[{:keys [::audit/tracked-at ::audit/created-at] :as event}]
(let [margin (inst-ms (ct/diff tracked-at created-at))]
(if (or (neg? margin)
(> margin 3600000))
;; If event is in future or lags more than 1 hour, we reasign
;; timestamp to the server creation date
;; tracked-at to the server creation date
(-> event
(assoc :timestamp created-at)
(update :context assoc :original-timestamp timestamp))
(assoc ::audit/tracked-at created-at)
(update ::audit/context assoc :original-tracked-at tracked-at))
event)))
(defn- handle-events
[{:keys [::db/pool]} {:keys [::rpc/profile-id events] :as params}]
(defn- exception-event?
[{:keys [::audit/type ::audit/name] :as ev}]
(and (= "action" type)
(or (= "unhandled-exception" name)
(= "exception-page" name))))
(def ^:private xf:map-event-row
(comp
(map adjust-timestamp)
(map event->row)))
(defn- get-events
[{:keys [::rpc/request-at ::rpc/profile-id events] :as params}]
(let [request (-> params meta ::http/request)
ip-addr (inet/parse-request request)
tnow (ct/now)
xform (comp
(map (fn [event]
(-> event
(assoc :created-at tnow)
(assoc :profile-id profile-id)
(assoc :ip-addr ip-addr)
(assoc :source "frontend"))))
(filter :profile-id)
(map adjust-timestamp)
(map event->row))
events (sequence xform events)]
xform (map (fn [event]
{::audit/id (uuid/next)
::audit/type (:type event)
::audit/name (:name event)
::audit/props (:props event)
::audit/context (:context event)
::audit/profile-id profile-id
::audit/ip-addr ip-addr
::audit/source "frontend"
::audit/tracked-at (:timestamp event)
::audit/created-at request-at}))]
(sequence xform events)))
(defn- handle-events
[{:keys [::db/pool] :as cfg} params]
(let [events (get-events params)]
;; Look for error reports and save them on internal reports table
(when-let [events (->> events
(sequence (filter exception-event?))
(not-empty))]
(run! (partial loggers.db/emit cfg) events)
(run! (partial loggers.mm/emit cfg) events))
;; Process and save events
(when (seq events)
(db/insert-many! pool :audit-log event-columns events))))
(let [rows (sequence xf:map-event-row events)]
(db/insert-many! pool :audit-log event-columns rows)))))
(def valid-event-types
#{"action" "identify"})
(def ^:private valid-event-types
#{"action" "identify" "trigger"})
(def schema:event
(def ^:private schema:frontend-event
[:map {:title "Event"}
[:name
[:and {:gen/elements ["update-file", "get-profile"]}
@@ -93,12 +122,13 @@
[::sm/one-of {:format "string"} valid-event-types]]]
[:props
[:map-of :keyword ::sm/any]]
[:timestamp ::ct/inst]
[:context {:optional true}
[:map-of :keyword ::sm/any]]])
(def schema:push-audit-events
(def ^:private schema:push-audit-events
[:map {:title "push-audit-events"}
[:events [:vector schema:event]]])
[:events [:vector schema:frontend-event]]])
(sv/defmethod ::push-audit-events
{::climit/id :submit-audit-events/by-profile
+2 -1
View File
@@ -307,7 +307,8 @@
:content-type (:mtype input)})]
(:id sobject))
(catch Throwable cause
(l/err :hint "unable to import profile picture"
(l/wrn :hint "unable to import profile picture"
:uri uri
:cause cause)
nil)))
+74 -136
View File
@@ -79,85 +79,14 @@
;; --- FILE PERMISSIONS
(def ^:private sql:file-permissions
"select fpr.is_owner,
fpr.is_admin,
fpr.can_edit
from file_profile_rel as fpr
inner join file as f on (f.id = fpr.file_id)
where fpr.file_id = ?
and fpr.profile_id = ?
and f.deleted_at is null
union all
select tpr.is_owner,
tpr.is_admin,
tpr.can_edit
from team_profile_rel as tpr
inner join project as p on (p.team_id = tpr.team_id)
inner join file as f on (p.id = f.project_id)
where f.id = ?
and tpr.profile_id = ?
and f.deleted_at is null
union all
select ppr.is_owner,
ppr.is_admin,
ppr.can_edit
from project_profile_rel as ppr
inner join file as f on (f.project_id = ppr.project_id)
where f.id = ?
and ppr.profile_id = ?
and f.deleted_at is null")
(defn get-file-permissions
[conn profile-id file-id]
(when (and profile-id file-id)
(db/exec! conn [sql:file-permissions
file-id profile-id
file-id profile-id
file-id profile-id])))
(defn get-permissions
([conn profile-id file-id]
(let [rows (get-file-permissions conn profile-id file-id)
is-owner (boolean (some :is-owner rows))
is-admin (boolean (some :is-admin rows))
can-edit (boolean (some :can-edit rows))]
(when (seq rows)
{:type :membership
:is-owner is-owner
:is-admin (or is-owner is-admin)
:can-edit (or is-owner is-admin can-edit)
:can-read true
:is-logged (some? profile-id)})))
([conn profile-id file-id share-id]
(let [perms (get-permissions conn profile-id file-id)
ldata (some-> (db/get* conn :share-link {:id share-id :file-id file-id})
(dissoc :flags)
(update :pages db/decode-pgarray #{}))]
;; NOTE: in a future when share-link becomes more powerful and
;; will allow us specify which parts of the app is available, we
;; will probably need to tweak this function in order to expose
;; this flags to the frontend.
(cond
(some? perms) perms
(some? ldata) {:type :share-link
:can-read true
:pages (:pages ldata)
:is-logged (some? profile-id)
:who-comment (:who-comment ldata)
:who-inspect (:who-inspect ldata)}))))
(def has-edit-permissions?
(perms/make-edition-predicate-fn get-permissions))
(perms/make-edition-predicate-fn bfc/get-file-permissions))
(def has-read-permissions?
(perms/make-read-predicate-fn get-permissions))
(perms/make-read-predicate-fn bfc/get-file-permissions))
(def has-comment-permissions?
(perms/make-comment-predicate-fn get-permissions))
(perms/make-comment-predicate-fn bfc/get-file-permissions))
(def check-edition-permissions!
(perms/make-check-fn has-edit-permissions?))
@@ -170,7 +99,7 @@
(defn check-comment-permissions!
[conn profile-id file-id share-id]
(let [perms (get-permissions conn profile-id file-id share-id)
(let [perms (bfc/get-file-permissions conn profile-id file-id share-id)
can-read (has-read-permissions? perms)
can-comment (has-comment-permissions? perms)]
(when-not (or can-read can-comment)
@@ -222,7 +151,7 @@
(defn- get-minimal-file-with-perms
[cfg {:keys [:id ::rpc/profile-id]}]
(let [mfile (get-minimal-file cfg id)
perms (get-permissions cfg profile-id id)]
perms (bfc/get-file-permissions cfg profile-id id)]
(assoc mfile :permissions perms)))
(defn get-file-etag
@@ -248,7 +177,7 @@
;; will be already prefetched and we just reuse them instead
;; of making an additional database queries.
(let [perms (or (:permissions (::cond/object params))
(get-permissions conn profile-id id))]
(bfc/get-file-permissions conn profile-id id))]
(check-read-permissions! perms)
(let [team (teams/get-team conn
@@ -311,7 +240,7 @@
::sm/result schema:file-fragment}
[cfg {:keys [::rpc/profile-id file-id fragment-id share-id]}]
(db/run! cfg (fn [cfg]
(let [perms (get-permissions cfg profile-id file-id share-id)]
(let [perms (bfc/get-file-permissions cfg profile-id file-id share-id)]
(check-read-permissions! perms)
(-> (get-file-fragment cfg file-id fragment-id)
(rph/with-http-cache long-cache-duration))))))
@@ -456,8 +385,7 @@
:code :params-validation
:hint "page-id is required when object-id is provided"))
(let [perms (get-permissions conn profile-id file-id share-id)
(let [perms (bfc/get-file-permissions conn profile-id file-id share-id)
file (bfc/get-file cfg file-id :read-only? true)
proj (db/get conn :project {:id (:project-id file)})
@@ -688,11 +616,10 @@
"Get libraries used by the specified file."
{::doc/added "1.17"
::sm/params schema:get-file-libraries}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id]}]
(dm/with-open [conn (db/open pool)]
(check-read-permissions! conn profile-id file-id)
(bfc/get-file-libraries conn file-id)))
[cfg {:keys [::rpc/profile-id file-id]}]
(bfc/check-file-exists cfg file-id)
(check-read-permissions! cfg profile-id file-id)
(bfc/get-file-libraries cfg file-id))
;; --- COMMAND QUERY: Files that use this File library
@@ -777,7 +704,6 @@
f.created_at,
f.modified_at,
f.name,
f.is_shared,
f.deleted_at AS will_be_deleted_at,
ft.media_id AS thumbnail_id,
row_number() OVER w AS row_num,
@@ -785,8 +711,7 @@
FROM file AS f
INNER JOIN project AS p ON (p.id = f.project_id)
LEFT JOIN file_thumbnail AS ft on (ft.file_id = f.id
AND ft.revn = f.revn
AND ft.deleted_at is null)
AND ft.revn = f.revn)
WHERE p.team_id = ?
AND (p.deleted_at > ?::timestamptz OR
f.deleted_at > ?::timestamptz)
@@ -888,7 +813,7 @@
AND (f.deleted_at IS NULL OR f.deleted_at > now())
ORDER BY f.created_at ASC;")
(defn- absorb-library-by-file!
(defn- absorb-library-by-file
[cfg ldata file-id]
(assert (db/connection-map? cfg)
@@ -912,7 +837,7 @@
:modified-at (ct/now)
:has-media-trimmed false}))))
(defn- absorb-library
(defn- absorb-library*
"Find all files using a shared library, and absorb all library assets
into the file local libraries"
[cfg {:keys [id data] :as library}]
@@ -927,10 +852,10 @@
:library-id (str id)
:files (str/join "," (map str ids)))
(run! (partial absorb-library-by-file! cfg data) ids)
(run! (partial absorb-library-by-file cfg data) ids)
library))
(defn absorb-library!
(defn absorb-library
[{:keys [::db/conn] :as cfg} id]
(let [file (-> (bfc/get-file cfg id
:realize? true
@@ -947,7 +872,7 @@
(-> (cfeat/get-team-enabled-features cf/flags team)
(cfeat/check-file-features! (:features file)))
(absorb-library cfg file)))
(absorb-library* cfg file)))
(defn- set-file-shared
[{:keys [::db/conn] :as cfg} {:keys [profile-id id] :as params}]
@@ -960,14 +885,14 @@
;; file, we need to perform more complex operation,
;; so in this case we retrieve the complete file and
;; perform all required validations.
(let [file (-> (absorb-library! cfg id)
(let [file (-> (absorb-library cfg id)
(assoc :is-shared false))]
(db/delete! conn :file-library-rel {:library-file-id id})
(db/update! conn :file
{:is-shared false
:modified-at (ct/now)}
{:id id})
(select-keys file [:id :name :is-shared]))
file)
(and (false? (:is-shared file))
(true? (:is-shared params)))
@@ -1014,6 +939,11 @@
{:id file-id}
{::db/return-keys [:id :name :is-shared :deleted-at
:project-id :created-at :modified-at]})]
;; Remove all possible relations for that file
(db/delete! conn :file-library-rel
{:library-file-id file-id})
(wrk/submit! {::db/conn conn
::wrk/task :delete-object
::wrk/params {:object :file
@@ -1075,19 +1005,19 @@
"Link a file to a library. Returns the recursive list of libraries used by that library"
{::doc/added "1.17"
::webhooks/event? true
::sm/params schema:link-file-to-library}
[cfg {:keys [::rpc/profile-id file-id library-id] :as params}]
::sm/params schema:link-file-to-library
::db/transaction true}
[{:keys [::db/conn] :as cfg} {:keys [::rpc/profile-id file-id library-id] :as params}]
(when (= file-id library-id)
(ex/raise :type :validation
:code :invalid-library
:hint "A file cannot be linked to itself"))
(db/tx-run! cfg
(fn [{:keys [::db/conn]}]
(check-edition-permissions! conn profile-id file-id)
(check-edition-permissions! conn profile-id library-id)
(link-file-to-library conn params)
(bfc/get-libraries cfg [library-id]))))
(check-edition-permissions! conn profile-id file-id)
(check-edition-permissions! conn profile-id library-id)
(link-file-to-library conn params)
(bfc/get-libraries cfg [library-id]))
;; --- MUTATION COMMAND: unlink-file-from-library
@@ -1107,8 +1037,9 @@
::webhooks/event? true
::sm/params schema:unlink-file-to-library
::db/transaction true}
[{:keys [::db/conn] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
[{:keys [::db/conn] :as cfg} {:keys [::rpc/profile-id file-id library-id] :as params}]
(check-edition-permissions! conn profile-id file-id)
(check-edition-permissions! conn profile-id library-id)
(unlink-file-from-library conn params)
nil)
@@ -1132,8 +1063,9 @@
{::doc/added "1.17"
::sm/params schema:update-file-library-sync-status
::db/transaction true}
[{:keys [::db/conn]} {:keys [::rpc/profile-id file-id] :as params}]
[{:keys [::db/conn]} {:keys [::rpc/profile-id file-id library-id] :as params}]
(check-edition-permissions! conn profile-id file-id)
(check-edition-permissions! conn profile-id library-id)
(update-sync conn params))
;; --- MUTATION COMMAND: ignore-sync
@@ -1164,47 +1096,53 @@
;; --- MUTATION COMMAND: delete-files-immediatelly
(def ^:private sql:delete-team-files
"UPDATE file AS uf SET deleted_at = ?::timestamptz
FROM (
SELECT f.id
FROM file AS f
JOIN project AS p ON (p.id = f.project_id)
JOIN team AS t ON (t.id = p.team_id)
WHERE t.deleted_at IS NULL
AND t.id = ?
AND f.id = ANY(?::uuid[])
) AS subquery
WHERE uf.id = subquery.id
RETURNING uf.id, uf.deleted_at;")
(def ^:private sql:get-delete-team-files-candidates
"SELECT f.id
FROM file AS f
JOIN project AS p ON (p.id = f.project_id)
JOIN team AS t ON (t.id = p.team_id)
WHERE t.deleted_at IS NULL
AND t.id = ?
AND f.id = ANY(?::uuid[])")
(def ^:private schema:permanently-delete-team-files
[:map {:title "permanently-delete-team-files"}
[:team-id ::sm/uuid]
[:ids [::sm/set ::sm/uuid]]])
(defn- permanently-delete-team-files
[{:keys [::db/conn]} {:keys [::rpc/request-at team-id ids]}]
(let [ids (into #{}
d/xf:map-id
(db/exec! conn [sql:get-delete-team-files-candidates team-id
(db/create-array conn "uuid" ids)]))]
(reduce (fn [acc id]
(events/tap :progress {:file-id id :index (inc (count acc)) :total (count ids)})
(db/update! conn :file
{:deleted-at request-at}
{:id id}
{::db/return-keys false})
(wrk/submit! {::db/conn conn
::wrk/task :delete-object
::wrk/params {:object :file
:deleted-at request-at
:id id}})
(conj acc id))
#{}
ids)))
(sv/defmethod ::permanently-delete-team-files
"Mark the specified files to be deleted immediatelly on the
specified team. The team-id on params will be used to filter and
check writable permissons on team."
{::doc/added "2.12"
::sm/params schema:permanently-delete-team-files
::db/transaction true}
{::doc/added "2.13"
::sm/params schema:permanently-delete-team-files}
[{:keys [::db/conn]} {:keys [::rpc/profile-id ::rpc/request-at team-id ids]}]
(teams/check-edition-permissions! conn profile-id team-id)
(reduce (fn [acc {:keys [id deleted-at]}]
(wrk/submit! {::db/conn conn
::wrk/task :delete-object
::wrk/params {:object :file
:deleted-at deleted-at
:id id}})
(conj acc id))
#{}
(db/plan conn [sql:delete-team-files request-at team-id
(db/create-array conn "uuid" ids)])))
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id] :as params}]
(teams/check-edition-permissions! pool profile-id team-id)
(sse/response #(db/tx-run! cfg permanently-delete-team-files params)))
;; --- MUTATION COMMAND: restore-files-immediatelly
@@ -1268,7 +1206,7 @@
{:keys [files projects]}
(reduce (fn [result {:keys [id project-id]}]
(let [index (-> result :files count)]
(events/tap :progress {:file-id id :index index :total total-files})
(events/tap :progress {:file-id id :index (inc index) :total total-files})
(restore-file conn id)
(-> result
@@ -1291,7 +1229,7 @@
(sv/defmethod ::restore-deleted-team-files
"Removes the deletion mark from the specified files (and respective
projects) on the specified team."
{::doc/added "2.12"
{::doc/added "2.13"
::sse/stream? true
::sm/params schema:restore-deleted-team-files}
[cfg params]
@@ -199,15 +199,13 @@
[cfg {:keys [::rpc/profile-id file-id strip-frames-with-thumbnails] :as params}]
(db/run! cfg (fn [{:keys [::db/conn] :as cfg}]
(files/check-read-permissions! conn profile-id file-id)
(let [team (teams/get-team conn
:profile-id profile-id
:file-id file-id)
file (bfc/get-file cfg file-id
:include-deleted? true
:realize? true
:read-only? true)
strip-frames-with-thumbnails
(or (nil? strip-frames-with-thumbnails) ;; if not present, default to true
(true? strip-frames-with-thumbnails))]
@@ -333,12 +331,16 @@
;; --- MUTATION COMMAND: create-file-thumbnail
(defn- create-file-thumbnail!
[{:keys [::db/conn ::sto/storage]} {:keys [file-id revn props media] :as params}]
(defn- create-file-thumbnail
[{:keys [::db/conn ::sto/storage] :as cfg} {:keys [file-id revn props media] :as params}]
(media/validate-media-type! media)
(media/validate-media-size! media)
(let [props (db/tjson (or props {}))
(let [file (bfc/get-file cfg file-id
:include-deleted? true
:load-data? false)
props (db/tjson (or props {}))
path (:path media)
mtype (:mtype media)
hash (sto/calculate-hash path)
@@ -367,7 +369,7 @@
(db/update! conn :file-thumbnail
{:media-id (:id media)
:deleted-at nil
:deleted-at (:deleted-at file)
:updated-at tnow
:props props}
{:file-id file-id
@@ -378,6 +380,7 @@
:revn revn
:created-at tnow
:updated-at tnow
:deleted-at (:deleted-at file)
:props props
:media-id (:id media)}))
@@ -402,6 +405,8 @@
::rtry/when rtry/conflict-exception?
::sm/params schema:create-file-thumbnail}
;; FIXME: do not run the thumbnail upload inside a transaction
[cfg {:keys [::rpc/profile-id file-id] :as params}]
(db/tx-run! cfg (fn [{:keys [::db/conn] :as cfg}]
;; TODO For now we check read permissions instead of write,
@@ -409,6 +414,6 @@
;; review this approach on the future.
(files/check-read-permissions! conn profile-id file-id)
(when-not (db/read-only? conn)
(let [media (create-file-thumbnail! cfg params)]
(let [media (create-file-thumbnail cfg params)]
{:uri (files/resolve-public-uri (:id media))
:id (:id media)})))))
+36 -5
View File
@@ -6,6 +6,7 @@
(ns app.rpc.commands.fonts
(:require
[app.binfile.common :as bfc]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.schema :as sm]
@@ -26,7 +27,17 @@
[app.rpc.helpers :as rph]
[app.rpc.quotes :as quotes]
[app.storage :as sto]
[app.util.services :as sv]))
[app.storage.tmp :as tmp]
[app.util.services :as sv]
[datoteka.io :as io])
(:import
java.io.InputStream
java.io.OutputStream
java.io.SequenceInputStream
java.util.Collections))
(set! *warn-on-reflection* true)
(def valid-weight #{100 200 300 400 500 600 700 800 900 950})
(def valid-style #{"normal" "italic"})
@@ -66,7 +77,7 @@
(uuid? file-id)
(let [file (db/get-by-id conn :file file-id {:columns [:id :project-id]})
project (db/get-by-id conn :project (:project-id file) {:columns [:id :team-id]})
perms (files/get-permissions conn profile-id file-id share-id)]
perms (bfc/get-file-permissions conn profile-id file-id share-id)]
(files/check-read-permissions! perms)
(db/query conn :team-font-variant
{:team-id (:team-id project)
@@ -78,7 +89,8 @@
(def ^:private schema:create-font-variant
[:map {:title "create-font-variant"}
[:team-id ::sm/uuid]
[:data [:map-of ::sm/text ::sm/any]]
[:data [:map-of ::sm/text [:or ::sm/bytes
[::sm/vec ::sm/bytes]]]]
[:font-id ::sm/uuid]
[:font-family ::sm/text]
[:font-weight [::sm/one-of {:format "number"} valid-weight]]
@@ -104,7 +116,7 @@
(defn create-font-variant
[{:keys [::sto/storage ::db/conn]} {:keys [data] :as params}]
(letfn [(generate-missing! [data]
(letfn [(generate-missing [data]
(let [data (media/run {:cmd :generate-fonts :input data})]
(when (and (not (contains? data "font/otf"))
(not (contains? data "font/ttf"))
@@ -115,8 +127,26 @@
:hint "invalid font upload, unable to generate missing font assets"))
data))
(process-chunks [chunks]
(let [tmp (tmp/tempfile :prefix "penpot.tempfont." :suffix "")
streams (map io/input-stream chunks)
streams (Collections/enumeration streams)]
(with-open [^OutputStream output (io/output-stream tmp)
^InputStream input (SequenceInputStream. streams)]
(io/copy input output))
tmp))
(join-chunks [data]
(reduce-kv (fn [data mtype content]
(if (vector? content)
(assoc data mtype (process-chunks content))
data))
data
data))
(prepare-font [data mtype]
(when-let [resource (get data mtype)]
(let [hash (sto/calculate-hash resource)
content (-> (sto/content resource)
(sto/wrap-with-hash hash))]
@@ -155,7 +185,8 @@
:otf-file-id (:id otf)
:ttf-file-id (:id ttf)}))]
(let [data (generate-missing! data)
(let [data (join-chunks data)
data (generate-missing data)
assets (persist-fonts-files! data)
result (insert-font-variant! assets)]
(vary-meta result assoc ::audit/replace-props (update params :data (comp vec keys))))))
+10 -3
View File
@@ -21,6 +21,7 @@
[app.loggers.audit :as audit]
[app.main :as-alias main]
[app.media :as media]
[app.nitrate :as nitrate]
[app.rpc :as-alias rpc]
[app.rpc.climit :as climit]
[app.rpc.doc :as-alias doc]
@@ -88,6 +89,8 @@
;; --- QUERY: Get profile (own)
(sv/defmethod ::get-profile
{::rpc/auth false
::doc/added "1.18"
@@ -98,9 +101,13 @@
;; no profile-id is in session, and when db call raises not found. In all other
;; cases we need to reraise the exception.
(try
(-> (get-profile pool profile-id)
(strip-private-attrs)
(update :props filter-props))
(let [profile (-> (get-profile pool profile-id)
(strip-private-attrs)
(update :props filter-props))]
(if (contains? cf/flags :nitrate)
(nitrate/add-nitrate-licence-to-profile cfg profile)
profile))
(catch Throwable _
{:id uuid/zero :fullname "Anonymous User"})))
+3 -3
View File
@@ -19,7 +19,7 @@
inner join team_profile_rel as tpr on (tpr.team_id = p.team_id)
where tpr.profile_id = ?
and p.team_id = ?
and (p.deleted_at is null or p.deleted_at > now())
and (p.deleted_at is null)
and (tpr.is_admin = true or
tpr.is_owner = true or
tpr.can_edit = true)
@@ -29,7 +29,7 @@
inner join project_profile_rel as ppr on (ppr.project_id = p.id)
where ppr.profile_id = ?
and p.team_id = ?
and (p.deleted_at is null or p.deleted_at > now())
and (p.deleted_at is null)
and (ppr.is_admin = true or
ppr.is_owner = true or
ppr.can_edit = true)
@@ -47,7 +47,7 @@
left join file_thumbnail as ft on (ft.file_id = f.id and ft.revn = f.revn)
inner join projects as pr on (f.project_id = pr.id)
where f.name ilike ('%' || ? || '%')
and (f.deleted_at is null or f.deleted_at > now())
and (f.deleted_at is null)
order by f.created_at asc")
(defn search-files
+4 -1
View File
@@ -23,6 +23,7 @@
[app.main :as-alias main]
[app.media :as media]
[app.msgbus :as mbus]
[app.nitrate :as nitrate]
[app.rpc :as-alias rpc]
[app.rpc.commands.profile :as profile]
[app.rpc.doc :as-alias doc]
@@ -190,7 +191,9 @@
::sm/params schema:get-teams}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
(dm/with-open [conn (db/open pool)]
(get-teams conn profile-id)))
(cond->> (get-teams conn profile-id)
(contains? cf/flags :nitrate)
(map #(nitrate/add-org-to-team cfg % params)))))
(def ^:private sql:get-owned-teams
"SELECT t.id, t.name,
@@ -248,11 +248,11 @@
invitations (into #{}
(comp
;; We don't re-send invitations to
;; already existing members
;; We don't re-send invitations to
;; already existing members
(remove #(contains? team-members (:email %)))
;; We don't send invitations to
;; join-requested members
;; We don't send invitations to
;; join-requested members
(remove #(contains? join-requests (:email %)))
(map (fn [{:keys [email role]}]
(create-invitation cfg
+1 -2
View File
@@ -13,7 +13,6 @@
[app.config :as cf]
[app.db :as db]
[app.rpc :as-alias rpc]
[app.rpc.commands.files :as files]
[app.rpc.commands.teams :as teams]
[app.rpc.cond :as-alias cond]
[app.rpc.doc :as-alias doc]
@@ -121,7 +120,7 @@
[system {:keys [::rpc/profile-id file-id share-id] :as params}]
(db/run! system
(fn [{:keys [::db/conn] :as system}]
(let [perms (files/get-permissions conn profile-id file-id share-id)
(let [perms (bfc/get-file-permissions conn profile-id file-id share-id)
params (-> params
(assoc ::perms perms)
(assoc :profile-id profile-id))]
+114
View File
@@ -0,0 +1,114 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.rpc.management.nitrate
"Internal Nitrate HTTP RPC API. Provides authenticated access to
organization management and token validation endpoints."
(:require
[app.common.schema :as sm]
[app.common.types.profile :refer [schema:profile, schema:basic-profile]]
[app.common.types.team :refer [schema:team]]
[app.common.uuid :as uuid]
[app.db :as db]
[app.msgbus :as mbus]
[app.rpc :as-alias rpc]
[app.rpc.commands.files :as files]
[app.rpc.commands.profile :as profile]
[app.rpc.doc :as doc]
[app.util.services :as sv]))
;; ---- API: authenticate
(sv/defmethod ::authenticate
"Authenticate the current user"
{::doc/added "2.14"
::sm/params [:map]
::sm/result schema:profile}
[cfg {:keys [::rpc/profile-id] :as params}]
(let [profile (profile/get-profile cfg profile-id)]
{:id (get profile :id)
:name (get profile :fullname)
:email (get profile :email)
:photo-url (files/resolve-public-uri (get profile :photo-id))}))
;; ---- API: get-teams
(def ^:private sql:get-teams
"SELECT t.*
FROM team AS t
JOIN team_profile_rel AS tpr ON t.id = tpr.team_id
WHERE tpr.profile_id = ?
AND tpr.is_owner IS TRUE
AND t.is_default IS FALSE
AND t.deleted_at IS NULL;")
(def ^:private schema:get-teams-result
[:vector schema:team])
(sv/defmethod ::get-teams
"List teams for which current user is owner"
{::doc/added "2.14"
::sm/params [:map]
::sm/result schema:get-teams-result}
[cfg {:keys [::rpc/profile-id]}]
(let [current-user-id (-> (profile/get-profile cfg profile-id) :id)]
(->> (db/exec! cfg [sql:get-teams current-user-id])
(map #(select-keys % [:id :name])))))
;; ---- API: notify-team-change
(def ^:private schema:notify-team-change
[:map
[:id ::sm/uuid]
[:organization-id ::sm/text]])
(sv/defmethod ::notify-team-change
"Notify to Penpot a team change from nitrate"
{::doc/added "2.14"
::sm/params schema:notify-team-change
::rpc/auth false}
[cfg {:keys [id organization-id organization-name]}]
(let [msgbus (::mbus/msgbus cfg)]
(mbus/pub! msgbus
;;TODO There is a bug on dashboard with teams notifications.
;;For now we send it to uuid/zero instead of team-id
:topic uuid/zero
:message {:type :team-org-change
:team-id id
:organization-id organization-id
:organization-name organization-name})))
;; ---- API: get-managed-profiles
(def ^:private sql:get-managed-profiles
"SELECT DISTINCT p.id, p.fullname as name, p.email
FROM profile p
JOIN team_profile_rel tpr_member
ON tpr_member.profile_id = p.id
WHERE p.id <> ?
AND EXISTS (
SELECT 1
FROM team_profile_rel tpr_owner
JOIN team t
ON t.id = tpr_owner.team_id
WHERE tpr_owner.profile_id = ?
AND tpr_owner.team_id = tpr_member.team_id
AND tpr_owner.is_owner IS TRUE
AND t.is_default IS FALSE
AND t.deleted_at IS NULL);")
(def schema:managed-profile-result
[:vector schema:basic-profile])
(sv/defmethod ::get-managed-profiles
"List profiles that belong to teams for which current user is owner"
{::doc/added "2.14"
::sm/params [:map]
::sm/result schema:managed-profile-result}
[cfg {:keys [::rpc/profile-id]}]
(let [current-user-id (-> (profile/get-profile cfg profile-id) :id)]
(db/exec! cfg [sql:get-managed-profiles current-user-id current-user-id])))
@@ -1,183 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.rpc.management.subscription
(:require
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.schema.generators :as sg]
[app.common.time :as ct]
[app.db :as db]
[app.rpc :as-alias rpc]
[app.rpc.commands.profile :as profile]
[app.rpc.doc :as doc]
[app.util.services :as sv]))
;; ---- RPC METHOD: AUTHENTICATE
(def ^:private
schema:authenticate-params
[:map {:title "authenticate-params"}])
(def ^:private
schema:authenticate-result
[:map {:title "authenticate-result"}
[:profile-id ::sm/uuid]])
(sv/defmethod ::auth
{::doc/added "2.12"
::sm/params schema:authenticate-params
::sm/result schema:authenticate-result}
[_ {:keys [::rpc/profile-id]}]
{:profile-id profile-id})
;; ---- RPC METHOD: GET-CUSTOMER
;; FIXME: move to app.common.time
(def ^:private schema:timestamp
(sm/type-schema
{:type ::timestamp
:pred ct/inst?
:type-properties
{:title "inst"
:description "The same as :app.common.time/inst but encodes to epoch"
:error/message "should be an instant"
:gen/gen (->> (sg/small-int)
(sg/fmap (fn [v] (ct/inst v))))
:decode/string #(some-> % ct/inst)
:encode/string #(some-> % inst-ms)
:decode/json #(some-> % ct/inst)
:encode/json #(some-> % inst-ms)}}))
(def ^:private schema:subscription
[:map {:title "Subscription"}
[:id ::sm/text]
[:customer-id ::sm/text]
[:type [:enum
"unlimited"
"professional"
"enterprise"]]
[:status [:enum
"active"
"canceled"
"incomplete"
"incomplete_expired"
"past_due"
"paused"
"trialing"
"unpaid"]]
[:billing-period [:enum
"month"
"day"
"week"
"year"]]
[:quantity :int]
[:description [:maybe ::sm/text]]
[:created-at schema:timestamp]
[:start-date [:maybe schema:timestamp]]
[:ended-at [:maybe schema:timestamp]]
[:trial-end [:maybe schema:timestamp]]
[:trial-start [:maybe schema:timestamp]]
[:cancel-at [:maybe schema:timestamp]]
[:canceled-at [:maybe schema:timestamp]]
[:current-period-end [:maybe schema:timestamp]]
[:current-period-start [:maybe schema:timestamp]]
[:cancel-at-period-end :boolean]
[:cancellation-details
[:map {:title "CancellationDetails"}
[:comment [:maybe ::sm/text]]
[:reason [:maybe ::sm/text]]
[:feedback [:maybe
[:enum
"customer_service"
"low_quality"
"missing_feature"
"other"
"switched_service"
"too_complex"
"too_expensive"
"unused"]]]]]])
(def ^:private sql:get-customer-slots
"WITH teams AS (
SELECT tpr.team_id AS id,
tpr.profile_id AS profile_id
FROM team_profile_rel AS tpr
WHERE tpr.is_owner IS true
AND tpr.profile_id = ?
), teams_with_slots AS (
SELECT tpr.team_id AS id,
count(*) AS total
FROM team_profile_rel AS tpr
WHERE tpr.team_id IN (SELECT id FROM teams)
AND tpr.can_edit IS true
GROUP BY 1
ORDER BY 2
)
SELECT max(total) AS total FROM teams_with_slots;")
(defn- get-customer-slots
[cfg profile-id]
(let [result (db/exec-one! cfg [sql:get-customer-slots profile-id])]
(:total result)))
(def ^:private schema:get-customer-params
[:map])
(def ^:private schema:get-customer-result
[:map
[:id ::sm/uuid]
[:name :string]
[:num-editors ::sm/int]
[:subscription {:optional true} schema:subscription]])
(sv/defmethod ::get-customer
{::doc/added "2.12"
::sm/params schema:get-customer-params
::sm/result schema:get-customer-result}
[cfg {:keys [::rpc/profile-id]}]
(let [profile (profile/get-profile cfg profile-id)]
{:id (get profile :id)
:name (get profile :fullname)
:email (get profile :email)
:num-editors (get-customer-slots cfg profile-id)
:subscription (-> profile :props :subscription)}))
;; ---- RPC METHOD: GET-CUSTOMER
(def ^:private schema:update-customer-params
[:map
[:subscription [:maybe schema:subscription]]])
(def ^:private schema:update-customer-result
[:map])
(sv/defmethod ::update-customer
{::doc/added "2.12"
::sm/params schema:update-customer-params
::sm/result schema:update-customer-result}
[cfg {:keys [::rpc/profile-id subscription]}]
(let [{:keys [props] :as profile}
(profile/get-profile cfg profile-id ::db/for-update true)
props
(assoc props :subscription subscription)]
(l/dbg :hint "update customer"
:profile-id (str profile-id)
:subscription-type (get subscription :type)
:subscription-status (get subscription :status)
:subscription-quantity (get subscription :quantity))
(db/update! cfg :profile
{:props (db/tjson props)}
{:id profile-id}
{::db/return-keys false})
nil))
+56 -44
View File
@@ -52,6 +52,8 @@
[app.common.uuid :as uuid]
[app.config :as cf]
[app.http :as-alias http]
[app.loggers.database :as loggers.db]
[app.loggers.mattermost :as loggers.mm]
[app.redis :as rds]
[app.redis.script :as-alias rscript]
[app.rpc :as-alias rpc]
@@ -104,28 +106,29 @@
(def ^:private schema:limit
[:and
[:map
[::name :any]
[::name :keyword]
[::strategy schema:strategy]
[::key :string]
[::opts :string]]
[:or
[:map
[::capacity ::sm/int]
[::rate ::sm/int]
[::internal ::ct/duration]
[::params [::sm/vec :any]]]
[:map
[::nreq ::sm/int]
[::unit [:enum :days :hours :minutes :seconds :weeks]]]]])
[::opts :string]
[::capacity {:optional true} ::sm/int]
[::rate {:optional true} ::sm/int]
[::interval {:optional true} ::ct/duration]
[::params {:optional true} [::sm/vec :any]]
[::permits {:optional true} ::sm/int]
[::unit {:optional true} [:enum :days :hours :minutes :seconds :weeks]]]
[:fn (fn [attrs]
(let [contains-fn (partial contains? attrs)]
(or (every? contains-fn [::capacity ::rate ::interval])
(every? contains-fn [::permits ::unit]))))]])
(def ^:private schema:limits
[:map-of :keyword [::sm/vec schema:limit]])
(def ^:private valid-limit-tuple?
(sm/lazy-validator schema:limit-tuple))
(sm/validator schema:limit-tuple))
(def ^:private valid-rlimit-instance?
(sm/lazy-validator ::rpc/rlimit))
(sm/validator ::rpc/rlimit))
(defmethod parse-limit :window
[[name strategy opts :as vlimit]]
@@ -134,16 +137,16 @@
(merge
{::name name
::strategy strategy}
(if-let [[_ nreq unit] (re-find window-opts-re opts)]
(let [nreq (parse-long nreq)]
{::nreq nreq
(if-let [[_ permits unit] (re-find window-opts-re opts)]
(let [permits (parse-long permits)]
{::permits permits
::unit (case unit
"d" :days
"h" :hours
"m" :minutes
"s" :seconds
"w" :weeks)
::key (str "ratelimit.window." (d/name name))
::key (str "penpot.rlimit." (cf/get :tenant) ".window." (d/name name))
::opts opts})
(ex/raise :type :validation
:code :invalid-window-limit-opts
@@ -164,15 +167,15 @@
::interval interval
::opts opts
::params [(->seconds interval) rate capacity]
::key (str "ratelimit.bucket." (d/name name))})
::key (str "penpot.rlimit." (cf/get :tenant) ".bucket." (d/name name))})
(ex/raise :type :validation
:code :invalid-bucket-limit-opts
:hint (str/ffmt "looks like '%' does not have a valid format" opts))))
(defmethod process-limit :bucket
[rconn user-id now {:keys [::key ::params ::service ::capacity ::interval ::rate] :as limit}]
[rconn profile-id now {:keys [::key ::params ::method ::capacity ::interval ::rate] :as limit}]
(let [script (-> bucket-rate-limit-script
(assoc ::rscript/keys [(str key "." service "." user-id)])
(assoc ::rscript/keys [(str key "." method "." profile-id)])
(assoc ::rscript/vals (conj params (->seconds now))))
result (rds/eval rconn script)
allowed? (boolean (nth result 0))
@@ -180,7 +183,7 @@
reset (* (/ (inst-ms interval) rate)
(- capacity remaining))]
(l/trace :hint "limit processed"
:service service
:method method
:limit (name (::name limit))
:strategy (name (::strategy limit))
:opts (::opts limit)
@@ -192,30 +195,31 @@
(assoc ::lresult/remaining remaining))))
(defmethod process-limit :window
[rconn user-id now {:keys [::nreq ::unit ::key ::service] :as limit}]
[rconn uid now {:keys [::permits ::unit ::key ::method] :as limit}]
(let [ts (ct/truncate now unit)
ttl (ct/diff now (ct/plus ts {unit 1}))
script (-> window-rate-limit-script
(assoc ::rscript/keys [(str key "." service "." user-id "." (ct/format-inst ts))])
(assoc ::rscript/vals [nreq (->seconds ttl)]))
(assoc ::rscript/keys [(str key "." method "." uid "." (ct/format-inst ts))])
(assoc ::rscript/vals [permits (->seconds ttl)]))
result (rds/eval rconn script)
allowed? (boolean (nth result 0))
remaining (nth result 1)]
(l/trace :hint "limit processed"
:service service
:limit (name (::name limit))
:method method
:name (name (::name limit))
:strategy (name (::strategy limit))
:opts (::opts limit)
:allowed allowed?
:remaining remaining)
(-> limit
(assoc ::lresult/allowed allowed?)
(assoc ::lresult/timestamp ts)
(assoc ::lresult/remaining remaining)
(assoc ::lresult/reset (ct/plus ts {unit 1})))))
(defn- process-limits
[rconn user-id limits now]
(let [results (into [] (map (partial process-limit rconn user-id now)) limits)
[{:keys [::rds/conn] :as cfg} uid limits now]
(let [results (into [] (map (partial process-limit conn uid now)) limits)
remaining (->> results
(d/index-by ::name ::lresult/remaining)
(uri/map->query-string))
@@ -226,11 +230,22 @@
rejected (d/seek (complement ::lresult/allowed) results)]
(when rejected
(l/warn :hint "rejected rate limit"
:user-id (str user-id)
:limit-service (-> rejected ::service name)
:limit-name (-> rejected ::name name)
:limit-strategy (-> rejected ::strategy name)))
(let [event {::id (uuid/next)
::uid uid
::method (-> rejected ::method name)
::name (-> rejected ::name name)
::strategy (-> rejected ::strategy name)
::results results}]
(l/warn :hint "rejected rate limit"
:method (-> rejected ::method name)
:name (-> rejected ::name name)
:strategy (-> rejected ::strategy name)
:uid (str uid)
:report-id (:id event))
(loggers.mm/emit cfg event)
(loggers.db/emit cfg event)))
{::enabled true
::allowed (not (some? rejected))
@@ -243,7 +258,7 @@
[state skey sname]
(when-let [limits (or (get-in @state [::limits skey])
(get-in @state [::limits :default]))]
(into [] (map #(assoc % ::service sname)) limits)))
(into [] (map #(assoc % ::method sname)) limits)))
(defn- get-uid
[{:keys [::rpc/profile-id] :as params}]
@@ -253,10 +268,10 @@
uuid/zero)))
(defn- process-request'
[{:keys [::rds/conn] :as cfg} limits params]
[cfg limits params]
(try
(let [uid (get-uid params)
result (process-limits conn uid limits (ct/now))]
result (process-limits cfg uid limits (ct/now))]
(if (contains? cf/flags :soft-rpc-rlimit)
{::enabled false}
result))
@@ -274,8 +289,8 @@
(assert (or (nil? rlimit) (valid-rlimit-instance? rlimit)) "expected a valid rlimit instance")
(if rlimit
(let [skey (keyword (::rpc/type cfg) (->> mdata ::sv/spec name))
sname (str (::rpc/type cfg) "." (->> mdata ::sv/spec name))
(let [skey (keyword (::rpc/module cfg) (->> mdata ::sv/spec name))
sname (str (::rpc/module cfg) "." (->> mdata ::sv/spec name))
cfg (-> cfg
(assoc ::skey skey)
(assoc ::sname sname))]
@@ -371,12 +386,9 @@
(defn- on-refresh-error
[_ cause]
(when-not (instance? java.util.concurrent.RejectedExecutionException cause)
(if-let [explain (-> cause ex-data ex/explain)]
(l/warn ::l/raw (str "unable to refresh config, invalid format:\n" explain)
::l/sync? true)
(l/warn :hint "unexpected exception on loading config"
:cause cause
::l/sync? true))))
(l/warn :hint "unexpected exception on loading config"
:cause cause
::l/sync? true)))
(defn- get-config-path
[]
+1 -1
View File
@@ -25,9 +25,9 @@ local allowed = filled >= requested
local newTokens = filled
if allowed then
newTokens = filled - requested
redis.call("hset", tokensKey, "tokens", newTokens, "timestamp", timestamp)
end
redis.call("hset", tokensKey, "tokens", newTokens, "timestamp", timestamp)
redis.call("expire", tokensKey, ttl)
return { allowed, newTokens }
+33 -1
View File
@@ -17,6 +17,7 @@
[app.setup.templates]
[buddy.core.codecs :as bc]
[buddy.core.nonce :as bn]
[cuerdas.core :as str]
[integrant.core :as ig]))
(defn- generate-random-key
@@ -88,7 +89,38 @@
(-> (get-all-props conn)
(assoc :secret-key secret)
(assoc :tokens-key (keys/derive secret :salt "tokens"))
(assoc :management-key (keys/derive secret :salt "management"))
(update :instance-id handle-instance-id conn (db/read-only? pool)))))))
(sm/register! ::props [:map-of :keyword ::sm/any])
(defmethod ig/init-key ::shared-keys
[_ {:keys [::props] :as cfg}]
(let [secret (get props :secret-key)]
(d/without-nils
{:exporter
(let [key (or (get cfg :exporter)
(-> (keys/derive secret :salt "exporter")
(bc/bytes->b64-str true)))]
(if (or (str/empty? key)
(str/blank? key))
(do
(l/wrn :hint "exporter key is disabled because empty string found")
nil)
(do
(l/inf :hint "exporter key initialized" :key (d/obfuscate-string key))
key)))
:nitrate
(let [key (or (get cfg :nitrate)
(-> (keys/derive secret :salt "nitrate")
(bc/bytes->b64-str true)))]
(if (or (str/empty? key)
(str/blank? key))
(do
(l/wrn :hint "nitrate key is disabled because empty string found")
nil)
(do
(l/inf :hint "nitrate key initialized" :key (d/obfuscate-string key))
key)))})))
+25 -38
View File
@@ -9,48 +9,35 @@
modification of time offset (useful for testing and time adjustments)."
(:require
[app.common.logging :as l]
[app.common.time :as ct]
[app.setup :as-alias setup]
[integrant.core :as ig])
(:import
java.time.Clock
java.time.Duration
java.time.Instant
java.time.ZoneId))
[app.common.time :as ct]))
(defonce current
(atom {:clock (Clock/systemDefaultZone)
:offset nil}))
(defonce state
(atom {}))
(defmethod ig/init-key ::setup/clock
[_ _]
(add-watch current ::common
(fn [_ _ _ {:keys [clock offset]}]
(let [clock (if (ct/duration? offset)
(Clock/offset ^Clock clock
^Duration offset)
clock)]
(l/wrn :hint "altering clock" :clock (str clock))
(alter-var-root #'ct/*clock* (constantly clock))))))
(defn assign-offset
"Assign virtual clock offset to a specific user. Is the responsability
of RPC module to properly bind the correct clock to the user
request."
[profile-id duration]
(swap! state (fn [state]
(if (nil? duration)
(dissoc state profile-id)
(assoc state profile-id duration)))))
(defn get-offset
[profile-id]
(get @state profile-id))
(defmethod ig/halt-key! ::setup/clock
[_ _]
(remove-watch current ::common))
(defn get-clock
[profile-id]
(if-let [offset (get-offset profile-id)]
(ct/offset-clock offset)
(ct/get-system-clock)))
(defn fixed
"Get fixed clock, mainly used in tests"
[instant]
(Clock/fixed ^Instant (ct/inst instant)
^ZoneId (ZoneId/of "Z")))
(defn set-offset!
[duration]
(swap! current assoc :offset (some-> duration ct/duration)))
(defn set-clock!
(defn set-global-clock
([]
(swap! current assoc :clock (Clock/systemDefaultZone)))
(set-global-clock (ct/get-system-clock)))
([clock]
(when (instance? Clock clock)
(swap! current assoc :clock clock))))
(assert (ct/clock? clock) "expected valid clock instance")
(l/wrn :hint "altering clock" :clock (str clock))
(alter-var-root #'ct/*clock* (constantly clock))))
+3
View File
@@ -35,6 +35,9 @@
:assets-s3 :s3
nil)))
(def default-bucket
"file-media-object")
(def valid-buckets
#{"file-media-object"
"team-font-variant"
+2 -2
View File
@@ -25,7 +25,7 @@
[app.common.time :as ct]
[app.config :as cf]
[app.db :as db]
[app.storage :as-alias sto]
[app.storage :as sto]
[app.storage.impl :as impl]
[integrant.core :as ig]))
@@ -130,7 +130,7 @@
[{:keys [metadata]}]
(or (some-> metadata :bucket)
(some-> metadata :reference d/name)
"file-media-object"))
sto/default-bucket))
(defn- process-objects!
[conn has-refs? bucket objects]
+7 -2
View File
@@ -33,6 +33,7 @@
java.util.Optional
java.util.concurrent.atomic.AtomicLong
org.reactivestreams.Subscriber
software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider
software.amazon.awssdk.core.ResponseBytes
software.amazon.awssdk.core.async.AsyncRequestBody
software.amazon.awssdk.core.async.AsyncResponseTransformer
@@ -199,7 +200,8 @@
(defn- build-s3-client
[{:keys [::region ::endpoint ::wrk/netty-io-executor]}]
(let [aconfig (-> (ClientAsyncConfiguration/builder)
(let [creds-provider (DefaultCredentialsProvider/create)
aconfig (-> (ClientAsyncConfiguration/builder)
(.build))
sconfig (-> (S3Configuration/builder)
@@ -221,6 +223,7 @@
builder (.asyncConfiguration ^S3AsyncClientBuilder builder ^ClientAsyncConfiguration aconfig)
builder (.httpClient ^S3AsyncClientBuilder builder ^NettyNioAsyncHttpClient hclient)
builder (.region ^S3AsyncClientBuilder builder (lookup-region region))
builder (.credentialsProvider ^S3AsyncClientBuilder builder creds-provider)
builder (cond-> ^S3AsyncClientBuilder builder
(some? endpoint)
(.endpointOverride (URI. (str endpoint))))]
@@ -237,7 +240,8 @@
(defn- build-s3-presigner
[{:keys [::region ::endpoint]}]
(let [config (-> (S3Configuration/builder)
(let [creds-provider (DefaultCredentialsProvider/create)
config (-> (S3Configuration/builder)
(cond-> (some? endpoint) (.pathStyleAccessEnabled true))
(.build))]
@@ -245,6 +249,7 @@
(cond-> (some? endpoint) (.endpointOverride (URI. (str endpoint))))
(.region (lookup-region region))
(.serviceConfiguration ^S3Configuration config)
(.credentialsProvider creds-provider)
(.build))))
(defn- write-input-stream
+3 -2
View File
@@ -45,7 +45,8 @@
:deleted-at (ct/format-inst deleted-at))
(db/update! conn :file
{:deleted-at deleted-at}
{:deleted-at deleted-at
:is-shared false}
{:id id}
{::db/return-keys false})
@@ -53,7 +54,7 @@
(not *team-deletion*))
;; NOTE: we don't prevent file deletion on absorb operation failure
(try
(db/tx-run! cfg files/absorb-library! id)
(db/tx-run! cfg files/absorb-library id)
(catch Throwable cause
(l/warn :hint "error on absorbing library"
:file-id id
+1 -1
View File
@@ -8,7 +8,7 @@
"A generic asynchronous events notifications subsystem; used mainly
for mark event points in functions and be able to attach listeners
to them. Mainly used in http.sse for progress reporting."
(:refer-clojure :exclude [tap run!])
(:refer-clojure :exclude [run!])
(:require
[app.common.exceptions :as ex]
[app.common.logging :as l]
+8
View File
@@ -7,10 +7,18 @@
(ns app.util.template
(:require
[app.common.exceptions :as ex]
[cuerdas.core :as str]
[selmer.filters :as sf]
[selmer.parser :as sp]))
;; (sp/cache-off!)
(sf/add-filter! :abbreviate
(fn [s n]
(let [n (parse-long n)]
(str/abbreviate s n))))
(defn render
[path context]
(try
+25 -24
View File
@@ -137,33 +137,34 @@ RETURNING task.id, task.queue")
::wait)))
(run-batch []
(let [rconn (rds/connect cfg)]
(try
(-> cfg
(assoc ::rds/conn rconn)
(db/tx-run! run-batch'))
(try
(let [rconn (rds/connect cfg)]
(try
(-> cfg
(assoc ::rds/conn rconn)
(db/tx-run! run-batch'))
(finally
(.close ^AutoCloseable rconn))))
(catch InterruptedException cause
(throw cause))
(catch Exception cause
(cond
(rds/exception? cause)
(do
(l/wrn :hint "redis exception (will retry in an instant)" :cause cause)
(px/sleep timeout))
(catch InterruptedException cause
(throw cause))
(db/sql-exception? cause)
(do
(l/wrn :hint "database exception (will retry in an instant)" :cause cause)
(px/sleep timeout))
(catch Exception cause
(cond
(rds/exception? cause)
(do
(l/wrn :hint "redis exception (will retry in an instant)" :cause cause)
(px/sleep timeout))
:else
(do
(l/err :hint "unhandled exception (will retry in an instant)" :cause cause)
(px/sleep timeout))))
(db/sql-exception? cause)
(do
(l/wrn :hint "database exception (will retry in an instant)" :cause cause)
(px/sleep timeout))
(finally
(.close ^AutoCloseable rconn)))))
:else
(do
(l/err :hint "unhandled exception (will retry in an instant)" :cause cause)
(px/sleep timeout))))))
(dispatcher []
(l/inf :hint "started")
@@ -176,7 +177,7 @@ RETURNING task.id, task.queue")
(catch InterruptedException _
(l/trc :hint "interrupted"))
(catch Throwable cause
(l/err :hint " unexpected exception" :cause cause))
(l/err :hint "unexpected exception" :cause cause))
(finally
(l/inf :hint "terminated"))))]
+10 -2
View File
@@ -103,6 +103,14 @@
(assoc-in [::db/pool ::db/username] (:database-username config))
(assoc-in [::db/pool ::db/password] (:database-password config))
(assoc-in [:app.rpc/methods :app.setup/templates] templates)
(assoc-in [:app.rpc/methods :app.setup/templates] templates)
(update :app.rpc/rlimit assoc
:app.loggers.mattermost/reporter nil
:app.loggers.database/reporter nil)
(update :app.rpc/methods assoc
:app.setup/templates templates
:app.loggers.mattermost/reporter nil
:app.loggers.database/reporter nil)
(dissoc :app.srepl/server
:app.http/server
:app.http/route
@@ -595,8 +603,8 @@
(px/exec! :virtual #(rcp/write-body-to-stream body nil output))
(into []
(map (fn [{:keys [event data]}]
[(keyword event)
(tr/decode-str data)]))
(d/vec2 (keyword event)
(tr/decode-str data))))
(parse-sse (slurp' input)))
(finally
(.close input)))))
@@ -86,7 +86,7 @@
(t/deftest shared-key-auth
(let [handler (#'app.http.middleware/wrap-shared-key-auth
(fn [req] {::yres/status 200})
"secret-key")]
{:test1 "secret-key"})]
(let [response (handler (->DummyRequest {} {}))]
(t/is (= 403 (::yres/status response))))
@@ -95,6 +95,9 @@
(t/is (= 403 (::yres/status response))))
(let [response (handler (->DummyRequest {"x-shared-key" "secret-key"} {}))]
(t/is (= 403 (::yres/status response))))
(let [response (handler (->DummyRequest {"x-shared-key" "test1 secret-key"} {}))]
(t/is (= 200 (::yres/status response))))))
(t/deftest access-token-authz
@@ -17,7 +17,6 @@
[app.db.sql :as sql]
[app.http :as http]
[app.rpc :as-alias rpc]
[app.setup.clock :as clock]
[app.storage :as sto]
[backend-tests.helpers :as th]
[clojure.test :as t]
@@ -134,7 +133,7 @@
;; this will run pending task triggered by deleting user snapshot
(th/run-pending-tasks!)
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
(let [res (th/run-task! :objects-gc {})]
;; delete 2 snapshots and 2 file data entries
(t/is (= 4 (:processed res)))))))))
+59 -10
View File
@@ -19,7 +19,6 @@
[app.http :as http]
[app.rpc :as-alias rpc]
[app.rpc.commands.files :as files]
[app.setup.clock :as clock]
[app.storage :as sto]
[backend-tests.helpers :as th]
[clojure.test :as t]
@@ -842,7 +841,7 @@
out (th/command! data)
error (:error out)]
;; (th/print-result! out)
;; (th/print-result! out)
(t/is (th/ex-info? error))
(t/is (th/ex-of-type? error :not-found))))
@@ -864,10 +863,56 @@
out (th/command! data)
error (:error out)]
;; (th/print-result! out)
;; (th/print-result! out)
(t/is (th/ex-info? error))
(t/is (th/ex-of-type? error :not-found))))
(t/deftest permissions-checks-unlink-library
(let [profile1 (th/create-profile* 1)
profile2 (th/create-profile* 2)
file1 (th/create-file* 1 {:project-id (:default-project-id profile1)
:profile-id (:id profile1)
:is-shared true})
file2 (th/create-file* 2 {:project-id (:default-project-id profile1)
:profile-id (:id profile1)})]
(let [data {::th/type :unlink-file-from-library
::rpc/profile-id (:id profile2)
:file-id (:id file2)
:library-id (:id file1)}
out (th/command! data)
error (:error out)]
;; (th/print-result! out)
(t/is (th/ex-info? error))
(t/is (th/ex-of-type? error :not-found)))))
(t/deftest permissions-checks-update-file-library-status
(let [profile1 (th/create-profile* 1)
profile2 (th/create-profile* 2)
file1 (th/create-file* 1 {:project-id (:default-project-id profile1)
:profile-id (:id profile1)
:is-shared true})
file2 (th/create-file* 2 {:project-id (:default-project-id profile1)
:profile-id (:id profile1)})]
(let [data {::th/type :update-file-library-sync-status
::rpc/profile-id (:id profile2)
:file-id (:id file2)
:library-id (:id file1)}
out (th/command! data)
error (:error out)]
;; (th/print-result! out)
(t/is (th/ex-info? error))
(t/is (th/ex-of-type? error :not-found)))))
(t/deftest deletion
(let [profile1 (th/create-profile* 1)
file (th/create-file* 1 {:project-id (:default-project-id profile1)
@@ -922,7 +967,7 @@
(t/is (= 0 (:processed result))))
;; run permanent deletion
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
(let [result (th/run-task! :objects-gc {})]
(t/is (= 3 (:processed result)))))
@@ -1262,7 +1307,7 @@
(t/is (= 1 (count rows)))
(t/is (every? #(some? (:data %)) rows)))
;; Mark the file ellegible again for GC
;; Mark the file ellegible again for GC
(th/db-update! :file
{:has-media-trimmed false}
{:id (:id file)})
@@ -1319,7 +1364,7 @@
{:file-id (:id file)
:type "fragment"}
{:order-by [:created-at]})]
;; (pp/pprint rows)
;; (pp/pprint rows)
(t/is (= 2 (count rows)))
(t/is (nil? (:data row1)))
(t/is (= "storage" (:backend row1)))
@@ -1875,7 +1920,7 @@
file-id (uuid/next)
now (ct/inst "2025-10-31T00:00:00Z")]
(binding [ct/*clock* (clock/fixed now)]
(binding [ct/*clock* (ct/fixed-clock now)]
(let [data {::th/type :create-file
::rpc/profile-id (:id prof)
:project-id proj-id
@@ -1921,7 +1966,11 @@
;; (th/print-result! out)
(t/is (nil? (:error out)))
(let [result (:result out)]
(t/is (= (:ids data) result)))
(t/is (fn? result))
(let [[ev1 ev2 :as events] (th/consume-sse result)]
(t/is (= 2 (count events)))
(t/is (= (:ids data) (val ev2)))))
(let [row (th/db-exec-one! ["select * from file where id = ?" file-id])]
(t/is (= (:deleted-at row) now)))))))
@@ -1933,7 +1982,7 @@
file-id (uuid/next)
now (ct/inst "2025-10-31T00:00:00Z")]
(binding [ct/*clock* (clock/fixed now)]
(binding [ct/*clock* (ct/fixed-clock now)]
(let [data {::th/type :create-file
::rpc/profile-id (:id prof)
:project-id proj-id
@@ -1996,7 +2045,7 @@
team-id (:default-team-id profile)
now (ct/inst "2025-10-31T00:00:00Z")]
(binding [ct/*clock* (clock/fixed now)]
(binding [ct/*clock* (ct/fixed-clock now)]
(let [project (th/create-project* 1 {:profile-id (:id profile)
:team-id team-id})
file (th/create-file* 1 {:profile-id (:id profile)
@@ -85,7 +85,7 @@
(t/is (map? (:result out))))
;; run the task again
(let [res (binding [ct/*clock* (clock/fixed (ct/in-future {:minutes 31}))]
(let [res (binding [ct/*clock* (ct/fixed-clock (ct/in-future {:minutes 31}))]
(th/run-task! "storage-gc-touched" {}))]
(t/is (= 2 (:freeze res))))
@@ -136,7 +136,7 @@
(t/is (some? (sto/get-object storage (:media-id row2))))
;; run the task again
(let [res (binding [ct/*clock* (clock/fixed (ct/in-future {:minutes 31}))]
(let [res (binding [ct/*clock* (ct/fixed-clock (ct/in-future {:minutes 31}))]
(th/run-task! :storage-gc-touched {}))]
(t/is (= 1 (:delete res)))
(t/is (= 0 (:freeze res))))
@@ -147,7 +147,7 @@
;; Run the storage gc deleted task, it should permanently delete
;; all storage objects related to the deleted thumbnails
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
(let [res (th/run-task! :storage-gc-deleted {})]
(t/is (= 1 (:deleted res)))))
@@ -247,7 +247,7 @@
;; Run the storage gc deleted task, it should permanently delete
;; all storage objects related to the deleted thumbnails
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
(let [result (th/run-task! :storage-gc-deleted {})]
(t/is (= 1 (:deleted result)))))
+30 -4
View File
@@ -12,7 +12,6 @@
[app.db :as db]
[app.http :as http]
[app.rpc :as-alias rpc]
[app.setup.clock :as clock]
[app.storage :as sto]
[backend-tests.helpers :as th]
[clojure.test :as t]
@@ -147,7 +146,7 @@
(t/is (= 0 (:freeze res)))
(t/is (= 0 (:delete res))))
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
(let [res (th/run-task! :objects-gc {})]
(t/is (= 2 (:processed res))))
@@ -208,7 +207,7 @@
(t/is (= 0 (:freeze res)))
(t/is (= 0 (:delete res))))
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
(let [res (th/run-task! :objects-gc {})]
(t/is (= 1 (:processed res))))
@@ -268,10 +267,37 @@
(t/is (= 0 (:freeze res)))
(t/is (= 0 (:delete res))))
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
(let [res (th/run-task! :objects-gc {})]
(t/is (= 1 (:processed res))))
(let [res (th/run-task! :storage-gc-touched {})]
(t/is (= 0 (:freeze res)))
(t/is (= 3 (:delete res)))))))
(t/deftest input-sanitization-1
(with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}]
(let [prof (th/create-profile* 1 {:is-active true})
team-id (:default-team-id prof)
proj-id (:default-project-id prof)
font-id (uuid/custom 10 1)
ttfdata (-> (io/resource "backend_tests/test_files/font-1.ttf")
(io/read*))
params {::th/type :create-font-variant
::rpc/profile-id (:id prof)
:team-id team-id
:font-id font-id
:font-family "somefont"
:font-weight 400
:font-style "normal"
:data {"font/ttf" "/etc/passwd"}}
out (th/command! params)]
(t/is (= 0 (:call-count @mock)))
;; (th/print-result! out)
(let [error (:error out)
error-data (ex-data error)]
(t/is (th/ex-info? error))))))
+101 -1
View File
@@ -9,11 +9,14 @@
[app.common.time :as ct]
[app.common.uuid :as uuid]
[app.db :as db]
[app.http.client :as http]
[app.media :as media]
[app.rpc :as-alias rpc]
[app.storage :as sto]
[backend-tests.helpers :as th]
[clojure.test :as t]
[datoteka.fs :as fs]))
[datoteka.fs :as fs]
[mockery.core :refer [with-mocks]]))
(t/use-fixtures :once th/state-init)
(t/use-fixtures :each th/database-reset)
@@ -278,3 +281,100 @@
error-data (ex-data error)]
(t/is (th/ex-info? error))
(t/is (= (:type error-data) :not-found)))))
(t/deftest download-image-connection-error
(t/testing "connection refused raises validation error"
(with-mocks [http-mock {:target 'app.http.client/req!
:throw (java.net.ConnectException. "Connection refused")}]
(let [cfg {::http/client :mock-client}
err (try
(media/download-image cfg "http://unreachable.invalid/image.png")
nil
(catch clojure.lang.ExceptionInfo e e))]
(t/is (some? err))
(t/is (= :validation (:type (ex-data err))))
(t/is (= :unable-to-download-image (:code (ex-data err)))))))
(t/testing "connection timeout raises validation error"
(with-mocks [http-mock {:target 'app.http.client/req!
:throw (java.net.http.HttpConnectTimeoutException. "Connect timed out")}]
(let [cfg {::http/client :mock-client}
err (try
(media/download-image cfg "http://unreachable.invalid/image.png")
nil
(catch clojure.lang.ExceptionInfo e e))]
(t/is (some? err))
(t/is (= :validation (:type (ex-data err))))
(t/is (= :unable-to-download-image (:code (ex-data err)))))))
(t/testing "request timeout raises validation error"
(with-mocks [http-mock {:target 'app.http.client/req!
:throw (java.net.http.HttpTimeoutException. "Request timed out")}]
(let [cfg {::http/client :mock-client}
err (try
(media/download-image cfg "http://unreachable.invalid/image.png")
nil
(catch clojure.lang.ExceptionInfo e e))]
(t/is (some? err))
(t/is (= :validation (:type (ex-data err))))
(t/is (= :unable-to-download-image (:code (ex-data err)))))))
(t/testing "I/O error raises validation error"
(with-mocks [http-mock {:target 'app.http.client/req!
:throw (java.io.IOException. "Stream closed")}]
(let [cfg {::http/client :mock-client}
err (try
(media/download-image cfg "http://unreachable.invalid/image.png")
nil
(catch clojure.lang.ExceptionInfo e e))]
(t/is (some? err))
(t/is (= :validation (:type (ex-data err))))
(t/is (= :unable-to-download-image (:code (ex-data err))))))))
(t/deftest download-image-status-code-error
(t/testing "404 status raises validation error"
(with-mocks [http-mock {:target 'app.http.client/req!
:return {:status 404
:headers {"content-type" "text/html"
"content-length" "0"}
:body nil}}]
(let [cfg {::http/client :mock-client}
err (try
(media/download-image cfg "http://example.com/not-found.png")
nil
(catch clojure.lang.ExceptionInfo e e))]
(t/is (some? err))
(t/is (= :validation (:type (ex-data err))))
(t/is (= :unable-to-download-image (:code (ex-data err)))))))
(t/testing "500 status raises validation error"
(with-mocks [http-mock {:target 'app.http.client/req!
:return {:status 500
:headers {"content-type" "text/html"
"content-length" "0"}
:body nil}}]
(let [cfg {::http/client :mock-client}
err (try
(media/download-image cfg "http://example.com/server-error.png")
nil
(catch clojure.lang.ExceptionInfo e e))]
(t/is (some? err))
(t/is (= :validation (:type (ex-data err))))
(t/is (= :unable-to-download-image (:code (ex-data err)))))))
(t/testing "302 status raises validation error"
(with-mocks [http-mock {:target 'app.http.client/req!
:return {:status 302
:headers {"content-type" "text/html"
"content-length" "0"}
:body nil}}]
(let [cfg {::http/client :mock-client}
err (try
(media/download-image cfg "http://example.com/redirect.png")
nil
(catch clojure.lang.ExceptionInfo e e))]
(t/is (some? err))
(t/is (= :validation (:type (ex-data err))))
(t/is (= :unable-to-download-image (:code (ex-data err))))))))
@@ -536,7 +536,7 @@
:token rtoken}
{:keys [result error] :as out} (th/command! data)]
;; (th/print-result! out)
;; (th/print-result! out)
(t/is (nil? error))
(t/is (map? result))
(t/is (string? (:invitation-token result))))))
@@ -12,7 +12,6 @@
[app.db :as db]
[app.http :as http]
[app.rpc :as-alias rpc]
[app.setup.clock :as clock]
[backend-tests.helpers :as th]
[clojure.test :as t]))
@@ -31,7 +30,7 @@
:team-id (:id team)
:name "test project"}
out (th/command! data)]
;; (th/print-result! out)
;; (th/print-result! out)
(t/is (nil? (:error out)))
(let [result (:result out)]
@@ -94,7 +93,7 @@
:id project-id}
out (th/command! data)]
;; (th/print-result! out)
;; (th/print-result! out)
(t/is (nil? (:error out)))
(t/is (nil? (:result out))))
@@ -228,7 +227,7 @@
(t/is (= 0 (count result)))))
;; run permanent deletion
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
(let [result (th/run-task! :objects-gc {})]
(t/is (= 1 (:processed result)))))
Loaded 100 of 1352 files, more files were not shown because too many files have changed in this diff. Show more