🔥 Remove onboarding A/B test and welcome file creation (#11707)

Drop the onboarding-03 experiment consulted through
external-feature-flag, keeping the false-branch behavior: registration
never requests a welcome file and the workspace never shows the
onboarding modals. Remove the now-unused welcome-file machinery on the
backend (RPC wiring, welcome_file namespace, welcome-file-id prop and
the post-login redirect). Keep the external-feature-flag helper as the
seam for future experiments and note it in mem:frontend/core.

Closes #11705

AI-assisted-by: muse-spark-1.3-contributor
This commit is contained in:
Andrey Antukh 2026-09-15 14:14:32 +02:00 committed by GitHub
parent 5d5f7fcc14
commit 39ca4c264c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 22 additions and 133 deletions

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

@ -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 INC Sucursal en España 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

@ -270,24 +270,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

@ -155,14 +155,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))))))]
@ -321,13 +314,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)))))]