Merge remote-tracking branch 'origin/staging' into develop

This commit is contained in:
Andrey Antukh 2026-05-18 20:00:47 +02:00
commit 595ec599c6
11 changed files with 173 additions and 72 deletions

View File

@ -172,19 +172,19 @@ query { repository(owner: "penpot", name: "penpot") {
### 8. Link the PR to the issue
Append `Fixes #<ISSUE_NUMBER>` to the PR body:
Append `Closes #<ISSUE_NUMBER>` to the PR body:
```bash
gh pr view <PR_NUMBER> --repo penpot/penpot --json body --jq '.body' > /tmp/pr-body.md
printf "\n\nFixes #<ISSUE_NUMBER>\n" >> /tmp/pr-body.md
printf "\n\nCloses #<ISSUE_NUMBER>\n" >> /tmp/pr-body.md
gh pr edit <PR_NUMBER> --repo penpot/penpot --body-file /tmp/pr-body.md
# Verify
gh pr view <PR_NUMBER> --repo penpot/penpot --json body \
--jq '.body | test("Fixes #<ISSUE_NUMBER>")'
--jq '.body | test("Closes #<ISSUE_NUMBER>")'
```
**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 #<NUMBER>` creates the "Development"
- **Link via PR body**`Closes #<NUMBER>` 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.

View File

@ -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

View File

@ -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?

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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)

View File

@ -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)

View File

@ -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"
}
}

108
pnpm-lock.yaml generated
View File

@ -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: {}

2
pnpm-workspace.yaml Normal file
View File

@ -0,0 +1,2 @@
allowBuilds:
opencode-ai: true