diff --git a/docs/css/index.css b/docs/css/index.css
index 63282f168a..9737456609 100644
--- a/docs/css/index.css
+++ b/docs/css/index.css
@@ -848,6 +848,9 @@ a[href].post-tag:visited {
.illus-techguide {
background-image: url(/img/home-technical-guide.webp);
}
+.illus-mcp {
+ background-image: url(/img/home-mcp-server.webp);
+}
.illus-plugins {
background-image: url(/img/home-plugins.webp);
}
diff --git a/docs/index.njk b/docs/index.njk
index ef15854588..908d2f00b0 100644
--- a/docs/index.njk
+++ b/docs/index.njk
@@ -18,24 +18,30 @@ eleventyNavigation:
Everything you need to know about how Penpot works.
-
-
- Contributing guide →
- How to report bugs, add translations and more.
-
-
Technical guide →
Installation, configuration and architecture.
+
+
+ MCP server →
+ Connect AI agents to your Penpot files for design and development workflows.
+
+
Plugins →
All about Penpot plugins.
+
+
+ Contributing guide →
+ How to report bugs, add translations and more.
+
+
FAQs →
diff --git a/docs/mcp/index.md b/docs/mcp/index.md
index e0d31fe6ce..466144b3f1 100644
--- a/docs/mcp/index.md
+++ b/docs/mcp/index.md
@@ -309,6 +309,38 @@ For client-specific setup, use the shared section **Connect your MCP client**.
For remote mode, use the URL shown in **Your account → Integrations → MCP Server**, which includes your `userToken`.
+### Setup videos
+
+
+
+ Penpot MCP Server – Remote setup with OpenCode and OpenRouter
+
+
+
+
+ Penpot MCP Server – Remote setup with Cursor
+
+
### Use
@@ -391,6 +423,23 @@ Leave this terminal running while you use MCP.
For advanced or repository-based workflows, see the [MCP README](https://github.com/penpot/penpot/blob/main/mcp/README.md) in the Penpot repository.
+### Setup video
+
+
+
+ Penpot MCP Server – Local setup
+
+
### Connect
diff --git a/exporter/scripts/build b/exporter/scripts/build
index 3daa2cf6f5..40eba8f44c 100755
--- a/exporter/scripts/build
+++ b/exporter/scripts/build
@@ -14,6 +14,7 @@ rm -rf target
pnpm run build;
cp pnpm-lock.yaml target/;
+cp pnpm-workspace.yaml target/;
cp package.json target/;
touch target/pnpm-workspace.yaml;
diff --git a/frontend/src/app/main/ui/onboarding/questions.cljs b/frontend/src/app/main/ui/onboarding/questions.cljs
index d3974ed2f2..64fff7896e 100644
--- a/frontend/src/app/main/ui/onboarding/questions.cljs
+++ b/frontend/src/app/main/ui/onboarding/questions.cljs
@@ -208,53 +208,28 @@
[:and
[:map {:title "QuestionsFormStep3"}
[:team-size
- [:enum "more-than-50" "31-50" "11-30" "2-10" "freelancer" "personal-project"]]
-
- [:planning ::sm/text]
-
- [:planning-other {:optional true}
- [::sm/text {:max 512}]]]
-
- [:fn {:error/field :planning-other}
- (fn [{:keys [planning planning-other]}]
- (or (not= planning "other")
- (and (= planning "other")
- (not (str/blank? planning-other)))))]])
+ [:enum "1" "2-100" "101-500" "501-1000" "1001-5000" "5001+"]]]])
(mf/defc step-3
{::mf/props :obj}
[{:keys [on-next on-prev form show-step-3]}]
(let [team-size-options
(mf/with-memo []
- [{:label (tr "labels.select-option") :value "" :key "team-size" :disabled true}
- {:label (tr "onboarding.questions.team-size.more-than-50") :value "more-than-50" :key "more-than-50"}
- {:label (tr "onboarding.questions.team-size.31-50") :value "31-50" :key "31-50"}
- {:label (tr "onboarding.questions.team-size.11-30") :value "11-30" :key "11-30"}
- {:label (tr "onboarding.questions.team-size.2-10") :value "2-10" :key "2-10"}
- {:label (tr "onboarding.questions.team-size.freelancer") :value "freelancer" :key "freelancer"}
- {:label (tr "onboarding.questions.team-size.personal-project") :value "personal-project" :key "personal-project"}])
-
- planning-options
- (mf/with-memo []
- (-> (shuffle [{:label (tr "labels.select-option")
- :value "" :key "questions:what-brings-you-here"
- :disabled true}
- {:label (tr "onboarding.questions.reasons.exploring")
- :value "discover-more-about-penpot"
- :key "discover-more-about-penpot"}
- {:label (tr "onboarding.questions.reasons.fit")
- :value "test-penpot-to-see-if-its-a-fit-for-team"
- :key "test-penpot-to-see-if-its-a-fit-for-team"}
- {:label (tr "onboarding.questions.reasons.alternative")
- :value "alternative-to-figma"
- :key "alternative-to-figma"}
- {:label (tr "onboarding.questions.reasons.testing")
- :value "try-out-before-using-penpot-on-premise"
- :key "try-out-before-using-penpot-on-premise"}])
- (conj {:label (tr "labels.other-short") :value "other"})))
-
- current-planning
- (dm/get-in @form [:data :planning])]
+ [{:label (tr "labels.select-option")
+ :value "" :key "team-size"
+ :disabled true}
+ {:label (tr "onboarding.questions.team-size.just-me")
+ :value "1" :key "1"}
+ {:label (tr "onboarding.questions.team-size.2-100")
+ :value "2-100" :key "2-100"}
+ {:label (tr "onboarding.questions.team-size.101-500")
+ :value "101-500" :key "101-500"}
+ {:label (tr "onboarding.questions.team-size.501-1000")
+ :value "501-1000" :key "501-1000"}
+ {:label (tr "onboarding.questions.team-size.1001-5000")
+ :value "1001-5000" :key "1001-5000"}
+ {:label (tr "onboarding.questions.team-size.more-than-5001")
+ :value "5001+" :key "5001+"}])]
[:& step-container {:form form
:step 3
@@ -267,26 +242,8 @@
[:h1 {:class (stl/css :modal-title)}
(tr "onboarding.questions.step3.title")]
- [:div {:class (stl/css :modal-question)}
- [:h3 {:class (stl/css :modal-subtitle)}
- (tr "onboarding.questions.step1.question2")]
-
- [:& fm/select
- {:options planning-options
- :select-class (stl/css :select-class)
- :default ""
- :name :planning
- :dropdown-class (stl/css :question-dropdown)}]]
-
- (when (= current-planning "other")
- [:& fm/input {:name :planning-other
- :class (stl/css :input-spacing)
- :placeholder (tr "labels.other")
- :show-error false
- :label ""}])
[:div {:class (stl/css :modal-question)}
- [:h3 {:class (stl/css :modal-subtitle)} (tr "onboarding.questions.step3.question3")]
[:& fm/select {:options team-size-options
:default ""
:select-class (stl/css :select-class)
diff --git a/frontend/src/app/main/ui/onboarding/questions.scss b/frontend/src/app/main/ui/onboarding/questions.scss
index 9265788c98..4438dcc1fb 100644
--- a/frontend/src/app/main/ui/onboarding/questions.scss
+++ b/frontend/src/app/main/ui/onboarding/questions.scss
@@ -119,7 +119,7 @@
// STEP-3
.step-3 {
- grid-template-rows: deprecated.$s-20 auto auto auto auto deprecated.$s-32;
+ grid-template-rows: deprecated.$s-20 auto auto deprecated.$s-32;
}
.image-radio {
diff --git a/frontend/src/app/main/ui/workspace/main_menu.cljs b/frontend/src/app/main/ui/workspace/main_menu.cljs
index d4d0dd9f28..20a50c729a 100644
--- a/frontend/src/app/main/ui/workspace/main_menu.cljs
+++ b/frontend/src/app/main/ui/workspace/main_menu.cljs
@@ -30,6 +30,7 @@
[app.main.data.workspace.versions :as dwv]
[app.main.features :as features]
[app.main.refs :as refs]
+ [app.main.repo :as rp]
[app.main.store :as st]
[app.main.ui.components.dropdown-menu :refer [dropdown-menu*
dropdown-menu-item*]]
@@ -938,15 +939,29 @@
(fn [event]
(dom/stop-propagation event)
(let [renderer (or (-> profile :props :renderer) :svg)
- next-renderer (if (= renderer :wasm) :svg :wasm)]
- (st/emit! (ev/event {::ev/name (if (= next-renderer :wasm)
- "enable-webgl-rendering"
- "disable-webgl-rendering")
- ::ev/origin "workspace:menu"})
- (du/update-profile-props {:renderer next-renderer})
- (ntf/success (tr (if (= next-renderer :wasm)
- "webgl.toast.webgl-render-enabled"
- "webgl.toast.webgl-render-disabled")))))))
+ next-renderer (if (= renderer :wasm) :svg :wasm)
+ ev-name (if (= next-renderer :wasm)
+ "enable-webgl-rendering"
+ "disable-webgl-rendering")]
+ (if (cf/external-feature-flag "renderer-hard-reload" "test")
+ ;; Bare RPC + hard reload: skips `du/update-profile-props`, so
+ ;; `features/recompute-features` is not run here; bootstrap
+ ;; after reload resolves render-wasm/v1 from the saved profile.
+ (do
+ (st/emit! (ev/event {::ev/name ev-name
+ ::ev/origin "workspace:menu"}))
+ (->> (rp/cmd! :update-profile-props {:props {:renderer next-renderer}})
+ (rx/subs! (fn [_] (dom/reload-current-window true))
+ (fn [_]
+ (st/emit! (ntf/error (tr "errors.generic")))))))
+ ;; `update-profile-props` WatchEvent calls
+ ;; `features/recompute-features`.
+ (st/emit! (ev/event {::ev/name ev-name
+ ::ev/origin "workspace:menu"})
+ (du/update-profile-props {:renderer next-renderer})
+ (ntf/success (tr (if (= next-renderer :wasm)
+ "webgl.toast.webgl-render-enabled"
+ "webgl.toast.webgl-render-disabled"))))))))
open-plugins-manager
(mf/use-fn
diff --git a/frontend/translations/en.po b/frontend/translations/en.po
index 64dc9c4a41..ad7034181f 100644
--- a/frontend/translations/en.po
+++ b/frontend/translations/en.po
@@ -4873,7 +4873,7 @@ msgstr "What's the size of your company?"
#: src/app/main/ui/onboarding/questions.cljs:269
msgid "onboarding.questions.step3.title"
-msgstr "Tell us about your job"
+msgstr "What is your company size?"
#: src/app/main/ui/onboarding/questions.cljs:344
msgid "onboarding.questions.step4.title"
@@ -4884,7 +4884,25 @@ msgstr "Where would you like to get started?"
msgid "onboarding.questions.step5.title"
msgstr "How did you hear about Penpot?"
-#: src/app/main/ui/onboarding/questions.cljs:232
+msgid "onboarding.questions.team-size.just-me"
+msgstr "Just me"
+
+msgid "onboarding.questions.team-size.2-100"
+msgstr "2-100"
+
+msgid "onboarding.questions.team-size.101-500"
+msgstr "101-500"
+
+msgid "onboarding.questions.team-size.501-1000"
+msgstr "501-1,000"
+
+msgid "onboarding.questions.team-size.1001-5000"
+msgstr "1,001-5,000"
+
+msgid "onboarding.questions.team-size.more-than-5001"
+msgstr "5,001+"
+
+#: src/app/main/ui/onboarding/questions.cljs:233
msgid "onboarding.questions.team-size.11-30"
msgstr "11-30"
diff --git a/frontend/translations/es.po b/frontend/translations/es.po
index 9050f78a21..c2a31d5670 100644
--- a/frontend/translations/es.po
+++ b/frontend/translations/es.po
@@ -4742,7 +4742,7 @@ msgstr "¿Cuál es el tamaño de tu empresa?"
#: src/app/main/ui/onboarding/questions.cljs:269
msgid "onboarding.questions.step3.title"
-msgstr "Háblanos de tu trabajo"
+msgstr "¿Cuál es el tamaño de tu empresa?"
#: src/app/main/ui/onboarding/questions.cljs:344
msgid "onboarding.questions.step4.title"
@@ -4753,7 +4753,25 @@ msgstr "¿Por dónde te apetecería empezar?"
msgid "onboarding.questions.step5.title"
msgstr "¿Cómo nos has descubierto?"
-#: src/app/main/ui/onboarding/questions.cljs:232
+msgid "onboarding.questions.team-size.just-me"
+msgstr "Sólo yo"
+
+msgid "onboarding.questions.team-size.2-100"
+msgstr "2-100"
+
+msgid "onboarding.questions.team-size.101-500"
+msgstr "101-500"
+
+msgid "onboarding.questions.team-size.501-1000"
+msgstr "501-1000"
+
+msgid "onboarding.questions.team-size.1001-5000"
+msgstr "1001-5000"
+
+msgid "onboarding.questions.team-size.more-than-5001"
+msgstr "5001+"
+
+#: src/app/main/ui/onboarding/questions.cljs:233
msgid "onboarding.questions.team-size.11-30"
msgstr "11-30"