From 57bbe2112ce54d68f33675b71f08092829d4ac0a Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Wed, 7 Oct 2026 15:45:10 +0200 Subject: [PATCH 01/11] :bug: Fix inner shadow covering the shape in the SVG renderer (#12141) * :bug: Fix inner shadow covering the shape in the SVG renderer The svg filter components moved to the `*` syntax (#9448), but filter-entry* still built their props as a raw JS object with kebab-case keys. Raw JS props skip the kebab to camel conversion, so the components read nil for filterId and filterIn, and no filter primitive got its result or in2. With the chain broken, an inner shadow was blended over the empty flood instead of the shape, hiding the fill (and a board's children) under the shadow color. Exports using these filters broke the same way. Pass the props in camelCase. Closes #11967 AI-assisted-by: claude-opus-5-5 * :recycle: Build filter entry props with mf/props Use mf/props instead of a raw JS object so the props keep the kebab-case keys used across the codebase. The macro converts them to camelCase at compile time, so there is no runtime cost, and the comment about the manual conversion is no longer needed. AI-assisted-by: claude-opus-5-5 --- frontend/src/app/main/ui/shapes/filters.cljs | 6 +-- frontend/test/frontend_tests/runner.cljs | 2 + .../frontend_tests/ui/shape_filters_test.cljs | 47 +++++++++++++++++++ 3 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 frontend/test/frontend_tests/ui/shape_filters_test.cljs diff --git a/frontend/src/app/main/ui/shapes/filters.cljs b/frontend/src/app/main/ui/shapes/filters.cljs index a4468d649d..bf82f08be0 100644 --- a/frontend/src/app/main/ui/shapes/filters.cljs +++ b/frontend/src/app/main/ui/shapes/filters.cljs @@ -116,9 +116,9 @@ :result filter-id}]) (mf/defc filter-entry* [{:keys [entry]}] - (let [props #js {:filter-id (:id entry) - :filter-in (:filter-in entry) - :params (:params entry)}] + (let [props (mf/props {:filter-id (:id entry) + :filter-in (:filter-in entry) + :params (:params entry)})] (case (:type entry) :drop-shadow [:> drop-shadow-filter* props] :inner-shadow [:> inner-shadow-filter* props] diff --git a/frontend/test/frontend_tests/runner.cljs b/frontend/test/frontend_tests/runner.cljs index 6a897ee8a1..6f916c1c19 100644 --- a/frontend/test/frontend_tests/runner.cljs +++ b/frontend/test/frontend_tests/runner.cljs @@ -117,6 +117,7 @@ [frontend-tests.ui.routes-test] [frontend-tests.ui.settings-password-schema-test] [frontend-tests.ui.settings-shortcuts-test] + [frontend-tests.ui.shape-filters-test] [frontend-tests.ui.shortcuts-labels-test] [frontend-tests.ui.sidebar-scroll-test] [frontend-tests.ui.stroke-menu-test] @@ -258,6 +259,7 @@ 'frontend-tests.text-editor-paste-guard-test 'frontend-tests.ui.settings-password-schema-test 'frontend-tests.ui.settings-shortcuts-test + 'frontend-tests.ui.shape-filters-test 'frontend-tests.ui.shortcuts-labels-test 'frontend-tests.ui.sidebar-scroll-test 'frontend-tests.ui.stroke-menu-test diff --git a/frontend/test/frontend_tests/ui/shape_filters_test.cljs b/frontend/test/frontend_tests/ui/shape_filters_test.cljs new file mode 100644 index 0000000000..0cfc20b149 --- /dev/null +++ b/frontend/test/frontend_tests/ui/shape_filters_test.cljs @@ -0,0 +1,47 @@ +;; 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 SUBSIDIARY SL + +(ns frontend-tests.ui.shape-filters-test + (:require + ["react-dom/server" :as rds] + [app.common.types.shape :as cts] + [app.common.uuid :as uuid] + [app.main.ui.shapes.filters :as filters] + [cljs.test :as t :include-macros true] + [cuerdas.core :as str] + [rumext.v2 :as mf])) + +(defn- render-filter + [shadow-style] + (let [shape (-> (cts/setup-shape {:type :rect :x 0 :y 0 :width 100 :height 100}) + (assoc :fills [{:fill-color "#4488ff" :fill-opacity 1}] + :shadow [{:id (uuid/next) + :style shadow-style + :offset-x 4 :offset-y 4 :blur 4 :spread 0 + :hidden false + :color {:color "#000000" :opacity 0.5}}]))] + (rds/renderToStaticMarkup + (mf/element filters/filters* #js {:filterId "filter" :shape shape})))) + +(defn- tags + [markup tag] + (re-seq (re-pattern (str "<" tag "[^>]*>")) markup)) + +;; Each filter primitive must name its result and read the previous one, or +;; the chain breaks: an inner shadow then covers the shape fill with the +;; shadow color (#11967). + +(t/deftest inner-shadow-filter-chain-is-wired + (let [markup (render-filter :inner-shadow) + blends (tags markup "feBlend")] + (t/is (str/includes? (first (tags markup "feFlood")) "result=")) + (t/is (= 2 (count blends))) + (t/is (every? #(str/includes? % "in2=") blends)) + (t/is (every? #(str/includes? % "result=") blends)))) + +(t/deftest drop-shadow-filter-chain-is-wired + (let [blends (tags (render-filter :drop-shadow) "feBlend")] + (t/is (every? #(and (str/includes? % "in2=") (str/includes? % "result=")) blends)))) From 92bcc0e6355485dcd468722e5cd2312158d86475 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 9 Oct 2026 13:56:39 +0200 Subject: [PATCH 02/11] :bug: Show live text size in Design tab while editing (#12250) Auto-grow text geometry is finalize-only to avoid typing lag, so the Design tab kept the committed selrect (0.01 for new texts) until Esc. Patch display W/H from live WASM layout while editing, matching the viewport outline, without restoring per-keystroke modifiers. Closes #12244 --- .../src/app/main/data/workspace/texts.cljs | 34 ++++++++++++++ .../shapes/text/text_edition_outline.cljs | 14 +++--- .../sidebar/options/shapes/text.cljs | 15 ++++++- .../data/workspace_texts_test.cljs | 45 +++++++++++++++++++ 4 files changed, 98 insertions(+), 10 deletions(-) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 4ddaf0eded..1eb94ca1c0 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -900,6 +900,40 @@ new-shape) shape)) +(defn merge-live-text-dimensions + "Pick displayed width/height while editing auto-grow text. + + Geometry commits are finalize-only (see `v2-update-text-shape-content`), so the + Design tab and edition outline read live WASM layout for growing axes: + - `:auto-width` → live width and height + - `:auto-height` → selrect width, live height + - `:fixed` → selrect for both + + Nil or non-positive live axes fall back to the committed selrect so a missing + WASM measure never flashes `0`." + [grow-type {:keys [width height]} live] + (let [lw (when (map? live) (:width live)) + lh (when (map? live) (:height live))] + {:width (if (and (= grow-type :auto-width) (number? lw) (pos? lw)) lw width) + :height (if (and (not= grow-type :fixed) (number? lh) (pos? lh)) lh height)})) + +(defn shape-with-live-text-dimensions + "Display-only patch of shape selrect (and :width/:height) from live WASM text + layout. Does not write to the store. Returns `shape` unchanged when WASM is + unavailable or dimensions do not change." + [shape] + (let [selrect (:selrect shape) + live (when (wasm.api/initialized?) + (wasm.api/get-text-dimensions (:id shape))) + {:keys [width height]} (merge-live-text-dimensions (:grow-type shape) selrect live)] + (if (and (mth/close? width (:width selrect)) + (mth/close? height (:height selrect))) + shape + (-> shape + (assoc :selrect (grc/make-rect (:x selrect) (:y selrect) width height)) + (cond-> (contains? shape :width) (assoc :width width)) + (cond-> (contains? shape :height) (assoc :height height)))))) + (defn commit-update-text-modifier [] (ptk/reify ::commit-update-text-modifier diff --git a/frontend/src/app/main/ui/workspace/shapes/text/text_edition_outline.cljs b/frontend/src/app/main/ui/workspace/shapes/text/text_edition_outline.cljs index 93e7562438..43d2b492d0 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/text_edition_outline.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/text_edition_outline.cljs @@ -22,17 +22,15 @@ [selrect transform] (dsh/get-selrect selrect-transform shape) ;; While editing, the committed selrect lags the text (geometry is - ;; finalize-only), so measure the live WASM text for the growing axes: - ;; width grows on auto-width, height on auto-width/auto-height. - grow-type (:grow-type shape) - {live-width :width live-height :height} (wasm.api/get-text-dimensions (:id shape)) - sr-width (if (= grow-type :auto-width) live-width (:width selrect)) - sr-height (if (= grow-type :fixed) (:height selrect) live-height)] + ;; finalize-only), so measure the live WASM text for the growing axes. + live (wasm.api/get-text-dimensions (:id shape)) + {:keys [width height]} (dwt/merge-live-text-dimensions + (:grow-type shape) selrect live)] [:rect.main.viewport-selrect {:x (:x selrect) :y (:y selrect) - :width sr-width - :height sr-height + :width width + :height height :transform transform :style {:stroke "var(--color-accent-tertiary)" :stroke-width (/ 1 zoom) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs index 3158f59be2..dd2c9dc102 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs @@ -31,8 +31,19 @@ (mf/defc options* [{:keys [shape libraries file-id page-id]}] - (let [id (dm/get-prop shape :id) - type (dm/get-prop shape :type) + (let [id (dm/get-prop shape :id) + type (dm/get-prop shape :type) + edition (mf/deref refs/selected-edition) + ;; While editing auto-grow text, committed selrect lags (finalize-only + ;; geometry). Patch display size from live WASM so Design W/H match the + ;; viewport outline without writing to the store. + shape + (cond-> shape + (and (= id edition) + (not= :fixed (:grow-type shape)) + (features/active-feature? @st/state "render-wasm/v1")) + (dwt/shape-with-live-text-dimensions)) + ids (mf/with-memo [id] [id]) shapes (mf/with-memo [shape] [shape]) diff --git a/frontend/test/frontend_tests/data/workspace_texts_test.cljs b/frontend/test/frontend_tests/data/workspace_texts_test.cljs index 14eef12d50..9fe41428e7 100644 --- a/frontend/test/frontend_tests/data/workspace_texts_test.cljs +++ b/frontend/test/frontend_tests/data/workspace_texts_test.cljs @@ -581,3 +581,48 @@ (t/testing "a non-root content (e.g. paragraph) is left alone" (let [node {:type "paragraph" :children []}] (t/is (= node (dwt/ensure-valid-text-content node)))))) + +;; --------------------------------------------------------------------------- +;; Tests: merge-live-text-dimensions (Design tab / edition outline) +;; --------------------------------------------------------------------------- + +(t/deftest merge-live-text-dimensions-auto-width-uses-live-axes + (t/testing "auto-width takes live width and height" + (let [selrect {:width 0.01 :height 0.01} + live {:width 70 :height 17} + result (dwt/merge-live-text-dimensions :auto-width selrect live)] + (t/is (= 70 (:width result))) + (t/is (= 17 (:height result)))))) + +(t/deftest merge-live-text-dimensions-auto-height-keeps-width + (t/testing "auto-height keeps selrect width and takes live height" + (let [selrect {:width 300 :height 40} + live {:width 120 :height 80} + result (dwt/merge-live-text-dimensions :auto-height selrect live)] + (t/is (= 300 (:width result))) + (t/is (= 80 (:height result)))))) + +(t/deftest merge-live-text-dimensions-fixed-keeps-selrect + (t/testing "fixed keeps both selrect axes" + (let [selrect {:width 300 :height 60} + live {:width 90 :height 20} + result (dwt/merge-live-text-dimensions :fixed selrect live)] + (t/is (= 300 (:width result))) + (t/is (= 60 (:height result)))))) + +(t/deftest merge-live-text-dimensions-nil-live-falls-back + (t/testing "nil live map falls back to selrect" + (let [selrect {:width 50 :height 25} + result (dwt/merge-live-text-dimensions :auto-width selrect nil)] + (t/is (= 50 (:width result))) + (t/is (= 25 (:height result))))) + (t/testing "nil live axes fall back per axis" + (let [selrect {:width 50 :height 25} + result (dwt/merge-live-text-dimensions :auto-width selrect {:width nil :height nil})] + (t/is (= 50 (:width result))) + (t/is (= 25 (:height result))))) + (t/testing "non-positive live axes do not wipe selrect" + (let [selrect {:width 50 :height 25} + result (dwt/merge-live-text-dimensions :auto-width selrect {:width 0 :height 0})] + (t/is (= 50 (:width result))) + (t/is (= 25 (:height result)))))) From f9515a69ca996fcb002f7f3d47b7dc9f23df6b07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?andr=C3=A9s=20gonz=C3=A1lez?= Date: Fri, 9 Oct 2026 14:12:17 +0200 Subject: [PATCH 03/11] :bug: Translate the This file tab texts in the libraries modal (#12204) * :bug: Fix inner shadow covering the shape in the SVG renderer (#12141) * :bug: Fix inner shadow covering the shape in the SVG renderer The svg filter components moved to the `*` syntax (#9448), but filter-entry* still built their props as a raw JS object with kebab-case keys. Raw JS props skip the kebab to camel conversion, so the components read nil for filterId and filterIn, and no filter primitive got its result or in2. With the chain broken, an inner shadow was blended over the empty flood instead of the shape, hiding the fill (and a board's children) under the shadow color. Exports using these filters broke the same way. Pass the props in camelCase. Closes #11967 AI-assisted-by: claude-opus-5-5 * :recycle: Build filter entry props with mf/props Use mf/props instead of a raw JS object so the props keep the kebab-case keys used across the codebase. The macro converts them to camelCase at compile time, so there is no runtime cost, and the comment about the manual conversion is no longer needed. AI-assisted-by: claude-opus-5-5 * :bug: Translate the This file tab texts in the libraries modal The tab name, the section title and the empty state button of the This file tab were hardcoded in English. Add translation keys with English and Spanish texts. Closes #12178 AI-assisted-by: claude-opus-5-5 --------- Co-authored-by: Eva Marco --- frontend/src/app/main/ui/workspace/libraries.cljs | 6 +++--- frontend/translations/en.po | 12 ++++++++++++ frontend/translations/es.po | 12 ++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index 71c7f32405..7ed01913be 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -819,7 +819,7 @@ [:div {:class (stl/css :lib-section)} [:> title-bar* {:collapsable false - :title "ASSETS IN THIS FILE" + :title (tr "workspace.libraries.assets-in-this-file") :class (stl/css :title-spacing-lib)}] [:div {:class (stl/css :section-list)} [:div {:class (stl/css :section-list-publish)} @@ -916,7 +916,7 @@ (tr "workspace.libraries.empty.no-connected-libraries")] [:button {:class (stl/css :go-to-shared-button) :on-click go-to-shared} - "Add a shared library"]])]]])) + (tr "workspace.libraries.add-shared-library")]])]]])) (defn- extract-assets @@ -1153,7 +1153,7 @@ tabs (mf/with-memo [token-lib-sync?] (if token-lib-sync? - [{:label "This file" + [{:label (tr "workspace.libraries.this-file") :id "file"} {:label (tr "workspace.libraries.libraries") :id "libraries"} diff --git a/frontend/translations/en.po b/frontend/translations/en.po index a7a3cd7606..eb04aa1bf8 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -6721,6 +6721,14 @@ msgstr "Locate" msgid "workspace.layout-grid.editor.top-bar.locate.tooltip" msgstr "Locate grid layout" +#: src/app/main/ui/workspace/libraries.cljs:898 +msgid "workspace.libraries.add-shared-library" +msgstr "Add a shared library" + +#: src/app/main/ui/workspace/libraries.cljs:801 +msgid "workspace.libraries.assets-in-this-file" +msgstr "ASSETS IN THIS FILE" + #: src/app/main/ui/workspace/libraries.cljs:126, src/app/main/ui/workspace/libraries.cljs:155, src/app/main/ui/workspace/libraries.cljs:199 msgid "workspace.libraries.colors" msgid_plural "workspace.libraries.colors" @@ -6875,6 +6883,10 @@ msgstr "Unlink all assets" msgid "workspace.libraries.text.multiple-token-tooltip" msgstr "Detach all tokens" +#: src/app/main/ui/workspace/libraries.cljs:1136 +msgid "workspace.libraries.this-file" +msgstr "This file" + #: src/app/main/ui/workspace/libraries.cljs:167, src/app/main/ui/workspace/libraries.cljs:211 msgid "workspace.libraries.token-sets" msgid_plural "workspace.libraries.token-sets" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 1c37a22c64..59bc353113 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -6784,6 +6784,14 @@ msgstr "Mostrar" msgid "workspace.layout-grid.editor.top-bar.locate.tooltip" msgstr "Mostrar grid layout" +#: src/app/main/ui/workspace/libraries.cljs:898 +msgid "workspace.libraries.add-shared-library" +msgstr "Añadir una biblioteca compartida" + +#: src/app/main/ui/workspace/libraries.cljs:801 +msgid "workspace.libraries.assets-in-this-file" +msgstr "RECURSOS EN ESTE ARCHIVO" + #: src/app/main/ui/workspace/libraries.cljs:126, src/app/main/ui/workspace/libraries.cljs:155, src/app/main/ui/workspace/libraries.cljs:199 msgid "workspace.libraries.colors" msgid_plural "workspace.libraries.colors" @@ -6938,6 +6946,10 @@ msgstr "Desvincular todos los recursos" msgid "workspace.libraries.text.multiple-token-tooltip" msgstr "Desvincular todos los tokens" +#: src/app/main/ui/workspace/libraries.cljs:1136 +msgid "workspace.libraries.this-file" +msgstr "Este archivo" + #: src/app/main/ui/workspace/libraries.cljs:167, src/app/main/ui/workspace/libraries.cljs:211 msgid "workspace.libraries.token-sets" msgid_plural "workspace.libraries.token-sets" From 9931d82379f8f8d77c19f795e6e5891c85e8526b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?andr=C3=A9s=20gonz=C3=A1lez?= Date: Fri, 9 Oct 2026 14:14:51 +0200 Subject: [PATCH 04/11] :bug: Show the themes permission message only to non-editors (#12203) The theme selector showed "You need to be an editor to use themes" whenever tokens could not be edited. That is also the case for editors when the tokens source is a library, and they can still switch themes. Base the message on file edit permission instead. Closes #12177 AI-assisted-by: claude-opus-5-5 --- frontend/src/app/main/ui/workspace/tokens/themes.cljs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/themes.cljs b/frontend/src/app/main/ui/workspace/tokens/themes.cljs index 5c116ca4ad..81ad0d033f 100644 --- a/frontend/src/app/main/ui/workspace/tokens/themes.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/themes.cljs @@ -23,6 +23,9 @@ (let [ordered-themes (mf/deref refs/workspace-token-themes-no-hidden) + can-edit-file? + (mf/use-ctx ctx/can-edit?) + can-edit-tokens? (mf/use-ctx ctx/can-edit-tokens?) @@ -50,6 +53,6 @@ :class (stl/css :edit-theme-button) :on-click open-modal} (tr "labels.edit")]] - [:div {:title (when-not can-edit-tokens? + [:div {:title (when-not can-edit-file? (tr "workspace.tokens.no-permission-themes"))} [:> theme-selector* {:tokens-source tokens-source}]]))])) From c4068a2256e67e45ad4e81cac6424686fbd0cb2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Torr=C3=B3?= Date: Fri, 9 Oct 2026 15:47:08 +0200 Subject: [PATCH 05/11] :bug: Fix additional suffix on new exporter files (#12256) --- frontend/src/app/main/data/exports/wasm.cljs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/main/data/exports/wasm.cljs b/frontend/src/app/main/data/exports/wasm.cljs index d2258b5e85..ff56fd5796 100644 --- a/frontend/src/app/main/data/exports/wasm.cljs +++ b/frontend/src/app/main/data/exports/wasm.cljs @@ -22,11 +22,10 @@ (wapi/create-uri blob))) (defn export-image - [{:keys [type suffix name] :as params}] + [{:keys [type name] :as params}] (let [url (export-image-uri params) - mtype (format->mtype type) - filename (str name (or suffix ""))] - (dom/trigger-download-uri filename mtype url) + mtype (format->mtype type)] + (dom/trigger-download-uri name mtype url) (wapi/revoke-uri url) nil)) @@ -37,9 +36,9 @@ (wapi/create-uri blob))) (defn export-pdf - [{:keys [suffix name] :as params}] + [{:keys [name] :as params}] (let [url (export-pdf-uri params) - filename (str name (or suffix "") ".pdf")] + filename (str name ".pdf")] (dom/trigger-download-uri filename "application/pdf" url) (js/queueMicrotask #(wapi/revoke-uri url)) nil)) @@ -51,9 +50,9 @@ (wapi/create-uri blob))) (defn export-svg - [{:keys [suffix name] :as params}] + [{:keys [name] :as params}] (let [url (export-svg-uri params) - filename (str name (or suffix "") ".svg")] + filename (str name ".svg")] (dom/trigger-download-uri filename "image/svg+xml" url) (js/queueMicrotask #(wapi/revoke-uri url)) nil)) From 4faac8f7461c67a4092fda2edef705efe419b1c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Torr=C3=B3?= Date: Fri, 9 Oct 2026 15:48:03 +0200 Subject: [PATCH 06/11] :bug: Fix text decoration selection on v3 editor (#12255) --- frontend/src/app/render_wasm/text_editor.cljs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/render_wasm/text_editor.cljs b/frontend/src/app/render_wasm/text_editor.cljs index 2f60004eac..d26088820a 100644 --- a/frontend/src/app/render_wasm/text_editor.cljs +++ b/frontend/src/app/render_wasm/text_editor.cljs @@ -771,11 +771,17 @@ (defn- clean-styles "Drop nil-valued attrs (unlike the DOM path, our merge would keep them and fail - the backend schema); a per-span fn is passed through untouched." + the backend schema); a per-span fn is passed through untouched. A nil + `:text-decoration` is the panel's toggle-off, so it becomes \"none\"." [styles] (if (fn? styles) styles - (into {} (remove (comp nil? val)) styles))) + (into {} + (keep (fn [[k v]] + (cond + (some? v) [k v] + (= k :text-decoration) [k "none"]))) + styles))) (defn apply-styles-to-selection "Apply `styles` (attrs map, or a fn per span) to the selected spans; `:with-fills?` also returns `:fills`." From 7be9254a55dcd37ad6d349c145ca99871f31a117 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 9 Oct 2026 16:33:49 +0000 Subject: [PATCH 07/11] :books: Document review comment structure and finding IDs The review-code flow skill now documents how to post a review as a GitHub comment: in English, with a minimal structure (intro line, main issues, minor suggestions, optional closing). Posting is explicit-only: the review stays in the session unless the user asks for the comment. The code-review-criteria skill replaces the flat finding ID sequence (F1, F2, ...) with severity classes: R1 for required findings, M1 for minor ones, and S1 for suggestions. IDs stay stable so follow-up discussion can reference findings unambiguously. AI-assisted-by: glm-5.3-flash --- .agents/skills/code-review-criteria/SKILL.md | 8 ++++---- .agents/skills/review-code/SKILL.md | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/.agents/skills/code-review-criteria/SKILL.md b/.agents/skills/code-review-criteria/SKILL.md index 59abf1c21e..398aa440ee 100644 --- a/.agents/skills/code-review-criteria/SKILL.md +++ b/.agents/skills/code-review-criteria/SKILL.md @@ -105,7 +105,7 @@ For detailed security guidance, see `security-and-hardening`. | **Low:** | Minor, optional | Author may ignore — formatting, style preferences | | **Suggestion:** | Worth considering | Not required, but improves the code | -**Unique finding IDs.** Assign every finding a stable identifier: `F1`, `F2`, `F3`, … numbered in order of severity (Critical first, then High, Medium, Low, Suggestion). Use the ID everywhere the finding is mentioned — in section headers, in the verdict, in follow-up discussion. Never renumber within a review. Example: `**F3 (High)** — `app/validate.cljs:42` — duplicate branch logic…`. +**Finding IDs.** Assign every finding a stable identifier by severity class: `R1`, `R2`, … for required findings (Critical, High), `M1`, `M2`, … for minor findings (Medium, Low), and `S1`, `S2`, … for suggestions. Number within each class in review order. Use the ID everywhere the finding is mentioned — in section headers, in the verdict, in follow-up discussion. Never renumber within a review. Example: `**R2** — `app/validate.cljs:42` — duplicate branch logic…`. For each finding, describe the circumstances under which it could fail: specific inputs, load conditions, timing, or user actions that trigger the problem. "This crashes when input is null" is actionable; "this might crash" is not. @@ -123,11 +123,11 @@ Briefly explain what the code does and give an overall assessment. ### Critical and High-Priority Issues -List problems that could cause security incidents, data loss, crashes, incorrect behavior, or major performance degradation. Each finding gets its unique ID (`F1`, `F2`, …). For each: state the severity, identify the file/function/code section, explain why it's a problem, describe failure circumstances, and provide a concrete improvement with corrected code when useful. +List problems that could cause security incidents, data loss, crashes, incorrect behavior, or major performance degradation. Each finding gets its unique ID (`R1`, `R2`, …). For each: state the severity, identify the file/function/code section, explain why it's a problem, describe failure circumstances, and provide a concrete improvement with corrected code when useful. ### Other Findings -List medium- and low-priority issues, including maintainability and design concerns. Continue the ID sequence started above (`F3`, `F4`, …). +List medium- and low-priority issues, including maintainability and design concerns. Use `M1`, `M2`, … for Medium/Low and `S1`, `S2`, … for Suggestions. ### Suggested Refactoring @@ -149,7 +149,7 @@ Choose one: - **Approve with minor changes** — Good to merge after addressing low/medium issues - **Request changes** — Critical or high issues must be resolved before merge -List the finding IDs the verdict depends on (e.g. "Request changes: F1, F4"). +List the finding IDs the verdict depends on (e.g. "Request changes: R1, R2"). ## Change Sizing diff --git a/.agents/skills/review-code/SKILL.md b/.agents/skills/review-code/SKILL.md index 613aae072e..21e7a7c7fd 100644 --- a/.agents/skills/review-code/SKILL.md +++ b/.agents/skills/review-code/SKILL.md @@ -67,6 +67,26 @@ agent again. 6. Skip generated files, lockfile-only changes, and unrelated modifications unless they introduce security risks. +## Posting as a GitHub comment + +Only post the review as a comment when the user asks for it explicitly. Keep +it in the session otherwise. + +When posting: PR comments go in English, with this minimal structure: + +1. **Intro line** — one sentence: what was found and the overall tone. No + greetings. +2. **`## Main issues`** — one subsection per required finding: what breaks, + the failure circumstances, and the minimal fix (code snippet when useful). + A missing test belongs here, not under minor suggestions. +3. **`## Minor suggestions`** — one bullet per minor finding or suggestion: + file/function, problem, concrete remedy. +4. **Closing** — optional and short. Cut it when it adds nothing. + +Reference every finding by its ID (`R1`, `M1`, `S1`, … from +`code-review-criteria`) so follow-up replies can point to findings +unambiguously. + ## User input, overrides and additional context $ARGUMENTS From c2125e1589b7b275031fc1e86e3f7d148a99f75b Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 9 Oct 2026 17:11:58 +0000 Subject: [PATCH 08/11] :arrow_down: Downgrade MCP TypeScript from 7 to 6 The 7.x line ships only a version stub at the `typescript` entry point and no longer exposes the synchronous compiler API, so the `ts.transpileModule` call that `ConnectionMetadata.test.ts` uses to compile `main.ts` and `plugin.ts` into a sandboxed browser context failed with "Cannot read properties of undefined (reading 'CommonJS')", taking all 11 plugin connection tests down. 6.0.3 restores that API and matches the version the frontend and plugins packages already pin. AI-assisted-by: step-5-preview-free --- mcp/packages/common/package.json | 2 +- mcp/packages/plugin/package.json | 2 +- mcp/packages/server/package.json | 2 +- mcp/pnpm-lock.yaml | 227 ++----------------------------- 4 files changed, 16 insertions(+), 217 deletions(-) diff --git a/mcp/packages/common/package.json b/mcp/packages/common/package.json index ed573b019a..c8bdde4bd0 100644 --- a/mcp/packages/common/package.json +++ b/mcp/packages/common/package.json @@ -12,7 +12,7 @@ "clean": "rm -rf dist/" }, "devDependencies": { - "typescript": "^7.0.2" + "typescript": "^6.0.3" }, "files": [ "dist/**/*" diff --git a/mcp/packages/plugin/package.json b/mcp/packages/plugin/package.json index 782150744f..64f7a0c8b6 100644 --- a/mcp/packages/plugin/package.json +++ b/mcp/packages/plugin/package.json @@ -18,7 +18,7 @@ }, "devDependencies": { "cross-env": "^10.1.0", - "typescript": "^7.0.2", + "typescript": "^6.0.3", "vite": "^8.3.2", "vite-live-preview": "^0.4.0" } diff --git a/mcp/packages/server/package.json b/mcp/packages/server/package.json index 124ab8b60a..d400b4f4e6 100644 --- a/mcp/packages/server/package.json +++ b/mcp/packages/server/package.json @@ -56,7 +56,7 @@ "esbuild": "^0.28.1", "ts-node": "^10.9.2", "tsx": "^4.23.15", - "typescript": "^7.0.2" + "typescript": "^6.0.3" }, "ts-node": { "esm": true diff --git a/mcp/pnpm-lock.yaml b/mcp/pnpm-lock.yaml index 053f1f6135..05834afcd4 100644 --- a/mcp/pnpm-lock.yaml +++ b/mcp/pnpm-lock.yaml @@ -180,8 +180,8 @@ importers: packages/common: devDependencies: typescript: - specifier: ^7.0.2 - version: 7.0.2 + specifier: ^6.0.3 + version: 6.0.3 packages/plugin: dependencies: @@ -196,8 +196,8 @@ importers: specifier: ^10.1.0 version: 10.1.0 typescript: - specifier: ^7.0.2 - version: 7.0.2 + specifier: ^6.0.3 + version: 6.0.3 vite: specifier: ^8.3.2 version: 8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(tsx@4.23.15) @@ -279,13 +279,13 @@ importers: version: 0.28.2 ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@26.6.4)(typescript@7.0.2) + version: 10.9.2(@types/node@26.6.4)(typescript@6.0.3) tsx: specifier: ^4.23.15 version: 4.23.15 typescript: - specifier: ^7.0.2 - version: 7.0.2 + specifier: ^6.0.3 + version: 6.0.3 packages: @@ -822,126 +822,6 @@ packages: '@types/ws@8.18.2': resolution: {integrity: sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w==} - '@typescript/typescript-aix-ppc64@7.0.2': - resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} - engines: {node: '>=16.20.0'} - cpu: [ppc64] - os: [aix] - - '@typescript/typescript-darwin-arm64@7.0.2': - resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [darwin] - - '@typescript/typescript-darwin-x64@7.0.2': - resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [darwin] - - '@typescript/typescript-freebsd-arm64@7.0.2': - resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [freebsd] - - '@typescript/typescript-freebsd-x64@7.0.2': - resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [freebsd] - - '@typescript/typescript-linux-arm64@7.0.2': - resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [linux] - - '@typescript/typescript-linux-arm@7.0.2': - resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} - engines: {node: '>=16.20.0'} - cpu: [arm] - os: [linux] - - '@typescript/typescript-linux-loong64@7.0.2': - resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} - engines: {node: '>=16.20.0'} - cpu: [loong64] - os: [linux] - - '@typescript/typescript-linux-mips64el@7.0.2': - resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} - engines: {node: '>=16.20.0'} - cpu: [mips64el] - os: [linux] - - '@typescript/typescript-linux-ppc64@7.0.2': - resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} - engines: {node: '>=16.20.0'} - cpu: [ppc64] - os: [linux] - - '@typescript/typescript-linux-riscv64@7.0.2': - resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} - engines: {node: '>=16.20.0'} - cpu: [riscv64] - os: [linux] - - '@typescript/typescript-linux-s390x@7.0.2': - resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} - engines: {node: '>=16.20.0'} - cpu: [s390x] - os: [linux] - - '@typescript/typescript-linux-x64@7.0.2': - resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [linux] - - '@typescript/typescript-netbsd-arm64@7.0.2': - resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [netbsd] - - '@typescript/typescript-netbsd-x64@7.0.2': - resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [netbsd] - - '@typescript/typescript-openbsd-arm64@7.0.2': - resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [openbsd] - - '@typescript/typescript-openbsd-x64@7.0.2': - resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [openbsd] - - '@typescript/typescript-sunos-x64@7.0.2': - resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [sunos] - - '@typescript/typescript-win32-arm64@7.0.2': - resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [win32] - - '@typescript/typescript-win32-x64@7.0.2': - resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [win32] - accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -1617,9 +1497,9 @@ packages: resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} engines: {node: '>= 18'} - typescript@7.0.2: - resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} - engines: {node: '>=16.20.0'} + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} hasBin: true undici-types@8.9.0: @@ -2084,66 +1964,6 @@ snapshots: dependencies: '@types/node': 26.6.4 - '@typescript/typescript-aix-ppc64@7.0.2': - optional: true - - '@typescript/typescript-darwin-arm64@7.0.2': - optional: true - - '@typescript/typescript-darwin-x64@7.0.2': - optional: true - - '@typescript/typescript-freebsd-arm64@7.0.2': - optional: true - - '@typescript/typescript-freebsd-x64@7.0.2': - optional: true - - '@typescript/typescript-linux-arm64@7.0.2': - optional: true - - '@typescript/typescript-linux-arm@7.0.2': - optional: true - - '@typescript/typescript-linux-loong64@7.0.2': - optional: true - - '@typescript/typescript-linux-mips64el@7.0.2': - optional: true - - '@typescript/typescript-linux-ppc64@7.0.2': - optional: true - - '@typescript/typescript-linux-riscv64@7.0.2': - optional: true - - '@typescript/typescript-linux-s390x@7.0.2': - optional: true - - '@typescript/typescript-linux-x64@7.0.2': - optional: true - - '@typescript/typescript-netbsd-arm64@7.0.2': - optional: true - - '@typescript/typescript-netbsd-x64@7.0.2': - optional: true - - '@typescript/typescript-openbsd-arm64@7.0.2': - optional: true - - '@typescript/typescript-openbsd-x64@7.0.2': - optional: true - - '@typescript/typescript-sunos-x64@7.0.2': - optional: true - - '@typescript/typescript-win32-arm64@7.0.2': - optional: true - - '@typescript/typescript-win32-x64@7.0.2': - optional: true - accepts@2.0.0: dependencies: mime-types: 3.0.2 @@ -2831,7 +2651,7 @@ snapshots: tree-kill@1.2.2: {} - ts-node@10.9.2(@types/node@26.6.4)(typescript@7.0.2): + ts-node@10.9.2(@types/node@26.6.4)(typescript@6.0.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.13 @@ -2845,7 +2665,7 @@ snapshots: create-require: 1.1.1 diff: 4.0.4 make-error: 1.3.6 - typescript: 7.0.2 + typescript: 6.0.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 @@ -2863,28 +2683,7 @@ snapshots: media-typer: 1.1.1 mime-types: 3.0.2 - typescript@7.0.2: - optionalDependencies: - '@typescript/typescript-aix-ppc64': 7.0.2 - '@typescript/typescript-darwin-arm64': 7.0.2 - '@typescript/typescript-darwin-x64': 7.0.2 - '@typescript/typescript-freebsd-arm64': 7.0.2 - '@typescript/typescript-freebsd-x64': 7.0.2 - '@typescript/typescript-linux-arm': 7.0.2 - '@typescript/typescript-linux-arm64': 7.0.2 - '@typescript/typescript-linux-loong64': 7.0.2 - '@typescript/typescript-linux-mips64el': 7.0.2 - '@typescript/typescript-linux-ppc64': 7.0.2 - '@typescript/typescript-linux-riscv64': 7.0.2 - '@typescript/typescript-linux-s390x': 7.0.2 - '@typescript/typescript-linux-x64': 7.0.2 - '@typescript/typescript-netbsd-arm64': 7.0.2 - '@typescript/typescript-netbsd-x64': 7.0.2 - '@typescript/typescript-openbsd-arm64': 7.0.2 - '@typescript/typescript-openbsd-x64': 7.0.2 - '@typescript/typescript-sunos-x64': 7.0.2 - '@typescript/typescript-win32-arm64': 7.0.2 - '@typescript/typescript-win32-x64': 7.0.2 + typescript@6.0.3: {} undici-types@8.9.0: {} From 0c450a8f60ccbdc1bcf3d27824a5ffbb75e69361 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 9 Oct 2026 17:12:10 +0000 Subject: [PATCH 09/11] :bug: Skip pino transports inside node:test processes The pino transports run in a worker thread reached through a message port that stays referenced once a log line is in flight, so the node:test child process kept running after every test and hook had finished. The runner then canceled the file's root test with "Promise resolution is still pending but the event loop has already resolved" and the suite hung until something killed it, which happened in roughly two runs out of five. `logTransportTargets` returns no targets inside a node:test child process, which `node --test` marks with `NODE_TEST_CONTEXT`, so tests log straight to stdout and the process exit is deterministic. Production still uses the console, file, and Loki transports. AI-assisted-by: step-5-preview-free --- .serena/memories/mcp/core.md | 15 +++++++++++++++ mcp/packages/server/src/logger.test.ts | 14 ++++++++++++++ mcp/packages/server/src/logger.ts | 24 +++++++++++++++++++----- 3 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 mcp/packages/server/src/logger.test.ts diff --git a/.serena/memories/mcp/core.md b/.serena/memories/mcp/core.md index 9d55a13105..a9d0b2164e 100644 --- a/.serena/memories/mcp/core.md +++ b/.serena/memories/mcp/core.md @@ -85,6 +85,21 @@ From the `mcp/` directory, run * `pnpm run build` to test the build of all packages * `pnpm run fmt` to apply the auto-formatter +* `pnpm run test` runs the tests of `plugin` (node test runner) and `server` (tsx). + The committed `typescript` version is 6.x: the 7.x line ships only a version stub + at the `typescript` entry point, and its synchronous compiler API is gone, so + `ts.transpileModule` (used by the plugin tests to compile `main.ts`/`plugin.ts` + into a sandboxed browser-like context) fails there. +* `packages/server` type-checks against the built `dist` of `@penpot/mcp-common`; + a stale `packages/common/dist` makes `types:check` fail with missing exports + (e.g. `PenpotSession`). Run `pnpm --filter mcp-common run build` first. +* The server logger (`packages/server/src/logger.ts`) uses pino transports only + outside `node:test` (`logTransportTargets`). A transport spawns a worker thread + whose message port stays referenced once a line is in flight, so the test child + process keeps running after the tests finish and the suite hangs intermittently + (node reports `Promise resolution is still pending but the event loop has + already resolved` for the file's root test). Inside tests the logger writes to + stdout directly. `NODE_TEST_CONTEXT` is the env var `node --test` sets. * Cross-cutting testing principles and anti-patterns: `mem:testing`. ## Devenv plugin/server wiring diff --git a/mcp/packages/server/src/logger.test.ts b/mcp/packages/server/src/logger.test.ts new file mode 100644 index 0000000000..ea0b6a0317 --- /dev/null +++ b/mcp/packages/server/src/logger.test.ts @@ -0,0 +1,14 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { logTransportTargets } from "./logger"; + +test("test processes log to stdout instead of a transport worker thread", () => { + assert.deepEqual(logTransportTargets({ NODE_TEST_CONTEXT: "child-v8" }), []); +}); + +test("regular processes keep the configured pino transports", () => { + assert.deepEqual( + logTransportTargets({}).map((target) => target.target), + ["pino-pretty"] + ); +}); diff --git a/mcp/packages/server/src/logger.ts b/mcp/packages/server/src/logger.ts index 45cb359e90..e74f7cdda9 100644 --- a/mcp/packages/server/src/logger.ts +++ b/mcp/packages/server/src/logger.ts @@ -198,17 +198,31 @@ const transports: LogTransportProvider[] = [consoleTransport, fileTransport, lok */ export const logFilePath: string | undefined = fileTransport.getFilePath(); +/** + * The pino transport targets to configure, empty when the logger writes to stdout directly. + * + * @param env - environment to inspect, injectable for tests + * @returns the active transport targets, empty inside a node:test child process + */ +export function logTransportTargets(env: NodeJS.ProcessEnv = process.env): TransportTargetSpec[] { + if (env.NODE_TEST_CONTEXT !== undefined) { + return []; + } + return transports.map((t) => t.getTarget()).filter((target): target is TransportTargetSpec => target !== null); +} + +const transportTargets = logTransportTargets(); + /** * Logger instance configured with the active transports (console, optional file, optional Loki). + * + * Inside a node:test child process the transports are skipped and the logger writes to stdout, + * so the test process does not outlive its tests over a transport worker thread. */ export const logger = pino({ level: LOG_LEVEL, timestamp: pino.stdTimeFunctions.isoTime, - transport: { - targets: transports - .map((t) => t.getTarget()) - .filter((target): target is TransportTargetSpec => target !== null), - }, + ...(transportTargets.length > 0 ? { transport: { targets: transportTargets } } : {}), }); /** From 25e8f349e1437e9faf49f929f970d7ef8493ef65 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 9 Oct 2026 17:13:57 +0000 Subject: [PATCH 10/11] :arrow_up: Update opencode to 2.0.26 in the devenv image Bumps the OpenCode CLI version pinned by the devenv image from 2.0.25 to 2.0.26 and refreshes the per-architecture SHA-256 checksums used to verify the downloaded tarballs. AI-assisted-by: step-5-preview-free --- docker/devenv/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/devenv/Dockerfile b/docker/devenv/Dockerfile index 882d94ce6b..1b452d6482 100644 --- a/docker/devenv/Dockerfile +++ b/docker/devenv/Dockerfile @@ -109,18 +109,18 @@ RUN set -eux; \ FROM base AS setup-opencode -ENV OPENCODE_VERSION=2.0.25 +ENV OPENCODE_VERSION=2.0.26 RUN set -eux; \ ARCH="$(dpkg --print-architecture)"; \ case "${ARCH}" in \ aarch64|arm64) \ BINARY_URL="https://registry.npmjs.org/@opencode/cli-linux-arm64/-/cli-linux-arm64-${OPENCODE_VERSION}.tgz"; \ - ESUM='92ce31022cd32f57ac3ccc775ba3a96f6aa36128a176aea0c7ef266986047537'; \ + ESUM='1d7117dbb31968456f89537b562fb5e20e48d9e9ef0325fc8845abdda6ef4842'; \ ;; \ amd64|x86_64) \ BINARY_URL="https://registry.npmjs.org/@opencode/cli-linux-x64/-/cli-linux-x64-${OPENCODE_VERSION}.tgz"; \ - ESUM='5b3e50ad1c06214aaf1c26eb30f90a15f66975a6e3e48fef5a31c34466e37b5a'; \ + ESUM='0a116e033a02804741d4644337d012bdf5a24aac33a34c0c96534d2d53961119'; \ ;; \ *) \ echo "Unsupported arch: ${ARCH}"; exit 1; \ From da2652f1dd2f692e88b2e973bb8348a2461c5955 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 9 Oct 2026 17:14:05 +0000 Subject: [PATCH 11/11] :books: Note GitHub assignee limits in the issues workflow `gh issue edit --add-assignee` fails with a 422 for users without write permission on the repo, for non-members, and for people who never commented, and the command still exits 0. The memory now records that failure and the `gh api` check that reveals whether the assignment actually took effect. AI-assisted-by: step-5-preview-free --- .serena/memories/workflow/creating-issues.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.serena/memories/workflow/creating-issues.md b/.serena/memories/workflow/creating-issues.md index 1032606ef4..dbe55c9241 100644 --- a/.serena/memories/workflow/creating-issues.md +++ b/.serena/memories/workflow/creating-issues.md @@ -263,6 +263,16 @@ AUTHOR=$(gh pr view --repo penpot/penpot --json author --jq '.author gh issue edit --repo penpot/penpot --add-assignee "$AUTHOR" ``` +GitHub only allows assigning users with write permission, org members, or +users who have already commented on the issue. Assigning an outside +contributor fails with `422 ... assignees ... invalid`, and `gh issue edit` +still exits 0 and prints the issue URL. Verify the result and, when the list +stays empty, skip the assignment: + +```bash +gh api repos/penpot/penpot/issues/ --jq '[.assignees[].login]' +``` + ### Set Issue Type and verify See the **Setting the Issue Type** and **Verification** sections above — the