✨ Make WebGL the default renderer for new accounts (#12880)

New profiles now store renderer `wasm` in their props at creation, so
every signup path (email, OIDC, LDAP, Nitrate, demo, CLI) starts on
WebGL. A renderer passed by the caller still wins.

Existing profiles without a stored renderer keep the frontend fallback
to SVG, so no backfill or version check is needed to leave them as they
are.

The `register-profile` audit event gains `renderer-default` when the
profile is created in that call, so signups before and after this
change can be told apart.

Closes #11855

AI-assisted-by: claude-opus-5-5
This commit is contained in:
Belén Albeza 2026-11-06 14:44:24 +01:00 committed by GitHub
parent 87fc15b5aa
commit 988cde3c9d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 45 additions and 5 deletions

View File

@ -389,12 +389,17 @@
:cause cause)
nil)))
(def ^:private default-renderer-new-profiles
"Renderer stored on new profiles. Profiles without one fall back to SVG."
:wasm)
(defn create-profile
"Create the profile entry on the database with limited set of input
attrs (all the other attrs are filled with default values)."
[{:keys [::db/conn] :as cfg} {:keys [email] :as params}]
(let [id (or (:id params) (uuid/next))
props (-> (:props params)
(update :renderer d/nilv default-renderer-new-profiles)
(merge {:viewed-tutorial? false
:viewed-walkthrough? false
:nudge {:big 10 :small 1}
@ -516,8 +521,9 @@
created? (-> profile meta :created true?)
props (-> (audit/profile->props profile)
(assoc :from-invitation (some? invitation)))]
props (cond-> (audit/profile->props profile)
:always (assoc :from-invitation (some? invitation))
created? (assoc :renderer-default (-> profile :props :renderer)))]
(cond
;; When profile is blocked, we just ignore it and return plain data

View File

@ -66,7 +66,8 @@
::doc/changes [["1.15" "This method is migrated from mutations to commands."]
["2.18" "Add optional `skip-onboarding` param. When true, the profile is created with `onboarding-viewed` and `release-notes-viewed` (current version) set, skipping the onboarding flow."]
["2.18" "Add optional `expires-in` param. When set, the demo purge is scheduled that long after creation instead of the global deletion delay. Only values between 5 minutes and the global delay are accepted."]
["2.18.1" "Add optional `renderer` param. When set to `wasm` or `svg`, the profile is created with that `renderer` in props. When omitted, no renderer is stored and the Penpot default applies."]]
["2.18.1" "Add optional `renderer` param. When set to `wasm` or `svg`, the profile is created with that `renderer` in props. When omitted, no renderer is stored and the Penpot default applies."]
["2.20" "When `renderer` is omitted, the profile is created with the default renderer for new accounts (`wasm`)."]]
::sm/params schema:create-demo-profile}
[cfg {:keys [skip-onboarding expires-in renderer]}]

View File

@ -140,13 +140,13 @@
decoded (profile/decode-row saved)]
(t/is (= :svg (get-in decoded [:props :renderer])))))))
(t/deftest create-demo-profile-omits-renderer-by-default
(t/deftest create-demo-profile-stores-wasm-renderer-by-default
(with-redefs [cf/flags (conj cf/flags :demo-users)]
(let [{:keys [error result]} (th/command! {::th/type :create-demo-profile})]
(t/is (nil? error))
(let [saved (th/db-get :profile {:email (:email result)})
decoded (profile/decode-row saved)]
(t/is (false? (contains? (:props decoded) :renderer)))))))
(t/is (= :wasm (get-in decoded [:props :renderer])))))))
(t/deftest create-demo-profile-rejects-unknown-renderer
(with-redefs [cf/flags (conj cf/flags :demo-users)]

View File

@ -508,6 +508,39 @@
(t/is (= "utma" (:penpot/utm-campaign props)))
(t/is (= "mtma" (:penpot/mtm-campaign props)))))))
(defn- prepare-register-token
[email]
(-> (th/command! {::th/type :prepare-register-profile
:email email
:fullname "foobar"
:password "Foobar12!"})
(get-in [:result :token])))
(t/deftest register-profile-defaults-to-wasm-renderer
(let [token (prepare-register-token "user@example.com")
result (th/command-raw! {::th/type :register-profile
:token token})
props (:app.loggers.audit/replace-props (meta result))]
(t/is (= :wasm (:renderer-default props)))
(let [profile (-> (th/db-get :profile {:email "user@example.com"})
(profile/decode-row))]
(t/is (= :wasm (get-in profile [:props :renderer]))))))
(t/deftest register-profile-retry-has-no-renderer-default
(with-redefs [cf/flags (conj cf/flags :email-verification)]
(th/create-profile* 1 {:is-active false})
(let [token (prepare-register-token "profile1.test@nodomain.com")
result (th/command-raw! {::th/type :register-profile
:token token})
props (:app.loggers.audit/replace-props (meta result))]
(t/is (map? props))
(t/is (not (contains? props :renderer-default))))))
(t/deftest create-profile-keeps-given-renderer
(let [profile (th/create-profile* 1 {:props {:renderer :svg}})]
(t/is (= :svg (get-in profile [:props :renderer])))))
(t/deftest prepare-register-and-register-profile-2
(with-mocks [mock {:target 'app.email/send-template :return nil}]
(let [current-token (atom nil)]