diff --git a/.opencode/skills/gh-issue-from-pr/SKILL.md b/.opencode/skills/gh-issue-from-pr/SKILL.md index 93f17345f2..012d58f7be 100644 --- a/.opencode/skills/gh-issue-from-pr/SKILL.md +++ b/.opencode/skills/gh-issue-from-pr/SKILL.md @@ -172,19 +172,19 @@ query { repository(owner: "penpot", name: "penpot") { ### 8. Link the PR to the issue -Append `Fixes #` to the PR body: +Append `Closes #` to the PR body: ```bash gh pr view --repo penpot/penpot --json body --jq '.body' > /tmp/pr-body.md -printf "\n\nFixes #\n" >> /tmp/pr-body.md +printf "\n\nCloses #\n" >> /tmp/pr-body.md gh pr edit --repo penpot/penpot --body-file /tmp/pr-body.md # Verify gh pr view --repo penpot/penpot --json body \ - --jq '.body | test("Fixes #")' + --jq '.body | test("Closes #")' ``` -**Note:** If the PR is already merged, `Fixes` won't auto-close the issue +**Note:** If the PR is already merged, `Closes` won't auto-close the issue — it only creates the "Development" sidebar link. This is the desired behavior since the issue is a tracking artifact. @@ -222,7 +222,7 @@ rm -f /tmp/issue-body.md /tmp/pr-body.md - **Copy the milestone from the PR.** Don't guess based on branch names. If the PR has no milestone, create the issue without one. - **Set Issue Type via GraphQL** — `gh issue create` can't set it. -- **Link via PR body** — `Fixes #` creates the "Development" +- **Link via PR body** — `Closes #` creates the "Development" sidebar link automatically. - **One issue per PR** — even if a PR fixes multiple things, create a single issue that summarizes the overall change. diff --git a/CHANGES.md b/CHANGES.md index 1c7a520c72..c45f9018b1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -164,7 +164,10 @@ ### :bug: Bugs fixed -- Emit `create-shape-layout` for flex/grid layout creation from plugins and MCP (same event as workspace) [Github #9652](https://github.com/penpot/penpot/issues/9652) +- Emit `create-shape-layout` for flex/grid layout creation from plugins and MCP (same event as workspace) [#9652](https://github.com/penpot/penpot/issues/9652) (PR: [#9654](https://github.com/penpot/penpot/pull/9654)) +- Fix broken authentication on /assets handlers [#9677](https://github.com/penpot/penpot/issues/9677) (PR: [#9679](https://github.com/penpot/penpot/pull/9679)) +- Fix API doc endpoint returning HTML as text/plain [#9680](https://github.com/penpot/penpot/issues/9680) (PR: [#9681](https://github.com/penpot/penpot/pull/9681)) +- Fix unexpected error when opening the export dialog [#9721](https://github.com/penpot/penpot/issues/9721) (PR: [#9704](https://github.com/penpot/penpot/pull/9704)) ## 2.15.3 diff --git a/backend/src/app/util/ssrf.clj b/backend/src/app/util/ssrf.clj index 5348f1eaf7..30463479fe 100644 --- a/backend/src/app/util/ssrf.clj +++ b/backend/src/app/util/ssrf.clj @@ -184,13 +184,17 @@ (not (contains? allowed-schemes (str/lower scheme)))) (ex/raise :type :validation :code :ssrf-blocked-target - :hint "url scheme is not allowed")) + :hint "url scheme is not allowed" + :uri (str uri) + :scheme scheme)) ;; Validate host presence (when (or (nil? host) (str/blank? host)) (ex/raise :type :validation :code :ssrf-blocked-target - :hint "url host is missing")) + :hint "url host is missing" + :uri (str uri) + :host host)) ;; Check allowlist (let [allowed-hosts (cf/get :ssrf-allowed-hosts #{}) @@ -210,13 +214,15 @@ (when (or (nil? addresses) (zero? (alength addresses))) (ex/raise :type :validation :code :ssrf-blocked-target - :hint "url host could not be resolved")) + :hint "uri host could not be resolved" + :uri (str uri))) ;; All-or-nothing: if ANY resolved address is blocked, reject (when (some blocked-address? (seq addresses)) (ex/raise :type :validation :code :ssrf-blocked-target - :hint "url target is not allowed"))))) + :hint "uri target is not allowed" + :uri (str uri)))))) (str uri))) (defn safe-url? diff --git a/backend/test/backend_tests/util_ssrf_test.clj b/backend/test/backend_tests/util_ssrf_test.clj index 04e86291fd..4e6c8ed93f 100644 --- a/backend/test/backend_tests/util_ssrf_test.clj +++ b/backend/test/backend_tests/util_ssrf_test.clj @@ -130,8 +130,42 @@ (ssrf/validate-uri "http://127.0.0.1/foo") (t/is false "should have thrown") (catch Exception e - (t/is (= :validation (:type (ex-data e)))) - (t/is (= :ssrf-blocked-target (:code (ex-data e))))))) + (let [data (ex-data e)] + (t/is (= :validation (:type data))) + (t/is (= :ssrf-blocked-target (:code data))) + (t/is (= "http://127.0.0.1/foo" (:uri data))))))) + +(t/deftest validate-url-throw-on-scheme + (try + (ssrf/validate-uri "file:///etc/passwd") + (t/is false "should have thrown") + (catch Exception e + (let [data (ex-data e)] + (t/is (= :validation (:type data))) + (t/is (= :ssrf-blocked-target (:code data))) + (t/is (= "file:///etc/passwd" (:uri data))) + (t/is (= "file" (:scheme data))))))) + +(t/deftest validate-url-throw-on-missing-host + (try + (ssrf/validate-uri "http:///path") + (t/is false "should have thrown") + (catch Exception e + (let [data (ex-data e)] + (t/is (= :validation (:type data))) + (t/is (= :ssrf-blocked-target (:code data))) + (t/is (= "http:///path" (:uri data))) + (t/is (nil? (:host data))))))) + +(t/deftest validate-url-throw-on-dns-failure + (try + (ssrf/validate-uri "http://nonexistent.invalid/foo") + (t/is false "should have thrown") + (catch Exception e + (let [data (ex-data e)] + (t/is (= :validation (:type data))) + (t/is (= :ssrf-blocked-target (:code data))) + (t/is (= "http://nonexistent.invalid/foo" (:uri data))))))) ;; --------------------------------------------------------------------------- ;; http/req automatic SSRF validation diff --git a/frontend/playwright/ui/specs/tokens/apply.spec.js b/frontend/playwright/ui/specs/tokens/apply.spec.js index 6272eb7585..5257c25329 100644 --- a/frontend/playwright/ui/specs/tokens/apply.spec.js +++ b/frontend/playwright/ui/specs/tokens/apply.spec.js @@ -1080,6 +1080,59 @@ test("BUG: 14136 Apply grid layout padding token to a shape from the sidebar doe ).toBe("16"); }); +test("BUG: 14191, Apply tokens from different set", async ({ page }) => { + const { + workspacePage, + tokensSidebar, + tokenContextMenuForToken, + tokenThemesSetsSidebar, + tokenSetGroupItems, + } = await setupTokensFileRender(page); + + await page.getByRole("tab", { name: "Layers" }).click(); + + await workspacePage.layers + .getByTestId("layer-row") + .filter({ hasText: "Rectangle" }) + .first() + .click(); + + await page.getByRole("tab", { name: "Tokens" }).click(); + + await unfoldTokenType(tokensSidebar, "Border radius"); + // Apply border radius token from core set + await tokensSidebar.getByRole("button", { name: "borderRadius.xl" }).click(); + + const borderRadiusSection = page.getByRole("region", { + name: "Border radius section", + }); + await expect(borderRadiusSection).toBeVisible(); + + // Check if token pill is visible on design tab on right sidebar + const brTokenPillxl = borderRadiusSection.getByRole("button", { + name: "borderRadius.xl", + }); + await expect(brTokenPillxl).toBeVisible(); + + // Change active token set + await expect( + tokenThemesSetsSidebar.getByRole("button", { name: "theme" }), + ).toBeVisible(); + + await tokenThemesSetsSidebar.getByRole("button", { name: "theme" }).click(); + // Apply border radius token from theme set + await unfoldTokenType(tokensSidebar, "Border radius"); + + await tokensSidebar + .getByRole("button", { name: "card.borderRadius" }) + .click(); + + const brTokenPillCard = borderRadiusSection.getByRole("button", { + name: "card.borderRadius", + }); + await expect(brTokenPillCard).toBeVisible(); +}); + 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/data/workspace/tokens/application.cljs b/frontend/src/app/main/data/workspace/tokens/application.cljs index 1d0eaeb796..a1d37e0b0d 100644 --- a/frontend/src/app/main/data/workspace/tokens/application.cljs +++ b/frontend/src/app/main/data/workspace/tokens/application.cljs @@ -726,10 +726,11 @@ (rx/of res)))) (rx/of (dwu/commit-undo-transaction undo-id))))))))) - (rx/of (ntf/show {:content (tr "workspace.tokens.error-text-edition") - :type :toast - :level :warning - :timeout 3000}))))))) + (when text-editing? + (rx/of (ntf/show {:content (tr "workspace.tokens.error-text-edition") + :type :toast + :level :warning + :timeout 3000})))))))) (defn apply-spacing-token-separated "Handles edge-case for spacing token when applying token via toggle button. diff --git a/frontend/src/app/main/ui/workspace/tokens/management/group.cljs b/frontend/src/app/main/ui/workspace/tokens/management/group.cljs index e73018223a..f3c2ad22a8 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/group.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/group.cljs @@ -148,7 +148,7 @@ on-token-pill-click (mf/use-fn - (mf/deps not-editing? selected-ids tokens-lib) + (mf/deps not-editing? selected-ids tokens-lib selected-token-set-id) (fn [event token] (let [token (ctob/get-token tokens-lib selected-token-set-id (:id token))] (dom/stop-propagation event) diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index 3de2bb2fb1..81a6a1db3a 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -321,7 +321,7 @@ show-selrect? (and selrect (empty? drawing) (not text-editing?) (not page-transition?)) show-measures? (and (not transform) (not path-editing?) - (or show-distances? mode-inspect?) + (or show-distances? mode-inspect? read-only?) (not page-transition?)) show-artboard-names? (and (contains? layout :display-artboard-names) (not page-transition?)) hide-ui? (contains? layout :hide-ui) diff --git a/package.json b/package.json index 67031cc6ef..543592511c 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,6 @@ "@types/node": "^25.6.2", "esbuild": "^0.28.0", "nrepl-client": "^0.3.0", - "opencode-ai": "^1.14.46" + "opencode-ai": "^1.15.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f019e4a575..aa7c8ea720 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,8 +21,8 @@ importers: specifier: ^0.3.0 version: 0.3.0 opencode-ai: - specifier: ^1.14.46 - version: 1.14.46 + specifier: ^1.15.4 + version: 1.15.4 packages: @@ -236,67 +236,69 @@ packages: nrepl-client@0.3.0: resolution: {integrity: sha512-EcROXUrzlGHKOdu/E/5WB0OESCI0iGHhdXeYk9cULYtd72eFJrM/Q1umvjTBfKWlT62y76cnyLG/3CmSCqT12w==} - opencode-ai@1.14.46: - resolution: {integrity: sha512-BX3xG3B/t85LpaVEZilbszOfP5aXc4C9e8oRBLX3rPsnnPXweKQZYfz9pn8/kfOz+8rOYuXGrBpJ86UxH/Le+Q==} + opencode-ai@1.15.4: + resolution: {integrity: sha512-Z5PeJwFNUW4sFW+jYHQTnJm6858dECvWOATvnG0S66Nn46zwjaaZJEJMKQEPOW7Yog99j6k7xRKvJPPAjKDXfQ==} + cpu: [arm64, x64] + os: [darwin, linux, win32] hasBin: true - opencode-darwin-arm64@1.14.46: - resolution: {integrity: sha512-KFUZxvEjYHz4cecbbHKJ3utc61iq4GYPjGDEM/GZ9x885FV0xQwhjQXVSQgd1gy/bj/3tFWzVLho3GC6Ogy03A==} + opencode-darwin-arm64@1.15.4: + resolution: {integrity: sha512-d+0sFAAhrDtjQbxRZvYSzy3g/xj/xUDRWUVBWGfkJAx0QEWc/v7cksmnYj3p3l88Gxm/rWeLCh6H32pw1/En8A==} cpu: [arm64] os: [darwin] - opencode-darwin-x64-baseline@1.14.46: - resolution: {integrity: sha512-3OZgCnPZZ19fI1Ju0EHTM7kHPIwadD58OBFSLhCAdJLH00OGRpbpoqxy24gbCm9LnompFjp2DnEQ+KA6+RFIeQ==} + opencode-darwin-x64-baseline@1.15.4: + resolution: {integrity: sha512-Lj9wsEPFyEOgLO6J3DsCXdSu/IAJnW/RjtD1oojAao6uHvhs5uXyj1mrsmK8GrtAfCT4JUh8W38o3YYXGjItSw==} cpu: [x64] os: [darwin] - opencode-darwin-x64@1.14.46: - resolution: {integrity: sha512-+cC9EoVeoU2yLWIq7835QQginkTE8S7rrjMRJk543ZZ+S0yHsrnhEQVkgqhX+Yzncb8Subt2wISF7yROAzq7zA==} + opencode-darwin-x64@1.15.4: + resolution: {integrity: sha512-H412BUw5O+bmXfzLo6UMCWVc3DOYEM0RCI5Kt+Ynqh+Q9878bXK6mwRR7VXgGVBkkH2U4GtT1uDgY0BzSK185Q==} cpu: [x64] os: [darwin] - opencode-linux-arm64-musl@1.14.46: - resolution: {integrity: sha512-2DmmX4WuqrKdEo/rxK/ARA39fCecco53F0/v8t7gk+njbe5fWrKYrfbtL3OaGr8LUsQolny5O05I0tjBsGcTUQ==} + opencode-linux-arm64-musl@1.15.4: + resolution: {integrity: sha512-TO2IVSoYolGKJahf73/hRsJBGxLKOdP/akYPzI0hQQvW4oVrmQkZ3s13jU1+LXIEn4Zbj/TB18QvLzvXrnrEhA==} cpu: [arm64] os: [linux] - opencode-linux-arm64@1.14.46: - resolution: {integrity: sha512-gahGbcQNJ6CAXWeG06wW/U2S+GB2ccmhvp4bAXDZeRm6Liw7M4NzOZX9H7sboTGtcYTnVouBPvoKPaX59qv9rA==} + opencode-linux-arm64@1.15.4: + resolution: {integrity: sha512-V+x/u9JnPOLPEfqbePSCL0OQdin5gs1V35VsVxj19WaZDEwxlMVjOe6HjVKEY64/O6htkPxCCZohmnMU4dVBMQ==} cpu: [arm64] os: [linux] - opencode-linux-x64-baseline-musl@1.14.46: - resolution: {integrity: sha512-lMJiPsb8b+aGjqXdAmfu9f5HyTAS6Cfk8O1GieZFu06pi8kO9oiJ6wPyQwwL8IM6J2ssNF4PxloHgqeNQI7kEg==} + opencode-linux-x64-baseline-musl@1.15.4: + resolution: {integrity: sha512-xOJ3aHg2+2GrT9F/KmAF0JLB1D6K3SCY/626n+fLjs/AEFvLdmE3TYhoXPEyGH2I9F4kF+4p2xk0pg2b+LVlZQ==} cpu: [x64] os: [linux] - opencode-linux-x64-baseline@1.14.46: - resolution: {integrity: sha512-oq6Px+0epCwk2nZn54EUDtxccGb9JlSnF1stAm4oKUeKg/G76hGEbCgg4x3TNiIrpA8QnS9grRlh1oFYIl/efg==} + opencode-linux-x64-baseline@1.15.4: + resolution: {integrity: sha512-dTlV8tAVN8nFdPb7527GR6/BpyIVavAcXJmZ2VbS1daXu4C6k6bpmjiS/ZFKlphRZiKKiEzFrHlimao4BMchVQ==} cpu: [x64] os: [linux] - opencode-linux-x64-musl@1.14.46: - resolution: {integrity: sha512-rUmQHsrlIWcLBmefiom71DWhRzQ0oSqjgvF2Xvg5ySs0GKklRzMB/HcS2ccIydqv02ImL94FbqVVXSZ2lnw4IQ==} + opencode-linux-x64-musl@1.15.4: + resolution: {integrity: sha512-IbMaM6zrakdtDD55GUhlT/WeXomXmKsVqo3XQuOaGXprBg3W5alsxXh60SZpV3ftbdcMD/eiB/PYtN/ZN8Fa5w==} cpu: [x64] os: [linux] - opencode-linux-x64@1.14.46: - resolution: {integrity: sha512-qxFHxbyP3dCFX6vibX71JfVntEbzE7rSMmot6EwbdDB0vq+tcahFEZ+/KVC7qV3zL3ZbDAMsx8OiZknI7Cscmw==} + opencode-linux-x64@1.15.4: + resolution: {integrity: sha512-2c20aldKLfNkg6N6nABvvK1fuaCwYLo/HNeL8ikellkFMeGalCGDhkL/VQ8R8KPV3ohVZJtZwG0nkFiA8MeHCg==} cpu: [x64] os: [linux] - opencode-windows-arm64@1.14.46: - resolution: {integrity: sha512-SmeKzxFNiiF9s9l0lj1CxIZesE+m0VvBX5GuW5CHAlVqNLjmQeW3X8qhHmXelvm9ujGxAudjmXBUlZTkBkH9FQ==} + opencode-windows-arm64@1.15.4: + resolution: {integrity: sha512-kr3nIWmYH7NC0Vgrhgjp9EmCuy5MuxjIRrSjzlfRLMaML6U/a0Hsr3AahBwI1KjT+HEhz5u6xpodZeeEDY3nPQ==} cpu: [arm64] os: [win32] - opencode-windows-x64-baseline@1.14.46: - resolution: {integrity: sha512-6rehvrK+KCqUUKBKFnjpk6YchpiH3TNJdOYTmg+QcRirynyhNjs83F7h29vr+WbSnImMCPYFeRfBAf51yLiAmQ==} + opencode-windows-x64-baseline@1.15.4: + resolution: {integrity: sha512-2/elQ163r4Q97bYJRrY09IG+bpqh0AKpfutDGCaokFdLWIWQN/cFvjzb4C+BKzLFsU9LRfoyvPhe4nXMm1+S4A==} cpu: [x64] os: [win32] - opencode-windows-x64@1.14.46: - resolution: {integrity: sha512-m9LPvvNV9UvgYc5AbzR+hBBD2wPx+bitjbBTXNx/7s+WE/5DhlaHsxVQPePrA9bZ8XAO0EndRnDjvCrLLrkxWA==} + opencode-windows-x64@1.15.4: + resolution: {integrity: sha512-f6p40u3yLEbiq4pzBOXAwtW/NP/dL8uTurHfraPcfezA4ua5DEm4vSoSePUY0CHtubUPuDe0wRUA1s53sysjPQ==} cpu: [x64] os: [win32] @@ -454,55 +456,55 @@ snapshots: bencode: 2.0.3 tree-kill: 1.2.2 - opencode-ai@1.14.46: + opencode-ai@1.15.4: optionalDependencies: - opencode-darwin-arm64: 1.14.46 - opencode-darwin-x64: 1.14.46 - opencode-darwin-x64-baseline: 1.14.46 - opencode-linux-arm64: 1.14.46 - opencode-linux-arm64-musl: 1.14.46 - opencode-linux-x64: 1.14.46 - opencode-linux-x64-baseline: 1.14.46 - opencode-linux-x64-baseline-musl: 1.14.46 - opencode-linux-x64-musl: 1.14.46 - opencode-windows-arm64: 1.14.46 - opencode-windows-x64: 1.14.46 - opencode-windows-x64-baseline: 1.14.46 + opencode-darwin-arm64: 1.15.4 + opencode-darwin-x64: 1.15.4 + opencode-darwin-x64-baseline: 1.15.4 + opencode-linux-arm64: 1.15.4 + opencode-linux-arm64-musl: 1.15.4 + opencode-linux-x64: 1.15.4 + opencode-linux-x64-baseline: 1.15.4 + opencode-linux-x64-baseline-musl: 1.15.4 + opencode-linux-x64-musl: 1.15.4 + opencode-windows-arm64: 1.15.4 + opencode-windows-x64: 1.15.4 + opencode-windows-x64-baseline: 1.15.4 - opencode-darwin-arm64@1.14.46: + opencode-darwin-arm64@1.15.4: optional: true - opencode-darwin-x64-baseline@1.14.46: + opencode-darwin-x64-baseline@1.15.4: optional: true - opencode-darwin-x64@1.14.46: + opencode-darwin-x64@1.15.4: optional: true - opencode-linux-arm64-musl@1.14.46: + opencode-linux-arm64-musl@1.15.4: optional: true - opencode-linux-arm64@1.14.46: + opencode-linux-arm64@1.15.4: optional: true - opencode-linux-x64-baseline-musl@1.14.46: + opencode-linux-x64-baseline-musl@1.15.4: optional: true - opencode-linux-x64-baseline@1.14.46: + opencode-linux-x64-baseline@1.15.4: optional: true - opencode-linux-x64-musl@1.14.46: + opencode-linux-x64-musl@1.15.4: optional: true - opencode-linux-x64@1.14.46: + opencode-linux-x64@1.15.4: optional: true - opencode-windows-arm64@1.14.46: + opencode-windows-arm64@1.15.4: optional: true - opencode-windows-x64-baseline@1.14.46: + opencode-windows-x64-baseline@1.15.4: optional: true - opencode-windows-x64@1.14.46: + opencode-windows-x64@1.15.4: optional: true tree-kill@1.2.2: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000000..ca93fb1e8d --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +allowBuilds: + opencode-ai: true