From b6a9579c98121898a823e4f96c9bb96630d59f93 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 21 Jan 2026 12:23:44 +0100 Subject: [PATCH 1/4] :sparkles: 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. --- frontend/src/app/main/ui/dashboard/file_menu.cljs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/main/ui/dashboard/file_menu.cljs b/frontend/src/app/main/ui/dashboard/file_menu.cljs index 85b3dc2ac8..c2e825c2a5 100644 --- a/frontend/src/app/main/ui/dashboard/file_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/file_menu.cljs @@ -193,11 +193,11 @@ restore-fn (fn [_] (st/emit! (dd/restore-files-immediately - (with-meta {:team-id (:id current-team) + (with-meta {:team-id current-team-id :ids (into #{} d/xf:map-id files)} {:on-success #(st/emit! (ntf/success (tr "dashboard.restore-success-notification" (:name file))) - (dd/fetch-projects (:id current-team)) - (dd/fetch-deleted-files (:id current-team))) + (dd/fetch-projects current-team-id) + (dd/fetch-deleted-files current-team-id)) :on-error #(st/emit! (ntf/error (tr "dashboard.errors.error-on-restore-file" (:name file))))})))) on-restore-immediately @@ -214,7 +214,7 @@ on-delete-immediately (fn [] (let [accept-fn #(st/emit! (dd/delete-files-immediately - {:team-id (:id current-team) + {:team-id current-team-id :ids (into #{} d/xf:map-id files)}))] (st/emit! (modal/show {:type :confirm @@ -244,8 +244,7 @@ (for [project current-projects] {:name (get-project-name project) :id (get-project-id project) - :handler (on-move (:id current-team) - (:id project))}) + :handler (on-move current-team-id (:id project))}) (when (seq other-teams) [{:name (tr "dashboard.move-to-other-team") :id "move-to-other-team" From 7cce4c65326697a6d4b186064e941110b141d344 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Wed, 21 Jan 2026 13:09:22 +0100 Subject: [PATCH 2/4] :bug: Fix unhandled exception tokens creation dialog (#8136) --- CHANGES.md | 2 +- frontend/src/app/main/refs.cljs | 3 +++ .../management/forms/controls/color_input.cljs | 10 +++++++++- .../tokens/management/forms/form_container.cljs | 13 ++++++------- .../tokens/management/forms/generic_form.cljs | 13 +++++++++---- 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f6d861db12..a6e50eb8f1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -29,7 +29,7 @@ - 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 unhandled exception tokens creation dialog [Github #8110](https://github.com/penpot/penpot/issues/8110) ## 2.12.1 diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 76d02544cc..7372e337f7 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -480,6 +480,9 @@ (def workspace-token-sets-tree (l/derived (d/nilf ctob/get-set-tree) tokens-lib)) +(def workspace-all-tokens-map + (l/derived (d/nilf ctob/get-all-tokens) tokens-lib)) + (def workspace-active-theme-paths (l/derived (d/nilf ctob/get-active-theme-paths) tokens-lib)) diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/color_input.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/color_input.cljs index ce18196c22..a4bfbf1b0c 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/color_input.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/color_input.cljs @@ -140,6 +140,9 @@ error (get-in @form [:errors input-name]) + extra-error + (get-in @form [:extra-errors input-name]) + value (get-in @form [:data input-name] "") @@ -247,9 +250,14 @@ :hint-type (:type hint)}) props - (if (and error touched?) + (cond + (and error touched?) (mf/spread-props props {:hint-type "error" :hint-message (:message error)}) + (and extra-error touched?) + (mf/spread-props props {:hint-type "error" + :hint-message (:message extra-error)}) + :else props)] (mf/with-effect [resolve-stream tokens token input-name] diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/form_container.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/form_container.cljs index 70797979c6..af394eadee 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/form_container.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/form_container.cljs @@ -23,20 +23,19 @@ (let [token-type (or (:type token) token-type) - tokens-in-selected-set - (mf/deref refs/workspace-all-tokens-in-selected-set) - token-path (mf/with-memo [token] (cft/token-name->path (:name token))) - tokens-tree-in-selected-set - (mf/with-memo [token-path tokens-in-selected-set] - (-> (ctob/tokens-tree tokens-in-selected-set) + all-tokens (mf/deref refs/workspace-all-tokens-map) + + all-tokens + (mf/with-memo [token-path all-tokens] + (-> (ctob/tokens-tree all-tokens) (d/dissoc-in token-path))) props (mf/spread-props props {:token-type token-type - :tokens-tree-in-selected-set tokens-tree-in-selected-set + :all-token-tree all-tokens :token token}) text-case-props (mf/spread-props props {:input-value-placeholder (tr "workspace.tokens.text-case-value-enter")}) text-decoration-props (mf/spread-props props {:input-value-placeholder (tr "workspace.tokens.text-decoration-value-enter")}) 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 038dbeae03..1a4a6b14b9 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 @@ -14,6 +14,7 @@ [app.main.constants :refer [max-input-length]] [app.main.data.modal :as modal] [app.main.data.workspace.tokens.application :as dwta] + [app.main.data.workspace.tokens.errors :as wte] [app.main.data.workspace.tokens.library-edit :as dwtl] [app.main.data.workspace.tokens.propagation :as dwtp] [app.main.refs :as refs] @@ -85,7 +86,7 @@ action is-create selected-token-set-id - tokens-tree-in-selected-set + all-token-tree token-type make-schema input-component @@ -123,8 +124,8 @@ (assoc (:name token) token))) schema - (mf/with-memo [tokens-tree-in-selected-set active-tab] - (make-schema tokens-tree-in-selected-set active-tab)) + (mf/with-memo [all-token-tree active-tab] + (make-schema all-token-tree active-tab)) initial (mf/with-memo [token] @@ -207,7 +208,11 @@ :value (:value valid-token) :description description})) (dwtp/propagate-workspace-tokens) - (modal/hide))))))))] + (modal/hide))))) + (fn [{:keys [errors]}] + (let [error-messages (wte/humanize-errors errors) + error-message (first error-messages)] + (swap! form assoc-in [:extra-errors :value] {:message error-message}))))))] [:> fc/form* {:class (stl/css :form-wrapper) :form form From 525adcfcbed2ba2e8e671a89ad13fe08ce27cd0b Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 21 Jan 2026 13:12:06 +0100 Subject: [PATCH 3/4] :sparkles: Add wasm build on watch app script (devenv) --- frontend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index 8279b3fd3b..a1a89771b1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -48,7 +48,7 @@ "watch:app:main": "clojure -M:dev:shadow-cljs watch main worker storybook", "clear:shadow-cache": "rm -rf .shadow-cljs", "watch": "exit 0", - "watch:app": "yarn run clear:shadow-cache && concurrently --kill-others-on-fail \"yarn run watch:app:assets\" \"yarn run watch:app:main\" \"yarn run watch:app:libs\"", + "watch:app": "yarn run clear:shadow-cache && yarn run build:wasm && concurrently --kill-others-on-fail \"yarn run watch:app:assets\" \"yarn run watch:app:main\" \"yarn run watch:app:libs\"", "watch:storybook": "yarn run build:storybook:assets && concurrently --kill-others-on-fail \"storybook dev -p 6006 --no-open\" \"node ./scripts/watch-storybook.js\"" }, "devDependencies": { From b8c70be9a26a399d4a6a930c7a5a805c81bfd5c9 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 21 Jan 2026 13:14:02 +0100 Subject: [PATCH 4/4] :sparkles: Make frontend build and watch process more resilent to errors --- frontend/scripts/_helpers.js | 50 +++++++++++++++++++++++++--- frontend/scripts/_worker.js | 2 -- frontend/scripts/build-app-assets.js | 1 + frontend/scripts/watch.js | 28 +++++++++++----- 4 files changed, 66 insertions(+), 15 deletions(-) diff --git a/frontend/scripts/_helpers.js b/frontend/scripts/_helpers.js index c1c018c74c..58d3586995 100644 --- a/frontend/scripts/_helpers.js +++ b/frontend/scripts/_helpers.js @@ -174,6 +174,7 @@ export async function watch(baseDir, predicate, callback) { const watcher = new Watcher(baseDir, { persistent: true, recursive: true, + debounce: 500 }); watcher.on("change", (path) => { @@ -181,8 +182,19 @@ export async function watch(baseDir, predicate, callback) { callback(path); } }); + + + watcher.on("error", (cause) => { + console.log("WATCHER ERROR", cause); + }); } +export async function ensureDirectories() { + await fs.mkdir("./resources/public/js/worker/", { recursive: true }); + await fs.mkdir("./resources/public/css/", { recursive: true }); +} + + async function readManifestFile(resource) { const manifestPath = "resources/public/" + resource; let content = await fs.readFile(manifestPath, { encoding: "utf8" }); @@ -260,6 +272,9 @@ const markedOptions = { marked.use(markedOptions); export async function compileTranslations() { + const outputDir = "resources/public/js/"; + await fs.mkdir(outputDir, { recursive: true }); + const langs = [ "ar", "ca", @@ -341,7 +356,6 @@ export async function compileTranslations() { } const esm = `export default ${JSON.stringify(result, null, 0)};\n`; - const outputDir = "resources/public/js/"; const outputFile = ph.join(outputDir, "translation." + lang + ".js"); await fs.writeFile(outputFile, esm); } @@ -499,17 +513,43 @@ export async function compileStyles() { export async function compileSvgSprites() { const start = process.hrtime(); log.info("init: compile svgsprite"); - await generateSvgSprites(); + let error = false; + + try { + await generateSvgSprites(); + } catch (cause) { + error = cause; + } + const end = process.hrtime(start); - log.info("done: compile svgsprite", `(${ppt(end)})`); + + if (error) { + log.error("error: compile svgsprite", `(${ppt(end)})`); + console.error(error); + } else { + log.info("done: compile svgsprite", `(${ppt(end)})`); + } } export async function compileTemplates() { const start = process.hrtime(); + let error = false; log.info("init: compile templates"); - await generateTemplates(); + + try { + await generateTemplates(); + } catch (cause) { + error = cause; + } + const end = process.hrtime(start); - log.info("done: compile templates", `(${ppt(end)})`); + + if (error) { + log.error("error: compile templates", `(${ppt(end)})`); + console.error(error); + } else { + log.info("done: compile templates", `(${ppt(end)})`); + } } export async function compilePolyfills() { diff --git a/frontend/scripts/_worker.js b/frontend/scripts/_worker.js index 80ba2f6bf6..c46b26993b 100644 --- a/frontend/scripts/_worker.js +++ b/frontend/scripts/_worker.js @@ -28,14 +28,12 @@ async function compileFile(path) { ], sourceMap: false, }); - // console.dir(result); resolve({ inputPath: path, outputPath: dest, css: result.css, }); } catch (cause) { - console.error(cause); reject(cause); } }); diff --git a/frontend/scripts/build-app-assets.js b/frontend/scripts/build-app-assets.js index 6ccc435839..e0fe385dce 100644 --- a/frontend/scripts/build-app-assets.js +++ b/frontend/scripts/build-app-assets.js @@ -1,5 +1,6 @@ import * as h from "./_helpers.js"; +await h.ensureDirectories(); await h.compileStyles(); await h.copyAssets(); await h.copyWasmPlayground(); diff --git a/frontend/scripts/watch.js b/frontend/scripts/watch.js index e2d9ff399d..38007d4454 100644 --- a/frontend/scripts/watch.js +++ b/frontend/scripts/watch.js @@ -12,19 +12,31 @@ let sass = null; async function compileSassAll() { const start = process.hrtime(); + let error = false; + log.info("init: compile styles"); - sass = await h.compileSassAll(worker); - let output = await h.concatSass(sass); - await fs.writeFile("./resources/public/css/main.css", output); + try { + sass = await h.compileSassAll(worker); + let output = await h.concatSass(sass); + await fs.writeFile("./resources/public/css/main.css", output); - if (isDebug) { - let debugCSS = await h.compileSassDebug(worker); - await fs.writeFile("./resources/public/css/debug.css", debugCSS); + if (isDebug) { + let debugCSS = await h.compileSassDebug(worker); + await fs.writeFile("./resources/public/css/debug.css", debugCSS); + } + } catch (cause) { + error = cause; } const end = process.hrtime(start); - log.info("done: compile styles", `(${ppt(end)})`); + + if (error) { + log.error("error: compile styles", `(${ppt(end)})`); + console.error(error); + } else { + log.info("done: compile styles", `(${ppt(end)})`); + } } async function compileSass(path) { @@ -48,7 +60,7 @@ async function compileSass(path) { } } -await fs.mkdir("./resources/public/css/", { recursive: true }); +await h.ensureDirectories(); await compileSassAll(); await h.copyAssets(); await h.copyWasmPlayground();