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

This commit is contained in:
Andrey Antukh 2026-09-15 19:59:54 +02:00
commit df383be6b2
10 changed files with 76 additions and 134 deletions

View File

@ -19,6 +19,9 @@ right flow.
- **Create from draft body** — Taiga story, user report, discussion; no PR
yet.
→ memory section **Creating Issues from Draft Body**
- **Create as sub-issue** — the issue must be grouped under an umbrella/EPIC
issue; create it first, then link it to its parent.
→ memory section **Adding an Issue as a Sub-issue**
- **Retitle existing issue** — current title is vague, prefixed, or stale.
→ memory section **Retitling an Existing Issue**

View File

@ -13,6 +13,7 @@ Frontend: CLJS SPA; React/Rumext; Potok; RxJS; okulary refs; SCSS modules; share
- `app.util.*`: DOM, HTTP, i18n, keyboard, codegen, and general frontend utilities.
- `frontend/packages/*` and `frontend/text-editor`: JS/TS workspace packages consumed by the app.
- Nitrate subscription/organization UI and flows live under `app.main.data.nitrate` and `app.main.ui.nitrate*`; backend/API behavior is covered by backend memories, and shared permission rules are in `common/src/app/common/types/nitrate_permissions.cljc`.
- `app.config` external bridges (`external-feature-flag`, `external-session-id`, `external-context-info`, `external-notify-register-success`, `initialize-external-context-info`) delegate to `globalThis` hooks injected by the SaaS host. Never delete `external-feature-flag`, even with zero call sites: it is the seam for future A/B tests.
## Lint and Format

View File

@ -159,6 +159,47 @@ query { repository(owner: "penpot", name: "penpot") {
rm -f /tmp/issue-body.md
```
## Adding an Issue as a Sub-issue
Sub-issues group work under an umbrella/EPIC issue. `gh issue create` cannot
link a sub-issue at creation time: create the issue first (normal flow above),
then link it.
**1. Create the sub-issue** as usual and note its number (`NNNN`).
**2. Get the issue's database id** (the REST `id`, not the `number`):
```bash
SUB_ID=$(gh api repos/penpot/penpot/issues/NNNN --jq .id)
```
**3. Link it to the parent** (`PARENT` = umbrella/EPIC issue number):
```bash
gh api --method POST repos/penpot/penpot/issues/PARENT/sub_issues \
-F sub_issue_id=$SUB_ID
```
Use `-F` (typed field), never `-f`: with `-f` the value is sent as a string
and the API rejects it with `422 ... /sub_issue_id ... is not of type integer`.
**4. Verify both directions:**
```bash
gh api repos/penpot/penpot/issues/NNNN/parent --jq '{number, title}'
gh api repos/penpot/penpot/issues/PARENT/sub_issues --jq '.[] | {number, title}'
```
Notes:
- The `POST` response is the parent issue and includes `sub_issues_summary`
with `total`, `completed` and `percent_completed`, useful to track EPIC
progress.
- A sub-issue has a single parent.
- Issue Type is independent of the parent relationship: choose it with the
normal mapping above (an EPIC child that fixes broken behavior is a Bug,
not a Task).
## Creating Issues from PRs
Used when the project board needs an issue as the primary changelog/release

View File

@ -134,6 +134,7 @@
- Normalize string inputs before processing [#11060](https://github.com/penpot/penpot/issues/11060) (PR: [#11061](https://github.com/penpot/penpot/pull/11061))
- Add cooldown to avoid sending duplicate invitation emails [#11062](https://github.com/penpot/penpot/issues/11062) (PR: [#11063](https://github.com/penpot/penpot/pull/11063))
- Enable SSRF protection for organization SSO validation [#11064](https://github.com/penpot/penpot/issues/11064) (PR: [#11065](https://github.com/penpot/penpot/pull/11065))
- Fix LDAP authentication storing client-supplied email instead of directory email and not escaping filter special characters [#11084](https://github.com/penpot/penpot/issues/11084) (PR: [#11085](https://github.com/penpot/penpot/pull/11085))
- Fix clone-file-media-object allowing to clone media objects from files without read access [#11087](https://github.com/penpot/penpot/issues/11087) (PR: [#11090](https://github.com/penpot/penpot/pull/11090))
- Fix 404 error page logo not visible in dark mode [#11091](https://github.com/penpot/penpot/issues/11091) (PR: [#11167](https://github.com/penpot/penpot/pull/11167))
- Fix incorrect permission handling when creating an invitation [#11098](https://github.com/penpot/penpot/issues/11098) (PR: [#11099](https://github.com/penpot/penpot/pull/11099))
@ -158,19 +159,27 @@
- Fix get-team-invitation-token requiring only read permissions [#11358](https://github.com/penpot/penpot/issues/11358) (PR: [#11359](https://github.com/penpot/penpot/pull/11359))
- Fix missing text in legacy SVG board thumbnails [#10182](https://github.com/penpot/penpot/issues/10182) (PR: [#11552](https://github.com/penpot/penpot/pull/11552))
- Fix workspace crash when applying transform modifiers in the WASM renderer [#10894](https://github.com/penpot/penpot/issues/10894) (PR: [#10896](https://github.com/penpot/penpot/pull/10896))
- Fix double-click on text bounding box entering edit mode instead of applying auto-width/auto-height with the WebGL renderer [#10934](https://github.com/penpot/penpot/issues/10934) (PR: [#11068](https://github.com/penpot/penpot/pull/11068))
- Limit ZIP entry count and object size on V3 binfile import [#11021](https://github.com/penpot/penpot/issues/11021) (PR: [#11022](https://github.com/penpot/penpot/pull/11022))
- Block plugin UI iframe URLs targeting the Penpot domain [#11271](https://github.com/penpot/penpot/issues/11271) (PR: [#11273](https://github.com/penpot/penpot/pull/11273))
- Restrict the MCP REPL code execution endpoint to development environments [#11283](https://github.com/penpot/penpot/issues/11283) (PR: [#11282](https://github.com/penpot/penpot/pull/11282))
- Filter share-link tokens from the get-view-only-bundle response [#11285](https://github.com/penpot/penpot/issues/11285) (PR: [#11286](https://github.com/penpot/penpot/pull/11286))
- Fix HTTP error responses disclosing internal database and filesystem paths to API clients [#11287](https://github.com/penpot/penpot/issues/11287) (PR: [#11288](https://github.com/penpot/penpot/pull/11288))
- Disable MCP developer tools in multi-user mode [#11291](https://github.com/penpot/penpot/issues/11291) (PR: [#11310](https://github.com/penpot/penpot/pull/11310))
- Fix Hide comments setting being ignored after opening the Comments section [#11308](https://github.com/penpot/penpot/issues/11308) (PR: [#11492](https://github.com/penpot/penpot/pull/11492))
- Block NAT64/6to4/Teredo IPv6 transition addresses in the SSRF guard [#11319](https://github.com/penpot/penpot/issues/11319) (PR: [#11320](https://github.com/penpot/penpot/pull/11320))
- Prevent team admins from removing the team owner [#11367](https://github.com/penpot/penpot/issues/11367) (PR: [#11368](https://github.com/penpot/penpot/pull/11368))
- Enforce share-link comment permissions and page scope [#11370](https://github.com/penpot/penpot/issues/11370) (PR: [#11371](https://github.com/penpot/penpot/pull/11371))
- Clean up orphaned teams, projects and files on profile deletion [#11394](https://github.com/penpot/penpot/issues/11394) (PR: [#11395](https://github.com/penpot/penpot/pull/11395))
- Use constant-time comparison for Management API shared key authentication [#11426](https://github.com/penpot/penpot/issues/11426) (PR: [#11534](https://github.com/penpot/penpot/pull/11534))
- Fix crash when pressing Ctrl+D with no shape selected [#11448](https://github.com/penpot/penpot/issues/11448) (PR: [#11491](https://github.com/penpot/penpot/pull/11491))
- Fix text layout not updating when auto-width is set by double-clicking the bounding box [#11480](https://github.com/penpot/penpot/issues/11480) (PR: [#11541](https://github.com/penpot/penpot/pull/11541))
- Fix boolean shapes rendering deformed in the WASM renderer and exports [#11482](https://github.com/penpot/penpot/issues/11482) (PR: [#11551](https://github.com/penpot/penpot/pull/11551))
- Fix workspace crash with index out of bounds when removing multiple interactions rapidly [#11546](https://github.com/penpot/penpot/issues/11546) (PR: [#11621](https://github.com/penpot/penpot/pull/11621))
- Fix workspace crash with 'too much recursion' error when editing grid layout [#11562](https://github.com/penpot/penpot/issues/11562) (PR: [#11563](https://github.com/penpot/penpot/pull/11563))
- Fix plugin API error when removing interactions [#11618](https://github.com/penpot/penpot/issues/11618) (PR: [#11621](https://github.com/penpot/penpot/pull/11621))
- Fix chunked upload storing duplicate objects when the same chunk index is sent twice [#11634](https://github.com/penpot/penpot/issues/11634) (PR: [#11635](https://github.com/penpot/penpot/pull/11635))
- Fix access token expiration select always showing Never after picking another option (by @chulgil) [#11663](https://github.com/penpot/penpot/issues/11663) (PR: [#11664](https://github.com/penpot/penpot/pull/11664))
- Fix workspace dropping unsaved changes when read-only blocks persistence [#11672](https://github.com/penpot/penpot/issues/11672) (PR: [#11699](https://github.com/penpot/penpot/pull/11699))
### :sparkles: New features & Enhancements

View File

@ -33,11 +33,9 @@
[app.rpc.doc :as-alias doc]
[app.rpc.helpers :as rph]
[app.setup :as-alias setup]
[app.setup.welcome-file :refer [create-welcome-file]]
[app.storage :as sto]
[app.tokens :as tokens]
[app.util.services :as sv]
[app.worker :as wrk]
[cuerdas.core :as str]))
(def schema:password
@ -308,7 +306,6 @@
[:fullname ::sm/text]
[:email ::sm/email]
[:password schema:password]
[:create-welcome-file {:optional true} :boolean]
[:accept-newsletter-updates {:optional true} :boolean]
[:invitation-token {:optional true} schema:token]])
@ -446,7 +443,7 @@
:extra-data ptoken}))))
(defn register-profile
[{:keys [::db/conn ::wrk/executor] :as cfg} {:keys [token] :as params}]
[{:keys [::db/conn] :as cfg} {:keys [token] :as params}]
(let [claims (tokens/verify cfg {:token token :iss :prepared-register})
params (cond-> claims
(:accept-newsletter-updates params)
@ -469,14 +466,7 @@
(tokens/verify cfg {:token token :iss :team-invitation}))
props (-> (audit/profile->props profile)
(assoc :from-invitation (some? invitation)))
create-welcome-file-when-needed
(fn []
(when (:create-welcome-file params)
(let [cfg (dissoc cfg ::db/conn)]
(wrk/submit! executor (create-welcome-file cfg profile)))))]
(assoc :from-invitation (some? invitation)))]
(cond
;; When profile is blocked, we just ignore it and return plain data
@ -525,7 +515,6 @@
:email (:email profile)
:invitation-token token}
(rph/with-transform (session/create-fn cfg profile claims))
(rph/with-defer create-welcome-file-when-needed)
(rph/with-meta {::audit/replace-props props
::audit/context {:action "accept-invitation"}
::audit/profile-id (:id profile)})))
@ -533,7 +522,6 @@
(:is-active profile)
(-> (profile/strip-private-attrs profile)
(rph/with-transform (session/create-fn cfg profile claims))
(rph/with-defer create-welcome-file-when-needed)
(rph/with-meta
{::audit/replace-props props
::audit/context {:action "login"}
@ -548,7 +536,6 @@
(-> {:id (:id profile)
:email (:email profile)}
(rph/with-defer create-welcome-file-when-needed)
(rph/with-meta
{::audit/replace-props props
::audit/context {:action "email-verification"}

View File

@ -69,7 +69,6 @@
[:onboarding-questions-answered {:optional true} ::sm/boolean]
[:nitrate-onboarding-viewed {:optional true} ::sm/boolean]
[:v2-info-shown {:optional true} ::sm/boolean]
[:welcome-file-id {:optional true} [:maybe ::sm/boolean]]
[:release-notes-viewed {:optional true}
[::sm/text {:max 100}]]
[:notifications {:optional true} schema:props-notifications]

View File

@ -1,67 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS SUBSIDIARY SL
(ns app.setup.welcome-file
(:require
[app.common.logging :as l]
[app.db :as db]
[app.rpc :as-alias rpc]
[app.rpc.climit :as-alias climit]
[app.rpc.commands.files :as files]
[app.rpc.commands.files-update :as fupdate]
[app.rpc.commands.management :as management]
[app.rpc.commands.profile :as profile]
[app.rpc.doc :as-alias doc]
[app.setup :as-alias setup]
[app.setup.templates :as tmpl]
[app.worker :as-alias wrk]))
(def ^:private page-id #uuid "2c6952ee-d00e-8160-8004-d2250b7210cb")
(def ^:private shape-id #uuid "765e9f82-c44e-802e-8004-d72a10b7b445")
(def ^:private update-path
[:data :pages-index page-id :objects shape-id
:content :children 0 :children 0 :children 0])
(def ^:private sql:mark-file-object-thumbnails-deleted
"UPDATE file_tagged_object_thumbnail
SET deleted_at = now()
WHERE file_id = ?")
(def ^:private sql:mark-file-thumbnail-deleted
"UPDATE file_thumbnail
SET deleted_at = now()
WHERE file_id = ?")
(defn- update-welcome-shape
[_ file name]
(let [text (str "Welcome to Penpot, " name "!")]
(-> file
(update-in update-path assoc :text text)
(update-in [:data :pages-index page-id :objects shape-id] assoc :name "Welcome to Penpot!")
(update-in [:data :pages-index page-id :objects shape-id] dissoc :position-data))))
(defn create-welcome-file
[cfg {:keys [id fullname] :as profile}]
(try
(let [cfg (dissoc cfg ::db/conn)
params {:profile-id (:id profile)
:project-id (:default-project-id profile)}
template-stream (tmpl/get-template-stream cfg "welcome")
file-id (-> (management/clone-template cfg params template-stream)
first)
file-name (str fullname "'s first file")]
(db/tx-run! cfg (fn [{:keys [::db/conn] :as cfg}]
(files/rename-file conn {:id file-id :name file-name})
(fupdate/update-file! cfg file-id update-welcome-shape fullname)
(profile/update-profile-props cfg id {:welcome-file-id file-id})
(db/exec-one! conn [sql:mark-file-object-thumbnails-deleted file-id])
(db/exec-one! conn [sql:mark-file-thumbnail-deleted file-id]))))
(catch Throwable cause
(l/error :hint "unexpected error on create welcome file " :cause cause))))

View File

@ -37,7 +37,7 @@
"This is the main event that is executed once we have logged in
profile. The profile can proceed from standard login or from
accepting invitation, or third party auth signup or singin."
[{:keys [props] :as profile}]
[profile]
(letfn [(get-redirect-events [teams]
(if-let [token (:invitation-token profile)]
(rx/of (rt/nav :auth-verify-token {:token token}))
@ -47,22 +47,16 @@
(if (= redirect-href (rt/get-current-href))
(rx/of (rt/reload true))
(rx/of (rt/nav-raw :href redirect-href))))
(if-let [file-id (get props :welcome-file-id)]
(rx/of (dcm/go-to-workspace
:file-id file-id
:team-id (:default-team-id profile))
(dp/update-profile-props {:welcome-file-id nil}))
(let [default-team-id (:default-team-id profile)
team-ids (into #{} (map :id) teams)
team-id (dtm/get-last-team-id)
team-id (if (and team-id (contains? team-ids team-id))
team-id
default-team-id)]
(->> (dtm/resolve-login-team-id {:team-id team-id
:default-team-id default-team-id})
(rx/mapcat (fn [team-id]
(rx/of (dcm/go-to-dashboard-recent {:team-id team-id}))))))))))]
(let [default-team-id (:default-team-id profile)
team-ids (into #{} (map :id) teams)
team-id (dtm/get-last-team-id)
team-id (if (and team-id (contains? team-ids team-id))
team-id
default-team-id)]
(->> (dtm/resolve-login-team-id {:team-id team-id
:default-team-id default-team-id})
(rx/mapcat (fn [team-id]
(rx/of (dcm/go-to-dashboard-recent {:team-id team-id})))))))))]
(ptk/reify ::logged-in
ptk/WatchEvent

View File

@ -190,24 +190,12 @@
file-id (some-> params :file-id uuid/parse*)
page-id (some-> params :page-id uuid/parse*)
layout (some-> params :layout keyword)]
[:? {}
(when (cf/external-feature-flag "onboarding-03" "test")
(cond
show-question-modal?
[:& questions-modal]
show-team-modal?
[:> onboarding-team-modal* {:go-to-team false}]
show-release-modal?
[:& release-notes-modal {:version (:main cf/version)}]))
[:> team-container* {:team-id team-id}
[:> workspace-page* {:team-id team-id
:file-id file-id
:page-id page-id
:layout-name layout
:key file-id}]]])
[:> team-container* {:team-id team-id}
[:> workspace-page* {:team-id team-id
:file-id file-id
:page-id page-id
:layout-name layout
:key file-id}]])
:viewer
(let [params (get params :query)

View File

@ -160,14 +160,7 @@
(mf/deps on-success-callback)
(fn [form _event]
(reset! submitted? true)
(let [create-welcome-file?
(cf/external-feature-flag "onboarding-03" "test")
cdata
(cond-> (:clean-data @form)
create-welcome-file?
(assoc :create-welcome-file true))]
(let [cdata (:clean-data @form)]
(->> (rp/cmd! :prepare-register-profile cdata)
(rx/subs! on-register-profile on-error #(reset! submitted? false))))))]
@ -326,13 +319,7 @@
(mf/deps on-success on-error)
(fn [form _]
(reset! submitted? true)
(let [create-welcome-file?
(cf/external-feature-flag "onboarding-03" "test")
params
(cond-> (:clean-data @form)
create-welcome-file? (assoc :create-welcome-file true))]
(let [params (:clean-data @form)]
(->> (rp/cmd! :register-profile params)
(rx/finalize #(reset! submitted? false))
(rx/subs! on-success on-error)))))]