diff --git a/CHANGES.md b/CHANGES.md index 2946799801..b6602e8a33 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -26,32 +26,43 @@ ### :bug: Bugs fixed -- Fix plugin API `LibraryTypography.remove()` failing with a UUID assertion error (by @leonaIee) [#8223](https://github.com/penpot/penpot/issues/8223) (PR: [#9279](https://github.com/penpot/penpot/pull/9279)) -- Fix MCP SSE sessions leaking on zombie connections by adding inactivity timeout (by @bitloi) [#9432](https://github.com/penpot/penpot/issues/9432) (PR: [#9464](https://github.com/penpot/penpot/pull/9464)) -- Use "copia" instead of "copiar" as Spanish duplicate-suffix for design tokens (by @Rene0422) [#9623](https://github.com/penpot/penpot/issues/9623) (PR: [#9671](https://github.com/penpot/penpot/pull/9671)) -- Expose Source Sans Pro semibold (weight 600) variants in the builtin fonts list (by @dhgoal) [#7378](https://github.com/penpot/penpot/issues/7378) (PR: [#9247](https://github.com/penpot/penpot/pull/9247)) -- Fix plugin API `shape.fills` and `shape.strokes` arrays being read-only (by @RenzoMXD) [#8357](https://github.com/penpot/penpot/issues/8357) (PR: [#9161](https://github.com/penpot/penpot/pull/9161)) -- Fix `get-profile` masking transient DB errors as anonymous user (by @jack-stormentswe) [#9253](https://github.com/penpot/penpot/issues/9253) (PR: [#9254](https://github.com/penpot/penpot/pull/9254)) -- Fix `Ctrl+'` "Show guides" shortcut on non-US keyboard layouts (by @RenzoMXD) [#8423](https://github.com/penpot/penpot/issues/8423) (PR: [#9209](https://github.com/penpot/penpot/pull/9209)) -- Fix lost-update race on `team.features` during concurrent file creation (by @web-dev0521) [#9197](https://github.com/penpot/penpot/issues/9197) (PR: [#9198](https://github.com/penpot/penpot/pull/9198)) -- Fix clipboard crash on insecure origins (plain HTTP / non-localhost) (by @MilosM348) [#6514](https://github.com/penpot/penpot/issues/6514) (PR: [#9188](https://github.com/penpot/penpot/pull/9188)) -- Fix blend-mode hover preview not reverting when dropdown is dismissed (by @jack-stormentswe) [#9235](https://github.com/penpot/penpot/issues/9235) (PR: [#9237](https://github.com/penpot/penpot/pull/9237)) -- Scoll to new created tokens on the tokens tree [#9711](https://github.com/penpot/penpot/issues/9711) (PR: [#9803](https://github.com/penpot/penpot/pull/9803)) -- Fix text style name input appending font name instead of replacing when edited [#9785](https://github.com/penpot/penpot/issues/9785) (PR: [#9784](https://github.com/penpot/penpot/pull/9784)) -- Fix crash when creating or editing a token named "white" or "black" [#9256](https://github.com/penpot/penpot/issues/9256) (PR: [#9034](https://github.com/penpot/penpot/pull/9034)) -- Fix MCP media upload errors and SVG import losing embedded images (by @claytonlin1110) [#9164](https://github.com/penpot/penpot/issues/9164) (PR: [#9201](https://github.com/penpot/penpot/pull/9201)) -- Fix conditional hook violation in shape-wrapper component (by @Dexterity104) [#9280](https://github.com/penpot/penpot/issues/9280) (PR: [#9281](https://github.com/penpot/penpot/pull/9281)) -- Fix panic paths in ShapeImageIds byte conversion (by @Dexterity104) [#9282](https://github.com/penpot/penpot/issues/9282) (PR: [#9283](https://github.com/penpot/penpot/pull/9283)) -- Fix viewers being able to overwrite file thumbnails (by @jony376) [#9284](https://github.com/penpot/penpot/issues/9284) (PR: [#9285](https://github.com/penpot/penpot/pull/9285)) -- Fix different behavior on switch for two identical copies (by @MischaPanch) [#9498](https://github.com/penpot/penpot/issues/9498) (PR: [#9434](https://github.com/penpot/penpot/pull/9434)) -- Populate is-indirect flag on file libraries from relation graph (by @Dexterity104) [#9506](https://github.com/penpot/penpot/issues/9506) (PR: [#9289](https://github.com/penpot/penpot/pull/9289)) -- Fix incorrect error message in plugins API (by @bitcompass) [#9417](https://github.com/penpot/penpot/issues/9417) (PR: [#9486](https://github.com/penpot/penpot/pull/9486)) -- Fix library update dialog triggered by component position changes [#9629](https://github.com/penpot/penpot/issues/9629) (PR: [#9616](https://github.com/penpot/penpot/pull/9616)) -- Fix missing error message for invalid shadow token [#9583](https://github.com/penpot/penpot/issues/9583) (PR: [#9809](https://github.com/penpot/penpot/pull/9809)) -- Fix misaligned B(V) input label in HSB color picker [#9731](https://github.com/penpot/penpot/issues/9731) (PR: [#9793](https://github.com/penpot/penpot/pull/9793)) -- Fix shadow token creation not allowing empty blur or spread value [#9808](https://github.com/penpot/penpot/issues/9808) (PR: [#9809](https://github.com/penpot/penpot/pull/9809)) -- Fix settings form visual layout broken after recent contribution [#9882](https://github.com/penpot/penpot/issues/9882) (PR: [#9883](https://github.com/penpot/penpot/pull/9883)) -- Fix token validation failure caused by group nodes in token tree [#9010](https://github.com/penpot/penpot/issues/9010) (PR: [#9025](https://github.com/penpot/penpot/pull/9025), [#9825](https://github.com/penpot/penpot/pull/9825)) +- Fix plugin API `fileVersion.restore()` promise hanging indefinitely on restore failure [Github #9092](https://github.com/penpot/penpot/issues/9092) +- Fix LDAP provider params schema typo (`bind-passwor` → `bind-password`) introduced during the `clojure.spec` → `malli` migration; the schema slot now matches the runtime key actually read by `prepare-params` (`:password (:bind-password cfg)`) and `try-connectivity` (`(:bind-password cfg)`), so a wrong type for the password no longer slips through unvalidated +- Fix `login-with-ldap` silently dropping its error message on the `ldap-not-initialized` restriction (typo `:hide` → `:hint`); the message `"ldap auth provider is not initialized"` now actually surfaces in logs and error responses instead of being discarded into an unread key +- Fix `PENPOT_OIDC_USER_INFO_SOURCE` flag being silently ignored (`userinfo` / `token`) in the OIDC callback, causing "incomplete user info" failures during registration [Github #9108](https://github.com/penpot/penpot/issues/9108) +- Fix `get-view-only-bundle` crashing when a share-link viewer encounters a team member whose email lacks `@` (NullPointerException in `obfuscate-email`) or whose domain has no `.` (previously produced a dangling-dot `****@****.`); now the viewer-side obfuscation is nil-safe and omits the trailing dot when the domain has no TLD +- Remove `corepack` from the MCP local launcher so it runs on Node.js 25+, where corepack is no longer bundled [Github #8877](https://github.com/penpot/penpot/issues/8877) +- Fix Copy as SVG: emit a single valid SVG document when multiple shapes are selected, and publish `image/svg+xml` to the clipboard so the paste target works in Inkscape and other SVG-native tools [Github #838](https://github.com/penpot/penpot/issues/838) +- Reset profile submenu state when the account menu closes (by @eureka0928) [Github #8947](https://github.com/penpot/penpot/issues/8947) +- Add export panel to inspect styles tab [Taiga #13582](https://tree.taiga.io/project/penpot/issue/13582) +- Fix styles between grid layout inputs [Taiga #13526](https://tree.taiga.io/project/penpot/issue/13526) +- Fix id prop on switch component [Taiga #13534](https://tree.taiga.io/project/penpot/issue/13534) +- Update copy on penpot update message [Taiga #12924](https://tree.taiga.io/project/penpot/issue/12924) +- Fix scroll on library modal [Taiga #13639](https://tree.taiga.io/project/penpot/issue/13639) +- Fix dates to avoid show them in english when browser is in auto [Taiga #13786](https://tree.taiga.io/project/penpot/issue/13786) +- Fix focus radio button [Taiga #13841](https://tree.taiga.io/project/penpot/issue/13841) +- Token tree should be expanded by default [Taiga #13631](https://tree.taiga.io/project/penpot/issue/13631) +- Fix opacity incorrectly disabled for visible shapes [Taiga #13906](https://tree.taiga.io/project/penpot/issue/13906) +- Update onboarding image [Taiga #13864](https://tree.taiga.io/project/penpot/issue/13864) +- Fix plugin modal drag interactions over iframe and close-button behavior (by @marekhrabe) [Github #8871](https://github.com/penpot/penpot/pull/8871) +- Fix hot update on color-row on texts [Taiga #13923](https://tree.taiga.io/project/penpot/issue/13923) +- Fix selected color tokens [Taiga #13930](https://tree.taiga.io/project/penpot/issue/13930) +- Fix dashboard Recent/Deleted titles overlapped by scrolling content (by @rockchris099) [Github #8577](https://github.com/penpot/penpot/issues/8577) +- Display resolved values of inactive tokens [Taiga #13628](https://tree.taiga.io/project/penpot/issue/13628) +- Fix hyphens stripped from export filenames (by @jamesrayammons) [Github #8901](https://github.com/penpot/penpot/issues/8901) +- Fix app crash when selecting shapes with one hidden [Taiga #13959](https://tree.taiga.io/project/penpot/issue/13959) +- Fix opacity mixed value [Taiga #13960](https://tree.taiga.io/project/penpot/issue/13960) +- Fix gap input throwing an error [Github #8984](https://github.com/penpot/penpot/pull/8984) +- Fix non-functional clear icon in change email modal inputs (by @Dexterity104) [Github #8977](https://github.com/penpot/penpot/issues/8977) +- Disable save button after saving account profile settings (by @Dexterity104) [Github #8979](https://github.com/penpot/penpot/issues/8979) +- Fix copy to be more specific [Taiga #13990](https://tree.taiga.io/project/penpot/issue/13990) +- Allow deleting the profile avatar after uploading [Github #9067](https://github.com/penpot/penpot/issues/9067) +- Fix incorrect rendering when exporting text as SVG, PNG and JPG (by @edwin-rivera-dev) [Github #8516](https://github.com/penpot/penpot/issues/8516) +- Fix Settings and Notifications "Update Settings" button enabled state when form has no changes (by @moorsecopers99) [Github #9090](https://github.com/penpot/penpot/issues/9090) +- Fix "Help & Learning" submenu vertical alignment in account menu (by @juan-flores077) [Github #9137](https://github.com/penpot/penpot/issues/9137) +- Fix plugin `addInteraction` silently rejecting `open-overlay` actions with `manualPositionLocation` [Github #8409](https://github.com/penpot/penpot/issues/8409) +- Fix typography style creation with tokenized line-height (by @juan-flores077) [Github #8479](https://github.com/penpot/penpot/issues/8479) +- Fix colorpicker layout so the eyedropper button is visible again [Taiga #14057](https://tree.taiga.io/project/penpot/issue/14057) ## 2.16.0 (Unreleased) diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc index 699d1d7d9a..8d203f92fe 100644 --- a/common/src/app/common/types/tokens_lib.cljc +++ b/common/src/app/common/types/tokens_lib.cljc @@ -1598,6 +1598,13 @@ Will return a value that matches this schema: ["type" :string]]])) (def ^:private schema:dtcg-node + ;; Per the DTCG Community Group Final Report (W3C, 2025-10-28): + ;; "The presence of a $value property definitively identifies an object as a token." + ;; "A token's type can be specified by the optional $type property [...] + ;; Furthermore, the $type property on a group applies to all tokens nested + ;; within that group." + ;; So $value is the only required property; $type may be inherited from a + ;; parent group and is therefore optional on the token itself. [:schema {:registry {::simple-value [:or :string :int :double ::sm/boolean] @@ -1610,7 +1617,7 @@ Will return a value that matches this schema: [:ref ::simple-value] [:vector ::simple-value]]]]}} [:map - ["$type" :string] + ["$type" {:optional true} :string] ["$value" [:ref ::value]]]]) (def ^:private dtcg-node? @@ -1749,33 +1756,51 @@ Will return a value that matches this schema: (defn- flatten-nested-tokens-json "Convert a tokens tree in the decoded json fragment into a flat map, - being the keys the token paths after joining the keys with '.'." - [decoded-json-tokens parent-path] - (reduce-kv - (fn [tokens k v] - (let [child-path (if (empty? parent-path) - (name k) - (str parent-path "." k))] - (if (and (map? v) - (not (contains? v "$type"))) - (merge tokens (flatten-nested-tokens-json v child-path)) - (let [token-type (cto/dtcg-token-type->token-type (get v "$type"))] - (if token-type - (assoc tokens child-path (make-token - :name child-path - :type token-type - :value - (let [token-value (get v "$value")] - (case token-type - :font-family (convert-dtcg-font-family token-value) - :typography (convert-dtcg-typography-composite token-value) - :shadow (convert-dtcg-shadow-composite token-value) - token-value)) - :description (get v "$description"))) - ;; Discard unknown type tokens - tokens))))) - {} - decoded-json-tokens)) + being the keys the token paths after joining the keys with '.'. + + Per the DTCG spec, a node is a token iff it carries a `$value` + property; every other map is a group. A group may declare a `$type` + that is inherited by every nested token that does not declare its + own `$type`, so this walker propagates the nearest enclosing group + `$type` down through the recursion." + ([decoded-json-tokens parent-path] + (flatten-nested-tokens-json decoded-json-tokens parent-path nil)) + ([decoded-json-tokens parent-path inherited-type] + (reduce-kv + (fn [tokens k v] + (let [child-path (if (empty? parent-path) + (name k) + (str parent-path "." k))] + (if (and (map? v) + (not (contains? v "$value"))) + ;; Group: recurse, letting the group's `$type` (if any) replace + ;; the inherited type for descendants that don't declare their + ;; own `$type`. + (merge tokens + (flatten-nested-tokens-json + v + child-path + (or (get v "$type") inherited-type))) + ;; Token: resolve the type from the token's own `$type`, + ;; falling back to the inherited group `$type`. + (let [type-key (or (get v "$type") inherited-type) + token-type (cto/dtcg-token-type->token-type type-key)] + (if token-type + (assoc tokens child-path (make-token + :name child-path + :type token-type + :value + (let [token-value (get v "$value")] + (case token-type + :font-family (convert-dtcg-font-family token-value) + :typography (convert-dtcg-typography-composite token-value) + :shadow (convert-dtcg-shadow-composite token-value) + token-value)) + :description (get v "$description"))) + ;; Discard unknown / un-typeable tokens + tokens))))) + {} + decoded-json-tokens))) (defn- parse-single-set-dtcg-json "Parse a decoded json file with a single set of tokens in DTCG format into a TokensLib." diff --git a/common/test/common_tests/types/tokens_lib_test.cljc b/common/test/common_tests/types/tokens_lib_test.cljc index abba5d9454..c19a5146d8 100644 --- a/common/test/common_tests/types/tokens_lib_test.cljc +++ b/common/test/common_tests/types/tokens_lib_test.cljc @@ -1348,6 +1348,50 @@ (t/testing "token added" (t/is (some? (ctob/get-token-by-name lib "single_set" "color.red.100"))))))) +#?(:clj + (t/deftest parse-dtcg-group-type-inheritance + ;; Per DTCG spec: $type on a group is inherited by every nested token + ;; that does not declare its own $type. Tokens are identified by the + ;; presence of $value, not by the presence of $type. + (let [json {"colors" {"$type" "color" + "red" {"$value" "#ff0000"} + "blue" {"$value" "#0000ff" + "$description" "Brand blue"} + "danger" {"$type" "color" + "$value" "#cc0000"}} + "space" {"$type" "dimension" + "small" {"$value" "4px"} + "large" {"$value" "16px" + "$type" "dimension"}}} + lib (ctob/parse-decoded-json json "set")] + (t/testing "group `$type` is inherited by tokens without their own `$type`" + (t/is (tht/token-data-eq? (ctob/get-token-by-name lib "set" "colors.red") + {:name "colors.red" + :type :color + :value "#ff0000" + :description ""})) + (t/is (tht/token-data-eq? (ctob/get-token-by-name lib "set" "colors.blue") + {:name "colors.blue" + :type :color + :value "#0000ff" + :description "Brand blue"})) + (t/is (tht/token-data-eq? (ctob/get-token-by-name lib "set" "space.small") + {:name "space.small" + :type :dimensions + :value "4px" + :description ""}))) + (t/testing "token `$type` overrides the inherited group `$type`" + (t/is (tht/token-data-eq? (ctob/get-token-by-name lib "set" "colors.danger") + {:name "colors.danger" + :type :color + :value "#cc0000" + :description ""})) + (t/is (tht/token-data-eq? (ctob/get-token-by-name lib "set" "space.large") + {:name "space.large" + :type :dimensions + :value "16px" + :description ""})))))) + #?(:clj (t/deftest parse-multi-set-legacy-json (let [json (-> (slurp "test/common_tests/types/data/tokens-multi-set-legacy-example.json")