From fc36fb0959f1a82158381350ca2dd383d0f48e53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Fri, 15 May 2026 12:04:51 +0200 Subject: [PATCH 01/14] :bug: Fix text editor being hidden to Playwright when empty text (#9682) --- frontend/playwright/ui/pages/WorkspacePage.js | 1 + .../playwright/ui/specs/text-editor-v2.spec.js | 16 ++++++++++++++++ .../main/ui/workspace/shapes/text/v2_editor.cljs | 4 ++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/frontend/playwright/ui/pages/WorkspacePage.js b/frontend/playwright/ui/pages/WorkspacePage.js index ec963f718a..e8e3a062b6 100644 --- a/frontend/playwright/ui/pages/WorkspacePage.js +++ b/frontend/playwright/ui/pages/WorkspacePage.js @@ -172,6 +172,7 @@ export class WorkspacePage extends BaseWebSocketPage { this.toolbarOptions = page.getByTestId("toolbar-options"); this.rectShapeButton = page.getByRole("button", { name: "Rectangle (R)" }); this.ellipseShapeButton = page.getByRole("button", { name: "Ellipse (E)" }); + this.textShapeButton = page.getByRole("button", { name: "Text (T)" }); this.moveButton = page.getByRole("button", { name: "Move (V)" }); this.boardButton = page.getByRole("button", { name: "Board (B)" }); this.toggleToolbarButton = page.getByRole("button", { diff --git a/frontend/playwright/ui/specs/text-editor-v2.spec.js b/frontend/playwright/ui/specs/text-editor-v2.spec.js index c12fef1bba..25676c45e1 100644 --- a/frontend/playwright/ui/specs/text-editor-v2.spec.js +++ b/frontend/playwright/ui/specs/text-editor-v2.spec.js @@ -315,3 +315,19 @@ test("BUG 11552 - Apply styles to the current caret", async ({ page }) => { await expect(fontSizeInput).toHaveValue(""); await expect(fontSizeInput).toHaveAttribute("placeholder", "Mixed"); }); + +// This is to prevent QA tests from failing due to playwright +// considering 0-width text boxes as invisible +test("BUG 14098 - Fix text editor having 0 width or height", async ({ page }) => { + const workspace = new WasmWorkspacePage(page); + + await workspace.setupEmptyFile(); + await workspace.mockRPC("update-file?id=*", "text-editor/update-file.json"); + await workspace.goToWorkspace(); + + await workspace.textShapeButton.click(); + await workspace.clickAt(200, 200); + + const textEditor = workspace.page.locator(`div[class*="viewport"]`).first().getByRole('textbox').first(); + await expect(textEditor).toBeVisible(); +}); diff --git a/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs index 779e7097aa..a92b0bf07b 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs @@ -423,8 +423,8 @@ (obj/merge! #js {"--editor-container-width" "auto" "--editor-container-height" "auto" - "--editor-container-min-width" (dm/str selrect-width "px") - "--editor-container-min-height" (dm/str selrect-height "px") + "--editor-container-min-width" (dm/str (max 1 selrect-width) "px") + "--editor-container-min-height" (dm/str (max 1 selrect-height) "px") "--fallback-families" (if (seq fallback-families) (dm/str (str/join ", " fallback-families)) "sourcesanspro") :display "flex"}) From ff23f786b4cba902790f0c1c642d5e153580107e Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 15 May 2026 08:33:42 +0000 Subject: [PATCH 02/14] :bug: Fix broken authentication on /assets handlers - Add ::setup/props and ::db/pool to :app.http.assets/routes config so session renewal works correctly for asset requests. - Add actoken/authz middleware to the assets middleware chain so access tokens are properly recognized. - Add authenticated? helper that checks both ::session/profile-id and ::actoken/profile-id, fixing 401 errors when accessing protected assets with a valid access token. - Add comprehensive test suite for assets auth scenarios. Closes #9677 Signed-off-by: Andrey Antukh --- backend/src/app/http/assets.clj | 16 +- backend/src/app/main.clj | 4 +- .../test/backend_tests/http_assets_test.clj | 461 ++++++++++++++++++ 3 files changed, 477 insertions(+), 4 deletions(-) create mode 100644 backend/test/backend_tests/http_assets_test.clj diff --git a/backend/src/app/http/assets.clj b/backend/src/app/http/assets.clj index d07090e074..430ee2bbc3 100644 --- a/backend/src/app/http/assets.clj +++ b/backend/src/app/http/assets.clj @@ -12,6 +12,7 @@ [app.common.time :as ct] [app.common.uri :as u] [app.db :as db] + [app.http.access-token :as actoken] [app.http.session :as session] [app.storage :as sto] [integrant.core :as ig] @@ -79,9 +80,17 @@ (let [bucket (-> obj meta :bucket)] (not (contains? public-buckets bucket)))) +(defn- authenticated? + "Check if the request has an authenticated profile, either via session + or access token." + [request] + (or (some? (::session/profile-id request)) + (some? (::actoken/profile-id request)))) + (defn objects-handler "Handler that serves storage objects by id. - For non-public buckets (e.g. profile), requires an authenticated session." + For non-public buckets (e.g. profile), requires authentication + via session cookie or access token." [{:keys [::sto/storage] :as cfg} request] (let [id (get-id request) obj (sto/get-object storage id)] @@ -90,7 +99,7 @@ {::yres/status 404} (and (requires-auth? obj) - (nil? (::session/profile-id request))) + (not (authenticated? request))) {::yres/status 401} :else @@ -128,7 +137,8 @@ (defmethod ig/init-key ::routes [_ cfg] - ["/assets" {:middleware [[session/authz cfg]]} + ["/assets" {:middleware [[session/authz cfg] + [actoken/authz cfg]]} ["/by-id/:id" {:handler (partial objects-handler cfg)}] ["/by-file-media-id/:id" {:handler (partial file-objects-handler cfg)}] ["/by-file-media-id/:id/thumbnail" {:handler (partial file-thumbnails-handler cfg)}]]) diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 018e6e301d..940775bdf0 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -302,7 +302,9 @@ ::http.assets/cache-max-age (ct/duration {:hours 24}) ::http.assets/signature-max-age (ct/duration {:hours 24 :minutes 15}) ::sto/storage (ig/ref ::sto/storage) - ::session/manager (ig/ref ::session/manager)} + ::session/manager (ig/ref ::session/manager) + ::setup/props (ig/ref ::setup/props) + ::db/pool (ig/ref ::db/pool)} ::rpc/climit {::mtx/metrics (ig/ref ::mtx/metrics) diff --git a/backend/test/backend_tests/http_assets_test.clj b/backend/test/backend_tests/http_assets_test.clj new file mode 100644 index 0000000000..df93d0b019 --- /dev/null +++ b/backend/test/backend_tests/http_assets_test.clj @@ -0,0 +1,461 @@ +;; 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 backend-tests.http-assets-test + (:require + [app.common.time :as ct] + [app.common.uuid :as uuid] + [app.db :as db] + [app.http :as-alias http] + [app.http.access-token :as actoken] + [app.http.assets :as assets] + [app.http.session :as session] + [app.rpc.commands.access-token :as access-token] + [app.storage :as sto] + [backend-tests.helpers :as th] + [clojure.test :as t] + [datoteka.fs :as fs] + [yetti.response :as-alias yres])) + +(t/use-fixtures :once th/state-init) +(t/use-fixtures :each (th/serial + th/database-reset + th/clean-storage)) + +;; ---------------------------------------------------------------- +;; Helpers +;; ---------------------------------------------------------------- + +(defn- configure-storage-backend + "Given storage map, returns a storage configured with the + appropriate backend for assets." + [storage] + (assoc storage ::sto/backend :fs)) + +(defn- create-storage-object! + "Create a storage object with the given bucket and content." + [storage bucket content] + (sto/put-object! storage {::sto/content (sto/content content) + :bucket bucket + :content-type "text/plain"})) + +(defn- make-handler-cfg + "Build a minimal cfg map for the assets handlers." + [storage] + {::sto/storage storage + ::assets/path "/assets"}) + +;; ---------------------------------------------------------------- +;; Tests: get-id +;; ---------------------------------------------------------------- + +(t/deftest get-id-with-valid-uuid + (let [id (uuid/next) + request {:path-params {:id (str id)}} + result (assets/get-id request)] + (t/is (= id result)))) + +(t/deftest get-id-with-invalid-uuid + (let [request {:path-params {:id "not-a-uuid"}}] + (try + (assets/get-id request) + (t/is false "should have thrown") + (catch Exception e + (t/is (= :not-found (:type (ex-data e)))))))) + +(t/deftest get-id-with-missing-id + (let [request {:path-params {}}] + (try + (assets/get-id request) + (t/is false "should have thrown") + (catch Exception e + (t/is (= :not-found (:type (ex-data e)))))))) + +;; ---------------------------------------------------------------- +;; Tests: objects-handler — non-existent objects +;; ---------------------------------------------------------------- + +(t/deftest objects-handler-non-existent-object + (let [storage (-> (:app.storage/storage th/*system*) + (configure-storage-backend)) + cfg (make-handler-cfg storage) + request {:path-params {:id (str (uuid/next))}} + response (assets/objects-handler cfg request)] + (t/is (= 404 (::yres/status response))))) + +(t/deftest objects-handler-invalid-uuid + (let [storage (-> (:app.storage/storage th/*system*) + (configure-storage-backend)) + cfg (make-handler-cfg storage) + request {:path-params {:id "not-a-uuid"}}] + (try + (assets/objects-handler cfg request) + (t/is false "should have thrown") + (catch Exception e + (t/is (= :not-found (:type (ex-data e)))))))) + +;; ---------------------------------------------------------------- +;; Tests: objects-handler — public buckets (no auth required) +;; ---------------------------------------------------------------- + +(t/deftest objects-handler-public-bucket-no-auth + ;; Objects in public buckets should be accessible without authentication. + (let [storage (-> (:app.storage/storage th/*system*) + (configure-storage-backend)) + cfg (make-handler-cfg storage)] + + (doseq [bucket ["file-media-object" + "file-object-thumbnail" + "team-font-variant" + "file-data-fragment"]] + (t/testing (str "bucket: " bucket) + (let [object (create-storage-object! storage bucket "public data") + request {:path-params {:id (str (:id object))}} + response (assets/objects-handler cfg request)] + (t/is (not= 401 (::yres/status response)) + (str "bucket " bucket " should not require auth")) + (t/is (not= 404 (::yres/status response)) + (str "bucket " bucket " object should exist"))))))) + +(t/deftest objects-handler-public-bucket-with-auth + ;; Objects in public buckets should also be accessible WITH authentication. + (let [storage (-> (:app.storage/storage th/*system*) + (configure-storage-backend)) + cfg (make-handler-cfg storage) + profile (th/create-profile* 1) + object (create-storage-object! storage "file-media-object" "public data") + request {:path-params {:id (str (:id object))} + ::session/profile-id (:id profile)} + response (assets/objects-handler cfg request)] + (t/is (not= 401 (::yres/status response))) + (t/is (not= 404 (::yres/status response))))) + +;; ---------------------------------------------------------------- +;; Tests: objects-handler — non-public buckets (auth required) +;; ---------------------------------------------------------------- + +(t/deftest objects-handler-non-public-bucket-no-auth + ;; Objects in non-public buckets should return 401 without authentication. + (let [storage (-> (:app.storage/storage th/*system*) + (configure-storage-backend)) + cfg (make-handler-cfg storage) + object (create-storage-object! storage "profile" "profile photo") + request {:path-params {:id (str (:id object))}} + response (assets/objects-handler cfg request)] + (t/is (= 401 (::yres/status response))))) + +(t/deftest objects-handler-non-public-bucket-with-session-auth + ;; Objects in non-public buckets should be accessible with session auth. + (let [storage (-> (:app.storage/storage th/*system*) + (configure-storage-backend)) + cfg (make-handler-cfg storage) + profile (th/create-profile* 1) + object (create-storage-object! storage "profile" "profile photo") + request {:path-params {:id (str (:id object))} + ::session/profile-id (:id profile)} + response (assets/objects-handler cfg request)] + (t/is (not= 401 (::yres/status response))) + (t/is (not= 404 (::yres/status response))))) + +(t/deftest objects-handler-non-public-bucket-with-access-token-auth + ;; Objects in non-public buckets should be accessible with access token auth. + (let [storage (-> (:app.storage/storage th/*system*) + (configure-storage-backend)) + cfg (make-handler-cfg storage) + profile (th/create-profile* 1) + object (create-storage-object! storage "profile" "profile photo") + request {:path-params {:id (str (:id object))} + ::actoken/profile-id (:id profile)} + response (assets/objects-handler cfg request)] + (t/is (not= 401 (::yres/status response))) + (t/is (not= 404 (::yres/status response))))) + +(t/deftest objects-handler-non-public-bucket-with-both-auth + ;; Objects should be accessible when both session and access token auth are present. + (let [storage (-> (:app.storage/storage th/*system*) + (configure-storage-backend)) + cfg (make-handler-cfg storage) + profile (th/create-profile* 1) + object (create-storage-object! storage "profile" "profile photo") + request {:path-params {:id (str (:id object))} + ::session/profile-id (:id profile) + ::actoken/profile-id (:id profile)} + response (assets/objects-handler cfg request)] + (t/is (not= 401 (::yres/status response))) + (t/is (not= 404 (::yres/status response))))) + +;; ---------------------------------------------------------------- +;; Tests: objects-handler — all non-public buckets +;; ---------------------------------------------------------------- + +(t/deftest objects-handler-all-non-public-buckets-require-auth + ;; Verify that all buckets NOT in the public set require authentication. + (let [storage (-> (:app.storage/storage th/*system*) + (configure-storage-backend)) + cfg (make-handler-cfg storage) + profile (th/create-profile* 1)] + + (doseq [bucket ["profile" + "tempfile" + "file-data" + "file-thumbnail" + "file-change"]] + (t/testing (str "bucket: " bucket) + (let [object (create-storage-object! storage bucket "some data") + request {:path-params {:id (str (:id object))}}] + + ;; Without auth → 401 + (let [response (assets/objects-handler cfg request)] + (t/is (= 401 (::yres/status response)) + (str "bucket " bucket " should require auth"))) + + ;; With session auth → not 401 + (let [response (assets/objects-handler cfg (assoc request ::session/profile-id (:id profile)))] + (t/is (not= 401 (::yres/status response)) + (str "bucket " bucket " should be accessible with session auth"))) + + ;; With access token auth → not 401 + (let [response (assets/objects-handler cfg (assoc request ::actoken/profile-id (:id profile)))] + (t/is (not= 401 (::yres/status response)) + (str "bucket " bucket " should be accessible with access token auth")))))))) + +;; ---------------------------------------------------------------- +;; Tests: objects-handler — serve-object response (FS backend) +;; ---------------------------------------------------------------- + +(t/deftest objects-handler-fs-backend-serves-object + ;; Verify that the FS backend returns the correct response structure. + (let [storage (-> (:app.storage/storage th/*system*) + (configure-storage-backend)) + cfg (make-handler-cfg storage) + profile (th/create-profile* 1) + object (create-storage-object! storage "file-media-object" "file content") + request {:path-params {:id (str (:id object))} + ::session/profile-id (:id profile)} + response (assets/objects-handler cfg request)] + ;; FS backend returns 204 with x-accel-redirect header + (t/is (= 204 (::yres/status response))) + (t/is (some? (get (::yres/headers response) "x-accel-redirect"))) + (t/is (= "text/plain" (get (::yres/headers response) "content-type"))) + (t/is (some? (get (::yres/headers response) "cache-control"))))) + +(t/deftest objects-handler-fs-backend-accel-redirect-path + ;; Verify that x-accel-redirect contains the object's relative path. + (let [storage (-> (:app.storage/storage th/*system*) + (configure-storage-backend)) + cfg (make-handler-cfg storage) + object (create-storage-object! storage "file-media-object" "file content") + request {:path-params {:id (str (:id object))}} + response (assets/objects-handler cfg request) + redirect (get (::yres/headers response) "x-accel-redirect")] + ;; The redirect path should contain the object's relative path + (t/is (string? redirect)) + (t/is (clojure.string/includes? redirect (sto/object->relative-path object))))) + +;; ---------------------------------------------------------------- +;; Tests: objects-handler — cache headers +;; ---------------------------------------------------------------- + +(t/deftest objects-handler-cache-control-header + (let [storage (-> (:app.storage/storage th/*system*) + (configure-storage-backend)) + cfg (make-handler-cfg storage) + object (create-storage-object! storage "file-media-object" "content") + request {:path-params {:id (str (:id object))}} + response (assets/objects-handler cfg request) + cc (get (::yres/headers response) "cache-control")] + (t/is (string? cc)) + (t/is (clojure.string/starts-with? cc "max-age=")))) + +;; ---------------------------------------------------------------- +;; Tests: middleware integration — session auth end-to-end +;; ---------------------------------------------------------------- + +(t/deftest session-auth-integration + ;; Test the full session auth flow: create session → assign token → + ;; authenticate request → access protected asset. + (let [cfg th/*system* + manager (session/inmemory-manager) + profile (th/create-profile* 1) + + ;; Create a session and generate a token + session (->> (session/create-session manager {:profile-id (:id profile) + :user-agent "test-agent"}) + (#'session/assign-token cfg)) + + ;; Create a storage object in a non-public bucket + storage (-> (:app.storage/storage th/*system*) + (configure-storage-backend)) + object (create-storage-object! storage "profile" "profile data") + + ;; Simulate what the middleware chain does: + ;; 1. mw/auth extracts token from cookie and sets ::http/auth-data + ;; 2. session/authz reads ::http/auth-data and sets ::session/profile-id + request {::http/auth-data {:type :cookie + :token (:token session) + :claims {:sid (:id session) + :uid (:id profile)} + :metadata {:ver 1}} + :path-params {:id (str (:id object))}} + + ;; Apply session/authz middleware + handler (#'session/wrap-authz + (fn [req] + ;; This is where the actual handler would be called + ;; We verify that ::session/profile-id is set + req) + {::session/manager manager}) + result (handler request)] + + ;; Verify the session auth set the profile-id + (t/is (= (:id profile) (::session/profile-id result))) + (t/is (some? (::session/session result))))) + +;; ---------------------------------------------------------------- +;; Tests: middleware integration — access token auth end-to-end +;; ---------------------------------------------------------------- + +(t/deftest access-token-auth-integration + ;; Test the full access token flow: create token → authenticate + ;; request → access protected asset. + (let [profile (th/create-profile* 1) + + ;; Create an access token in the database + atoken (db/tx-run! th/*system* + access-token/create-access-token + (:id profile) "test-token" nil nil) + + ;; Create a storage object in a non-public bucket + storage (-> (:app.storage/storage th/*system*) + (configure-storage-backend)) + object (create-storage-object! storage "profile" "profile data") + + ;; Simulate what the middleware chain does: + ;; 1. mw/auth extracts token from Authorization header and sets ::http/auth-data + ;; 2. actoken/authz reads ::http/auth-data and sets ::actoken/profile-id + request {::http/auth-data {:type :token + :token (:token atoken) + :claims {:tid (:id atoken)}} + :path-params {:id (str (:id object))}} + + ;; Apply actoken/authz middleware + handler (#'actoken/wrap-authz + (fn [req] + ;; Verify that ::actoken/profile-id is set + req) + th/*system*) + result (handler request)] + + ;; Verify the access token auth set the profile-id + (t/is (= (:id profile) (::actoken/profile-id result))))) + +;; ---------------------------------------------------------------- +;; Tests: middleware chain — combined session + access token +;; ---------------------------------------------------------------- + +(t/deftest combined-middleware-chain + ;; Test that both session/authz and actoken/authz work together + ;; in the middleware chain, matching the assets route configuration. + (let [cfg th/*system* + manager (session/inmemory-manager) + profile (th/create-profile* 1) + + ;; Create a session + session (->> (session/create-session manager {:profile-id (:id profile) + :user-agent "test-agent"}) + (#'session/assign-token cfg)) + + ;; Create an access token + atoken (db/tx-run! th/*system* + access-token/create-access-token + (:id profile) "test-token" nil nil) + + ;; Build the middleware chain like assets routes do: + ;; session/authz → actoken/authz → handler + inner-handler (fn [request] request) + with-actoken (#'actoken/wrap-authz inner-handler th/*system*) + with-session (#'session/wrap-authz with-actoken {::session/manager manager})] + + (t/testing "session cookie auth sets ::session/profile-id" + (let [request {::http/auth-data {:type :cookie + :token (:token session) + :claims {:sid (:id session) + :uid (:id profile)} + :metadata {:ver 1}}} + result (with-session request)] + (t/is (= (:id profile) (::session/profile-id result))))) + + (t/testing "access token auth sets ::actoken/profile-id" + (let [request {::http/auth-data {:type :token + :token (:token atoken) + :claims {:tid (:id atoken)}}} + result (with-session request)] + (t/is (= (:id profile) (::actoken/profile-id result))))) + + (t/testing "no auth sets neither profile-id" + (let [request {} + result (with-session request)] + (t/is (nil? (::session/profile-id result))) + (t/is (nil? (::actoken/profile-id result))))))) + +;; ---------------------------------------------------------------- +;; Tests: objects-handler — edge cases +;; ---------------------------------------------------------------- + +(t/deftest objects-handler-nil-profile-id-in-session + ;; When session auth is present but profile-id is nil (e.g. invalid session), + ;; non-public objects should still be denied. + (let [storage (-> (:app.storage/storage th/*system*) + (configure-storage-backend)) + cfg (make-handler-cfg storage) + object (create-storage-object! storage "profile" "data") + request {:path-params {:id (str (:id object))} + ::session/profile-id nil} + response (assets/objects-handler cfg request)] + (t/is (= 401 (::yres/status response))))) + +(t/deftest objects-handler-nil-profile-id-in-access-token + ;; When access token auth is present but profile-id is nil, + ;; non-public objects should still be denied. + (let [storage (-> (:app.storage/storage th/*system*) + (configure-storage-backend)) + cfg (make-handler-cfg storage) + object (create-storage-object! storage "profile" "data") + request {:path-params {:id (str (:id object))} + ::actoken/profile-id nil} + response (assets/objects-handler cfg request)] + (t/is (= 401 (::yres/status response))))) + +(t/deftest objects-handler-empty-request + ;; A request with no path-params should raise a not-found error. + (let [storage (-> (:app.storage/storage th/*system*) + (configure-storage-backend)) + cfg (make-handler-cfg storage) + request {}] + (try + (assets/objects-handler cfg request) + (t/is false "should have thrown") + (catch Exception e + (t/is (= :not-found (:type (ex-data e)))))))) + +;; ---------------------------------------------------------------- +;; Tests: objects-handler — expired objects +;; ---------------------------------------------------------------- + +(t/deftest objects-handler-expired-object + ;; Expired objects should return 404 (get-object filters them out). + (let [storage (-> (:app.storage/storage th/*system*) + (configure-storage-backend)) + cfg (make-handler-cfg storage) + profile (th/create-profile* 1) + object (sto/put-object! storage {::sto/content (sto/content "expired") + ::sto/expired-at (ct/now) + :bucket "profile" + :content-type "text/plain"}) + request {:path-params {:id (str (:id object))} + ::session/profile-id (:id profile)} + response (assets/objects-handler cfg request)] + (t/is (= 404 (::yres/status response))))) From 030005860573eda664398c426512a22863f586d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Fri, 15 May 2026 13:41:38 +0200 Subject: [PATCH 03/14] :bug: Fix delete page icon being clipped (#9685) --- .../main/ui/workspace/sidebar/sitemap.cljs | 3 ++- .../main/ui/workspace/sidebar/sitemap.scss | 21 ++++--------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs b/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs index 5d3a1b95dd..88d9332ef1 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs @@ -220,10 +220,11 @@ name] [:div {:class (stl/css :page-actions)} (when (and deletable? (not read-only?)) - [:> icon-button* {:variant "ghost" + [:> icon-button* {:variant "action" :aria-label (tr "modals.delete-page.title") :on-click on-delete :icon-size "s" + :icon-class (stl/css :page-delete-button-icon) :icon i/delete}])]])])]]))) ;; --- Page Item Wrapper diff --git a/frontend/src/app/main/ui/workspace/sidebar/sitemap.scss b/frontend/src/app/main/ui/workspace/sidebar/sitemap.scss index 72f028d09c..b642d89450 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/sitemap.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/sitemap.scss @@ -55,6 +55,10 @@ margin-bottom: deprecated.$s-12; } +.page-delete-button-icon { + color: transparent; +} + .page-element { @include deprecated.body-small-typography; @@ -102,23 +106,6 @@ height: deprecated.$s-32; display: flex; align-items: center; - - button { - @include deprecated.button-style; - @include deprecated.flex-center; - - width: deprecated.$s-24; - height: 100%; - opacity: deprecated.$op-0; - - svg { - @extend %button-icon-small; - - color: transparent; - fill: none; - stroke: var(--icon-foreground); - } - } } .element-name { From 24fe5559c541b65b2bdb3b6d7c217a056f9b005c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?andr=C3=A9s=20gonz=C3=A1lez?= Date: Mon, 18 May 2026 10:31:24 +0200 Subject: [PATCH 04/14] :books: Update 2.16 changelog (#9689) Co-authored-by: Cursor --- CHANGES.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 291d520443..6c45a520e4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,8 @@ ### :boom: Breaking changes & Deprecations ### :rocket: Epics and highlights +- WebGL rendering (beta) user preference [#9683](https://github.com/penpot/penpot/issues/9683) (PR:[9113](https://github.com/penpot/penpot/pull/9113)) +- Design Tokens at the design tab: numeric fields with token selection in place [#9358](https://github.com/penpot/penpot/issues/9358) ### :sparkles: New features & Enhancements @@ -56,15 +58,15 @@ - Add clipboard read/write permissions to the plugin system (by @wdeveloper16) [#6980](https://github.com/penpot/penpot/issues/6980) (PR: [#9053](https://github.com/penpot/penpot/pull/9053)) - Update auth hero illustration on login screen [#9532](https://github.com/penpot/penpot/issues/9532) (PR: [#9552](https://github.com/penpot/penpot/pull/9552)) - Update Open Graph link preview metadata [#9555](https://github.com/penpot/penpot/issues/9555) (PR: [#9557](https://github.com/penpot/penpot/pull/9557)) -- Fix library update button freezing [#9330](https://github.com/penpot/penpot/issues/9330) (PR: [#9513](https://github.com/penpot/penpot/pull/9513)) -- Add new numeric inputs for token management on the right sidebar [#9358](https://github.com/penpot/penpot/issues/9358) - Restore deleted team files in bulk instead of per file (by @Dexterity104) [#9246](https://github.com/penpot/penpot/issues/9246) (PR: [#9248](https://github.com/penpot/penpot/pull/9248)) - Preserve Inkscape labels when pasting SVGs (by @jeffrey701) [#7869](https://github.com/penpot/penpot/issues/7869) (PR: [#9252](https://github.com/penpot/penpot/pull/9252)) - Add Alt+click to expand layer subtree (by @MilosM348) [#7736](https://github.com/penpot/penpot/issues/7736) (PR: [#9179](https://github.com/penpot/penpot/pull/9179)) -- Fix typo in subscription settings success key (by @jack-stormentswe) [#9203](https://github.com/penpot/penpot/issues/9203) (PR: [#9204](https://github.com/penpot/penpot/pull/9204)) -### :bug: Bugs fixed +- Allow deleting the profile avatar after uploading (by @moorsecopers99) [#9067](https://github.com/penpot/penpot/issues/9067) (PR: [#9068](https://github.com/penpot/penpot/pull/9068)) +### :bug: Bugs fixed - Fix Alt/Option to draw shapes from center point (by @offreal) [#8360](https://github.com/penpot/penpot/issues/8360) (PR: [#8361](https://github.com/penpot/penpot/pull/8361)) +- Fix library update button freezing [#9330](https://github.com/penpot/penpot/issues/9330) (PR: [#9513](https://github.com/penpot/penpot/pull/9513)) +- Fix typo in subscription settings success key (by @jack-stormentswe) [#9203](https://github.com/penpot/penpot/issues/9203) (PR: [#9204](https://github.com/penpot/penpot/pull/9204)) - Add token name on broken token pill on sidebar [#9534](https://github.com/penpot/penpot/issues/9534) (PR: [#8527](https://github.com/penpot/penpot/pull/8527)) - Fix tooltip activated when tab change [#9539](https://github.com/penpot/penpot/issues/9539) (PR: [#8719](https://github.com/penpot/penpot/pull/8719)) - Fix title on shared button [#9541](https://github.com/penpot/penpot/issues/9541) (PR: [#8696](https://github.com/penpot/penpot/pull/8696)) @@ -100,7 +102,6 @@ - Fix app crash on multiselection with hidden shapes and opacity mixed value [#9666](https://github.com/penpot/penpot/issues/9666) (PR: [#8932](https://github.com/penpot/penpot/pull/8932)) - Fix gap input throwing an error [#9667](https://github.com/penpot/penpot/issues/9667) (PR: [#8984](https://github.com/penpot/penpot/pull/8984)) - Fix copy to be more specific [#9668](https://github.com/penpot/penpot/issues/9668) (PR: [#9028](https://github.com/penpot/penpot/pull/9028)) -- Allow deleting the profile avatar after uploading (by @moorsecopers99) [#9067](https://github.com/penpot/penpot/issues/9067) (PR: [#9068](https://github.com/penpot/penpot/pull/9068)) - Fix incorrect rendering when exporting text as SVG, PNG and JPG (by @edwin-rivera-dev) [#8516](https://github.com/penpot/penpot/issues/8516) (PR: [#9094](https://github.com/penpot/penpot/pull/9094)) - Fix typography style creation with tokenized line-height (by @juan-flores077) [#8479](https://github.com/penpot/penpot/issues/8479) (PR: [#9121](https://github.com/penpot/penpot/pull/9121)) - Fix colorpicker layout hiding eyedropper button [#9669](https://github.com/penpot/penpot/issues/9669) (PR: [#9125](https://github.com/penpot/penpot/pull/9125)) From b2bfd627aef4b8ac8b1275906354d504b60a2127 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 11 May 2026 18:55:14 +0200 Subject: [PATCH 05/14] :bug: Fix problem with viewer texts --- .../ui/specs/multiseleccion.spec.js | 5 +++ frontend/playwright/ui/specs/versions.spec.js | 7 +++- frontend/src/app/main/data/workspace.cljs | 32 +++++++++++++++++++ .../app/main/ui/workspace/viewport_wasm.cljs | 6 +++- 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/frontend/playwright/ui/specs/multiseleccion.spec.js b/frontend/playwright/ui/specs/multiseleccion.spec.js index 5d7ee1c92c..c81e4ef5f2 100644 --- a/frontend/playwright/ui/specs/multiseleccion.spec.js +++ b/frontend/playwright/ui/specs/multiseleccion.spec.js @@ -214,6 +214,11 @@ test("Multiselection of text and typographies", async ({ page }) => { pageId: "1062e0a0-8fe0-80ae-8007-e70b4993f5f0", }); + await workspacePage.mockRPC( + "update-file?id=*", + "workspace/update-file-empty.json", + ); + const plainTextLayer = workspacePage.layers.getByTestId("layer-row").nth(5); const plainTextLayerTwo = workspacePage.layers .getByTestId("layer-row") diff --git a/frontend/playwright/ui/specs/versions.spec.js b/frontend/playwright/ui/specs/versions.spec.js index 2c5fa018f2..98bb92acb0 100644 --- a/frontend/playwright/ui/specs/versions.spec.js +++ b/frontend/playwright/ui/specs/versions.spec.js @@ -121,6 +121,11 @@ test("BUG 13385 - Fix viewport not updating when restoring version", async ({ pa await workspacePage.mockGetFile("workspace/get-file-13385.json"); await workspacePage.mockRPC("get-profiles-for-file-comments?file-id=*", "workspace/get-profiles-for-file-comments-13385.json"); + await workspacePage.mockRPC( + "update-file?id=*", + "workspace/update-file-empty.json", + ); + // navigate to workspace and check that the circle shape is not there await workspacePage.goToWorkspace(); await expect(workspacePage.layers.getByText("Ellipse")).not.toBeVisible(); @@ -141,4 +146,4 @@ test("BUG 13385 - Fix viewport not updating when restoring version", async ({ pa // assert that the circle shape exists await expect(workspacePage.layers.getByText("Ellipse")).toBeVisible(); -}); \ No newline at end of file +}); diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 28123c294f..d61ed1fded 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -203,6 +203,38 @@ (rx/of (ptk/data-event ::all-libraries-resolved {:file-id file-id}))) (rx/take-until stopper-s)))))) + +(defn check-file-position-data + [file-id] + (ptk/reify ::fix-position-data + ptk/WatchEvent + (watch [it state _] + (let [file (dsh/lookup-file state file-id) + changes + (->> file :data :pages + (mapcat + (fn [page-id] + (->> (dsh/lookup-page-objects state file-id page-id) + (vals) + (filter cfh/text-shape?) + (filter #(nil? (:position-data %))) + (map (fn [shape] + {:type :mod-obj + :id (:id shape) + :page-id page-id + :operations + [{:type :set + :attr :position-data + :val (wasm.api/calculate-position-data shape) + :ignore-touched true + :ignore-geometry true}]}))))) + (into []))] + (rx/of (dch/commit-changes + {:redo-changes changes :undo-changes [] + :save-undo? false + :origin it + :tags #{:position-data}})))))) + (defn- workspace-initialized [file-id] (ptk/reify ::workspace-initialized diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index ce90438c45..42bcd59e64 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -17,6 +17,7 @@ [app.common.types.shape :as cts] [app.common.types.shape.layout :as ctl] [app.main.data.modal :as modal] + [app.main.data.workspace :as dw] [app.main.data.workspace.transforms :as dwt] [app.main.data.workspace.variants :as dwv] [app.main.features :as features] @@ -455,7 +456,10 @@ ;; blank canvas (first load) visible while shapes load. ;; The loading overlay is suppressed because on-shapes-ready ;; is set. - (wasm.api/initialize-viewport base-objects zoom vbox :background background) + (wasm.api/initialize-viewport base-objects zoom vbox + :background background + :on-shapes-ready + #(st/emit! (dw/check-file-position-data file-id))) (reset! initialized? true)) (when (and (some? vern) (not= vern (mf/ref-val last-vern-ref))) From 25ee8dee7829340a4996804154500081ed794fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 18 May 2026 12:28:48 +0200 Subject: [PATCH 06/14] :bug: Fix editing a text element detaches applied tokens (#9525) --- CHANGES.md | 1 + common/src/app/common/data.cljc | 4 +- common/src/app/common/types/text.cljc | 7 +- .../data/workspace/get-file-13958.json | 4321 +++++++++++++++++ .../playwright/ui/specs/tokens/text.spec.js | 29 + 5 files changed, 4359 insertions(+), 3 deletions(-) create mode 100644 frontend/playwright/data/workspace/get-file-13958.json create mode 100644 frontend/playwright/ui/specs/tokens/text.spec.js diff --git a/CHANGES.md b/CHANGES.md index 6c45a520e4..7905f7cdbd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -123,6 +123,7 @@ - Fix several color picker issues [#9556](https://github.com/penpot/penpot/issues/9556) (PR: [#9558](https://github.com/penpot/penpot/pull/9558)) - Fix asset icon broken on Asset tab [#9587](https://github.com/penpot/penpot/issues/9587) (PR: [#9612](https://github.com/penpot/penpot/pull/9612)) - Fix text fill color stops updating in multiselect with texts [#9608](https://github.com/penpot/penpot/issues/9608) (PR: [#9549](https://github.com/penpot/penpot/pull/9549)) +- Fix editing a legacy text element silently detaches its color token [Taiga #13958](https://tree.taiga.io/project/penpot/issue/13958) ## 2.15.4 (Unreleased) diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index e0ad3cab15..a97acd0f8a 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -177,7 +177,9 @@ (defn not-empty? [coll] - (boolean (seq coll))) + (if (coll? coll) + (boolean (seq coll)) + (not (nil? coll)))) (defn editable-collection? [m] diff --git a/common/src/app/common/types/text.cljc b/common/src/app/common/types/text.cljc index cde27c19c4..05516a7295 100644 --- a/common/src/app/common/types/text.cljc +++ b/common/src/app/common/types/text.cljc @@ -242,8 +242,11 @@ acc) :else - ;; If the key is not :text, and they are different, it is an attribute differece - (if (not= v1 v2) + ;; If the key is not :text, and they are different, it is an attribute difference. + ;; Take into account that some processes remove empty attributes, so in some + ;; cases we will compare [] with nil, and this is not a difference. + (if (and (not= v1 v2) + (or (d/not-empty? v1) (d/not-empty? v2))) (attribute-cb acc k) acc)))) #{} diff --git a/frontend/playwright/data/workspace/get-file-13958.json b/frontend/playwright/data/workspace/get-file-13958.json new file mode 100644 index 0000000000..77b9815d1a --- /dev/null +++ b/frontend/playwright/data/workspace/get-file-13958.json @@ -0,0 +1,4321 @@ +{ + "~:features": { + "~#set": [ + "fdata/path-data", + "design-tokens/v1", + "variants/v1", + "layout/grid", + "fdata/objects-map", + "components/v2", + "fdata/shape-data-type" + ] + }, + "~:team-id": "~uc6b102e2-5aaa-809c-8007-dcd1eab2135d", + "~:permissions": { + "~:type": "~:membership", + "~:is-owner": true, + "~:is-admin": true, + "~:can-edit": true, + "~:can-read": true, + "~:is-logged": true + }, + "~:has-media-trimmed": false, + "~:comment-thread-seqn": 0, + "~:name": "color tokens detach (copy)", + "~:revn": 11, + "~:modified-at": "~m1778577544627", + "~:vern": 151920609, + "~:id": "~u1bac06a1-a942-80a6-8008-0222e1ec38d5", + "~:is-shared": false, + "~:migrations": { + "~#ordered-set": [ + "legacy-2", + "legacy-3", + "legacy-5", + "legacy-6", + "legacy-7", + "legacy-8", + "legacy-9", + "legacy-10", + "legacy-11", + "legacy-12", + "legacy-13", + "legacy-14", + "legacy-16", + "legacy-17", + "legacy-18", + "legacy-19", + "legacy-25", + "legacy-26", + "legacy-27", + "legacy-28", + "legacy-29", + "legacy-31", + "legacy-32", + "legacy-33", + "legacy-34", + "legacy-36", + "legacy-37", + "legacy-38", + "legacy-39", + "legacy-40", + "legacy-41", + "legacy-42", + "legacy-43", + "legacy-44", + "legacy-45", + "legacy-46", + "legacy-47", + "legacy-48", + "legacy-49", + "legacy-50", + "legacy-51", + "legacy-52", + "legacy-53", + "legacy-54", + "legacy-55", + "legacy-56", + "legacy-57", + "legacy-59", + "legacy-62", + "legacy-65", + "legacy-66", + "legacy-67", + "0001-remove-tokens-from-groups", + "0002-normalize-bool-content-v2", + "0002-clean-shape-interactions", + "0003-fix-root-shape", + "0003-convert-path-content-v2", + "0005-deprecate-image-type", + "0006-fix-old-texts-fills", + "0008-fix-library-colors-v4", + "0009-clean-library-colors", + "0009-add-partial-text-touched-flags", + "0010-fix-swap-slots-pointing-non-existent-shapes", + "0011-fix-invalid-text-touched-flags", + "0012-fix-position-data", + "0013-fix-component-path", + "0013-clear-invalid-strokes-and-fills", + "0014-fix-tokens-lib-duplicate-ids", + "0014-clear-components-nil-objects", + "0015-fix-text-attrs-blank-strings", + "0015-clean-shadow-color", + "0016-copy-fills-from-position-data-to-text-node", + "0017-fix-layout-flex-dir", + "0018-remove-unneeded-objects-from-components", + "0019-fix-missing-swap-slots", + "0020-sync-component-id-with-near-main", + "0021-repair-bad-tokens" + ] + }, + "~:version": 67, + "~:project-id": "~u4cdd76d8-0e6d-8168-8008-0118118e1a1a", + "~:created-at": "~m1778507716647", + "~:backend": "legacy-db", + "~:data": { + "~:pages": [ + "~u1919607f-7059-80e9-8007-d7b6ba437f17" + ], + "~:pages-index": { + "~u1919607f-7059-80e9-8007-d7b6ba437f17": { + "~:id": "~u1919607f-7059-80e9-8007-d7b6ba437f17", + "~:name": "Page 1", + "~:objects": { + "~#penpot/objects-map/v2": { + "~ufb6e1db9-e999-8049-8007-d7b7ac11fbac": "[\"~#shape\",[\"^ \",\"~:y\",603,\"~:layout-item-hsizing\",\"fill\",\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:auto-height\",\"~:content\",[\"^ \",\"~:type\",\"root\",\"~:children\",[[\"^ \",\"^8\",\"paragraph-set\",\"^9\",[[\"^ \",\"~:line-height\",\"1.5\",\"~:path\",\"font-screen-lg / body\",\"~:font-style\",\"normal\",\"^9\",[[\"^ \",\"^:\",\"1.5\",\"^;\",\"font-screen-lg / body\",\"^<\",\"normal\",\"~:text-transform\",\"none\",\"~:text-align\",\"left\",\"~:font-id\",\"gfont-dm-sans\",\"~:text-style-id\",\"S:61089755d06cc251915221fee7a45ae2841996c6,\",\"~:font-size\",\"16\",\"~:font-weight\",\"400\",\"~:modified-at\",\"2025-01-24T18:57:32.766Z\",\"~:font-variant-id\",\"regular\",\"~:text-decoration\",\"none\",\"~:letter-spacing\",\"0\",\"~:fills\",[[\"^ \",\"~:fill-color\",\"#2f2c35\",\"~:fill-opacity\",1]],\"~:font-family\",\"DM Sans\",\"~:text\",\"Design tokens are a set of codified design decisions that store raw design values as named entities, making them core pieces of a design system. \\n\"],[\"^ \",\"^:\",\"1\",\"^<\",\"normal\",\"^?\",\"sourcesanspro\",\"^A\",\"16\",\"^B\",\"400\",\"^D\",\"regular\",\"^F\",\"0\",\"^G\",[[\"^ \",\"^H\",\"#2f2c35\",\"^I\",1]],\"^K\",\"\\n\"],[\"^ \",\"^:\",\"1.5\",\"^;\",\"font-screen-lg / body\",\"^<\",\"normal\",\"^=\",\"none\",\"^>\",\"left\",\"^?\",\"gfont-dm-sans\",\"^@\",\"S:61089755d06cc251915221fee7a45ae2841996c6,\",\"^A\",\"16\",\"^B\",\"400\",\"^C\",\"2025-01-24T18:57:32.766Z\",\"^D\",\"regular\",\"^E\",\"none\",\"^F\",\"0\",\"^G\",[[\"^ \",\"^H\",\"#2f2c35\",\"^I\",1]],\"^J\",\"DM Sans\",\"^K\",\"\\n\"],[\"^ \",\"^:\",\"1\",\"^<\",\"normal\",\"^?\",\"sourcesanspro\",\"^A\",\"16\",\"^B\",\"400\",\"^D\",\"regular\",\"^F\",\"0\",\"^G\",[[\"^ \",\"^H\",\"#2f2c35\",\"^I\",1]],\"^K\",\"\\n\"],[\"^ \",\"^:\",\"1.5\",\"^;\",\"font-screen-lg / body\",\"^<\",\"normal\",\"^=\",\"none\",\"^>\",\"left\",\"^?\",\"gfont-dm-sans\",\"^@\",\"S:61089755d06cc251915221fee7a45ae2841996c6,\",\"^A\",\"16\",\"^B\",\"400\",\"^C\",\"2025-01-24T18:57:32.766Z\",\"^D\",\"regular\",\"^E\",\"none\",\"^F\",\"0\",\"^G\",[[\"^ \",\"^H\",\"#2f2c35\",\"^I\",1]],\"^J\",\"DM Sans\",\"^K\",\"They are adaptable to any platform and are used to replace hard-coded variables.\"]],\"^=\",\"none\",\"^>\",\"left\",\"^?\",\"gfont-dm-sans\",\"~:key\",\"6m627\",\"^@\",\"S:61089755d06cc251915221fee7a45ae2841996c6,\",\"^A\",\"16\",\"^B\",\"400\",\"^8\",\"paragraph\",\"^C\",\"2025-01-24T18:57:32.766Z\",\"^D\",\"regular\",\"^E\",\"none\",\"^F\",\"0\",\"^G\",[[\"^ \",\"^H\",\"#2f2c35\",\"^I\",1]],\"^J\",\"DM Sans\"]]]],\"~:vertical-align\",\"top\",\"^G\",[]],\"~:blend-mode\",\"~:normal\",\"~:name\",\"Design tokens are a set\",\"~:width\",704,\"^8\",\"^K\",\"~:svg-attrs\",[\"^ \"],\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",727,\"~:y\",603]],[\"^T\",[\"^ \",\"~:x\",1431,\"~:y\",603]],[\"^T\",[\"^ \",\"~:x\",1431,\"~:y\",747]],[\"^T\",[\"^ \",\"~:x\",727,\"~:y\",747]]],\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u1919607f-7059-80e9-8007-d7b6ba437f17\",\"~:hidden\",false,\"~:opacity\",1,\"~:id\",\"~ufb6e1db9-e999-8049-8007-d7b7ac11fbac\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:applied-tokens\",[\"^ \",\"~:fill\",\"xx.alias.color.text.default\"],\"~:layout-item-vsizing\",\"auto\",\"~:position-data\",[[\"^ \",\"~:y\",625.4199829101562,\"^:\",\"1.2\",\"^<\",\"normal\",\"~:typography-ref-id\",null,\"^=\",\"none\",\"^>\",\"left\",\"^?\",\"gfont-dm-sans\",\"^A\",\"16px\",\"^B\",\"400\",\"~:typography-ref-file\",null,\"~:text-direction\",\"ltr\",\"^Q\",676.6099853515625,\"^D\",\"regular\",\"^E\",\"none\",\"^F\",\"0px\",\"~:x\",727,\"^G\",[[\"^ \",\"^H\",\"#2f2c35\",\"^I\",1]],\"~:direction\",\"ltr\",\"^J\",\"DM Sans\",\"~:height\",20.8399658203125,\"^K\",\"Design tokens are a set of codified design decisions that store raw design values as named \"],[\"^ \",\"~:y\",649.4199829101562,\"^:\",\"1.2\",\"^<\",\"normal\",\"^14\",null,\"^=\",\"none\",\"^>\",\"left\",\"^?\",\"gfont-dm-sans\",\"^A\",\"16px\",\"^B\",\"400\",\"^15\",null,\"^16\",\"ltr\",\"^Q\",400.2900390625,\"^D\",\"regular\",\"^E\",\"none\",\"^F\",\"0px\",\"~:x\",727,\"^G\",[[\"^ \",\"^H\",\"#2f2c35\",\"^I\",1]],\"^17\",\"ltr\",\"^J\",\"DM Sans\",\"^18\",20.8399658203125,\"^K\",\"entities, making them core pieces of a design system. \"],[\"^ \",\"~:y\",673.3599853515625,\"^:\",\"1.2\",\"^<\",\"normal\",\"^14\",null,\"^=\",\"none\",\"^>\",\"left\",\"^?\",\"sourcesanspro\",\"^A\",\"16px\",\"^B\",\"400\",\"^15\",null,\"^16\",\"ltr\",\"^Q\",0,\"^D\",\"regular\",\"^E\",\"none\",\"^F\",\"0px\",\"~:x\",727,\"^G\",[[\"^ \",\"^H\",\"#2f2c35\",\"^I\",1]],\"^17\",\"ltr\",\"^J\",\"sourcesanspro\",\"^18\",20.719970703125,\"^K\",\"\"],[\"^ \",\"~:y\",697.4199829101562,\"^:\",\"1.2\",\"^<\",\"normal\",\"^14\",null,\"^=\",\"none\",\"^>\",\"left\",\"^?\",\"gfont-dm-sans\",\"^A\",\"16px\",\"^B\",\"400\",\"^15\",null,\"^16\",\"ltr\",\"^Q\",0,\"^D\",\"regular\",\"^E\",\"none\",\"^F\",\"0px\",\"~:x\",727,\"^G\",[[\"^ \",\"^H\",\"#2f2c35\",\"^I\",1]],\"^17\",\"ltr\",\"^J\",\"DM Sans\",\"^18\",20.8399658203125,\"^K\",\"\"],[\"^ \",\"~:y\",721.3599853515625,\"^:\",\"1.2\",\"^<\",\"normal\",\"^14\",null,\"^=\",\"none\",\"^>\",\"left\",\"^?\",\"sourcesanspro\",\"^A\",\"16px\",\"^B\",\"400\",\"^15\",null,\"^16\",\"ltr\",\"^Q\",0,\"^D\",\"regular\",\"^E\",\"none\",\"^F\",\"0px\",\"~:x\",727,\"^G\",[[\"^ \",\"^H\",\"#2f2c35\",\"^I\",1]],\"^17\",\"ltr\",\"^J\",\"sourcesanspro\",\"^18\",20.719970703125,\"^K\",\"\"],[\"^ \",\"~:y\",745.4199829101562,\"^:\",\"1.2\",\"^<\",\"normal\",\"^14\",null,\"^=\",\"none\",\"^>\",\"left\",\"^?\",\"gfont-dm-sans\",\"^A\",\"16px\",\"^B\",\"400\",\"^15\",null,\"^16\",\"ltr\",\"^Q\",607.7900390625,\"^D\",\"regular\",\"^E\",\"none\",\"^F\",\"0px\",\"~:x\",727,\"^G\",[[\"^ \",\"^H\",\"#2f2c35\",\"^I\",1]],\"^17\",\"ltr\",\"^J\",\"DM Sans\",\"^18\",20.8399658203125,\"^K\",\"They are adaptable to any platform and are used to replace hard-coded variables.\"]],\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",727,\"~:blocked\",false,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",727,\"~:y\",603,\"^Q\",704,\"^18\",144,\"~:x1\",727,\"~:y1\",603,\"~:x2\",1431,\"~:y2\",747]],\"^G\",[],\"~:flip-x\",null,\"^18\",144,\"~:flip-y\",null]]", + "~u00000000-0000-0000-0000-000000000000": "[\"~#shape\",[\"^ \",\"~:y\",0,\"~:hide-fill-on-export\",false,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:name\",\"Root Frame\",\"~:width\",0.01,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",0.0,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.01]],[\"^:\",[\"^ \",\"~:x\",0.0,\"~:y\",0.01]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u1919607f-7059-80e9-8007-d7b6ba437f17\",\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",0,\"~:proportion\",1.0,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",0,\"~:y\",0,\"^6\",0.01,\"~:height\",0.01,\"~:x1\",0,\"~:y1\",0,\"~:x2\",0.01,\"~:y2\",0.01]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#FFFFFF\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^I\",0.01,\"~:flip-y\",null,\"~:shapes\",[\"~ufb6e1db9-e999-8049-8007-d7b7ac11fbac\"]]]" + } + } + } + }, + "~:tokens-lib": { + "~#penpot/tokens-lib": { + "~:sets": { + "~#ordered-map": [ + [ + "G-global", + { + "~#ordered-map": [ + [ + "S-color", + { + "~#penpot/token-set": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0cf6d3", + "~:name": "global/color", + "~:description": "", + "~:modified-at": "~m1778507716659", + "~:tokens": { + "~#ordered-map": [ + [ + "xx.global.color.mint.500", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dcb", + "~:name": "xx.global.color.mint.500", + "~:type": "~:color", + "~:value": "#188675", + "~:description": "global color mint of weight 500", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.violet.200", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca44a", + "~:name": "xx.global.color.violet.200", + "~:type": "~:color", + "~:value": "#D0D2F3", + "~:description": "global color violet of weight 200", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.neutral.300", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dba", + "~:name": "xx.global.color.neutral.300", + "~:type": "~:color", + "~:value": "#DCDCDD", + "~:description": "global color neutral of weight 300", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.mint.600", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dc5", + "~:name": "xx.global.color.mint.600", + "~:type": "~:color", + "~:value": "#1A6A5D", + "~:description": "global color mint of weight 600", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.neutral.50", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4db8", + "~:name": "xx.global.color.neutral.50", + "~:type": "~:color", + "~:value": "#FCFCFC", + "~:description": "global color neutral of weight 50", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.orange.500", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dd5", + "~:name": "xx.global.color.orange.500", + "~:type": "~:color", + "~:value": "#C07B4B", + "~:description": "global color orange of weight 500", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.neutral.600", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4db9", + "~:name": "xx.global.color.neutral.600", + "~:type": "~:color", + "~:value": "#747279", + "~:description": "global color neutral of weight 600", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.blue.200", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4db4", + "~:name": "xx.global.color.blue.200", + "~:type": "~:color", + "~:value": "#B1DBEF", + "~:description": "global color blue of weight 200", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.red.400", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca466", + "~:name": "xx.global.color.red.400", + "~:type": "~:color", + "~:value": "#F37A73", + "~:description": "global color red of weight 400", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.blue.50", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dae", + "~:name": "xx.global.color.blue.50", + "~:type": "~:color", + "~:value": "#F6FBFD", + "~:description": "global color blue of weight 50", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.violet.800", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca44d", + "~:name": "xx.global.color.violet.800", + "~:type": "~:color", + "~:value": "#39345A", + "~:description": "global color violet of weight 800", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.green.200", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca45e", + "~:name": "xx.global.color.green.200", + "~:type": "~:color", + "~:value": "#B0E1AA", + "~:description": "global color green of weight 200", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.orange.800", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dd7", + "~:name": "xx.global.color.orange.800", + "~:type": "~:color", + "~:value": "#6B432B", + "~:description": "global color orange of weight 800", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.violet.500", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca44b", + "~:name": "xx.global.color.violet.500", + "~:type": "~:color", + "~:value": "#686FC8", + "~:description": "global color violet of weight 500", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.blue.700", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4db1", + "~:name": "xx.global.color.blue.700", + "~:type": "~:color", + "~:value": "#006596", + "~:description": "global color blue of weight 700", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.blue.900", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4db6", + "~:name": "xx.global.color.blue.900", + "~:type": "~:color", + "~:value": "#003C5A", + "~:description": "global color blue of weight 900", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.green.50", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca458", + "~:name": "xx.global.color.green.50", + "~:type": "~:color", + "~:value": "#f7fbeb", + "~:description": "global color green of weight 50", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.red.600", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca463", + "~:name": "xx.global.color.red.600", + "~:type": "~:color", + "~:value": "#934846", + "~:description": "global color red of weight 600", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.yellow.300", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca450", + "~:name": "xx.global.color.yellow.300", + "~:type": "~:color", + "~:value": "#D4BB5F", + "~:description": "global color yellow of weight 300", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.orange.200", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dd4", + "~:name": "xx.global.color.orange.200", + "~:type": "~:color", + "~:value": "#F9CCA9", + "~:description": "global color orange of weight 200", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.violet.700", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca447", + "~:name": "xx.global.color.violet.700", + "~:type": "~:color", + "~:value": "#49457A", + "~:description": "global color violet of weight 700", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.violet.50", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca444", + "~:name": "xx.global.color.violet.50", + "~:type": "~:color", + "~:value": "#FCFCFE", + "~:description": "global color violet of weight 50", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.yellow.500", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca455", + "~:name": "xx.global.color.yellow.500", + "~:type": "~:color", + "~:value": "#84773F", + "~:description": "global color yellow of weight 500", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.yellow.50", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca44e", + "~:name": "xx.global.color.yellow.50", + "~:type": "~:color", + "~:value": "#FFFCF5", + "~:description": "global color yellow of weight 50", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.violet.400", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca448", + "~:name": "xx.global.color.violet.400", + "~:type": "~:color", + "~:value": "#9598E0", + "~:description": "global color violet of weight 400", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.red.300", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca464", + "~:name": "xx.global.color.red.300", + "~:type": "~:color", + "~:value": "#FCA69C", + "~:description": "global color red of weight 300", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.orange.700", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dd1", + "~:name": "xx.global.color.orange.700", + "~:type": "~:color", + "~:value": "#855435", + "~:description": "global color orange of weight 700", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.green.400", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca45c", + "~:name": "xx.global.color.green.400", + "~:type": "~:color", + "~:value": "#69AD65", + "~:description": "global color green of weight 400", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.blue.300", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4db0", + "~:name": "xx.global.color.blue.300", + "~:type": "~:color", + "~:value": "#84C5E6", + "~:description": "global color blue of weight 300", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.blue.100", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4db3", + "~:name": "xx.global.color.blue.100", + "~:type": "~:color", + "~:value": "#E0F0F9", + "~:description": "global color blue of weight 100", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.orange.600", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dcf", + "~:name": "xx.global.color.orange.600", + "~:type": "~:color", + "~:value": "#9D633E", + "~:description": "global color orange of weight 600", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.yellow.200", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca454", + "~:name": "xx.global.color.yellow.200", + "~:type": "~:color", + "~:value": "#F0D36B", + "~:description": "global color yellow of weight 200", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.red.50", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca462", + "~:name": "xx.global.color.red.50", + "~:type": "~:color", + "~:value": "#FFFBFB", + "~:description": "global color red of weight 50", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.neutral.500", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dbf", + "~:name": "xx.global.color.neutral.500", + "~:type": "~:color", + "~:value": "#8B898F", + "~:description": "global color neutral of weight 500", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.green.300", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca45a", + "~:name": "xx.global.color.green.300", + "~:type": "~:color", + "~:value": "#7FCE7A", + "~:description": "global color green of weight 300", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.green.500", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca45f", + "~:name": "xx.global.color.green.500", + "~:type": "~:color", + "~:value": "#51834E", + "~:description": "global color green of weight 500", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.red.200", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca468", + "~:name": "xx.global.color.red.200", + "~:type": "~:color", + "~:value": "#FFC7BF", + "~:description": "global color red of weight 200", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.yellow.700", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca451", + "~:name": "xx.global.color.yellow.700", + "~:type": "~:color", + "~:value": "#534B2B", + "~:description": "global color yellow of weight 700", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.blue.800", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4db7", + "~:name": "xx.global.color.blue.800", + "~:type": "~:color", + "~:value": "#005179", + "~:description": "global color blue of weight 800", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.red.500", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca469", + "~:name": "xx.global.color.red.500", + "~:type": "~:color", + "~:value": "#BA5A56", + "~:description": "global color red of weight 500", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.blue.600", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4daf", + "~:name": "xx.global.color.blue.600", + "~:type": "~:color", + "~:value": "#0077B2", + "~:description": "global color blue of weight 600", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.yellow.600", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca44f", + "~:name": "xx.global.color.yellow.600", + "~:type": "~:color", + "~:value": "#685E34", + "~:description": "global color yellow of weight 600", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.yellow.100", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca453", + "~:name": "xx.global.color.yellow.100", + "~:type": "~:color", + "~:value": "#FDECC3", + "~:description": "global color yellow of weight 100", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.red.700", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca465", + "~:name": "xx.global.color.red.700", + "~:type": "~:color", + "~:value": "#743A3A", + "~:description": "global color red of weight 700", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.violet.300", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca446", + "~:name": "xx.global.color.violet.300", + "~:type": "~:color", + "~:value": "#B6B8EB", + "~:description": "global color violet of weight 300", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.orange.900", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dd6", + "~:name": "xx.global.color.orange.900", + "~:type": "~:color", + "~:value": "#4F3120", + "~:description": "global color orange of weight 900", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.mint.50", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dc4", + "~:name": "xx.global.color.mint.50", + "~:type": "~:color", + "~:value": "#F6FEFB", + "~:description": "global color mint of weight 50", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.yellow.800", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca457", + "~:name": "xx.global.color.yellow.800", + "~:type": "~:color", + "~:value": "#3E3922", + "~:description": "global color yellow of weight 800", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.black", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca46c", + "~:name": "xx.global.color.black", + "~:type": "~:color", + "~:value": "#000000", + "~:description": "", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.blue.500", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4db5", + "~:name": "xx.global.color.blue.500", + "~:type": "~:color", + "~:value": "#1993D0", + "~:description": "global color blue of weight 500", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.mint.800", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dcd", + "~:name": "xx.global.color.mint.800", + "~:type": "~:color", + "~:value": "#174039", + "~:description": "global color mint of weight 800", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.neutral.950", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dc2", + "~:name": "xx.global.color.neutral.950", + "~:type": "~:color", + "~:value": "#1C1C1E", + "~:description": "global color neutral of weight 950", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.violet.600", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca445", + "~:name": "xx.global.color.violet.600", + "~:type": "~:color", + "~:value": "#57579C", + "~:description": "global color violet of weight 600", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.neutral.200", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dbe", + "~:name": "xx.global.color.neutral.200", + "~:type": "~:color", + "~:value": "#F3F3F4", + "~:description": "global color neutral of weight 200", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.neutral.800", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dc1", + "~:name": "xx.global.color.neutral.800", + "~:type": "~:color", + "~:value": "#3B3741", + "~:description": "global color neutral of weight 800", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.red.900", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca46a", + "~:name": "xx.global.color.red.900", + "~:type": "~:color", + "~:value": "#3B2020", + "~:description": "global color red of weight 900", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.yellow.900", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca456", + "~:name": "xx.global.color.yellow.900", + "~:type": "~:color", + "~:value": "#2B2819", + "~:description": "global color yellow of weight 900", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.orange.100", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dd3", + "~:name": "xx.global.color.orange.100", + "~:type": "~:color", + "~:value": "#FCEBDC", + "~:description": "global color orange of weight 100", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.red.100", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca467", + "~:name": "xx.global.color.red.100", + "~:type": "~:color", + "~:value": "#FFE9E5", + "~:description": "global color red of weight 100", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.neutral.400", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dbc", + "~:name": "xx.global.color.neutral.400", + "~:type": "~:color", + "~:value": "#AEADB1", + "~:description": "global color neutral of weight 400", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.red.800", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca46b", + "~:name": "xx.global.color.red.800", + "~:type": "~:color", + "~:value": "#572D2D", + "~:description": "global color red of weight 800", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.orange.50", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dce", + "~:name": "xx.global.color.orange.50", + "~:type": "~:color", + "~:value": "#FEF9F5", + "~:description": "global color orange of weight 50", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.mint.200", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dca", + "~:name": "xx.global.color.mint.200", + "~:type": "~:color", + "~:value": "#55EBCE", + "~:description": "global color mint of weight 200", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.mint.900", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dcc", + "~:name": "xx.global.color.mint.900", + "~:type": "~:color", + "~:value": "#142C28", + "~:description": "global color mint of weight 900", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.mint.300", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dc6", + "~:name": "xx.global.color.mint.300", + "~:type": "~:color", + "~:value": "#00D4B6", + "~:description": "global color mint of weight 300", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.green.800", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca461", + "~:name": "xx.global.color.green.800", + "~:type": "~:color", + "~:value": "#2A3E28", + "~:description": "global color green of weight 800", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.green.600", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca459", + "~:name": "xx.global.color.green.600", + "~:type": "~:color", + "~:value": "#42673F", + "~:description": "global color green of weight 600", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.green.700", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca45b", + "~:name": "xx.global.color.green.700", + "~:type": "~:color", + "~:value": "#365233", + "~:description": "global color green of weight 700", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.green.100", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca45d", + "~:name": "xx.global.color.green.100", + "~:type": "~:color", + "~:value": "#DFF3DC", + "~:description": "global color green of weight 100", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.neutral.900", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dc0", + "~:name": "xx.global.color.neutral.900", + "~:type": "~:color", + "~:value": "#2F2C35", + "~:description": "global color neutral of weight 900", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.neutral.700", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dbb", + "~:name": "xx.global.color.neutral.700", + "~:type": "~:color", + "~:value": "#49454E", + "~:description": "global color neutral of weight 700", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.orange.400", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dd2", + "~:name": "xx.global.color.orange.400", + "~:type": "~:color", + "~:value": "#E39258", + "~:description": "global color orange of weight 400", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.mint.400", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dc8", + "~:name": "xx.global.color.mint.400", + "~:type": "~:color", + "~:value": "#0AB29A", + "~:description": "global color mint of weight 400", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.mint.700", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dc7", + "~:name": "xx.global.color.mint.700", + "~:type": "~:color", + "~:value": "#19544B", + "~:description": "global color mint of weight 700", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.yellow.400", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca452", + "~:name": "xx.global.color.yellow.400", + "~:type": "~:color", + "~:value": "#B19D51", + "~:description": "global color yellow of weight 400", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.neutral.100", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dbd", + "~:name": "xx.global.color.neutral.100", + "~:type": "~:color", + "~:value": "#FAFAFB", + "~:description": "global color neutral of weight 100", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.violet.100", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca449", + "~:name": "xx.global.color.violet.100", + "~:type": "~:color", + "~:value": "#ECEDFA", + "~:description": "global color violet of weight 100", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.orange.300", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dd0", + "~:name": "xx.global.color.orange.300", + "~:type": "~:color", + "~:value": "#F6AD76", + "~:description": "global color orange of weight 300", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.white", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dc3", + "~:name": "xx.global.color.white", + "~:type": "~:color", + "~:value": "#ffffff", + "~:description": "", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.mint.100", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dc9", + "~:name": "xx.global.color.mint.100", + "~:type": "~:color", + "~:value": "#C5F8EA", + "~:description": "global color mint of weight 100", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.blue.400", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4db2", + "~:name": "xx.global.color.blue.400", + "~:type": "~:color", + "~:value": "#53AFDC", + "~:description": "global color blue of weight 400", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.color.violet.900", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca44c", + "~:name": "xx.global.color.violet.900", + "~:type": "~:color", + "~:value": "#29253C", + "~:description": "global color violet of weight 900", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.global.color.green.900", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca460", + "~:name": "xx.global.color.green.900", + "~:type": "~:color", + "~:value": "#1E2B1D", + "~:description": "global color green of weight 900", + "~:modified-at": "~m1778507716658" + } + } + ] + ] + } + } + } + ], + [ + "S-dimension", + { + "~#penpot/token-set": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0e2151", + "~:name": "global/dimension", + "~:description": "", + "~:modified-at": "~m1778507716664", + "~:tokens": { + "~#ordered-map": [ + [ + "xx.global.dimensions.13", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4da8", + "~:name": "xx.global.dimensions.13", + "~:type": "~:dimensions", + "~:value": "64px", + "~:description": "13th step of global dimensions sequence", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.dimensions.11", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dab", + "~:name": "xx.global.dimensions.11", + "~:type": "~:dimensions", + "~:value": "52px", + "~:description": "11th step of global dimensions sequence", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.dimensions.14", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4da3", + "~:name": "xx.global.dimensions.14", + "~:type": "~:dimensions", + "~:value": "72px", + "~:description": "14th step of global dimensions sequence", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.dimensions.10", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dad", + "~:name": "xx.global.dimensions.10", + "~:type": "~:dimensions", + "~:value": "48px", + "~:description": "10th step of global dimensions sequence", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.dimensions.6", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4da9", + "~:name": "xx.global.dimensions.6", + "~:type": "~:dimensions", + "~:value": "20px", + "~:description": "6th step of global dimensions sequence", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.dimensions.3", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4da0", + "~:name": "xx.global.dimensions.3", + "~:type": "~:dimensions", + "~:value": "8px", + "~:description": "3rd step of global dimensions sequence", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.dimensions.2", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4dac", + "~:name": "xx.global.dimensions.2", + "~:type": "~:dimensions", + "~:value": "4px", + "~:description": "2nd step of global dimensions sequence", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.dimensions.12", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4da7", + "~:name": "xx.global.dimensions.12", + "~:type": "~:dimensions", + "~:value": "60px", + "~:description": "12th step of global dimensions sequence", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.dimensions.8", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4da2", + "~:name": "xx.global.dimensions.8", + "~:type": "~:dimensions", + "~:value": "32px", + "~:description": "8th step of global dimensions sequence", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.dimensions.7", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4da5", + "~:name": "xx.global.dimensions.7", + "~:type": "~:dimensions", + "~:value": "24px", + "~:description": "7th step of global dimensions sequence", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.dimensions.15", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4da4", + "~:name": "xx.global.dimensions.15", + "~:type": "~:dimensions", + "~:value": "80px", + "~:description": "15th step of global dimensions sequence", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.dimensions.4", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4da1", + "~:name": "xx.global.dimensions.4", + "~:type": "~:dimensions", + "~:value": "12px", + "~:description": "4th step of global dimensions sequence", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.dimensions.9", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d9f", + "~:name": "xx.global.dimensions.9", + "~:type": "~:dimensions", + "~:value": "44px", + "~:description": "9th step of global dimensions sequence", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.dimensions.1", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4daa", + "~:name": "xx.global.dimensions.1", + "~:type": "~:dimensions", + "~:value": "2px", + "~:description": "1st step of global dimensions sequence", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.global.dimensions.5", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4da6", + "~:name": "xx.global.dimensions.5", + "~:type": "~:dimensions", + "~:value": "16px", + "~:description": "5th step of global dimensions sequence", + "~:modified-at": "~m1778507716657" + } + } + ] + ] + } + } + } + ], + [ + "S-Opacity", + { + "~#penpot/token-set": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0e7da8", + "~:name": "global/Opacity", + "~:description": "", + "~:modified-at": "~m1778507716665", + "~:tokens": { + "~#ordered-map": [ + [ + "opacity.0", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca494", + "~:name": "opacity.0", + "~:type": "~:opacity", + "~:value": "0", + "~:description": "", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "opacity.10", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca495", + "~:name": "opacity.10", + "~:type": "~:opacity", + "~:value": "0.1", + "~:description": "", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "opacity.50", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca496", + "~:name": "opacity.50", + "~:type": "~:opacity", + "~:value": "0.5", + "~:description": "", + "~:modified-at": "~m1778507716658" + } + } + ] + ] + } + } + } + ] + ] + } + ], + [ + "G-alias", + { + "~#ordered-map": [ + [ + "S-border", + { + "~#penpot/token-set": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ece1d", + "~:name": "alias/border", + "~:description": "", + "~:modified-at": "~m1778507716667", + "~:tokens": { + "~#ordered-map": [ + [ + "xx.alias.border.radius.sm", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c38b0", + "~:name": "xx.alias.border.radius.sm", + "~:type": "~:border-radius", + "~:value": "{xx.global.dimensions.2}", + "~:description": "small border radius", + "~:modified-at": "~m1778507716656" + } + } + ], + [ + "xx.alias.border.radius.md", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c38b1", + "~:name": "xx.alias.border.radius.md", + "~:type": "~:border-radius", + "~:value": "{xx.global.dimensions.3}", + "~:description": "medium border radius", + "~:modified-at": "~m1778507716656" + } + } + ], + [ + "xx.alias.border.radius.lg", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c38b2", + "~:name": "xx.alias.border.radius.lg", + "~:type": "~:border-radius", + "~:value": "{xx.global.dimensions.5}", + "~:description": "large border radius", + "~:modified-at": "~m1778507716656" + } + } + ], + [ + "xx.alias.border.radius.round", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c38b3", + "~:name": "xx.alias.border.radius.round", + "~:type": "~:border-radius", + "~:value": "100%", + "~:description": "", + "~:modified-at": "~m1778507716656" + } + } + ], + [ + "stroke.outline", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c38b4", + "~:name": "stroke.outline", + "~:type": "~:stroke-width", + "~:value": "{xx.global.dimensions.1}", + "~:description": "", + "~:modified-at": "~m1778507716656" + } + } + ], + [ + "stroke.medium", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c38b5", + "~:name": "stroke.medium", + "~:type": "~:stroke-width", + "~:value": "3", + "~:description": "", + "~:modified-at": "~m1778507716656" + } + } + ], + [ + "stroke.small", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c38b6", + "~:name": "stroke.small", + "~:type": "~:stroke-width", + "~:value": "1", + "~:description": "", + "~:modified-at": "~m1778507716656" + } + } + ], + [ + "stroke.large-test", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c38b7", + "~:name": "stroke.large-test", + "~:type": "~:stroke-width", + "~:value": "25", + "~:description": "", + "~:modified-at": "~m1778507716656" + } + } + ] + ] + } + } + } + ], + [ + "S-dimension", + { + "~#penpot/token-set": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f101a3f", + "~:name": "alias/dimension", + "~:description": "", + "~:modified-at": "~m1778507716672", + "~:tokens": { + "~#ordered-map": [ + [ + "xx.alias.size.xs", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca499", + "~:name": "xx.alias.size.xs", + "~:type": "~:sizing", + "~:value": "{xx.global.dimensions.6}", + "~:description": "extra small size unit", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "rotation.45", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0cf6d1", + "~:name": "rotation.45", + "~:type": "~:rotation", + "~:value": "45", + "~:description": "", + "~:modified-at": "~m1778507716659" + } + } + ], + [ + "xx.alias.spacing.lg", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca4a7", + "~:name": "xx.alias.spacing.lg", + "~:type": "~:spacing", + "~:value": "{xx.global.dimensions.7}", + "~:description": "large spacing unit", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "dimension.top.position", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0cf6d2", + "~:name": "dimension.top.position", + "~:type": "~:dimensions", + "~:value": "{xx.global.dimensions.7}", + "~:description": "", + "~:modified-at": "~m1778507716659" + } + } + ], + [ + "xx.alias.icon.size.m", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0cf6ce", + "~:name": "xx.alias.icon.size.m", + "~:type": "~:sizing", + "~:value": "24", + "~:description": "", + "~:modified-at": "~m1778507716659" + } + } + ], + [ + "xx.alias.spacing.xxxl", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca4ab", + "~:name": "xx.alias.spacing.xxxl", + "~:type": "~:spacing", + "~:value": "{xx.global.dimensions.13}", + "~:description": "extra extra extra small spacing unit", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.size.lg", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca49d", + "~:name": "xx.alias.size.lg", + "~:type": "~:sizing", + "~:value": "{xx.global.dimensions.10}", + "~:description": "large size unit", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.spacing.xxxs", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca4a9", + "~:name": "xx.alias.spacing.xxxs", + "~:type": "~:spacing", + "~:value": "{xx.global.dimensions.1}", + "~:description": "extra extra extra small spacing unit", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.size.xxl", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca4a3", + "~:name": "xx.alias.size.xxl", + "~:type": "~:sizing", + "~:value": "{xx.global.dimensions.13}", + "~:description": "extra extra large size unit", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.size.xxxxl", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca49c", + "~:name": "xx.alias.size.xxxxl", + "~:type": "~:sizing", + "~:value": "{xx.global.dimensions.15}", + "~:description": "extra extra extra large size unit", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.spacing.xxs", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca4a8", + "~:name": "xx.alias.spacing.xxs", + "~:type": "~:spacing", + "~:value": "{xx.global.dimensions.2}", + "~:description": "extra extra small spacing unit", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.icon.size.xs", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0cf6cf", + "~:name": "xx.alias.icon.size.xs", + "~:type": "~:sizing", + "~:value": "12", + "~:description": "", + "~:modified-at": "~m1778507716659" + } + } + ], + [ + "xx.alias.size.xxs", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca49e", + "~:name": "xx.alias.size.xxs", + "~:type": "~:sizing", + "~:value": "{xx.global.dimensions.5}", + "~:description": "extra extra small size unit", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.spacing.sm", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca4ad", + "~:name": "xx.alias.spacing.sm", + "~:type": "~:spacing", + "~:value": "{xx.global.dimensions.4}", + "~:description": "small spacing unit", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.size.sm", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca4a4", + "~:name": "xx.alias.size.sm", + "~:type": "~:sizing", + "~:value": "{xx.global.dimensions.7}", + "~:description": "small size unit", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.icon.size.s", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0cf6d0", + "~:name": "xx.alias.icon.size.s", + "~:type": "~:sizing", + "~:value": "16", + "~:description": "", + "~:modified-at": "~m1778507716659" + } + } + ], + [ + "xx.alias.size.xl", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca4a1", + "~:name": "xx.alias.size.xl", + "~:type": "~:sizing", + "~:value": "{xx.global.dimensions.11}", + "~:description": "extra large size unit", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.spacing.xxl", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca4ac", + "~:name": "xx.alias.spacing.xxl", + "~:type": "~:spacing", + "~:value": "{xx.global.dimensions.10}", + "~:description": "extra extra small spacing unit", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.size.xxxxs", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca4a0", + "~:name": "xx.alias.size.xxxxs", + "~:type": "~:sizing", + "~:value": "{xx.global.dimensions.3}", + "~:description": "extra extra extra small size unit", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.size.xxxs", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca49f", + "~:name": "xx.alias.size.xxxs", + "~:type": "~:sizing", + "~:value": "{xx.global.dimensions.4}", + "~:description": "extra extra extra small size unit", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.spacing.xl", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca4aa", + "~:name": "xx.alias.spacing.xl", + "~:type": "~:spacing", + "~:value": "{xx.global.dimensions.8}", + "~:description": "extra large spacing unit", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.size.md", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca49a", + "~:name": "xx.alias.size.md", + "~:type": "~:sizing", + "~:value": "{xx.global.dimensions.8}", + "~:description": "medium size unit", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.spacing.md", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca4a6", + "~:name": "xx.alias.spacing.md", + "~:type": "~:spacing", + "~:value": "{xx.global.dimensions.5}", + "~:description": "medium spacing unit", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.size.xxxxxs", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca49b", + "~:name": "xx.alias.size.xxxxxs", + "~:type": "~:sizing", + "~:value": "{xx.global.dimensions.2}", + "~:description": "extra extra extra small size unit", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.size.xxxl", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca4a2", + "~:name": "xx.alias.size.xxxl", + "~:type": "~:sizing", + "~:value": "{xx.global.dimensions.14}", + "~:description": "extra extra extra large size unit", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.spacing.xs", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca4a5", + "~:name": "xx.alias.spacing.xs", + "~:type": "~:spacing", + "~:value": "{xx.global.dimensions.3}", + "~:description": "extra small spacing unit", + "~:modified-at": "~m1778507716658" + } + } + ] + ] + } + } + } + ], + [ + "S-color-dark", + { + "~#penpot/token-set": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f12beff", + "~:name": "alias/color-dark", + "~:description": "", + "~:modified-at": "~m1778507716682", + "~:tokens": { + "~#ordered-map": [ + [ + "xx.alias.color.purpose.onInfo", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d7e", + "~:name": "xx.alias.color.purpose.onInfo", + "~:type": "~:color", + "~:value": "{xx.global.color.blue.800}", + "~:description": "color for elements placed on informational purpose background", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.border.heavy", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d85", + "~:name": "xx.alias.color.border.heavy", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.200}", + "~:description": "heavy border color", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.state.select", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d8d", + "~:name": "xx.alias.color.state.select", + "~:type": "~:color", + "~:value": "{xx.global.color.mint.100}", + "~:description": "background color for selected elements", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.primary.onBrand", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d92", + "~:name": "xx.alias.color.primary.onBrand", + "~:type": "~:color", + "~:value": "{xx.global.color.violet.800}", + "~:description": "color for elements that are placed on brand background color", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.state.focus", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d8a", + "~:name": "xx.alias.color.state.focus", + "~:type": "~:color", + "~:value": "rgba({xx.global.color.neutral.100},0.16)", + "~:description": "Background color for state overlay of a focused element", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.state.press", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d8c", + "~:name": "xx.alias.color.state.press", + "~:type": "~:color", + "~:value": "rgba({xx.global.color.neutral.100},0.8)", + "~:description": "Background color for state overlay of a pressed element", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.purpose.warningHighEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d83", + "~:name": "xx.alias.color.purpose.warningHighEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.yellow.100}", + "~:description": "color for elements with warning purpose of high emphasis", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.state.onSelect", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d91", + "~:name": "xx.alias.color.state.onSelect", + "~:type": "~:color", + "~:value": "{xx.global.color.mint.600}", + "~:description": "Foreground color for selected elements", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.primary.interaction", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d93", + "~:name": "xx.alias.color.primary.interaction", + "~:type": "~:color", + "~:value": "{xx.global.color.violet.600}", + "~:description": "Color that indicates interactions", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.border.focusOutline", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d86", + "~:name": "xx.alias.color.border.focusOutline", + "~:type": "~:color", + "~:value": "rgba({xx.global.color.blue.400},0.5)", + "~:description": "color for default focus outline", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.state.disable", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d8e", + "~:name": "xx.alias.color.state.disable", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.300}", + "~:description": "background color for disabled elements", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.purpose.infoHighEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d84", + "~:name": "xx.alias.color.purpose.infoHighEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.blue.200}", + "~:description": "color for elements with informational purpose of high emphasis", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.purpose.infoLowEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d79", + "~:name": "xx.alias.color.purpose.infoLowEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.blue.400}", + "~:description": "color for elements with informational purpose of low emphasis", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.primary.brand", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d94", + "~:name": "xx.alias.color.primary.brand", + "~:type": "~:color", + "~:value": "{xx.global.color.violet.400}", + "~:description": "background color for primary brand elements ", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.text.default", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d96", + "~:name": "xx.alias.color.text.default", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.50}", + "~:description": "Default text color ", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.text.inverted", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d97", + "~:name": "xx.alias.color.text.inverted", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.900}", + "~:description": "Inverted text color", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.purpose.onSuccess", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d81", + "~:name": "xx.alias.color.purpose.onSuccess", + "~:type": "~:color", + "~:value": "{xx.global.color.green.800}", + "~:description": "color for elements placed on success purpose background", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.purpose.criticalLowEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d7f", + "~:name": "xx.alias.color.purpose.criticalLowEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.red.400}", + "~:description": "color for elements with critical purpose of low emphasis", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.background.mediumEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d9b", + "~:name": "xx.alias.color.background.mediumEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.800}", + "~:description": "Background Color for components with medium emphasis", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.text.emphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d98", + "~:name": "xx.alias.color.text.emphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.white}", + "~:description": "Default text color ", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.background.highEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d9c", + "~:name": "xx.alias.color.background.highEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.700}", + "~:description": "Background Color for components with high emphasis", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.state.hover", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d90", + "~:name": "xx.alias.color.state.hover", + "~:type": "~:color", + "~:value": "rgba({xx.global.color.neutral.100},0.16)", + "~:description": "Background color for state overlay of a hovered element", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.background.body", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d9d", + "~:name": "xx.alias.color.background.body", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.950}", + "~:description": "Body background color", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.purpose.successLowEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d7d", + "~:name": "xx.alias.color.purpose.successLowEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.green.400}", + "~:description": "color for elements with success purpose of low emphasis", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.primary.onInteraction", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d95", + "~:name": "xx.alias.color.primary.onInteraction", + "~:type": "~:color", + "~:value": "{xx.global.color.violet.900}", + "~:description": "color for elements that are placed on interactive background colors", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.purpose.onCritical", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d7a", + "~:name": "xx.alias.color.purpose.onCritical", + "~:type": "~:color", + "~:value": "{xx.global.color.red.900}", + "~:description": "color for elements placed on critical purpose background", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.text.medium", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d99", + "~:name": "xx.alias.color.text.medium", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.600}", + "~:description": "Text color in subtle weight", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.purpose.criticalHighEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d7b", + "~:name": "xx.alias.color.purpose.criticalHighEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.red.200}", + "~:description": "color for elements with critical purpose of high emphasis", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.border.medium", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d87", + "~:name": "xx.alias.color.border.medium", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.500}", + "~:description": "medium border color", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.border.subtle", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d88", + "~:name": "xx.alias.color.border.subtle", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.700}", + "~:description": "subtle border color", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.background.lowEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d9e", + "~:name": "xx.alias.color.background.lowEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.900}", + "~:description": "background color for elements with a low emphasis", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.purpose.onWarning", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d80", + "~:name": "xx.alias.color.purpose.onWarning", + "~:type": "~:color", + "~:value": "{xx.global.color.yellow.800}", + "~:description": "color for elements placed on warning purpose background", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.text.subtle", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d9a", + "~:name": "xx.alias.color.text.subtle", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.400}", + "~:description": "Text color in subtle weight", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.purpose.successHighEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d7c", + "~:name": "xx.alias.color.purpose.successHighEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.green.100}", + "~:description": "color for elements with success purpose of high emphasis", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.state.onDisable", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d8b", + "~:name": "xx.alias.color.state.onDisable", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.700}", + "~:description": "color for elements that are palced on disabled background color", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.state.selectInverted", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d8f", + "~:name": "xx.alias.color.state.selectInverted", + "~:type": "~:color", + "~:value": "{xx.global.color.mint.600}", + "~:description": "Inverted background color for selected elements", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.purpose.warningLowEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d82", + "~:name": "xx.alias.color.purpose.warningLowEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.yellow.400}", + "~:description": "color for elements with warning purpose of low emphasis", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.state.onSelectInverted", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d89", + "~:name": "xx.alias.color.state.onSelectInverted", + "~:type": "~:color", + "~:value": "{xx.global.color.mint.100}", + "~:description": "inverted color for elements on selected backgrounds", + "~:modified-at": "~m1778507716657" + } + } + ] + ] + } + } + } + ], + [ + "S-color-light", + { + "~#penpot/token-set": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f161e35", + "~:name": "alias/color-light", + "~:description": "", + "~:modified-at": "~m1778507716696", + "~:tokens": { + "~#ordered-map": [ + [ + "xx.alias.color.purpose.onInfo", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca472", + "~:name": "xx.alias.color.purpose.onInfo", + "~:type": "~:color", + "~:value": "{xx.global.color.blue.100}", + "~:description": "color for elements placed on informational purpose background", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.border.heavy", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca479", + "~:name": "xx.alias.color.border.heavy", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.700}", + "~:description": "heavy border color", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.state.select", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca481", + "~:name": "xx.alias.color.state.select", + "~:type": "~:color", + "~:value": "{xx.global.color.mint.100}", + "~:description": "background color for selected elements", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.primary.onBrand", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca486", + "~:name": "xx.alias.color.primary.onBrand", + "~:type": "~:color", + "~:value": "{xx.global.color.violet.200}", + "~:description": "color for elements that are placed on brand background color", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.state.focus", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca47e", + "~:name": "xx.alias.color.state.focus", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.100}", + "~:description": "Background color for state overlay of a focused element", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.state.press", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca480", + "~:name": "xx.alias.color.state.press", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.100}", + "~:description": "Background color for state overlay of a pressed element", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.purpose.warningHighEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca477", + "~:name": "xx.alias.color.purpose.warningHighEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.yellow.500}", + "~:description": "color for elements with warning purpose of high emphasis", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.state.onSelect", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca485", + "~:name": "xx.alias.color.state.onSelect", + "~:type": "~:color", + "~:value": "{xx.global.color.mint.600}", + "~:description": "Foreground color for selected elements", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.primary.interaction", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca487", + "~:name": "xx.alias.color.primary.interaction", + "~:type": "~:color", + "~:value": "{xx.global.color.violet.600}", + "~:description": "Color that indicates interactions", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.border.focusOutline", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca47a", + "~:name": "xx.alias.color.border.focusOutline", + "~:type": "~:color", + "~:value": "rgba({xx.global.color.blue.500},0.8)", + "~:description": "color for default focus outline", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.state.disable", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca482", + "~:name": "xx.alias.color.state.disable", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.300}", + "~:description": "background color for disabled elements", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.purpose.infoHighEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca478", + "~:name": "xx.alias.color.purpose.infoHighEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.blue.500}", + "~:description": "color for elements with informational purpose of high emphasis", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.purpose.infoLowEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca46d", + "~:name": "xx.alias.color.purpose.infoLowEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.blue.400}", + "~:description": "color for elements with informational purpose of low emphasis", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.primary.brand", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca488", + "~:name": "xx.alias.color.primary.brand", + "~:type": "~:color", + "~:value": "{xx.global.color.violet.500}", + "~:description": "background color for primary brand elements ", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.text.default", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca48b", + "~:name": "xx.alias.color.text.default", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.900}", + "~:description": "Default text color ", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.text.inverted", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca48c", + "~:name": "xx.alias.color.text.inverted", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.50}", + "~:description": "Inverted text color", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.purpose.onSuccess", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca475", + "~:name": "xx.alias.color.purpose.onSuccess", + "~:type": "~:color", + "~:value": "{xx.global.color.green.100}", + "~:description": "color for elements placed on success purpose background", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.purpose.criticalLowEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca473", + "~:name": "xx.alias.color.purpose.criticalLowEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.red.500}", + "~:description": "color for elements with critical purpose of low emphasis", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.background.mediumEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca490", + "~:name": "xx.alias.color.background.mediumEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.100}", + "~:description": "background Color for components with medium emphasis", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.text.emphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca48d", + "~:name": "xx.alias.color.text.emphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.black}", + "~:description": "Default text color ", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.background.highEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca491", + "~:name": "xx.alias.color.background.highEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.50}", + "~:description": "background Color for components with high emphasis", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.state.hover", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca484", + "~:name": "xx.alias.color.state.hover", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.100}", + "~:description": "Background color for state overlay of a hovered element", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.background.body", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca492", + "~:name": "xx.alias.color.background.body", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.200}", + "~:description": "body background color", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.purpose.successLowEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca471", + "~:name": "xx.alias.color.purpose.successLowEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.green.400}", + "~:description": "color for elements with success purpose of low emphasis", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.primary.onInteraction", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca489", + "~:name": "xx.alias.color.primary.onInteraction", + "~:type": "~:color", + "~:value": "{xx.global.color.violet.300}", + "~:description": "color for elements that are placed on interactive background colors", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.purpose.onCritical", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca46e", + "~:name": "xx.alias.color.purpose.onCritical", + "~:type": "~:color", + "~:value": "{xx.global.color.red.200}", + "~:description": "color for elements placed on critical purpose background", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.text.medium", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca48e", + "~:name": "xx.alias.color.text.medium", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.400}", + "~:description": "Text color in subtle weight", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.purpose.criticalHighEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca46f", + "~:name": "xx.alias.color.purpose.criticalHighEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.red.600}", + "~:description": "color for elements with critical purpose of high emphasis", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.border.medium", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca47b", + "~:name": "xx.alias.color.border.medium", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.500}", + "~:description": "medium border color", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.border.subtle", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca47c", + "~:name": "xx.alias.color.border.subtle", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.300}", + "~:description": "subtle border color", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.background.lowEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca493", + "~:name": "xx.alias.color.background.lowEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.300}", + "~:description": "background color for elements with a low emphasis", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.purpose.onWarning", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca474", + "~:name": "xx.alias.color.purpose.onWarning", + "~:type": "~:color", + "~:value": "{xx.global.color.yellow.100}", + "~:description": "color for elements placed on warning purpose background", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.text.subtle", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca48f", + "~:name": "xx.alias.color.text.subtle", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.600}", + "~:description": "Text color in subtle weight", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.purpose.successHighEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca470", + "~:name": "xx.alias.color.purpose.successHighEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.green.500}", + "~:description": "color for elements with success purpose of high emphasis", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.state.onDisable", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca47f", + "~:name": "xx.alias.color.state.onDisable", + "~:type": "~:color", + "~:value": "{xx.global.color.neutral.700}", + "~:description": "color for elements that are palced on disabled background color", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.state.selectInverted", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca483", + "~:name": "xx.alias.color.state.selectInverted", + "~:type": "~:color", + "~:value": "{xx.global.color.mint.600}", + "~:description": "Inverted background color for selected elements", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.purpose.warningLowEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca476", + "~:name": "xx.alias.color.purpose.warningLowEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.yellow.400}", + "~:description": "color for elements with warning purpose of low emphasis", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.state.onSelectInverted", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca47d", + "~:name": "xx.alias.color.state.onSelectInverted", + "~:type": "~:color", + "~:value": "{xx.global.color.mint.100}", + "~:description": "inverted color for elements on selected backgrounds", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "xx.alias.color.primary.highEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca48a", + "~:name": "xx.alias.color.primary.highEmphasis", + "~:type": "~:color", + "~:value": "{xx.global.color.violet.200}", + "~:description": "background color for primary brand elements ", + "~:modified-at": "~m1778507716658" + } + } + ] + ] + } + } + } + ], + [ + "S-media-queries", + { + "~#penpot/token-set": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f16cbda", + "~:name": "alias/media-queries", + "~:description": "", + "~:modified-at": "~m1778507716699", + "~:tokens": { + "~#ordered-map": [ + [ + "xx.media.xs", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d3a", + "~:name": "xx.media.xs", + "~:type": "~:dimensions", + "~:value": "576px", + "~:description": "(min-width: 576px)", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.media.sm", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d3b", + "~:name": "xx.media.sm", + "~:type": "~:dimensions", + "~:value": "768px", + "~:description": "(min-width: 768px)", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.media.md", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d3c", + "~:name": "xx.media.md", + "~:type": "~:dimensions", + "~:value": "992px", + "~:description": "min-width: 992px - Tablet portrait", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.media.lg", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d3d", + "~:name": "xx.media.lg", + "~:type": "~:dimensions", + "~:value": "1200px", + "~:description": " (min-width: 1200px) - Tablet landscape", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.media.xl", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d3e", + "~:name": "xx.media.xl", + "~:type": "~:dimensions", + "~:value": "1440px", + "~:description": " (min-width: 1440px) - Desktop small", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.media.2xl", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d3f", + "~:name": "xx.media.2xl", + "~:type": "~:dimensions", + "~:value": "1680px", + "~:description": " (min-width: 1680px) - Desktop Large", + "~:modified-at": "~m1778507716657" + } + } + ] + ] + } + } + } + ], + [ + "S-web-typography", + { + "~#penpot/token-set": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f171543", + "~:name": "alias/web-typography", + "~:description": "", + "~:modified-at": "~m1778507716700", + "~:tokens": { + "~#ordered-map": [ + [ + "lineHeight.paragraph", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca497", + "~:name": "lineHeight.paragraph", + "~:type": "~:number", + "~:value": "1.5", + "~:description": "", + "~:modified-at": "~m1778507716658" + } + } + ], + [ + "lineHeight.inline", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0ca498", + "~:name": "lineHeight.inline", + "~:type": "~:number", + "~:value": "1.2", + "~:description": "", + "~:modified-at": "~m1778507716658" + } + } + ] + ] + } + } + } + ], + [ + "S-app-typography", + { + "~#penpot/token-set": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f17704b", + "~:name": "alias/app-typography", + "~:description": "", + "~:modified-at": "~m1778507716701", + "~:tokens": { + "~#ordered-map": [ + [ + "lineHeight.paragraph", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c38b8", + "~:name": "lineHeight.paragraph", + "~:type": "~:number", + "~:value": "1.4", + "~:description": "", + "~:modified-at": "~m1778507716656" + } + } + ], + [ + "lineHeight.inline", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c38b9", + "~:name": "lineHeight.inline", + "~:type": "~:number", + "~:value": "1.2", + "~:description": "", + "~:modified-at": "~m1778507716656" + } + } + ] + ] + } + } + } + ], + [ + "S-shadows", + { + "~#penpot/token-set": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f18b19c", + "~:name": "alias/shadows", + "~:description": "", + "~:modified-at": "~m1778507716706", + "~:tokens": { + "~#ordered-map": [ + [ + "color.shadow.strong", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d63", + "~:name": "color.shadow.strong", + "~:type": "~:color", + "~:value": "rgba(\t24,20,31, 0.30)", + "~:description": "", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "shadow.lower.hover", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d68", + "~:name": "shadow.lower.hover", + "~:type": "~:shadow", + "~:value": [ + { + "~:offset-x": "0", + "~:offset-y": "1", + "~:blur": "2", + "~:spread": "0", + "~:color": "{color.shadow.strong}", + "~:inset": false + }, + { + "~:offset-x": "0", + "~:offset-y": "0", + "~:blur": "2", + "~:spread": "0", + "~:color": "{color.shadow.base}", + "~:inset": false + } + ], + "~:description": "", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "shadow.lower.default", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d69", + "~:name": "shadow.lower.default", + "~:type": "~:shadow", + "~:value": [ + { + "~:offset-x": "0", + "~:offset-y": "1", + "~:blur": "2", + "~:spread": "0", + "~:color": "{color.shadow.low}", + "~:inset": false + }, + { + "~:offset-x": "0", + "~:offset-y": "0", + "~:blur": "2", + "~:spread": "0", + "~:color": "{color.shadow.base}", + "~:inset": false + } + ], + "~:description": "", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "shadow.low.hover", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d6a", + "~:name": "shadow.low.hover", + "~:type": "~:shadow", + "~:value": [ + { + "~:offset-x": "0", + "~:offset-y": "2", + "~:blur": "8", + "~:spread": "0", + "~:color": "{color.shadow.strong}", + "~:inset": false + }, + { + "~:offset-x": "0", + "~:offset-y": "0", + "~:blur": "2", + "~:spread": "0", + "~:color": "{color.shadow.base}", + "~:inset": false + } + ], + "~:description": "", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "color.shadow.base", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d64", + "~:name": "color.shadow.base", + "~:type": "~:color", + "~:value": "rgba(\t24,20,31, 0.06)", + "~:description": "", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "lower", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d6c", + "~:name": "lower", + "~:type": "~:shadow", + "~:value": [ + { + "~:offset-x": "0", + "~:offset-y": "1", + "~:blur": "2", + "~:spread": "0", + "~:color": "{color.shadow.low}", + "~:inset": false + } + ], + "~:description": "", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "color.shadow.stronger", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d65", + "~:name": "color.shadow.stronger", + "~:type": "~:color", + "~:value": "rgba(\t24,20,31, 0.35)", + "~:description": "", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "color.shadow.medium", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d66", + "~:name": "color.shadow.medium", + "~:type": "~:color", + "~:value": "rgba(\t24,20,31, 0.25)", + "~:description": "", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "shadow.low.default", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d6b", + "~:name": "shadow.low.default", + "~:type": "~:shadow", + "~:value": [ + { + "~:offset-x": "0", + "~:offset-y": "2", + "~:blur": "8", + "~:spread": "0", + "~:color": "{color.shadow.low}", + "~:inset": false + }, + { + "~:offset-x": "0", + "~:offset-y": "0", + "~:blur": "2", + "~:spread": "0", + "~:color": "{color.shadow.base}", + "~:inset": false + } + ], + "~:description": "", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "color.shadow.low", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d67", + "~:name": "color.shadow.low", + "~:type": "~:color", + "~:value": "rgba(\t24,20,31, 0.20)", + "~:description": "", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "base.shadow", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d6d", + "~:name": "base.shadow", + "~:type": "~:shadow", + "~:value": [ + { + "~:offset-x": "0", + "~:offset-y": "0", + "~:blur": "2", + "~:spread": "0", + "~:color": "{color.shadow.base}", + "~:inset": false + } + ], + "~:description": "sombra común", + "~:modified-at": "~m1778507716657" + } + } + ] + ] + } + } + } + ] + ] + } + ], + [ + "G-components", + { + "~#ordered-map": [ + [ + "S-section-message", + { + "~#penpot/token-set": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f1a31f0", + "~:name": "components/section-message", + "~:description": "", + "~:modified-at": "~m1778507716712", + "~:tokens": { + "~#ordered-map": [ + [ + "xx.component.sectionMessage.warning.icon", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d6e", + "~:name": "xx.component.sectionMessage.warning.icon", + "~:type": "~:color", + "~:value": "{xx.alias.color.purpose.onWarning}", + "~:description": "icon color for warning section messages", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.component.sectionMessage.warning.background", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d6f", + "~:name": "xx.component.sectionMessage.warning.background", + "~:type": "~:color", + "~:value": "{xx.alias.color.purpose.warningHighEmphasis}", + "~:description": "background color for warning section messages", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.component.sectionMessage.borderRadius", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d71", + "~:name": "xx.component.sectionMessage.borderRadius", + "~:type": "~:border-radius", + "~:value": "{xx.alias.border.radius.md}", + "~:description": "border radius for section messages", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.component.sectionMessage.spacing.innerY", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d72", + "~:name": "xx.component.sectionMessage.spacing.innerY", + "~:type": "~:spacing", + "~:value": "{xx.alias.spacing.md}", + "~:description": "top and bottom inner spacing for section messages", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.component.sectionMessage.height.min", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d75", + "~:name": "xx.component.sectionMessage.height.min", + "~:type": "~:sizing", + "~:value": "{xx.alias.size.md}", + "~:description": "min height of section messages", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.component.sectionMessage.success.background", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d76", + "~:name": "xx.component.sectionMessage.success.background", + "~:type": "~:color", + "~:value": "{xx.alias.color.purpose.successHighEmphasis}", + "~:description": "background color for success section messages ", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.component.sectionMessage.spacing.innerX", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d73", + "~:name": "xx.component.sectionMessage.spacing.innerX", + "~:type": "~:spacing", + "~:value": "{xx.alias.spacing.sm}", + "~:description": "left and right inner spacing for section messages", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.component.sectionMessage.warning.text", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d70", + "~:name": "xx.component.sectionMessage.warning.text", + "~:type": "~:color", + "~:value": "{xx.alias.color.purpose.onWarning}", + "~:description": "text color for warning section messages", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.component.sectionMessage.spacing.gap", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d74", + "~:name": "xx.component.sectionMessage.spacing.gap", + "~:type": "~:spacing", + "~:value": "{xx.alias.spacing.xs}", + "~:description": "gap for separating elements in section message", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.component.sectionMessage.success.icon", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d77", + "~:name": "xx.component.sectionMessage.success.icon", + "~:type": "~:color", + "~:value": "{xx.alias.color.purpose.onSuccess}", + "~:description": "icon color for success section messages", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.component.sectionMessage.success.text", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d78", + "~:name": "xx.component.sectionMessage.success.text", + "~:type": "~:color", + "~:value": "{xx.alias.color.purpose.onSuccess}", + "~:description": "text color for success section messages", + "~:modified-at": "~m1778507716657" + } + } + ] + ] + } + } + } + ] + ] + } + ], + [ + "G-wl-themes", + { + "~#ordered-map": [ + [ + "S-client_theme_template", + { + "~#penpot/token-set": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f1eae34", + "~:name": "wl-themes/client_theme_template", + "~:description": "", + "~:modified-at": "~m1778507716730", + "~:tokens": { + "~#ordered-map": [ + [ + "xx.alias.color.purpose.onInfo", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d45", + "~:name": "xx.alias.color.purpose.onInfo", + "~:type": "~:color", + "~:value": "#ffffff", + "~:description": "color for elements placed on informational purpose background", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.border.heavy", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d4c", + "~:name": "xx.alias.color.border.heavy", + "~:type": "~:color", + "~:value": "#728090", + "~:description": "heavy border color", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.state.select", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d50", + "~:name": "xx.alias.color.state.select", + "~:type": "~:color", + "~:value": "#B4B4F2", + "~:description": "background color for selected elements", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.primary.onBrand", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d56", + "~:name": "xx.alias.color.primary.onBrand", + "~:type": "~:color", + "~:value": "#f5f5fa", + "~:description": "color for elements that are placed on brand background color", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.purpose.warningHighEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d4a", + "~:name": "xx.alias.color.purpose.warningHighEmphasis", + "~:type": "~:color", + "~:value": "#f1c40f", + "~:description": "color for elements with warning purpose of high emphasis", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.state.onSelect", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d51", + "~:name": "xx.alias.color.state.onSelect", + "~:type": "~:color", + "~:value": "#0707D3", + "~:description": "Foreground color for selected elements", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.primary.interaction", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d57", + "~:name": "xx.alias.color.primary.interaction", + "~:type": "~:color", + "~:value": "#DC7258", + "~:description": "Color that indicates interactions", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.border.focusOutline", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d4d", + "~:name": "xx.alias.color.border.focusOutline", + "~:type": "~:color", + "~:value": "#0505A3", + "~:description": "color for default focus outline", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.state.disable", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d52", + "~:name": "xx.alias.color.state.disable", + "~:type": "~:color", + "~:value": "#bdc3c7", + "~:description": "background color for disabled elements", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.purpose.infoHighEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d4b", + "~:name": "xx.alias.color.purpose.infoHighEmphasis", + "~:type": "~:color", + "~:value": "#2980b9", + "~:description": "color for elements with informational purpose of high emphasis", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.purpose.infoLowEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d40", + "~:name": "xx.alias.color.purpose.infoLowEmphasis", + "~:type": "~:color", + "~:value": "#2980b9", + "~:description": "color for elements with informational purpose of low emphasis", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.primary.brand", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d58", + "~:name": "xx.alias.color.primary.brand", + "~:type": "~:color", + "~:value": "#3333C2", + "~:description": "background color for primary brand elements ", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.text.default", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d5a", + "~:name": "xx.alias.color.text.default", + "~:type": "~:color", + "~:value": "#353535", + "~:description": "Default text color ", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.text.inverted", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d5b", + "~:name": "xx.alias.color.text.inverted", + "~:type": "~:color", + "~:value": "#f5f5fa", + "~:description": "Inverted text color", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.purpose.onSuccess", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d48", + "~:name": "xx.alias.color.purpose.onSuccess", + "~:type": "~:color", + "~:value": "#ffffff", + "~:description": "color for elements placed on success purpose background", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.purpose.criticalLowEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d46", + "~:name": "xx.alias.color.purpose.criticalLowEmphasis", + "~:type": "~:color", + "~:value": "#e74c3c", + "~:description": "color for elements with critical purpose of low emphasis", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.background.highEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d5d", + "~:name": "xx.alias.color.background.highEmphasis", + "~:type": "~:color", + "~:value": "#ffffff", + "~:description": "background Color for components with high emphasis", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.border.radius.md", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d60", + "~:name": "xx.alias.border.radius.md", + "~:type": "~:border-radius", + "~:value": "4px", + "~:description": "medium border radius", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.background.body", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d5e", + "~:name": "xx.alias.color.background.body", + "~:type": "~:color", + "~:value": "#E3E6E9", + "~:description": "body background color", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.purpose.successLowEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d44", + "~:name": "xx.alias.color.purpose.successLowEmphasis", + "~:type": "~:color", + "~:value": "#2ecc71", + "~:description": "color for elements with success purpose of low emphasis", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.primary.onInteraction", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d59", + "~:name": "xx.alias.color.primary.onInteraction", + "~:type": "~:color", + "~:value": "#f5f5fa", + "~:description": "color for elements that are placed on interactive background colors", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.purpose.onCritical", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d41", + "~:name": "xx.alias.color.purpose.onCritical", + "~:type": "~:color", + "~:value": "#ffffff", + "~:description": "color for elements placed on critical purpose background", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.purpose.criticalHighEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d42", + "~:name": "xx.alias.color.purpose.criticalHighEmphasis", + "~:type": "~:color", + "~:value": "#e74c3c", + "~:description": "color for elements with critical purpose of high emphasis", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.border.radius.lg", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d61", + "~:name": "xx.alias.border.radius.lg", + "~:type": "~:border-radius", + "~:value": "8px", + "~:description": "large border radius", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.border.medium", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d4e", + "~:name": "xx.alias.color.border.medium", + "~:type": "~:color", + "~:value": "#9AA5B1", + "~:description": "medium border color", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.border.subtle", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d4f", + "~:name": "xx.alias.color.border.subtle", + "~:type": "~:color", + "~:value": "#C3C9D0", + "~:description": "subtle border color", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.background.lowEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d5f", + "~:name": "xx.alias.color.background.lowEmphasis", + "~:type": "~:color", + "~:value": "#F6F7F8", + "~:description": "background color for elements with a low emphasis", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.purpose.onWarning", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d47", + "~:name": "xx.alias.color.purpose.onWarning", + "~:type": "~:color", + "~:value": "#ffffff", + "~:description": "color for elements placed on warning purpose background", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.text.subtle", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d5c", + "~:name": "xx.alias.color.text.subtle", + "~:type": "~:color", + "~:value": "#9E9E9E", + "~:description": "Text color in subtle weight", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.purpose.successHighEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d43", + "~:name": "xx.alias.color.purpose.successHighEmphasis", + "~:type": "~:color", + "~:value": "#2ecc71", + "~:description": "color for elements with success purpose of high emphasis", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.state.onDisable", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d53", + "~:name": "xx.alias.color.state.onDisable", + "~:type": "~:color", + "~:value": "#7f8c8d", + "~:description": "color for elements that are palced on disabled background color", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.border.radius.sm", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d62", + "~:name": "xx.alias.border.radius.sm", + "~:type": "~:border-radius", + "~:value": "0", + "~:description": "small border radius", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.state.selectInverted", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d54", + "~:name": "xx.alias.color.state.selectInverted", + "~:type": "~:color", + "~:value": "#0707D3", + "~:description": "Inverted background color for selected elements", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.purpose.warningLowEmphasis", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d49", + "~:name": "xx.alias.color.purpose.warningLowEmphasis", + "~:type": "~:color", + "~:value": "#f1c40f", + "~:description": "color for elements with warning purpose of low emphasis", + "~:modified-at": "~m1778507716657" + } + } + ], + [ + "xx.alias.color.state.onSelectInverted", + { + "~#penpot/token": { + "~:id": "~u4cdd76d8-0e6d-8168-8008-01189f0c4d55", + "~:name": "xx.alias.color.state.onSelectInverted", + "~:type": "~:color", + "~:value": "#B4B4F2", + "~:description": "inverted color for elements on selected backgrounds", + "~:modified-at": "~m1778507716657" + } + } + ] + ] + } + } + } + ] + ] + } + ] + ] + }, + "~:themes": { + "~#ordered-map": [ + [ + "", + { + "~#ordered-map": [ + [ + "__PENPOT__HIDDEN__TOKEN__THEME__", + { + "~#penpot/token-theme": { + "~:id": "~u00000000-0000-0000-0000-000000000000", + "~:name": "__PENPOT__HIDDEN__TOKEN__THEME__", + "~:group": "", + "~:description": "", + "~:is-source": false, + "~:external-id": "", + "~:modified-at": "~m1778507716730", + "~:sets": { + "~#set": [ + "alias/border", + "global/dimension", + "global/color", + "alias/color-light", + "global/Opacity", + "alias/dimension" + ] + } + } + } + ] + ] + } + ], + [ + "Global", + { + "~#ordered-map": [ + [ + "Brand", + { + "~#penpot/token-theme": { + "~:id": "~uc00d9d2e-76f9-815b-8005-e1260332abac", + "~:name": "Brand", + "~:group": "Global", + "~:description": "", + "~:is-source": true, + "~:external-id": "c00d9d2e-76f9-815b-8005-e1260332abac", + "~:modified-at": "~m1778507716656", + "~:sets": { + "~#set": [ + "global/dimension", + "global/color", + "global/Opacity" + ] + } + } + } + ] + ] + } + ], + [ + "Alias", + { + "~#ordered-map": [ + [ + "Light Mode", + { + "~#penpot/token-theme": { + "~:id": "~uc00d9d2e-76f9-815b-8005-e1260332abb0", + "~:name": "Light Mode", + "~:group": "Alias", + "~:description": "", + "~:is-source": true, + "~:external-id": "c00d9d2e-76f9-815b-8005-e1260332abb0", + "~:modified-at": "~m1778507716656", + "~:sets": { + "~#set": [ + "alias/border", + "alias/color-light", + "alias/dimension" + ] + } + } + } + ], + [ + "Dark Mode", + { + "~#penpot/token-theme": { + "~:id": "~uc00d9d2e-76f9-815b-8005-e1260332abb2", + "~:name": "Dark Mode", + "~:group": "Alias", + "~:description": "", + "~:is-source": true, + "~:external-id": "c00d9d2e-76f9-815b-8005-e1260332abb2", + "~:modified-at": "~m1778507716656", + "~:sets": { + "~#set": [ + "alias/border", + "alias/color-dark", + "alias/dimension" + ] + } + } + } + ] + ] + } + ], + [ + "Components", + { + "~#ordered-map": [ + [ + "Section Message", + { + "~#penpot/token-theme": { + "~:id": "~uc00d9d2e-76f9-815b-8005-e1260332abb4", + "~:name": "Section Message", + "~:group": "Components", + "~:description": "", + "~:is-source": true, + "~:external-id": "c00d9d2e-76f9-815b-8005-e1260332abb4", + "~:modified-at": "~m1778507716656", + "~:sets": { + "~#set": [ + "components/section-message" + ] + } + } + } + ] + ] + } + ], + [ + "White Label", + { + "~#ordered-map": [ + [ + "Client Theme Template", + { + "~#penpot/token-theme": { + "~:id": "~uc00d9d2e-76f9-815b-8005-e1260332abb6", + "~:name": "Client Theme Template", + "~:group": "White Label", + "~:description": "", + "~:is-source": true, + "~:external-id": "c00d9d2e-76f9-815b-8005-e1260332abb6", + "~:modified-at": "~m1778507716656", + "~:sets": { + "~#set": [ + "wl-themes/client_theme_template" + ] + } + } + } + ] + ] + } + ], + [ + "patata", + { + "~#ordered-map": [ + [ + "patatilla", + { + "~#penpot/token-theme": { + "~:id": "~uc00d9d2e-76f9-815b-8005-e1260332abb8", + "~:name": "patatilla", + "~:group": "patata", + "~:description": "", + "~:is-source": false, + "~:external-id": "c00d9d2e-76f9-815b-8005-e1260332abb8", + "~:modified-at": "~m1778507716656", + "~:sets": { + "~#set": [] + } + } + } + ] + ] + } + ], + [ + "typography", + { + "~#ordered-map": [ + [ + "web", + { + "~#penpot/token-theme": { + "~:id": "~u312c1822-c99f-807a-8006-62605689cee0", + "~:name": "web", + "~:group": "typography", + "~:description": "", + "~:is-source": false, + "~:external-id": "312c1822-c99f-807a-8006-62605689cee0", + "~:modified-at": "~m1778507716656", + "~:sets": { + "~#set": [ + "alias/web-typography" + ] + } + } + } + ], + [ + "app", + { + "~#penpot/token-theme": { + "~:id": "~u312c1822-c99f-807a-8006-626064817196", + "~:name": "app", + "~:group": "typography", + "~:description": "", + "~:is-source": false, + "~:external-id": "312c1822-c99f-807a-8006-626064817196", + "~:modified-at": "~m1778507716656", + "~:sets": { + "~#set": [ + "alias/app-typography" + ] + } + } + } + ] + ] + } + ] + ] + }, + "~:active-themes": { + "~#set": [ + "Global/Brand", + "Alias/Light Mode", + "/__PENPOT__HIDDEN__TOKEN__THEME__" + ] + } + } + }, + "~:id": "~u1bac06a1-a942-80a6-8008-0222e1ec38d5", + "~:options": { + "~:components-v2": true, + "~:base-font-size": "16px" + } + } +} \ No newline at end of file diff --git a/frontend/playwright/ui/specs/tokens/text.spec.js b/frontend/playwright/ui/specs/tokens/text.spec.js new file mode 100644 index 0000000000..3c34376934 --- /dev/null +++ b/frontend/playwright/ui/specs/tokens/text.spec.js @@ -0,0 +1,29 @@ +import { test, expect } from "@playwright/test"; +import { WasmWorkspacePage } from "../../pages/WasmWorkspacePage"; + +test.beforeEach(async ({ page }) => { + await WasmWorkspacePage.init(page); +}); + +test("BUG 13958 - Fill token gets detached when editing text shape", async ({ page }) => { + const workspacePage = new WasmWorkspacePage(page); + await workspacePage.setupEmptyFile(); + // Load a file that has a text shape, whose content contains a `:fills []` in the root node. + // This attribute is removed when editing the text, causing the old code to detect that the + // fills had changed and detaching the token. + await workspacePage.mockGetFile("workspace/get-file-13958.json"); + await workspacePage.goToWorkspace(); + + // Check token is attached to the shape + await workspacePage.clickLeafLayer("Design tokens are a set"); + await expect(workspacePage.rightSidebar.getByLabel("xx.alias.color.text.default", { exact: true })).toBeVisible(); + + // Enter and exit the text editor + await workspacePage.page.keyboard.press("Enter"); + await expect(workspacePage.page.getByTestId("text-editor")).toBeVisible(); + await workspacePage.page.keyboard.press("Escape"); + await expect(workspacePage.page.getByTestId("text-editor")).not.toBeAttached(); + + // Assert token is still attached to the shape + await expect(workspacePage.rightSidebar.getByLabel("xx.alias.color.text.default", { exact: true })).toBeVisible(); +}); From 0956becd12d3b38ecb07597a2acc13b90ccfc62b Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 18 May 2026 12:35:16 +0200 Subject: [PATCH 07/14] :tada: Reduce heap allocations --- render-wasm/src/main.rs | 9 +++--- render-wasm/src/render.rs | 47 +++++++++++++++------------- render-wasm/src/state.rs | 3 +- render-wasm/src/state/shapes_pool.rs | 46 +++++++++++++-------------- 4 files changed, 52 insertions(+), 53 deletions(-) diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 74cc7478fe..c1ebc94839 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -121,9 +121,11 @@ pub extern "C" fn render(timestamp: i32) -> Result<()> { // modifier set, so the cost is paid once per rAF rather than // once per pointer move. if get_render_state().options.is_interactive_transform() { - let ids = state.shapes.modifier_ids(); + // Collect into an owned Vec to release the immutable borrow on + // `state.shapes` before the mutable `rebuild_modifier_tiles` call. + let ids = state.shapes.modifier_ids().to_vec(); if !ids.is_empty() { - state.rebuild_modifier_tiles(ids)?; + state.rebuild_modifier_tiles(&ids)?; } } state @@ -856,9 +858,8 @@ pub extern "C" fn set_modifiers() -> Result<()> { with_state!(state, { state.set_modifiers(modifiers); - // TO CHECK if !get_render_state().options.is_interactive_transform() { - state.rebuild_modifier_tiles(ids)?; + state.rebuild_modifier_tiles(&ids)?; } }); Ok(()) diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 416c13bb0c..72cc2078f2 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -3040,24 +3040,27 @@ impl RenderState { // modified shapes (doc-space @ 100% zoom, scale=1.0). This is used as a cheap overlap // guard to decide when cached top-level crops are unsafe to reuse (something is moving // over/inside them), without doing expensive ancestor walks per node. - let moved_bounds = - if self.options.is_interactive_transform() && !tree.modifier_ids().is_empty() { - let mut acc: Option = None; - for id in tree.modifier_ids().iter() { - let Some(s) = tree.get(id) else { continue }; - let r = self.get_cached_extrect(s, tree, 1.0); - acc = Some(match acc { - None => r, - Some(mut prev) => { - prev.join(r); - prev - } - }); - } - acc - } else { - None - }; + // + // `modifier_ids` is pre-computed once here and reused throughout the loop to avoid + // repeated allocations (formerly O(N_shapes) HashMap builds) per node. + let modifier_ids = tree.modifier_ids(); + let moved_bounds = if self.options.is_interactive_transform() && !modifier_ids.is_empty() { + let mut acc: Option = None; + for id in modifier_ids.iter() { + let Some(s) = tree.get(id) else { continue }; + let r = self.get_cached_extrect(s, tree, 1.0); + acc = Some(match acc { + None => r, + Some(mut prev) => { + prev.join(r); + prev + } + }); + } + acc + } else { + None + }; while let Some(node_render_state) = self.pending_nodes.pop() { let node_id = node_render_state.id; @@ -3136,7 +3139,7 @@ impl RenderState { let use_cached = self.should_use_cached_top_level_during_interactive( node_id, tree, - &tree.modifier_ids(), + modifier_ids, moved_bounds, ); @@ -3857,7 +3860,7 @@ impl RenderState { pub fn rebuild_modifier_tiles( &mut self, tree: ShapesPoolMutRef<'_>, - ids: Vec, + ids: &[Uuid], ) -> Result<()> { // During interactive transform, skip ancestor invalidation: walking up to the // parent frame evicts every tile the frame covers, including dense tiles with @@ -3865,9 +3868,9 @@ impl RenderState { // `ShapesPool::set_modifiers`; the tile index is reconciled post-gesture by // the committing code path (rebuild_touched_tiles). if self.options.is_interactive_transform() { - self.update_tiles_shapes(&ids, tree)?; + self.update_tiles_shapes(ids, tree)?; } else { - let ancestors = all_with_ancestors(&ids, tree, false); + let ancestors = all_with_ancestors(ids, tree, false); self.update_tiles_shapes(&ancestors, tree)?; } Ok(()) diff --git a/render-wasm/src/state.rs b/render-wasm/src/state.rs index 5eed9d66fb..116e7a67c4 100644 --- a/render-wasm/src/state.rs +++ b/render-wasm/src/state.rs @@ -225,8 +225,7 @@ impl State { let _ = get_render_state().render_preview(&self.shapes, timestamp); } - pub fn rebuild_modifier_tiles(&mut self, ids: Vec) -> Result<()> { - // Index-based storage is safe + pub fn rebuild_modifier_tiles(&mut self, ids: &[Uuid]) -> Result<()> { get_render_state().rebuild_modifier_tiles(&mut self.shapes, ids) } diff --git a/render-wasm/src/state/shapes_pool.rs b/render-wasm/src/state/shapes_pool.rs index 9f19dbc50a..d51ce1cabe 100644 --- a/render-wasm/src/state/shapes_pool.rs +++ b/render-wasm/src/state/shapes_pool.rs @@ -49,6 +49,11 @@ pub struct ShapesPoolImpl { modified_shape_cache: HashMap>, /// Transform modifiers, keyed by index modifiers: HashMap, + /// UUIDs of shapes that have an active transform modifier, kept in sync + /// with `modifiers`. Stored explicitly so that `modifier_ids()` is O(K) + /// (K = number of modified shapes) instead of O(N_shapes) — avoids + /// building a full reverse-index HashMap on every call. + modifier_uuids: Vec, /// Structure entries, keyed by index structure: HashMap>, /// Scale content values, keyed by index @@ -69,6 +74,7 @@ impl ShapesPoolImpl { modified_shape_cache: HashMap::default(), modifiers: HashMap::default(), + modifier_uuids: Vec::new(), structure: HashMap::default(), scale_content: HashMap::default(), } @@ -238,7 +244,11 @@ impl ShapesPoolImpl { } self.modifiers = modifiers_with_idx; + // Compute ancestors before consuming `ids` so we can move it into + // `modifier_uuids` without a clone. let all_ids = shapes::all_with_ancestors(&ids, self, true); + // Keep modifier_uuids in sync so modifier_ids() is O(K) not O(N_shapes). + self.modifier_uuids = ids; for uuid in all_ids { if let Some(idx) = self.uuid_to_idx.get(&uuid).copied() { self.modified_shape_cache.insert(idx, OnceCell::new()); @@ -300,19 +310,9 @@ impl ShapesPoolImpl { pub fn clean_all(&mut self) -> Vec { self.clean_shape_cache(); - let modified_uuids: Vec = if self.modifiers.is_empty() { - Vec::new() - } else { - let mut idx_to_uuid: HashMap = - HashMap::with_capacity(self.uuid_to_idx.len()); - for (uuid, idx) in self.uuid_to_idx.iter() { - idx_to_uuid.insert(*idx, *uuid); - } - self.modifiers - .keys() - .filter_map(|idx| idx_to_uuid.get(idx).copied()) - .collect() - }; + // `modifier_uuids` is kept in sync with `modifiers` by `set_modifiers`, + // so we can take it directly — no need to rebuild a reverse index. + let modified_uuids = std::mem::take(&mut self.modifier_uuids); self.modifiers = HashMap::default(); self.structure = HashMap::default(); @@ -325,18 +325,12 @@ impl ShapesPoolImpl { /// Used by the throttled drag path so per-rAF tile invalidation can /// be done once with the current modifier set instead of once per /// pointer move. - pub fn modifier_ids(&self) -> Vec { - if self.modifiers.is_empty() { - return Vec::new(); - } - let mut idx_to_uuid: HashMap = HashMap::with_capacity(self.uuid_to_idx.len()); - for (uuid, idx) in self.uuid_to_idx.iter() { - idx_to_uuid.insert(*idx, *uuid); - } - self.modifiers - .keys() - .filter_map(|idx| idx_to_uuid.get(idx).copied()) - .collect() + /// + /// Returns a reference to avoid allocation on every call — callers + /// inside hot render loops should hold this reference rather than + /// calling `modifier_ids()` repeatedly. + pub fn modifier_ids(&self) -> &[Uuid] { + &self.modifier_uuids } pub fn subtree(&self, id: &Uuid) -> ShapesPoolImpl { @@ -363,6 +357,7 @@ impl ShapesPoolImpl { uuid_to_idx, modified_shape_cache: HashMap::default(), modifiers: HashMap::default(), + modifier_uuids: Vec::new(), structure: HashMap::default(), scale_content: HashMap::default(), } @@ -409,6 +404,7 @@ impl Clone for ShapesPoolImpl { // so it gets lazily rebuilt on demand rather than cloning OnceCell state. modified_shape_cache: HashMap::default(), modifiers: self.modifiers.clone(), + modifier_uuids: self.modifier_uuids.clone(), structure: self.structure.clone(), scale_content: self.scale_content.clone(), } From 9928249d4fec7fd0063ab34812a295a22e29fed1 Mon Sep 17 00:00:00 2001 From: Alonso Torres Date: Mon, 18 May 2026 12:47:41 +0200 Subject: [PATCH 08/14] :arrow_up: Downgrade archive dependency (#9704) --- exporter/package.json | 2 +- exporter/pnpm-lock.yaml | 365 ++++++++++++++++++++++++++++++++++------ 2 files changed, 316 insertions(+), 51 deletions(-) diff --git a/exporter/package.json b/exporter/package.json index e05895882c..376393ab7b 100644 --- a/exporter/package.json +++ b/exporter/package.json @@ -11,7 +11,7 @@ }, "type": "module", "dependencies": { - "archiver": "^8.0.0", + "archiver": "7.0.1", "cookies": "^0.9.1", "date-fns": "^4.1.0", "generic-pool": "^3.9.0", diff --git a/exporter/pnpm-lock.yaml b/exporter/pnpm-lock.yaml index 7acfafe825..16082370ea 100644 --- a/exporter/pnpm-lock.yaml +++ b/exporter/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: archiver: - specifier: ^8.0.0 - version: 8.0.0 + specifier: 7.0.1 + version: 7.0.1 cookies: specifier: ^0.9.1 version: 0.9.1 @@ -61,6 +61,14 @@ packages: '@ioredis/commands@1.5.1': resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==} + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + '@trysound/sax@0.2.0': resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} @@ -69,9 +77,29 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} - archiver@8.0.0: - resolution: {integrity: sha512-fV1orZfsnPn9BaSByR/qE67rJCLJEy2Ox5bq7nJh+jquWaNh6Sfec75kJ2T6PtdGUbPQlrVoSVCEOa5SdiTQ1g==} - engines: {node: '>=18'} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + archiver-utils@5.0.2: + resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==} + engines: {node: '>= 14'} + + archiver@7.0.1: + resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} + engines: {node: '>= 14'} async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} @@ -84,9 +112,8 @@ packages: react-native-b4a: optional: true - balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} bare-events@2.8.2: resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} @@ -135,9 +162,8 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - brace-expansion@5.0.6: - resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} - engines: {node: 18 || 20 || >=22} + brace-expansion@2.1.0: + resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} buffer-crc32@1.0.0: resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} @@ -157,9 +183,16 @@ packages: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} - compress-commons@7.0.1: - resolution: {integrity: sha512-g0S8KAD8qf4+V//pr3BfB1aBnARLXNz2Gx+jmHU0LEriUuoQUOPOulVquHKTJ8+EAIIO7fhseNDr9wK5Q9FKBQ==} - engines: {node: '>=18'} + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + compress-commons@6.0.2: + resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} + engines: {node: '>= 14'} cookies@0.9.1: resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} @@ -176,9 +209,13 @@ packages: engines: {node: '>=0.8'} hasBin: true - crc32-stream@7.0.1: - resolution: {integrity: sha512-IBWsY8xznyQrcHn8h4bC8/4ErNke5elzgG8GcqF4RFPw6aHkWWRc7Tgw6upjaTX/CT/yQgqYENkxYsTYN+hW2g==} - engines: {node: '>=18'} + crc32-stream@6.0.0: + resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} + engines: {node: '>= 14'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} css-select@5.2.2: resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} @@ -232,6 +269,15 @@ packages: domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -250,6 +296,10 @@ packages: fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -259,6 +309,14 @@ packages: resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} engines: {node: '>= 4'} + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} @@ -281,13 +339,23 @@ packages: resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==} engines: {node: '>=12.22.0'} - is-stream@4.0.1: - resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} - engines: {node: '>=18'} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + keygrip@1.1.0: resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} engines: {node: '>= 0.6'} @@ -306,15 +374,26 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + mdn-data@2.0.28: resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} mdn-data@2.12.2: resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} - minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} - engines: {node: 18 || 20 || >=22} + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -326,6 +405,17 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + playwright-core@1.60.0: resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==} engines: {node: '>=18'} @@ -354,9 +444,8 @@ packages: resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - readdir-glob@3.0.0: - resolution: {integrity: sha512-AhNB2KgKeVJr16nK9LLZbJNWnYoT23ZrumNKFDebHBdkC8KHSqWo871JAUhoWC/RtjEVdqNMFpM6qrwRbaUqpw==} - engines: {node: '>=18'} + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} redis-errors@1.2.0: resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} @@ -381,6 +470,18 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -402,12 +503,28 @@ packages: streamx@2.25.0: resolution: {integrity: sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==} + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + svgo@https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180: resolution: {tarball: https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180} version: 4.0.0 @@ -441,6 +558,19 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + ws@8.20.1: resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} engines: {node: '>=10.0.0'} @@ -460,9 +590,9 @@ packages: xregexp@5.1.2: resolution: {integrity: sha512-6hGgEMCGhqCTFEJbqmWrNIPqfpdirdGWkqshu7fFZddmTSfgv5Sn9D2SaKloR79s5VUiUlpwzg3CM3G6D3VIlw==} - zip-stream@7.0.5: - resolution: {integrity: sha512-dSvYKdvLsAHCDqPOhIwk/q5CvuWtTB3Dgpoe0uVEFjTzIOAmsQpprX25InCvrvJsirEbu1OHyy67n/kAj1Sw/w==} - engines: {node: '>=18'} + zip-stream@6.0.1: + resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} + engines: {node: '>= 14'} snapshots: @@ -472,23 +602,53 @@ snapshots: '@ioredis/commands@1.5.1': {} + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@pkgjs/parseargs@0.11.0': + optional: true + '@trysound/sax@0.2.0': {} abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 - archiver@8.0.0: + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: dependencies: - async: 3.2.6 - buffer-crc32: 1.0.0 - is-stream: 4.0.1 + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + archiver-utils@5.0.2: + dependencies: + glob: 10.5.0 + graceful-fs: 4.2.11 + is-stream: 2.0.1 lazystream: 1.0.1 + lodash: 4.17.21 normalize-path: 3.0.0 readable-stream: 4.7.0 - readdir-glob: 3.0.0 + + archiver@7.0.1: + dependencies: + archiver-utils: 5.0.2 + async: 3.2.6 + buffer-crc32: 1.0.0 + readable-stream: 4.7.0 + readdir-glob: 1.1.3 tar-stream: 3.2.0 - zip-stream: 7.0.5 + zip-stream: 6.0.1 transitivePeerDependencies: - bare-abort-controller - bare-buffer @@ -498,7 +658,7 @@ snapshots: b4a@1.8.1: {} - balanced-match@4.0.4: {} + balanced-match@1.0.2: {} bare-events@2.8.2: {} @@ -536,9 +696,9 @@ snapshots: boolbase@1.0.0: {} - brace-expansion@5.0.6: + brace-expansion@2.1.0: dependencies: - balanced-match: 4.0.4 + balanced-match: 1.0.2 buffer-crc32@1.0.0: {} @@ -553,11 +713,17 @@ snapshots: cluster-key-slot@1.1.2: {} - compress-commons@7.0.1: + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + compress-commons@6.0.2: dependencies: crc-32: 1.2.2 - crc32-stream: 7.0.1 - is-stream: 4.0.1 + crc32-stream: 6.0.0 + is-stream: 2.0.1 normalize-path: 3.0.0 readable-stream: 4.7.0 @@ -572,11 +738,17 @@ snapshots: crc-32@1.2.2: {} - crc32-stream@7.0.1: + crc32-stream@6.0.0: dependencies: crc-32: 1.2.2 readable-stream: 4.7.0 + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + css-select@5.2.2: dependencies: boolbase: 1.0.0 @@ -629,6 +801,12 @@ snapshots: domelementtype: 2.3.0 domhandler: 5.0.3 + eastasianwidth@0.2.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + entities@4.5.0: {} event-target-shim@5.0.1: {} @@ -643,11 +821,27 @@ snapshots: fast-fifo@1.3.2: {} + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + fsevents@2.3.2: optional: true generic-pool@3.9.0: {} + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + graceful-fs@4.2.11: {} + http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -680,10 +874,20 @@ snapshots: transitivePeerDependencies: - supports-color - is-stream@4.0.1: {} + is-fullwidth-code-point@3.0.0: {} + + is-stream@2.0.1: {} isarray@1.0.0: {} + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + keygrip@1.1.0: dependencies: tsscmp: 1.0.6 @@ -698,13 +902,21 @@ snapshots: lodash@4.17.21: {} + lru-cache@10.4.3: {} + mdn-data@2.0.28: {} mdn-data@2.12.2: {} - minimatch@10.2.5: + minimatch@5.1.9: dependencies: - brace-expansion: 5.0.6 + brace-expansion: 2.1.0 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.0 + + minipass@7.1.3: {} ms@2.1.3: {} @@ -714,6 +926,15 @@ snapshots: dependencies: boolbase: 1.0.0 + package-json-from-dist@1.0.1: {} + + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + playwright-core@1.60.0: {} playwright@1.60.0: @@ -751,9 +972,9 @@ snapshots: process: 0.11.10 string_decoder: 1.3.0 - readdir-glob@3.0.0: + readdir-glob@1.1.3: dependencies: - minimatch: 10.2.5 + minimatch: 5.1.9 redis-errors@1.2.0: {} @@ -771,6 +992,14 @@ snapshots: setprototypeof@1.2.0: {} + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@4.1.0: {} + source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -793,6 +1022,18 @@ snapshots: - bare-abort-controller - react-native-b4a + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 @@ -801,6 +1042,14 @@ snapshots: dependencies: safe-buffer: 5.2.1 + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + svgo@https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180: dependencies: '@trysound/sax': 0.2.0 @@ -843,6 +1092,22 @@ snapshots: util-deprecate@1.0.2: {} + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + ws@8.20.1: {} xml-js@1.6.11: @@ -853,8 +1118,8 @@ snapshots: dependencies: '@babel/runtime-corejs3': 7.28.4 - zip-stream@7.0.5: + zip-stream@6.0.1: dependencies: - compress-commons: 7.0.1 - normalize-path: 3.0.0 + archiver-utils: 5.0.2 + compress-commons: 6.0.2 readable-stream: 4.7.0 From 9de25c540464e95d1b67f1df1055b5013f5c3b41 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 18 May 2026 12:54:16 +0200 Subject: [PATCH 09/14] :bug: Fix incorrect content-type on doc endpoint response (#9681) The /api/main/doc endpoint was returning HTML content with a text/plain content-type header instead of text/html. This caused browsers to render the response as plain text. Added content-type: text/html; charset=utf-8 header to the response in the doc handler and added a regression test to verify the fix. Closes #9680 Signed-off-by: Andrey Antukh --- backend/src/app/rpc/doc.clj | 1 + backend/test/backend_tests/rpc_doc_test.clj | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/backend/src/app/rpc/doc.clj b/backend/src/app/rpc/doc.clj index 611be93b15..62dffae23f 100644 --- a/backend/src/app/rpc/doc.clj +++ b/backend/src/app/rpc/doc.clj @@ -96,6 +96,7 @@ context (assoc @context :param-style pstyle)] {::yres/status 200 + ::yres/headers {"content-type" "text/html; charset=utf-8"} ::yres/body (-> (io/resource template) (tmpl/render context))}))) (fn [_] diff --git a/backend/test/backend_tests/rpc_doc_test.clj b/backend/test/backend_tests/rpc_doc_test.clj index 964ed57b6a..82c0dc28ff 100644 --- a/backend/test/backend_tests/rpc_doc_test.clj +++ b/backend/test/backend_tests/rpc_doc_test.clj @@ -12,10 +12,12 @@ [app.common.schema :as sm] [app.common.schema.generators :as sg] [app.common.schema.test :as smt] + [app.config :as cf] [app.rpc :as-alias rpc] [app.rpc.doc :as rpc.doc] [backend-tests.helpers :as th] - [clojure.test :as t])) + [clojure.test :as t] + [yetti.response :as-alias yres])) (t/use-fixtures :once th/state-init) @@ -31,6 +33,17 @@ false))) {:num 15})) - - +(t/deftest doc-handler-returns-html-content-type + (with-redefs [cf/flags #{:backend-api-doc}] + (let [methods (::rpc/methods th/*system*) + handler (#'rpc.doc/handler :methods methods + :label "main" + :entrypoint "http://localhost/api/main/methods" + :openapi "http://localhost/api/main/doc/openapi" + :template "app/templates/main-api-doc.tmpl") + request {} + response (handler request)] + (t/is (= 200 (::yres/status response))) + (t/is (= "text/html; charset=utf-8" + (get-in response [::yres/headers "content-type"])))))) From ab284febf7afabbe959b5f970ad621cd2b053a52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 18 May 2026 13:32:28 +0200 Subject: [PATCH 10/14] :bug: Fix token application to grid padding (#9630) --- CHANGES.md | 1 + .../playwright/ui/specs/tokens/apply.spec.js | 50 +++++++++++++++++++ .../options/menus/layout_container.cljs | 1 + 3 files changed, 52 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 7905f7cdbd..00d3918679 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -124,6 +124,7 @@ - Fix asset icon broken on Asset tab [#9587](https://github.com/penpot/penpot/issues/9587) (PR: [#9612](https://github.com/penpot/penpot/pull/9612)) - Fix text fill color stops updating in multiselect with texts [#9608](https://github.com/penpot/penpot/issues/9608) (PR: [#9549](https://github.com/penpot/penpot/pull/9549)) - Fix editing a legacy text element silently detaches its color token [Taiga #13958](https://tree.taiga.io/project/penpot/issue/13958) +- Fix token application to grid paddings [Taiga #14136](https://tree.taiga.io/project/penpot/issue/14136) ## 2.15.4 (Unreleased) diff --git a/frontend/playwright/ui/specs/tokens/apply.spec.js b/frontend/playwright/ui/specs/tokens/apply.spec.js index 64f49733ce..df4fcfb247 100644 --- a/frontend/playwright/ui/specs/tokens/apply.spec.js +++ b/frontend/playwright/ui/specs/tokens/apply.spec.js @@ -1030,6 +1030,56 @@ test("BUG: 13930, Token colors are shown on selected colors section", async ({ ).toBeVisible(); }); +test("BUG: 14136 Apply grid layout padding token to a shape from the sidebar does not change values", async ({ + page, +}) => { + // Setup the workspace with token features enabled + const { workspacePage, tokensSidebar, tokenContextMenuForToken } = + await setupTokensFileRender(page, { + flags: ["enable-token-combobox", "enable-feature-token-input"], + }); + + // Transform a rectangle into a grid container to expose gap properties + await page.getByRole("tab", { name: "Layers" }).click(); + + await workspacePage.layers.getByTestId("layer-row").nth(1).click(); + + const layoutSection = page.getByTestId("inspect-layout"); + await expect(layoutSection).toBeVisible(); + + const addLayoutButton = layoutSection + .getByRole("button", { name: "Add layout" }) + .first(); + await addLayoutButton.click(); + await page.getByText("Grid layout").click(); + + // Apply a dimension token to the vertical padding property + await layoutSection.getByLabel("Open token list").nth(2).click(); + const tokenDimensionMd = layoutSection.getByRole("option", { + name: "dimension.md", + }); + await expect(tokenDimensionMd).toBeVisible(); + await tokenDimensionMd.click(); + + // Expand padding to all sides + await layoutSection.getByRole('button', { name: 'Show 4 sided padding options' }).click(); + const topPaddingSection = layoutSection.getByLabel("Top padding"); + const bottomPaddingSection = layoutSection.getByLabel("Bottom padding"); + await expect(topPaddingSection).toBeVisible(); + + // Check if token is still applied to top and bottom padding + await expect(topPaddingSection.getByLabel("Detach token")).toBeVisible(); + await expect(bottomPaddingSection.getByLabel("Detach token")).toBeVisible(); + + // Check if the value of the attribute is still correct + await expect( + await topPaddingSection.getByRole("button", { name: "dimension.md" }).textContent() + ).toBe("16"); + await expect( + await bottomPaddingSection.getByRole("button", { name: "dimension.md" }).textContent() + ).toBe("16"); +}); + test.describe("Numeric Input and Token Integration Tests", () => { test("Token pill persists after blur in gap inputs", async ({ page }) => { // Setup the workspace with token features enabled diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs index dd992ce08b..08e1774904 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs @@ -1377,6 +1377,7 @@ [:div {:class (stl/css :padding-row)} [:> padding-section* {:value (:layout-padding values) :type (:layout-padding-type values) + :ids ids :applied-tokens applied-tokens :on-type-change on-padding-type-change :on-change on-padding-change}]]] From 725a0c966c8d155f1a332510e4696265897388b6 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 18 May 2026 14:23:18 +0200 Subject: [PATCH 11/14] :paperclip: Fix incorrect entries on changelog --- CHANGES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 00d3918679..87c3bc33e9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -123,8 +123,8 @@ - Fix several color picker issues [#9556](https://github.com/penpot/penpot/issues/9556) (PR: [#9558](https://github.com/penpot/penpot/pull/9558)) - Fix asset icon broken on Asset tab [#9587](https://github.com/penpot/penpot/issues/9587) (PR: [#9612](https://github.com/penpot/penpot/pull/9612)) - Fix text fill color stops updating in multiselect with texts [#9608](https://github.com/penpot/penpot/issues/9608) (PR: [#9549](https://github.com/penpot/penpot/pull/9549)) -- Fix editing a legacy text element silently detaches its color token [Taiga #13958](https://tree.taiga.io/project/penpot/issue/13958) -- Fix token application to grid paddings [Taiga #14136](https://tree.taiga.io/project/penpot/issue/14136) +- Fix editing a legacy text element silently detaches its color token [#9255](https://github.com/penpot/penpot/issues/9255) +- Fix token application to grid paddings [#9494](https://github.com/penpot/penpot/issues/9494) ## 2.15.4 (Unreleased) From 82169bc0a3fc12bec30982c561ec41f7ceb55e73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 18 May 2026 14:25:32 +0200 Subject: [PATCH 12/14] :bug: Fix loss of swap slot in some cases of variant switch (#9147) Signed-off-by: Andrey Antukh Co-authored-by: Andrey Antukh --- CHANGES.md | 2 + .../src/app/common/files/changes_builder.cljc | 4 +- common/src/app/common/logic/libraries.cljc | 12 +- .../logic/variants_switch_test.cljc | 211 ++++++++++++++++-- 4 files changed, 207 insertions(+), 22 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 87c3bc33e9..a4b500c9f2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -125,6 +125,8 @@ - Fix text fill color stops updating in multiselect with texts [#9608](https://github.com/penpot/penpot/issues/9608) (PR: [#9549](https://github.com/penpot/penpot/pull/9549)) - Fix editing a legacy text element silently detaches its color token [#9255](https://github.com/penpot/penpot/issues/9255) - Fix token application to grid paddings [#9494](https://github.com/penpot/penpot/issues/9494) +- Fix file crashing when switching a variant [#9259](https://github.com/penpot/penpot/issues/9259) + ## 2.15.4 (Unreleased) diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc index 5a3838c8c1..49a78726f5 100644 --- a/common/src/app/common/files/changes_builder.cljc +++ b/common/src/app/common/files/changes_builder.cljc @@ -46,8 +46,8 @@ (with-meta changes {::page-id page-id}))) ([] - {:redo-changes [] - :undo-changes '()}) + {:redo-changes [] ;; redo-changes is a vector so that conj adds things at the end, in order of execution + :undo-changes '()}) ;; undo-changes is a list to conj things at the beginning, so they execute in the reverse order when undoing several changes ([origin] {:redo-changes [] :undo-changes '() diff --git a/common/src/app/common/logic/libraries.cljc b/common/src/app/common/logic/libraries.cljc index d1d03f68aa..917f2afe72 100644 --- a/common/src/app/common/logic/libraries.cljc +++ b/common/src/app/common/logic/libraries.cljc @@ -2119,8 +2119,8 @@ (contains? #{:auto-height :auto-width} (:grow-type current-shape)))] (loop [attrs updatable-attrs - roperations [{:type :set-touched :touched (:touched previous-shape)}] - uoperations (list {:type :set-touched :touched (:touched current-shape)})] + roperations [] + uoperations '()] (if-let [attr (first attrs)] (let [sync-group (ctk/resolve-sync-group (:type previous-shape) attr) @@ -2262,7 +2262,13 @@ (let [updated-attrs (into #{} (comp (filter #(= :set (:type %))) (map :attr)) - roperations)] + roperations) + updated-sync-groups (into #{} + (keep #(ctk/resolve-sync-group (:type previous-shape) %)) + updated-attrs) + new-touched (set/union (or (:touched current-shape) #{}) updated-sync-groups) + roperations (into [{:type :set-touched :touched new-touched}] roperations) + uoperations (into (list {:type :set-touched :touched (:touched current-shape)}) uoperations)] (cond-> changes (> (count roperations) 1) (-> (add-update-attr-changes current-shape container roperations uoperations) diff --git a/common/test/common_tests/logic/variants_switch_test.cljc b/common/test/common_tests/logic/variants_switch_test.cljc index c991f35ab6..99784dade5 100644 --- a/common/test/common_tests/logic/variants_switch_test.cljc +++ b/common/test/common_tests/logic/variants_switch_test.cljc @@ -14,6 +14,7 @@ [app.common.test-helpers.ids-map :as thi] [app.common.test-helpers.shapes :as ths] [app.common.test-helpers.variants :as thv] + [app.common.types.component :as ctk] [clojure.test :as t])) (t/use-fixtures :each thi/test-fixture) @@ -35,13 +36,13 @@ copy01 (ths/get-shape file :copy01) ;; ==== Action - file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) + file' (tho/swap-component-in-shape file :copy01 :c02 {:keep-touched? true}) - copy01' (ths/get-shape file' :copy02)] + copy01' (ths/get-shape file' :copy01)] (thf/dump-file file :keys [:width]) ;; The copy had width 5 before the switch (t/is (= (:width copy01) 5)) - ;; The rect has width 15 after the switch + ;; The copy has width 15 after the switch (t/is (= (:width copy01') 15)))) (t/deftest test-simple-switch @@ -61,15 +62,15 @@ rect01 (get-in page [:objects (-> copy01 :shapes first)]) ;; ==== Action - file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) + file' (tho/swap-component-in-shape file :copy01 :c02 {:keep-touched? true}) page' (thf/current-page file') - copy02' (ths/get-shape file' :copy02) - rect02' (get-in page' [:objects (-> copy02' :shapes first)])] + copy01' (ths/get-shape file' :copy01) + rect01' (get-in page' [:objects (-> copy01' :shapes first)])] ;; The rect had width 5 before the switch (t/is (= (:width rect01) 5)) ;; The rect has width 15 after the switch - (t/is (= (:width rect02') 15)))) + (t/is (= (:width rect01') 15)))) ;; ============================================================ ;; SIMPLE ATTRIBUTE OVERRIDES (identical variants) @@ -100,9 +101,9 @@ copy01 (ths/get-shape file :copy01) ;; ==== Action - file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) + file' (tho/swap-component-in-shape file :copy01 :c02 {:keep-touched? true}) - copy01' (ths/get-shape file' :copy02)] + copy01' (ths/get-shape file' :copy01)] (thf/dump-file file :keys [:width]) ;; The copy had width 25 before the switch (t/is (= (:width copy01) 25)) @@ -137,16 +138,16 @@ rect01 (get-in page [:objects (:id rect01)]) ;; ==== Action - file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) + file' (tho/swap-component-in-shape file :copy01 :c02 {:keep-touched? true}) page' (thf/current-page file') - copy02' (ths/get-shape file' :copy02) - rect02' (get-in page' [:objects (-> copy02' :shapes first)])] + copy01' (ths/get-shape file' :copy01) + rect01' (get-in page' [:objects (-> copy01' :shapes first)])] ;; The rect had width 25 before the switch (t/is (= (:width rect01) 25)) ;; The override is keept: The rect still has width 25 after the switch - (t/is (= (:width rect02') 25)))) + (t/is (= (:width rect01') 25)))) ;; ============================================================ ;; SIMPLE ATTRIBUTE OVERRIDES (different variants) @@ -180,17 +181,193 @@ rect01 (get-in page [:objects (:id rect01)]) ;; ==== Action - file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) + file' (tho/swap-component-in-shape file :copy01 :c02 {:keep-touched? true}) page' (thf/current-page file') - copy02' (ths/get-shape file' :copy02) - rect02' (get-in page' [:objects (-> copy02' :shapes first)])] + copy01' (ths/get-shape file' :copy01) + rect01' (get-in page' [:objects (-> copy01' :shapes first)])] ;; The rect had width 25 before the switch (t/is (= (:width rect01) 25)) ;; The override isn't keept, because the property is different in the mains ;; The rect has width 15 after the switch - (t/is (= (:width rect02') 15)))) + (t/is (= (:width rect01') 15)))) + +;; ============================================================ +;; NESTED COPY SWITCH (no overrides) +;; ============================================================ + +(t/deftest test-nested-switch-in-main + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (thv/add-variant + :v01 :c01 :m01 :c02 :m02 + {:variant1-params {:width 5} + :variant2-params {:width 15}}) + + (tho/add-frame :m03) + (thc/instantiate-component :c01 + :copy01 + :parent-label :m03) + (thc/make-component :c03 :m03)) + + copy01 (ths/get-shape file :copy01) + + ;; ==== Action + file' (tho/swap-component-in-shape file :copy01 :c02 {:keep-touched? true}) + + copy01' (ths/get-shape file' :copy01)] + + (thf/dump-file file :keys [:width]) + + ;; The copy had width 5 before the switch + (t/is (= (:width copy01) 5)) + ;; The copy has width 15 after the switch + (t/is (= (:width copy01') 15)) + ;; The copy is not touched but has swap slot + (t/is (= (count (:touched copy01')) 1)) + (t/is (= (ctk/get-swap-slot copy01') (thi/id :copy01))))) + +(t/deftest test-nested-switch-in-copy + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (thv/add-variant + :v01 :c01 :m01 :c02 :m02 + {:variant1-params {:width 5} + :variant2-params {:width 15}}) + + (tho/add-frame :m03) + (thc/instantiate-component :c01 + :nested01 + :parent-label :m03) + (thc/make-component :c03 :m03) + + (thc/instantiate-component :c03 + :nested02 + :children-labels [:child01])) + + child01 (ths/get-shape file :child01) + + ;; ==== Action + file' (tho/swap-component-in-shape file :child01 :c02 {:keep-touched? true}) + + child01' (ths/get-shape file' :child01)] + + (thf/dump-file file :keys [:width]) + + ;; The copy had width 5 before the switch + (t/is (= (:width child01) 5)) + ;; The copy has width 15 after the switch + (t/is (= (:width child01') 15)) + ;; The copy is not touched but has swap slot + (t/is (= (count (:touched child01')) 1)) + (t/is (= (ctk/get-swap-slot child01') (thi/id :nested01))))) + +;; ============================================================ +;; NESTED COPY SWITCH (with overrides) +;; ============================================================ + +(t/deftest test-nested-switch-in-main-with-override + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (thv/add-variant + :v01 :c01 :m01 :c02 :m02 + {:variant1-params {:width 5} + :variant2-params {:width 15}}) + + (tho/add-frame :m03) + (thc/instantiate-component :c01 + :copy01 + :parent-label :m03) + (thc/make-component :c03 :m03)) + + page (thf/current-page file) + fills (ths/sample-fills-color :fill-color "#fabada") + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(thi/id :copy01)} + (fn [shape] + (assoc shape + :width 25 + :fills fills)) + (:objects page) + {}) + + file (thf/apply-changes file changes) + + copy01 (ths/get-shape file :copy01) + + ;; ==== Action + file' (tho/swap-component-in-shape file :copy01 :c02 {:keep-touched? true}) + + copy01' (ths/get-shape file' :copy01)] + + (thf/dump-file file :keys [:width :touched]) + + ;; The copy had fill color before the switch + (t/is (= (:fills copy01) fills)) + ;; The copy still has fill color after the switch + (t/is (= (:fills copy01') fills)) + ;; The copy had width 25 before the switch + (t/is (= (:width copy01) 25)) + ;; The copy gets the switched variant width 15, because this is the value changed in the variant + (t/is (= (:width copy01') 15)) + ;; The copy is fills touched and has swap slot + (t/is (= (count (:touched copy01')) 2)) + (t/is (= (ctk/get-swap-slot copy01') (thi/id :copy01))) + (t/is (contains? (:touched copy01') :fill-group)))) + +(t/deftest test-nested-switch-in-copy-with-override + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (thv/add-variant + :v01 :c01 :m01 :c02 :m02 + {:variant1-params {:width 5} + :variant2-params {:width 15}}) + + (tho/add-frame :m03) + (thc/instantiate-component :c01 + :nested01 + :parent-label :m03) + (thc/make-component :c03 :m03) + + (thc/instantiate-component :c03 + :copy02 + :children-labels [:nested02])) + + page (thf/current-page file) + fills (ths/sample-fills-color :fill-color "#fabada") + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(thi/id :nested02)} + (fn [shape] + (assoc shape + :width 25 + :fills fills)) + (:objects page) + {}) + + file (thf/apply-changes file changes) + + nested02 (ths/get-shape file :nested02) + + ;; ==== Action + file' (tho/swap-component-in-shape file :nested02 :c02 {:keep-touched? true}) + + nested02' (ths/get-shape file' :nested02)] + + (thf/dump-file file :keys [:width]) + + ;; The copy had fill color before the switch + (t/is (= (:fills nested02) fills)) + ;; The copy still has fill color after the switch + (t/is (= (:fills nested02') fills)) + ;; The copy had width 5 before the switch + (t/is (not= (:width nested02) 5)) + ;; The copy gets the switched variant width 15, because this is the value changed in the variant + (t/is (= (:width nested02') 15)) + ;; The copy is fills touched and has swap slot + (t/is (= (count (:touched nested02')) 2)) + (t/is (= (ctk/get-swap-slot nested02') (thi/id :nested01))) + (t/is (contains? (:touched nested02') :fill-group)))) ;; ============================================================ ;; TEXT OVERRIDES (identical variants) From 7e522ae777e209aa96b22ef1e4ae62efc175ea72 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 18 May 2026 15:10:51 +0200 Subject: [PATCH 13/14] :paperclip: Fix inconsistencies on CHANGES.md --- CHANGES.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a4b500c9f2..0a8327a5ff 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,11 +24,8 @@ - Rename token group [#9637](https://github.com/penpot/penpot/issues/9637) (PR: [#8275](https://github.com/penpot/penpot/pull/8275)) - Duplicate token group [#9638](https://github.com/penpot/penpot/issues/9638) (PR: [#8886](https://github.com/penpot/penpot/pull/8886)) - Copy token name from contextual menu [#9639](https://github.com/penpot/penpot/issues/9639) (PR: [#8566](https://github.com/penpot/penpot/pull/8566)) -- Add natural sorting on token names [#8635](https://github.com/penpot/penpot/issues/8635) (PR: [#8672](https://github.com/penpot/penpot/pull/8672)) - Add drag-to-change for numeric inputs in workspace sidebar (by @RenzoMXD) [#2466](https://github.com/penpot/penpot/issues/2466) (PR: [#8536](https://github.com/penpot/penpot/pull/8536)) - Add CSS linter [#9636](https://github.com/penpot/penpot/issues/9636) (PR: [#8592](https://github.com/penpot/penpot/pull/8592)) -- Save and restore selection state in undo/redo (by @eureka0928) [#6007](https://github.com/penpot/penpot/issues/6007) (PR: [#8652](https://github.com/penpot/penpot/pull/8652)) -- Fix warnings for unsupported token $type (by @Dexterity104) [#8790](https://github.com/penpot/penpot/issues/8790) (PR: [#8873](https://github.com/penpot/penpot/pull/8873)) - Add per-group add button for typographies (by @eureka0928) [#5275](https://github.com/penpot/penpot/issues/5275) (PR: [#8895](https://github.com/penpot/penpot/pull/8895)) - Add Find & Replace for text content and layer names (by @statxc) [#7108](https://github.com/penpot/penpot/issues/7108) (PR: [#8899](https://github.com/penpot/penpot/pull/8899)) - Use page name for multi-export ZIP/PDF downloads (by @Dexterity104) [#8773](https://github.com/penpot/penpot/issues/8773) (PR: [#8874](https://github.com/penpot/penpot/pull/8874)) @@ -37,8 +34,6 @@ - Sort asset library subfolders alphabetically at every nesting level (by @eureka0928) [#2572](https://github.com/penpot/penpot/issues/2572) (PR: [#8952](https://github.com/penpot/penpot/pull/8952)) - Add Paste to replace (Cmd+Shift+V) for selected shapes (by @eureka0928) [#4240](https://github.com/penpot/penpot/issues/4240) (PR: [#9033](https://github.com/penpot/penpot/pull/9033)) - Differentiate incoming and outgoing interaction link colors (by @claytonlin1110) [#7794](https://github.com/penpot/penpot/issues/7794) (PR: [#8923](https://github.com/penpot/penpot/pull/8923)) -- Add guide locking and fix locked element selection in viewer (by @Dexterity104) [#8358](https://github.com/penpot/penpot/issues/8358) (PR: [#8949](https://github.com/penpot/penpot/pull/8949)) -- Apply styles to selection (by @AzazelN28) [#9661](https://github.com/penpot/penpot/issues/9661) (PR: [#8625](https://github.com/penpot/penpot/pull/8625)) - Reorder prototyping overlay options to show Position before Relative to (by @rockchris099) [#2910](https://github.com/penpot/penpot/issues/2910) - Add customizable colors for ruler guides (by @Dexterity104) [#5199](https://github.com/penpot/penpot/issues/5199) (PR: [#8986](https://github.com/penpot/penpot/pull/8986)) - Persist asset search and section filter across sidebar tabs (by @eureka0928) [#2913](https://github.com/penpot/penpot/issues/2913) (PR: [#8985](https://github.com/penpot/penpot/pull/8985)) @@ -49,7 +44,6 @@ - Allow customising the OIDC login button label (by @wdeveloper16) [#7027](https://github.com/penpot/penpot/issues/7027) (PR: [#9026](https://github.com/penpot/penpot/pull/9026)) - Add page separators in Workspace [#9180](https://github.com/penpot/penpot/issues/9180) (PR: [#8561](https://github.com/penpot/penpot/pull/8561)) - Preserve vector content when pasting SVG from external tools (by @RenzoMXD) [#546](https://github.com/penpot/penpot/issues/546) (PR: [#9182](https://github.com/penpot/penpot/pull/9182)) -- Add Shift+Numpad aliases for zoom shortcuts (by @RenzoMXD) [#2457](https://github.com/penpot/penpot/issues/2457) (PR: [#9063](https://github.com/penpot/penpot/pull/9063)) - Add pixel grid color picker in viewport settings (by @jack-stormentswe) [#7750](https://github.com/penpot/penpot/issues/7750) (PR: [#9155](https://github.com/penpot/penpot/pull/9155)) - Add HEX/HSB/HSL support to color picker with persistent model switcher (by @edwin-rivera-dev) [#9133](https://github.com/penpot/penpot/issues/9133) (PR: [#9134](https://github.com/penpot/penpot/pull/9134)) - Show specific invitation-link error messages (by @niwinz) [#9220](https://github.com/penpot/penpot/issues/9220) (PR: [#9223](https://github.com/penpot/penpot/pull/9223)) @@ -61,9 +55,15 @@ - Restore deleted team files in bulk instead of per file (by @Dexterity104) [#9246](https://github.com/penpot/penpot/issues/9246) (PR: [#9248](https://github.com/penpot/penpot/pull/9248)) - Preserve Inkscape labels when pasting SVGs (by @jeffrey701) [#7869](https://github.com/penpot/penpot/issues/7869) (PR: [#9252](https://github.com/penpot/penpot/pull/9252)) - Add Alt+click to expand layer subtree (by @MilosM348) [#7736](https://github.com/penpot/penpot/issues/7736) (PR: [#9179](https://github.com/penpot/penpot/pull/9179)) -- Allow deleting the profile avatar after uploading (by @moorsecopers99) [#9067](https://github.com/penpot/penpot/issues/9067) (PR: [#9068](https://github.com/penpot/penpot/pull/9068)) ### :bug: Bugs fixed +- Add Shift+Numpad aliases for zoom shortcuts (by @RenzoMXD) [#2457](https://github.com/penpot/penpot/issues/2457) (PR: [#9063](https://github.com/penpot/penpot/pull/9063)) +- Save and restore selection state in undo/redo (by @eureka0928) [#6007](https://github.com/penpot/penpot/issues/6007) (PR: [#8652](https://github.com/penpot/penpot/pull/8652)) +- Add guide locking and fix locked element selection in viewer (by @Dexterity104) [#8358](https://github.com/penpot/penpot/issues/8358) (PR: [#8949](https://github.com/penpot/penpot/pull/8949)) +- Add natural sorting on token names [#8635](https://github.com/penpot/penpot/issues/8635) (PR: [#8672](https://github.com/penpot/penpot/pull/8672)) +- Fix warnings for unsupported token $type (by @Dexterity104) [#8790](https://github.com/penpot/penpot/issues/8790) (PR: [#8873](https://github.com/penpot/penpot/pull/8873)) +- Allow deleting the profile avatar after uploading (by @moorsecopers99) [#9067](https://github.com/penpot/penpot/issues/9067) (PR: [#9068](https://github.com/penpot/penpot/pull/9068)) +- Apply styles to selection (by @AzazelN28) [#9661](https://github.com/penpot/penpot/issues/9661) (PR: [#8625](https://github.com/penpot/penpot/pull/8625)) - Fix Alt/Option to draw shapes from center point (by @offreal) [#8360](https://github.com/penpot/penpot/issues/8360) (PR: [#8361](https://github.com/penpot/penpot/pull/8361)) - Fix library update button freezing [#9330](https://github.com/penpot/penpot/issues/9330) (PR: [#9513](https://github.com/penpot/penpot/pull/9513)) - Fix typo in subscription settings success key (by @jack-stormentswe) [#9203](https://github.com/penpot/penpot/issues/9203) (PR: [#9204](https://github.com/penpot/penpot/pull/9204)) From f5acea7cd79f33f696581e33fdf2ea059893d2ef Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 18 May 2026 15:22:32 +0200 Subject: [PATCH 14/14] :paperclip: Update opencode 'update-changelog' skill --- .opencode/skills/update-changelog/SKILL.md | 32 ++++++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/.opencode/skills/update-changelog/SKILL.md b/.opencode/skills/update-changelog/SKILL.md index 5752b65aa3..5d89393c91 100644 --- a/.opencode/skills/update-changelog/SKILL.md +++ b/.opencode/skills/update-changelog/SKILL.md @@ -86,13 +86,32 @@ batches of 50 via GraphQL to stay within API limits. ### 5. Categorize entries -Check the labels on each issue to determine which section it belongs to: +Use the **Issue Type** field (GitHub's native issue type, accessible via GraphQL +`issueType { name }`) to determine which section an entry belongs to. +**Do not** use labels or title emoji prefixes as the source of truth — they are +often inaccurate or missing. -| Label / Title prefix | Changelog section | -|----------------------|-------------------| -| `bug` label or `:bug:` title prefix | `### :bug: Bugs fixed` | -| `enhancement` label or `:sparkles:` prefix | `### :sparkles: New features & Enhancements` | -| No label | Infer from title convention, default to bug fix | +| Issue Type (`issueType.name`) | Changelog section | +|------------------------------|-------------------| +| `Bug` | `### :bug: Bugs fixed` | +| `Feature` or `Enhancement` | `### :sparkles: New features & Enhancements` | +| No type set | Fetch the issue and check its labels as a fallback: `bug` label → bugs section, otherwise default to enhancements | + +To fetch Issue Types for all issues in a milestone efficiently, use a single +GraphQL query with aliases rather than N+1 REST calls: + +```graphql +query { + repository(owner: "penpot", name: "penpot") { + i123: issue(number: 123) { + number state milestone { number } issueType { name } + } + i456: issue(number: 456) { + number state milestone { number } issueType { name } + } + } +} +``` **Community contribution attribution:** If the issue or its fix PR has the `community contribution` label, add an attribution `(by @)` @@ -205,6 +224,7 @@ Read the top of `CHANGES.md` and confirm: can find the code changes. - **Latest version first.** New sections are inserted at the top of the changelog, below the `# CHANGELOG` header. +- **Issue Type determines section.** Use GitHub's `issueType` field (Bug → `:bug:`, Feature/Enhancement → `:sparkles:`) to categorize entries. Ignore labels and title emoji prefixes — they are unreliable for categorization. - **User-facing descriptions.** Write from the user's perspective — describe what broke and what was fixed, not internal implementation details. - **Community attribution.** When the issue or fix PR has the