From 935728aa3908724ed9d8dbe47e41e60183438fe2 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 5 Dec 2025 10:25:03 +0100 Subject: [PATCH 01/12] :wrench: Backport build-tag github workflow from develop --- .github/workflows/build-tag.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/build-tag.yml b/.github/workflows/build-tag.yml index 9f5bc8a512..ca51181cbd 100644 --- a/.github/workflows/build-tag.yml +++ b/.github/workflows/build-tag.yml @@ -21,6 +21,22 @@ jobs: with: gh_ref: ${{ github.ref_name }} + notify: + name: Notifications + runs-on: ubuntu-24.04 + needs: build-docker + + steps: + - name: Notify Mattermost + uses: mattermost/action-mattermost-notify@master + with: + MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }} + MATTERMOST_CHANNEL: bot-alerts-cicd + TEXT: | + 🐳 *[PENPOT] Docker image available.* + 🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + @infra + publish-final-tag: if: ${{ !contains(github.ref_name, '-RC') && !contains(github.ref_name, '-alpha') && !contains(github.ref_name, '-beta') && contains(github.ref_name, '.') }} needs: build-docker From 983487d73cf86125edeea8587332254a7c0db6d8 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Tue, 20 Jan 2026 10:56:27 +0100 Subject: [PATCH 02/12] :bug: Fix shadow token reference validation (#8128) --- frontend/src/app/main/ui/forms.cljs | 3 ++- .../management/forms/controls/input.cljs | 1 + .../tokens/management/forms/generic_form.cljs | 18 +++++++++++------- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/main/ui/forms.cljs b/frontend/src/app/main/ui/forms.cljs index 4988f589d4..a126ff4320 100644 --- a/frontend/src/app/main/ui/forms.cljs +++ b/frontend/src/app/main/ui/forms.cljs @@ -52,7 +52,8 @@ (let [form (mf/use-ctx context) disabled? (or (and (some? form) (or (not (:valid @form)) - (seq (:external-errors @form)))) + (seq (:async-errors @form)) + (seq (:extra-errors @form)))) (true? disabled)) handle-key-down-save (mf/use-fn diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs index d45afcd9c2..cc9cb79426 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs @@ -332,6 +332,7 @@ message (tr "workspace.tokens.resolved-value" (or resolved-value value))] (swap! form update :errors dissoc :value) (swap! form update :extra-errors dissoc :value) + (swap! form update :async-errors dissoc :reference) (if (= input-value (str resolved-value)) (reset! hint* {}) (reset! hint* {:message message :type "hint"})))))))] diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs index cb1f3a1902..038dbeae03 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs @@ -101,13 +101,6 @@ active-tab* (mf/use-state #(if (cft/is-reference? token) :reference :composite)) active-tab (deref active-tab*) - on-toggle-tab - (mf/use-fn - (mf/deps) - (fn [new-tab] - (let [new-tab (keyword new-tab)] - (reset! active-tab* new-tab)))) - token (mf/with-memo [token] (or token {:type token-type})) @@ -144,6 +137,17 @@ (fm/use-form :schema schema :initial initial) + on-toggle-tab + (mf/use-fn + (mf/deps form) + (fn [new-tab] + (let [new-tab (keyword new-tab)] + (if (= new-tab :reference) + (swap! form assoc-in [:async-errors :reference] + {:message "Need valid reference"}) + (swap! form update :async-errors dissoc :reference)) + (reset! active-tab* new-tab)))) + warning-name-change? (not= (get-in @form [:data :name]) (:name initial)) From 31054099ff539af9d5821f65042e115e1557a854 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 19 Jan 2026 11:23:35 +0100 Subject: [PATCH 03/12] :sparkles: Use pseudo-names on release builds of frontend (#8105) --- frontend/shadow-cljs.edn | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/shadow-cljs.edn b/frontend/shadow-cljs.edn index d1776b8064..bd09d46e6d 100644 --- a/frontend/shadow-cljs.edn +++ b/frontend/shadow-cljs.edn @@ -75,6 +75,7 @@ {:fn-invoke-direct true :optimizations #shadow/env ["PENPOT_BUILD_OPTIMIZATIONS" :as :keyword :default :advanced] :source-map true + :pseudo-names true :elide-asserts true :anon-fn-naming-policy :off :cross-chunk-method-motion false From 1ffa956251aad3579ab1d3a1813e1f42cda7e011 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 20 Jan 2026 12:13:42 +0100 Subject: [PATCH 04/12] :sparkles: Include timestamp on version tag --- frontend/resources/templates/index.mustache | 7 ++- .../resources/templates/rasterizer.mustache | 3 +- frontend/resources/templates/render.mustache | 4 +- frontend/scripts/_helpers.js | 57 +++++++++---------- frontend/scripts/build | 20 +++---- frontend/scripts/build-storybook | 14 ++--- frontend/src/app/config.cljs | 12 ++-- frontend/src/app/util/i18n.cljs | 2 +- render-wasm/_build_env | 4 +- 9 files changed, 62 insertions(+), 61 deletions(-) diff --git a/frontend/resources/templates/index.mustache b/frontend/resources/templates/index.mustache index f6636c9e9e..d6437a8969 100644 --- a/frontend/resources/templates/index.mustache +++ b/frontend/resources/templates/index.mustache @@ -17,17 +17,18 @@ - + {{#isDebug}} - + {{/isDebug}} - + diff --git a/frontend/resources/templates/rasterizer.mustache b/frontend/resources/templates/rasterizer.mustache index 56e2adaf7a..90a7f1dfdc 100644 --- a/frontend/resources/templates/rasterizer.mustache +++ b/frontend/resources/templates/rasterizer.mustache @@ -3,10 +3,11 @@ Penpot - Rasterizer - + diff --git a/frontend/resources/templates/render.mustache b/frontend/resources/templates/render.mustache index cf938583ec..4de213f9ad 100644 --- a/frontend/resources/templates/render.mustache +++ b/frontend/resources/templates/render.mustache @@ -4,10 +4,12 @@ Penpot - Render - + + diff --git a/frontend/scripts/_helpers.js b/frontend/scripts/_helpers.js index 1065327d83..c1c018c74c 100644 --- a/frontend/scripts/_helpers.js +++ b/frontend/scripts/_helpers.js @@ -27,9 +27,11 @@ export function startWorker() { }); } -export const isDebug = process.env.NODE_ENV !== "production"; -export const CURRENT_VERSION = process.env.CURRENT_VERSION || "develop"; -export const BUILD_DATE = process.env.BUILD_DATE || "" + new Date(); +export const IS_DEBUG = process.env.NODE_ENV !== "production"; +export const BUILD_DATE = process.env.BUILD_DATE || (new Date().toString()) ; +export const BUILD_TS = process.env.BUILD_TS || Date.now(); +export const VERSION = process.env.VERSION || "develop"; +export const VERSION_TAG = process.env.VERSION_TAG || VERSION; async function findFiles(basePath, predicate, options = {}) { predicate = @@ -193,25 +195,25 @@ async function generateManifest() { render_main: "./js/render.js", rasterizer_main: "./js/rasterizer.js", - config: "./js/config.js?version=" + CURRENT_VERSION, - polyfills: "./js/polyfills.js?version=" + CURRENT_VERSION, - libs: "./js/libs.js?version=" + CURRENT_VERSION, - worker_main: "./js/worker/main.js?version=" + CURRENT_VERSION, - default_translations: "./js/translation.en.js?version=" + CURRENT_VERSION, + config: "./js/config.js?version=" + VERSION_TAG, + polyfills: "./js/polyfills.js?version=" + VERSION_TAG, + libs: "./js/libs.js?version=" + VERSION_TAG, + worker_main: "./js/worker/main.js?version=" + VERSION_TAG, + default_translations: "./js/translation.en.js?version=" + VERSION_TAG, importmap: JSON.stringify({ "imports": { - "./js/shared.js": "./js/shared.js?version=" + CURRENT_VERSION, - "./js/main.js": "./js/main.js?version=" + CURRENT_VERSION, - "./js/render.js": "./js/render.js?version=" + CURRENT_VERSION, - "./js/render-wasm.js": "./js/render-wasm.js?version=" + CURRENT_VERSION, - "./js/rasterizer.js": "./js/rasterizer.js?version=" + CURRENT_VERSION, - "./js/main-dashboard.js": "./js/main-dashboard.js?version=" + CURRENT_VERSION, - "./js/main-auth.js": "./js/main-auth.js?version=" + CURRENT_VERSION, - "./js/main-viewer.js": "./js/main-viewer.js?version=" + CURRENT_VERSION, - "./js/main-settings.js": "./js/main-settings.js?version=" + CURRENT_VERSION, - "./js/main-workspace.js": "./js/main-workspace.js?version=" + CURRENT_VERSION, - "./js/util-highlight.js": "./js/util-highlight.js?version=" + CURRENT_VERSION + "./js/shared.js": "./js/shared.js?version=" + VERSION_TAG, + "./js/main.js": "./js/main.js?version=" + VERSION_TAG, + "./js/render.js": "./js/render.js?version=" + VERSION_TAG, + "./js/render-wasm.js": "./js/render-wasm.js?version=" + VERSION_TAG, + "./js/rasterizer.js": "./js/rasterizer.js?version=" + VERSION_TAG, + "./js/main-dashboard.js": "./js/main-dashboard.js?version=" + VERSION_TAG, + "./js/main-auth.js": "./js/main-auth.js?version=" + VERSION_TAG, + "./js/main-viewer.js": "./js/main-viewer.js?version=" + VERSION_TAG, + "./js/main-settings.js": "./js/main-settings.js?version=" + VERSION_TAG, + "./js/main-workspace.js": "./js/main-workspace.js?version=" + VERSION_TAG, + "./js/util-highlight.js": "./js/util-highlight.js?version=" + VERSION_TAG } }) }; @@ -222,11 +224,12 @@ async function generateManifest() { async function renderTemplate(path, context = {}, partials = {}) { const content = await fs.readFile(path, { encoding: "utf-8" }); - const ts = Math.floor(new Date()); - context = Object.assign({}, context, { - ts: ts, - isDebug, + isDebug: IS_DEBUG, + version: VERSION, + version_tag: VERSION_TAG, + build_date: BUILD_DATE, + build_ts: BUILD_TS, }); return mustache.render(content, context, partials); @@ -390,7 +393,6 @@ async function generateSvgSprites() { } async function generateTemplates() { - const isDebug = process.env.NODE_ENV !== "production"; await fs.mkdir("./resources/public/", { recursive: true }); const manifest = await generateManifest(); @@ -415,10 +417,7 @@ async function generateTemplates() { }; const context = { - manifest: manifest, - version: CURRENT_VERSION, - build_date: BUILD_DATE, - isDebug, + manifest: manifest }; content = await renderTemplate( @@ -487,7 +486,7 @@ export async function compileStyles() { await fs.mkdir("./resources/public/css", { recursive: true }); await fs.writeFile("./resources/public/css/main.css", result); - if (isDebug) { + if (IS_DEBUG) { let debugCSS = await compileSassDebug(worker); await fs.writeFile("./resources/public/css/debug.css", debugCSS); } diff --git a/frontend/scripts/build b/frontend/scripts/build index 613bb29fd2..eb0d2dd221 100755 --- a/frontend/scripts/build +++ b/frontend/scripts/build @@ -2,26 +2,26 @@ # NOTE: this script should be called from the parent directory to # properly work. +set -ex + export INCLUDE_STORYBOOK=${BUILD_STORYBOOK:-no}; export INCLUDE_WASM=${BUILD_WASM:-yes}; -export CURRENT_VERSION=$1; -export BUILD_DATE=$(date -R); -export CURRENT_HASH=${CURRENT_HASH:-$(git rev-parse --short HEAD)}; export EXTRA_PARAMS=$SHADOWCLJS_EXTRA_PARAMS; -export TS=$(date +%s); + +export BUILD_DATE=$(date -R); +export BUILD_TS=$(date +%s); + +export VERSION=${1:-develop}; +export VERSION_TAG="${VERSION}-${BUILD_TS}"; # Some cljs reacts on this environment variable for define more # performant code on macros (example: rumext) export NODE_ENV=production; -echo "Current path:" -echo $PATH - -set -ex corepack enable; corepack install; -yarn install || exit 1; +yarn install; rm -rf target/dist; rm -rf resources/public; @@ -37,7 +37,7 @@ yarn run build:app:main $EXTRA_PARAMS; yarn run build:app:libs; yarn run build:app:assets; -sed -i "s/\.\/render.js/.\/render.js?version=$CURRENT_VERSION/g" resources/public/js/worker/main*.js +sed -i "s/\.\/render.js/.\/render.js?version=$VERSION_TAG/g" resources/public/js/worker/main*.js rsync -avr resources/public/ target/dist/ diff --git a/frontend/scripts/build-storybook b/frontend/scripts/build-storybook index 8588fd7866..1b4a0ab791 100755 --- a/frontend/scripts/build-storybook +++ b/frontend/scripts/build-storybook @@ -2,18 +2,16 @@ # NOTE: this script should be called from the parent directory to # properly work. -export CURRENT_VERSION=$1; +set -ex + +export BUILD_TS=$(date +%s); export BUILD_DATE=$(date -R); -export CURRENT_HASH=${CURRENT_HASH:-$(git rev-parse --short HEAD)}; -export TS=$(date +%s); + +export VERSION=${1:-develop}; +export VERSION_TAG="${VERSION}-${BUILD_TS}"; export NODE_ENV=production; -echo "Current path:" -echo $PATH - -set -ex - corepack enable; corepack install || exit 1; yarn install || exit 1; diff --git a/frontend/src/app/config.cljs b/frontend/src/app/config.cljs index 9e71ac802e..a1e6a6afda 100644 --- a/frontend/src/app/config.cljs +++ b/frontend/src/app/config.cljs @@ -95,6 +95,7 @@ (def browser (parse-browser)) (def platform (parse-platform)) +(def version-tag (obj/get global "penpotVersionTag")) (def terms-of-service-uri (obj/get global "penpotTermsOfServiceURI")) (def privacy-policy-uri (obj/get global "penpotPrivacyPolicyURI")) (def flex-help-uri (obj/get global "penpotGridHelpURI" "https://help.penpot.app/user-guide/flexible-layouts/")) @@ -190,9 +191,8 @@ (defn resolve-href [resource] - (let [version (get version :full) - href (-> public-uri - (u/ensure-path-slash) - (u/join resource) - (get :path))] - (str href "?version=" version))) + (let [href (-> public-uri + (u/ensure-path-slash) + (u/join resource) + (get :path))] + (str href "?version=" version-tag))) diff --git a/frontend/src/app/util/i18n.cljs b/frontend/src/app/util/i18n.cljs index 61826a798e..9eab6a6018 100644 --- a/frontend/src/app/util/i18n.cljs +++ b/frontend/src/app/util/i18n.cljs @@ -114,7 +114,7 @@ (defn- load [locale] - (let [path (str "./translation." locale ".js?version=" (:full cf/version))] + (let [path (str "./translation." locale ".js?version=" cf/version-tag)] (->> (mod/import path) (p/fmap (fn [result] (unchecked-get result "default"))) (p/fnly (fn [data cause] diff --git a/render-wasm/_build_env b/render-wasm/_build_env index d18526caa1..f8e0faf598 100644 --- a/render-wasm/_build_env +++ b/render-wasm/_build_env @@ -1,6 +1,6 @@ #!/usr/bin/env bash -export CURRENT_VERSION=${CURRENT_VERSION:-develop}; +export VERSION_TAG=${VERSION:-develop}; if [ "$NODE_ENV" = "production" ]; then export BUILD_MODE="release"; @@ -81,7 +81,7 @@ function copy_artifacts { cp target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.js $DEST/$BUILD_NAME.js; cp target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.wasm $DEST/$BUILD_NAME.wasm; - sed -i "s/render_wasm.wasm/$BUILD_NAME.wasm?version=$CURRENT_VERSION/g" $DEST/$BUILD_NAME.js; + sed -i "s/render_wasm.wasm/$BUILD_NAME.wasm?version=$VERSION_TAG/g" $DEST/$BUILD_NAME.js; yarn esbuild target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.js \ --log-level=error \ From 884954f4ff19663c736cb0a5d765117dd331b773 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 16 Jan 2026 13:14:42 +0100 Subject: [PATCH 05/12] :bug: Fix text selrect calculation --- render-wasm/src/shapes.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index 7c757a4e5e..eabbd0dcd4 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -1529,6 +1529,7 @@ impl Shape { || !self.transform.is_identity() || !math::is_close_to(self.rotation, 0.0) || matches!(self.shape_type, Type::Group(_) | Type::Frame(_)) + || matches!(self.shape_type, Type::Text(_)) } pub fn count_visible_inner_strokes(&self) -> usize { From 260b9fb040214d8cccec8ff13d280a0cc1394e7c Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 16 Jan 2026 13:41:59 +0100 Subject: [PATCH 06/12] :bug: Fix texts with auto size updated via tokens with render wasm activated --- .../data/workspace/tokens/application.cljs | 51 ++++++++++++++----- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/frontend/src/app/main/data/workspace/tokens/application.cljs b/frontend/src/app/main/data/workspace/tokens/application.cljs index c58b8c8135..9110e81da0 100644 --- a/frontend/src/app/main/data/workspace/tokens/application.cljs +++ b/frontend/src/app/main/data/workspace/tokens/application.cljs @@ -27,8 +27,10 @@ [app.main.data.workspace.colors :as wdc] [app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.shapes :as dwsh] + [app.main.data.workspace.texts :as dwt] [app.main.data.workspace.transforms :as dwtr] [app.main.data.workspace.undo :as dwu] + [app.main.features :as features] [app.main.fonts :as fonts] [app.main.store :as st] [app.util.i18n :refer [tr]] @@ -300,11 +302,20 @@ update-fn (fn [node _] (-> node (d/txt-merge txt-attrs) - (cty/remove-typography-from-node)))] - (dwsh/update-shapes shape-ids - #(txt/update-text-content % update-node? update-fn nil) - {:ignore-touched true - :page-id page-id}))) + (cty/remove-typography-from-node))) + ;; Check if any attribute affects text layout (requires resize) + affects-layout? (some #(contains? txt-attrs %) [:font-size :font-family :font-weight :letter-spacing :line-height])] + (ptk/reify ::generate-text-shape-update + ptk/WatchEvent + (watch [_ state _] + (cond-> (rx/of (dwsh/update-shapes shape-ids + #(txt/update-text-content % update-node? update-fn nil) + {:ignore-touched true + :page-id page-id})) + (and affects-layout? + (features/active-feature? state "render-wasm/v1")) + (rx/merge + (rx/of (dwt/resize-wasm-text-all shape-ids)))))))) (defn update-line-height ([value shape-ids attributes] (update-line-height value shape-ids attributes nil)) @@ -353,11 +364,17 @@ (-> node (d/txt-merge txt-attrs) (cty/remove-typography-from-node))))] - (dwsh/update-shapes shape-ids - (fn [shape] - (txt/update-text-content shape update-node? #(update-fn %1 (ctst/font-weight-applied? shape)) nil)) - {:ignore-touched true - :page-id page-id}))) + (ptk/reify ::generate-font-family-text-shape-update + ptk/WatchEvent + (watch [_ state _] + (cond-> (rx/of (dwsh/update-shapes shape-ids + (fn [shape] + (txt/update-text-content shape update-node? #(update-fn %1 (ctst/font-weight-applied? shape)) nil)) + {:ignore-touched true + :page-id page-id})) + (features/active-feature? state "render-wasm/v1") + (rx/merge + (rx/of (dwt/resize-wasm-text-all shape-ids)))))))) (defn- create-font-family-text-attrs [value] @@ -425,10 +442,16 @@ (-> node (d/txt-merge txt-attrs) (cty/remove-typography-from-node))))] - (dwsh/update-shapes shape-ids - #(txt/update-text-content % update-node? update-fn nil) - {:ignore-touched true - :page-id page-id}))) + (ptk/reify ::generate-font-weight-text-shape-update + ptk/WatchEvent + (watch [_ state _] + (cond-> (rx/of (dwsh/update-shapes shape-ids + #(txt/update-text-content % update-node? update-fn nil) + {:ignore-touched true + :page-id page-id})) + (features/active-feature? state "render-wasm/v1") + (rx/merge + (rx/of (dwt/resize-wasm-text-all shape-ids)))))))) (defn update-font-weight ([value shape-ids attributes] (update-font-weight value shape-ids attributes nil)) From 6fa0c3af0c1026e75610a67e2f7bf9d4d49bf549 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 20 Jan 2026 12:18:18 +0100 Subject: [PATCH 07/12] :bug: Fix some tiles disappear after fast zoom and pan --- render-wasm/src/main.rs | 1 + render-wasm/src/render.rs | 5 +++++ render-wasm/src/state.rs | 10 ++++++++++ 3 files changed, 16 insertions(+) diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 9d4ff41617..162732d551 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -284,6 +284,7 @@ pub extern "C" fn set_view_end() { performance::end_measure!("set_view_end::clear_tile_index"); performance::end_timed_log!("clear_tile_index", _clear_start); } + state.render_state.sync_cached_viewbox(); performance::end_measure!("set_view_end"); performance::end_timed_log!("set_view_end", _end_start); #[cfg(feature = "profile-macros")] diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 1d80780bb1..f0619a9e37 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -1136,6 +1136,7 @@ impl RenderState { ) -> Result<(), String> { let _start = performance::begin_timed_log!("start_render_loop"); let scale = self.get_scale(); + self.tile_viewbox.update(self.viewbox, scale); self.focus_mode.reset(); @@ -2292,6 +2293,10 @@ impl RenderState { (self.viewbox.zoom - self.cached_viewbox.zoom).abs() > f32::EPSILON } + pub fn sync_cached_viewbox(&mut self) { + self.cached_viewbox = self.viewbox; + } + pub fn mark_touched(&mut self, uuid: Uuid) { self.touched_ids.insert(uuid); } diff --git a/render-wasm/src/state.rs b/render-wasm/src/state.rs index 40b272007c..f178ed4477 100644 --- a/render-wasm/src/state.rs +++ b/render-wasm/src/state.rs @@ -100,6 +100,16 @@ impl<'a> State<'a> { } pub fn start_render_loop(&mut self, timestamp: i32) -> Result<(), String> { + // If zoom changed, we MUST rebuild the tile index before using it. + // Otherwise, the index will have tiles from the old zoom level, causing visible + // tiles to appear empty. This can happen if start_render_loop() is called before + // set_view_end() finishes rebuilding the index, or if set_view_end() hasn't been + // called yet. + let zoom_changed = self.render_state.zoom_changed(); + if zoom_changed { + self.rebuild_tiles_shallow(); + } + self.render_state .start_render_loop(None, &self.shapes, timestamp, false)?; Ok(()) From 8191d04114738590aab8e0bdced48c143dbc3008 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 20 Jan 2026 13:25:55 +0100 Subject: [PATCH 08/12] :sparkles: Use non-legacy config example on docker compose file --- docker/images/docker-compose.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/images/docker-compose.yaml b/docker/images/docker-compose.yaml index 2e9628f613..8076064b94 100644 --- a/docker/images/docker-compose.yaml +++ b/docker/images/docker-compose.yaml @@ -152,9 +152,9 @@ services: # AWS_ACCESS_KEY_ID: # AWS_SECRET_ACCESS_KEY: - # PENPOT_ASSETS_STORAGE_BACKEND: assets-s3 - # PENPOT_STORAGE_ASSETS_S3_ENDPOINT: - # PENPOT_STORAGE_ASSETS_S3_BUCKET: + # PENPOT_OBJECTS_STORAGE_BACKEND: s3 + # PENPOT_OBJECTS_STORAGE_S3_ENDPOINT: + # PENPOT_OBJECTS_STORAGE_S3_BUCKET: ## Telemetry. When enabled, a periodical process will send anonymous data about this ## instance. Telemetry data will enable us to learn how the application is used, From 47775a9e2cee66bcf09fef1b98b83e1605d52fa2 Mon Sep 17 00:00:00 2001 From: Alonso Torres Date: Tue, 20 Jan 2026 15:03:04 +0100 Subject: [PATCH 09/12] Merge pull request #8134 from penpot/alotor-fix-plugins-export :bug: Fix problem with export in plugins --- frontend/src/app/plugins/shape.cljs | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index f4794ef6e8..ec39712420 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -1185,7 +1185,6 @@ {:cmd :export-shapes :profile-id (:profile-id @st/state) :wait true - :skip-children (:skip-children value false) :exports [{:file-id file-id :page-id page-id :object-id id From 8252bc485e57daa3a6ae3890e17c08915bc1b012 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 20 Jan 2026 16:24:12 +0100 Subject: [PATCH 10/12] :books: Fix oidc callback related documentation issue --- docs/technical-guide/configuration.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/technical-guide/configuration.md b/docs/technical-guide/configuration.md index 648df7ea67..f48117b071 100644 --- a/docs/technical-guide/configuration.md +++ b/docs/technical-guide/configuration.md @@ -114,14 +114,7 @@ configuration. The callback has the following format: ```html -https:///api/auth/oauth//callback -``` - -You will need to change and according to your setup. -This is how it looks with Gitlab provider: - -```html -https:///api/auth/oauth/gitlab/callback +https:///api/auth/oidc/callback ``` #### Google From cf46051f569cdcd974247e004f8d25af69d21981 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 20 Jan 2026 19:39:52 +0100 Subject: [PATCH 11/12] :fire: Remove .traivis.yml file from the repository --- .travis.yml | 40 ---------------------------------------- 1 file changed, 40 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4419ff22ff..0000000000 --- a/.travis.yml +++ /dev/null @@ -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 From 8c7fd0af4b4966f8ae48cd724106f0f878d7c888 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Wed, 21 Jan 2026 09:17:03 +0100 Subject: [PATCH 12/12] :bug: Fix shadow reference validation (#8132) --- frontend/src/app/main/ui/forms.cljs | 1 + .../tokens/management/forms/controls/input.cljs | 13 +++++++++---- .../workspace/tokens/management/forms/shadow.cljs | 1 + frontend/translations/es.po | 2 +- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/main/ui/forms.cljs b/frontend/src/app/main/ui/forms.cljs index a126ff4320..6e49615470 100644 --- a/frontend/src/app/main/ui/forms.cljs +++ b/frontend/src/app/main/ui/forms.cljs @@ -23,6 +23,7 @@ touched? (and (contains? (:data @form) input-name) (get-in @form [:touched input-name])) + error (get-in @form [:errors input-name]) value (get-in @form [:data input-name] "") diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs index cc9cb79426..2e57f197be 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs @@ -236,12 +236,14 @@ (on-composite-input-change form field value false)) ([form field value trim?] (letfn [(clean-errors [errors] - (-> errors - (dissoc field) - (not-empty)))] + (some-> errors + (update :value #(when (map? %) (dissoc % field))) + (update :value #(when (seq %) %)) + (not-empty)))] (swap! form (fn [state] (-> state (assoc-in [:data :value field] (if trim? (str/trim value) value)) + (assoc-in [:touched :value field] true) (update :errors clean-errors) (update :extra-errors clean-errors))))))) @@ -257,6 +259,9 @@ value (get-in @form [:data :value input-name] "") + touched? + (get-in @form [:touched :value input-name]) + resolve-stream (mf/with-memo [token] (if-let [value (get-in token [:value input-name])] @@ -284,7 +289,7 @@ :hint-message (:message hint) :hint-type (:type hint)}) props - (if error + (if (and touched? error) (mf/spread-props props {:hint-type "error" :hint-message (:message error)}) props) diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/shadow.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/shadow.cljs index e243042a3d..53c6d8e402 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/shadow.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/shadow.cljs @@ -291,6 +291,7 @@ [:color {:optional true} [:maybe :string]] [:color-result {:optional true} ::sm/any] [:inset {:optional true} [:maybe :boolean]]]]] + (if (= active-tab :reference) [:reference {:optional false} ::sm/text] [:reference {:optional true} [:maybe :string]])]] diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 2101493c92..d69baf6480 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -7757,7 +7757,7 @@ msgstr "Line height (multiplicador, px o %) o {alias}" #: src/app/main/data/workspace/tokens/errors.cljs:57 msgid "workspace.tokens.missing-references" -msgstr "Referéncias de tokens no encontradas:" +msgstr "Referencias de tokens no encontradas: " #: src/app/main/ui/workspace/tokens/management/token_pill.cljs:123 msgid "workspace.tokens.more-options"