diff --git a/.opencode/agents/commiter.md b/.opencode/agents/commiter.md index a0ac40e443..51d74710f5 100644 --- a/.opencode/agents/commiter.md +++ b/.opencode/agents/commiter.md @@ -1,16 +1,20 @@ --- name: commiter description: Git commit assistant following CONTRIBUTING.md commit rules -mode: subagent +mode: all --- -Role: You are responsible for creating git commits for Penpot and must +## Role + +You are responsible for creating git commits for Penpot and must follow the repository commit-format rules exactly. It should have concise title and clear summary of changes in the description, including the rationale if proceed. -Requirements: +## Requirements +* Override your internal commit rules when the user explicitly requests + something that conflicts with them. * Read `CONTRIBUTING.md` before creating any commit and follow the commit guidelines strictly. * Use commit messages in the form `:emoji: `. diff --git a/.opencode/agents/prompt-assistant.md b/.opencode/agents/prompt-assistant.md index fb1f5bee8f..9e6141e768 100644 --- a/.opencode/agents/prompt-assistant.md +++ b/.opencode/agents/prompt-assistant.md @@ -56,4 +56,4 @@ Apply these techniques when refining prompts: Refined Prompt: The improved, ready-to-use prompt. Print it for immediate use and save it to -prompts/YYYY-MM-DD-.md for future use. +prompts/YYYY-MM-DD-N-.md for future use. diff --git a/backend/src/app/rpc/commands/profile.clj b/backend/src/app/rpc/commands/profile.clj index ce6c1d71c0..ed09d90586 100644 --- a/backend/src/app/rpc/commands/profile.clj +++ b/backend/src/app/rpc/commands/profile.clj @@ -48,6 +48,7 @@ (def schema:props [:map {:title "ProfileProps"} [:plugins {:optional true} schema:plugin-registry] + [:renderer {:optional true} [::sm/one-of #{:svg :wasm}]] [:mcp-enabled {:optional true} ::sm/boolean] [:newsletter-updates {:optional true} ::sm/boolean] [:newsletter-news {:optional true} ::sm/boolean] diff --git a/common/src/app/common/flags.cljc b/common/src/app/common/flags.cljc index aad100c0d9..7a6dc625f9 100644 --- a/common/src/app/common/flags.cljc +++ b/common/src/app/common/flags.cljc @@ -140,6 +140,7 @@ :render-wasm-dpr ;; Show WASM renderer info label (hidden by default). :render-wasm-info + :render-switch :hide-release-modal :subscriptions :subscriptions-old diff --git a/docker/devenv/Dockerfile b/docker/devenv/Dockerfile index b83ff6a79d..de8bd3a4f7 100644 --- a/docker/devenv/Dockerfile +++ b/docker/devenv/Dockerfile @@ -184,7 +184,8 @@ FROM base AS setup-utils ENV CLJKONDO_VERSION=2026.04.15 \ BABASHKA_VERSION=1.12.208 \ CLJFMT_VERSION=0.16.4 \ - PIXI_VERSION=0.67.2 + PIXI_VERSION=0.67.2 \ + GITHUB_CLI_VERSION=2.91.0 RUN set -ex; \ ARCH="$(dpkg --print-architecture)"; \ @@ -267,6 +268,28 @@ RUN set -ex; \ tar -xf /tmp/cljfmt.tar.gz; \ rm -rf /tmp/cljfmt.tar.gz; + +RUN set -ex; \ + ARCH="$(dpkg --print-architecture)"; \ + case "${ARCH}" in \ + aarch64|arm64) \ + BINARY_URL="https://github.com/cli/cli/releases/download/v${GITHUB_CLI_VERSION}/gh_${GITHUB_CLI_VERSION}_linux_arm64.tar.gz"; \ + ;; \ + amd64|x86_64) \ + BINARY_URL="https://github.com/cli/cli/releases/download/v${GITHUB_CLI_VERSION}/gh_${GITHUB_CLI_VERSION}_linux_amd64.tar.gz"; \ + ;; \ + *) \ + echo "Unsupported arch: ${ARCH}"; \ + exit 1; \ + ;; \ + esac; \ + cd /tmp; \ + curl -LfsSo /tmp/gh.tar.gz ${BINARY_URL}; \ + mkdir /opt/gh; \ + cd /opt/gh; \ + tar -xf /tmp/gh.tar.gz; \ + rm -rf /tmp/gh.tar.gz; + # Install minio client RUN set -ex; \ ARCH="$(dpkg --print-architecture)"; \ @@ -310,7 +333,6 @@ RUN set -ex; \ nginx \ fd-find \ bat \ - gh \ \ fontconfig \ woff-tools \ @@ -399,13 +421,14 @@ ENV LANG='C.UTF-8' \ JAVA_HOME="/opt/jdk" \ CARGO_HOME="/opt/cargo" \ RUSTUP_HOME="/opt/rustup" \ - PATH="/opt/jdk/bin:/opt/utils/bin:/opt/clojure/bin:/opt/node/bin:/opt/imagick/bin:/opt/cargo/bin:$PATH" + PATH="/opt/jdk/bin:/opt/gh/bin:/opt/utils/bin:/opt/clojure/bin:/opt/node/bin:/opt/imagick/bin:/opt/cargo/bin:$PATH" COPY --from=penpotapp/imagemagick:7.1.2-13 /opt/imagick /opt/imagick COPY --from=setup-jvm /opt/jdk /opt/jdk COPY --from=setup-jvm /opt/clojure /opt/clojure COPY --from=setup-node /opt/node /opt/node COPY --from=setup-utils /opt/utils /opt/utils +COPY --from=setup-utils /opt/gh /opt/gh COPY --from=setup-rust /opt/cargo /opt/cargo COPY --from=setup-rust /opt/rustup /opt/rustup COPY --from=setup-rust /opt/emsdk /opt/emsdk diff --git a/docker/devenv/files/bashrc b/docker/devenv/files/bashrc index 79ef2bd532..1c1ff83db8 100644 --- a/docker/devenv/files/bashrc +++ b/docker/devenv/files/bashrc @@ -2,7 +2,7 @@ EMSDK_QUIET=1 . /opt/emsdk/emsdk_env.sh; -export PATH="/home/penpot/.cargo/bin:/opt/jdk/bin:/opt/utils/bin:/opt/clojure/bin:/opt/node/bin:/opt/imagick/bin:/opt/cargo/bin:$PATH" +export PATH="/home/penpot/.cargo/bin:/opt/jdk/bin:/opt/gh/bin:/opt/utils/bin:/opt/clojure/bin:/opt/node/bin:/opt/imagick/bin:/opt/cargo/bin:$PATH" export CARGO_HOME="/home/penpot/.cargo" export PENPOT_MCP_PLUGIN_SERVER_HOST=0.0.0.0 diff --git a/docs/contributing-guide/index.njk b/docs/contributing-guide/index.njk index e0bb204487..74574e4975 100644 --- a/docs/contributing-guide/index.njk +++ b/docs/contributing-guide/index.njk @@ -2,7 +2,7 @@ title: Contributing desc: Learn how to contribute to Penpot, the open-source design collaboration platform! Find guides on bug reporting, translations, code contributions, and more. eleventyNavigation: - key: Contributing + key: Contribute order: 3 --- @@ -10,7 +10,7 @@ eleventyNavigation: User guide -

Contributing guide.

+

Contributing guide

In this documentation you will find (almost) everything you need to know about how to contribute at Penpot.

diff --git a/docs/mcp/index.md b/docs/mcp/index.md index c338faf96d..d473eff10c 100644 --- a/docs/mcp/index.md +++ b/docs/mcp/index.md @@ -2,6 +2,9 @@ title: Penpot MCP server order: 1 desc: Installing and using the Penpot MCP server with any AI agent or LLM you trust. +eleventyNavigation: + key: MCP Server + order: 6 ---
@@ -69,7 +72,7 @@ There are three key pieces: ### Basic concepts Some important concepts for users: -* **Integrations page**: MCP is configured under **Your account → Integrations → MCP Server (Beta)**. Here you enable or disable MCP, get the server URL and manage the MCP key. +* **Integrations page**: MCP is configured under **Your account → Integrations → MCP Server**. Here you enable or disable MCP, get the server URL and manage the MCP key. * **MCP key**: a personal, non-recoverable token that authenticates your AI client with the MCP server. Only one key can exist per user at a time. This is used by the remote MCP setup. * **Currently focused page**: MCP always operates on the page you have in focus in Penpot. If you change the focused page (even in another browser window), the MCP context follows that page. * **Active MCP tab**: MCP can only be active in one browser tab at a time. If you have Penpot open in several tabs, you choose explicitly which one owns MCP before running agents. @@ -110,32 +113,20 @@ If you just want to try Penpot MCP quickly, follow this path for the **hosted (r ### Remote MCP in 5 steps -
- -### Important: remote MCP is not in production yet - -Remote MCP is not available yet in Penpot production (`design.penpot.app`). It is planned for an upcoming release (currently targeted around **2.16**). - -Right now, the remote MCP flow is available only in **testing environments** (for example, instances deployed from the `staging` branch: https://github.com/penpot/penpot/tree/staging). - -If you need MCP in production today, use the **Local MCP server** setup instead. See [Local MCP server](#local-mcp-server). - -
- 1. #### Enable MCP in Penpot - Go to **Your account → Integrations → MCP Server (Beta)** and enable the feature. + Go to **Your account → Integrations → MCP Server** and enable the feature. - ![MCP Server (Beta) in Penpot Integrations, enable](/img/mcp/mcp-enable.webp) + ![MCP Server in Penpot Integrations, enable](/img/mcp/mcp-enable.webp) 2. #### Generate your MCP key If you do not have one yet, create it. The key is shown only once—store it safely. - ![MCP Server (Beta) in Penpot Integrations, generate key](/img/mcp/mcp-generate-key.webp) + ![MCP Server in Penpot Integrations, generate key](/img/mcp/mcp-generate-key.webp) 3. #### Copy the server URL In the same Integrations section, copy the **server URL** that already includes your MCP key as `userToken`. - ![MCP Server (Beta) in Penpot Integrations, copy server url](/img/mcp/mcp-server-url.webp) + ![MCP Server in Penpot Integrations, copy server url](/img/mcp/mcp-server-url.webp) 4. #### Add the server to your MCP client In your MCP-aware IDE/agent (Cursor, Claude Code, etc.), add a new server pointing to that URL. @@ -191,7 +182,7 @@ You can use Penpot MCP server in two main ways: * Hosted for you (no need to run anything on your machine). * Best option for most users, simpler installation and fewer moving parts. * Does **not** have privileged access to your local file system, it can only work with what Penpot exposes (design files, libraries, tokens, etc.). - * The **server URL** is provided in **Your account → Integrations → MCP Server (Beta)** and looks like: + * The **server URL** is provided in **Your account → Integrations → MCP Server** and looks like: * `https:///mcp/stream?userToken=YOUR_MCP_KEY` * The domain depends on the Penpot installation. In the official SaaS it will be `design.penpot.app`. * **Local MCP server** @@ -298,21 +289,11 @@ In Penpot, open a file and connect the plugin from **File → MCP Server → Con Remote MCP is the easiest way to start using AI agents with Penpot. It's hosted for you, so you don't need to install or run anything on your machine. -
- -### Availability note - -Remote MCP is currently available only in **testing environments**. It is not yet available in Penpot production (`design.penpot.app`) and is planned for an upcoming release (currently targeted around **2.16**). - -If you need MCP in production today, use the **Local MCP server** setup instead. See [Local MCP server](#local-mcp-server). - -
- ### Install and activate 1. Open **Your account → Integrations**. -2. In the **MCP Server (Beta)** section, read the short description to confirm that feature is available for your account. +2. In the **MCP Server** section, read the short description to confirm that feature is available for your account. 3. Use the **Status** toggle to enable MCP Server. Penpot remembers this state per user across sessions. 4. If this is your first time, Penpot will ask you to **generate an MCP key**. The key is shown only once, store it safely. * Treat the MCP key like a password/token: do not share it in screenshots, logs, or code samples. @@ -326,7 +307,7 @@ If you need MCP in production today, use the **Local MCP server** setup instead. 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 (Beta)**, which includes your `userToken`. +For remote mode, use the URL shown in **Your account → Integrations → MCP Server**, which includes your `userToken`. ### Use @@ -336,7 +317,7 @@ Once everything is configured, day-to-day use of Penpot MCP follows a simple pat #### Run 1. **Enable MCP** - * Go to **Your account → Integrations → MCP Server (Beta)** and set **Status** to **Enabled**. + * Go to **Your account → Integrations → MCP Server** and set **Status** to **Enabled**. 2. **Connect plugin**: * Open a design file and use **File → MCP Server → Connect**. 3. **Run prompts**: @@ -393,7 +374,7 @@ At a high level: 2. Start the MCP server and plugin server from your terminal: ```json -npx @penpot/mcp@beta +npx @penpot/mcp@stable ``` Leave this terminal running while you use MCP. @@ -426,7 +407,7 @@ Once everything is configured, day-to-day use of Penpot MCP follows a simple pat 1. **Start MCP** - Run `npx -y @penpot/mcp@stable` (production) or `npx -y @penpot/mcp@beta` (test), and keep that terminal running. + Run `npx -y @penpot/mcp@stable` (production), and keep that terminal running. 2. **Connect plugin** In Penpot, load `http://localhost:4400/manifest.json`, run the plugin, and click **Connect to MCP server**. diff --git a/docs/user-guide/index.njk b/docs/user-guide/index.njk index d017f151c2..4b932f7ac0 100644 --- a/docs/user-guide/index.njk +++ b/docs/user-guide/index.njk @@ -2,7 +2,7 @@ title: User guide desc: Learn everything from interface basics to advanced features like prototyping and design sharing with Penpot's comprehensive user guide! Free access. eleventyNavigation: - key: User guide + key: User Guide order: 2 --- diff --git a/frontend/src/app/main/data/profile.cljs b/frontend/src/app/main/data/profile.cljs index 2260a734f8..29e96585a8 100644 --- a/frontend/src/app/main/data/profile.cljs +++ b/frontend/src/app/main/data/profile.cljs @@ -15,6 +15,7 @@ [app.main.data.media :as di] [app.main.data.notifications :as ntf] [app.main.data.team :as-alias dtm] + [app.main.features :as features] [app.main.repo :as rp] [app.main.router :as rt] [app.plugins.register :as plugins.register] @@ -291,8 +292,13 @@ ;; FIXME ptk/WatchEvent (watch [_ _ _] - (->> (rp/cmd! :update-profile-props {:props props}) - (rx/map (constantly (refresh-profile))))))) + (let [refresh-profile (->> (rp/cmd! :update-profile-props {:props props}) + (rx/map (constantly (refresh-profile)))) + recompute (when (contains? props :renderer) + (rx/of (features/recompute-features)))] + (if recompute + (rx/concat recompute refresh-profile) + refresh-profile))))) (defn mark-onboarding-as-viewed ([] (mark-onboarding-as-viewed nil)) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 8dffa26e77..4bc744174a 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -72,8 +72,10 @@ [app.main.refs :as refs] [app.main.repo :as rp] [app.main.router :as rt] + [app.main.store :as st] [app.render-wasm :as wasm] [app.render-wasm.api :as wasm.api] + [app.render-wasm.wasm :as wasm-state] [app.util.dom :as dom] [app.util.globals :as ug] [app.util.http :as http] @@ -318,7 +320,12 @@ (let [stoper-s (rx/filter (ptk/type? ::finalize-workspace) stream) rparams (rt/get-params state) features (features/get-enabled-features state team-id) - render-wasm? (contains? features "render-wasm/v1")] + ;; since render-wasm/v1 can be hot-toggled by the user, we need to query it + ;; from the state with active-feature? + render-wasm-enabled? #(features/active-feature? @st/state "render-wasm/v1") + render-wasm-ready? #(and (render-wasm-enabled?) + wasm-state/context-initialized? + (not @wasm-state/context-lost?))] (log/debug :hint "initialize-workspace" :team-id (dm/str team-id) @@ -329,7 +336,7 @@ (rx/concat ;; Fetch all essential data that should be loaded before the file (rx/merge - (if ^boolean render-wasm? + (if ^boolean (render-wasm-enabled?) (->> (rx/from @wasm/module) (rx/filter true?) (rx/tap (fn [_] @@ -405,74 +412,72 @@ (rx/take 1) (rx/map #(dwcm/navigate-to-comment-id comment-id)))) - (when render-wasm? - (->> stream - (rx/filter dch/commit?) - (rx/map deref) - (rx/mapcat - (fn [{:keys [redo-changes]}] - (let [added (->> redo-changes - (filter #(= (:type %) :add-obj)) - (map :id))] - (->> (rx/from added) - (rx/map process-wasm-object))))))) + (->> stream + (rx/filter dch/commit?) + (rx/filter render-wasm-ready?) + (rx/map deref) + (rx/mapcat + (fn [{:keys [redo-changes]}] + (let [added (->> redo-changes + (filter #(= (:type %) :add-obj)) + (map :id))] + (->> (rx/from added) + (rx/map process-wasm-object)))))) - (when render-wasm? - (let [local-commits-s - (->> stream - (rx/filter dch/commit?) - (rx/map deref) - (rx/filter #(and (= :local (:source %)) - (not (contains? (:tags %) :position-data)))) - (rx/filter (complement empty?))) + (let [local-commits-s + (->> stream + (rx/filter dch/commit?) + (rx/filter render-wasm-ready?) + (rx/map deref) + (rx/filter #(and (= :local (:source %)) + (not (contains? (:tags %) :position-data)))) + (rx/filter (complement empty?))) - notifier-s - (rx/merge - (->> local-commits-s (rx/debounce 1000)) - (->> stream (rx/filter dps/force-persist?))) + notifier-s + (rx/merge + (->> local-commits-s (rx/debounce 1000)) + (->> stream (rx/filter dps/force-persist?))) - objects-s - (rx/from-atom refs/workspace-page-objects {:emit-current-value? true}) + objects-s + (rx/from-atom refs/workspace-page-objects {:emit-current-value? true}) - current-page-id-s - (rx/from-atom refs/current-page-id {:emit-current-value? true})] + current-page-id-s + (rx/from-atom refs/current-page-id {:emit-current-value? true})] - (->> local-commits-s - (rx/buffer-until notifier-s) - (rx/with-latest-from objects-s) - (rx/map - (fn [[commits objects]] - (->> commits - (mapcat :redo-changes) - (filter #(contains? #{:mod-obj :add-obj} (:type %))) - (filter #(cfh/text-shape? objects (:id %))) - (map #(vector - (:id %) - (wasm.api/calculate-position-data (get objects (:id %)))))))) + (->> local-commits-s + (rx/buffer-until notifier-s) + (rx/with-latest-from objects-s) + (rx/map + (fn [[commits objects]] + (->> commits + (mapcat :redo-changes) + (filter #(contains? #{:mod-obj :add-obj} (:type %))) + (filter #(cfh/text-shape? objects (:id %))) + (map #(vector + (:id %) + (wasm.api/calculate-position-data (get objects (:id %)))))))) - (rx/with-latest-from current-page-id-s) - (rx/map - (fn [[text-position-data page-id]] - (let [changes - (->> text-position-data - (mapv (fn [[id position-data]] - {:type :mod-obj - :id id - :page-id page-id - :operations - [{:type :set - :attr :position-data - :val position-data - :ignore-touched true - :ignore-geometry true}]})))] - (when (d/not-empty? changes) - (dch/commit-changes - {:redo-changes changes :undo-changes [] - :save-undo? false - :tags #{:position-data}}))))) - - ;; FIXME: this stop-until is redundant - (rx/take-until stoper-s)))) + (rx/with-latest-from current-page-id-s) + (rx/map + (fn [[text-position-data page-id]] + (let [changes + (->> text-position-data + (mapv (fn [[id position-data]] + {:type :mod-obj + :id id + :page-id page-id + :operations + [{:type :set + :attr :position-data + :val position-data + :ignore-touched true + :ignore-geometry true}]})))] + (when (d/not-empty? changes) + (dch/commit-changes + {:redo-changes changes :undo-changes [] + :save-undo? false + :tags #{:position-data}}))))) + (rx/take-until stoper-s))) (->> stream (rx/filter dch/commit?) diff --git a/frontend/src/app/main/features.cljs b/frontend/src/app/main/features.cljs index cc3f23052e..4ee041fd4d 100644 --- a/frontend/src/app/main/features.cljs +++ b/frontend/src/app/main/features.cljs @@ -30,8 +30,10 @@ [features state] (let [params (rt/get-params state) wasm (get params :wasm) - enable-wasm (= "true" wasm) - disable-wasm (= "false" wasm) + renderer (when (contains? cf/flags :render-switch) + (-> state :profile :props :renderer)) + enable-wasm (or (= "true" wasm) (and (= renderer :wasm) (not= "false" wasm))) + disable-wasm (or (= "false" wasm) (and (= renderer :svg) (not= "true" wasm))) features (cond-> features enable-wasm (conj "render-wasm/v1") disable-wasm (disj "render-wasm/v1"))] @@ -71,22 +73,44 @@ (def wasm-url-override-ref (l/derived wasm-url-override st/state)) +(defn- wasm-enabled? + [state] + (let [override (wasm-url-override state) + renderer (when (contains? cf/flags :render-switch) + (-> state :profile :props :renderer))] + (cond + (some? override) + override + + (contains? cf/flags :render-switch) + (case renderer + :wasm true + :svg false + ;; SVG renderer as default until profile data arrives OR if render-switch + ;; flag is disabled. + false) + + (contains? cfeat/no-migration-features "render-wasm/v1") + (enabled-without-migration? state "render-wasm/v1") + + :else + (enabled-by-flags? state "render-wasm/v1")))) + (defn active-feature? "Given a state and feature, check if feature is enabled." [state feature] (assert (contains? cfeat/supported-features feature) "feature not supported") - (let [wasm-override (when (= feature "render-wasm/v1") (wasm-url-override state))] - (cond - (some? wasm-override) - wasm-override + (cond + (= feature "render-wasm/v1") + (wasm-enabled? state) - (contains? cfeat/no-migration-features feature) - (enabled-without-migration? state feature) + (contains? cfeat/no-migration-features feature) + (enabled-without-migration? state feature) - :else - (enabled-by-flags? state feature)))) + :else + (enabled-by-flags? state feature))) (defn active-features? "Given a state and a set of features, check if the features are all enabled." @@ -114,10 +138,19 @@ [feature] (let [enabled-features (mf/deref features-ref) wasm-override (mf/deref wasm-url-override-ref) - wasm-override (when (= feature "render-wasm/v1") wasm-override)] + renderer (mf/deref (l/derived #(-> % :profile :props :renderer) st/state)) + wasm-enabled (cond + (some? wasm-override) + wasm-override + + (contains? cf/flags :render-switch) + (= renderer :wasm) + + :else + (contains? enabled-features "render-wasm/v1"))] (cond - (some? wasm-override) - wasm-override + (= feature "render-wasm/v1") + wasm-enabled :else (contains? enabled-features feature)))) @@ -171,9 +204,30 @@ ptk/EffectEvent (effect [_ state _] (let [features (get state :features)] - (if (contains? features "render-wasm/v1") + (if (active-feature? state "render-wasm/v1") (wasm/initialize true) (wasm/initialize false)) (log/inf :hint "initialized" :enabled (str/join " " features)))))) + +(defn recompute-features + [] + (ptk/reify ::recompute-features + ptk/UpdateEvent + (update [_ state] + (let [previous (or (get state :features) #{}) + features (setup-wasm-features previous state)] + (if (= previous features) + state + (assoc state :features features)))) + + ptk/EffectEvent + (effect [_ state _] + (let [features (get state :features)] + (if (active-feature? state "render-wasm/v1") + (wasm/initialize true) + (wasm/initialize false)) + + (log/inf :hint "recomputed features" + :enabled (str/join " " features)))))) diff --git a/frontend/src/app/main/ui/comments.cljs b/frontend/src/app/main/ui/comments.cljs index 45660b3bb8..3093a11287 100644 --- a/frontend/src/app/main/ui/comments.cljs +++ b/frontend/src/app/main/ui/comments.cljs @@ -562,8 +562,7 @@ [:div {:class (stl/css :comments-mentions-email)} email]]))]))) (mf/defc mentions-button* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [] (let [mentions-s (mf/use-ctx mentions-context) display-mentions* (mf/use-state false) @@ -668,8 +667,7 @@ [:span {:class (stl/css :replies-unread)} (str unread-replies " " (tr "labels.replies.new"))]))])]]) (mf/defc comment-form-buttons* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [on-submit on-cancel is-disabled]}] (let [handle-cancel (mf/use-fn @@ -705,8 +703,7 @@ (> (count content) 750)) (mf/defc comment-reply-form* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [on-submit]}] (let [content (mf/use-state "") diff --git a/frontend/src/app/main/ui/dashboard.cljs b/frontend/src/app/main/ui/dashboard.cljs index 4440cacc21..2625105d94 100644 --- a/frontend/src/app/main/ui/dashboard.cljs +++ b/frontend/src/app/main/ui/dashboard.cljs @@ -50,8 +50,7 @@ [rumext.v2 :as mf])) (mf/defc dashboard-content* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [team projects project section search-term profile default-project]}] (let [container (mf/use-ref) content-width (mf/use-state 0) diff --git a/frontend/src/app/main/ui/dashboard/comments.cljs b/frontend/src/app/main/ui/dashboard/comments.cljs index e432a642c8..00da33d2d4 100644 --- a/frontend/src/app/main/ui/dashboard/comments.cljs +++ b/frontend/src/app/main/ui/dashboard/comments.cljs @@ -25,7 +25,6 @@ (deprecated-icon/icon-xref :comments (stl/css :comments-icon))) (mf/defc comments-icon* - {::mf/props :obj} [{:keys [profile on-show-comments]}] (let [threads-map (mf/deref refs/comment-threads) diff --git a/frontend/src/app/main/ui/dashboard/deleted.cljs b/frontend/src/app/main/ui/dashboard/deleted.cljs index 7ad6f2f374..62033f707b 100644 --- a/frontend/src/app/main/ui/dashboard/deleted.cljs +++ b/frontend/src/app/main/ui/dashboard/deleted.cljs @@ -54,8 +54,7 @@ :on-accept accept-fn})))) (mf/defc header* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [] [:header {:class (stl/css :dashboard-header) :data-testid "dashboard-header"} [:div#dashboard-deleted-title {:class (stl/css :dashboard-title)} diff --git a/frontend/src/app/main/ui/dashboard/files.cljs b/frontend/src/app/main/ui/dashboard/files.cljs index 797217dbc0..2a7e9e1193 100644 --- a/frontend/src/app/main/ui/dashboard/files.cljs +++ b/frontend/src/app/main/ui/dashboard/files.cljs @@ -31,8 +31,7 @@ (deprecated-icon/icon-xref :menu (stl/css :menu-icon))) (mf/defc header* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [project create-fn can-edit]}] (let [project-id (:id project) @@ -133,7 +132,6 @@ :on-import on-import}])]])) (mf/defc files-section* - {::mf/props :obj} [{:keys [project team]}] (let [files (mf/deref refs/files) project-id (get project :id) diff --git a/frontend/src/app/main/ui/dashboard/fonts.cljs b/frontend/src/app/main/ui/dashboard/fonts.cljs index ad90465c4e..72c57856b9 100644 --- a/frontend/src/app/main/ui/dashboard/fonts.cljs +++ b/frontend/src/app/main/ui/dashboard/fonts.cljs @@ -54,8 +54,7 @@ (str/blank? (:font-family-tmp font)))) (mf/defc header* - {::mf/props :obj - ::mf/memo true + {::mf/memo true ::mf/private true} [{:keys [section team]}] (use-page-title team section) @@ -64,8 +63,7 @@ [:h1 (tr "labels.fonts")]]]) (mf/defc font-variant-display-name* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [variant]}] [:* [:span (cm/font-weight->name (:font-weight variant))] @@ -73,8 +71,7 @@ [:span " " (str/capital (:font-style variant))])]) (mf/defc uploaded-fonts* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [team installed-fonts]}] (let [fonts* (mf/use-state {}) fonts (deref fonts*) @@ -438,7 +435,6 @@ :on-edit on-edit}]]))])) (mf/defc installed-fonts* - {::mf/props :obj} [{:keys [fonts can-edit]}] (let [sterm (mf/use-state "") @@ -491,7 +487,6 @@ (l/derived :fonts st/state)) (mf/defc fonts-page* - {::mf/props :obj} [{:keys [team]}] (let [fonts (mf/deref ref:fonts) permissions (:permissions team) @@ -505,7 +500,6 @@ {:team team :fonts fonts :can-edit can-edit}]]])) (mf/defc font-providers-page* - {::mf/props :obj} [{:keys [team]}] [:* [:> header* {:team team :section :providers}] diff --git a/frontend/src/app/main/ui/dashboard/grid.cljs b/frontend/src/app/main/ui/dashboard/grid.cljs index 345a8a6075..c1a813adc4 100644 --- a/frontend/src/app/main/ui/dashboard/grid.cljs +++ b/frontend/src/app/main/ui/dashboard/grid.cljs @@ -84,8 +84,7 @@ (rx/mapcat (partial persist-thumbnail file-id revn)))) (mf/defc grid-item-thumbnail* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [can-edit file can-restore]}] (let [file-id (get file :id) revn (get file :revn) @@ -131,7 +130,6 @@ (deprecated-icon/icon-xref :menu (stl/css :menu-icon))) (mf/defc grid-item-library* - {::mf/props :obj} [{:keys [file can-restore]}] (mf/with-effect [file] (when file @@ -467,7 +465,6 @@ :can-restore can-restore}]])]]]]])) (mf/defc grid* - {::mf/props :obj} [{:keys [files project origin limit create-fn can-edit selected-files can-restore]}] (let [dragging? (mf/use-state false) project-id (get project :id) diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs index 1aa4282d7a..7bb0f0b60f 100644 --- a/frontend/src/app/main/ui/dashboard/import.cljs +++ b/frontend/src/app/main/ui/dashboard/import.cljs @@ -192,8 +192,7 @@ (swap! state update-entry-status message)))))) (mf/defc import-entry* - {::mf/props :obj - ::mf/memo true + {::mf/memo true ::mf/private true} [{:keys [entries entry edition can-be-deleted importing? on-edit on-change on-delete]}] (let [status (:status entry) diff --git a/frontend/src/app/main/ui/dashboard/libraries.cljs b/frontend/src/app/main/ui/dashboard/libraries.cljs index 8e265914dd..53908cd86a 100644 --- a/frontend/src/app/main/ui/dashboard/libraries.cljs +++ b/frontend/src/app/main/ui/dashboard/libraries.cljs @@ -27,7 +27,6 @@ st/state)) (mf/defc libraries-page* - {::mf/props :obj} [{:keys [team default-project]}] (let [files (mf/deref refs/shared-files) diff --git a/frontend/src/app/main/ui/dashboard/pin_button.cljs b/frontend/src/app/main/ui/dashboard/pin_button.cljs index ffa76fb6c3..5927e0493c 100644 --- a/frontend/src/app/main/ui/dashboard/pin_button.cljs +++ b/frontend/src/app/main/ui/dashboard/pin_button.cljs @@ -18,7 +18,6 @@ (deprecated-icon/icon-xref :pin (stl/css :icon))) (mf/defc pin-button* - {::mf/props :obj} [{:keys [aria-label is-pinned class] :as props}] (let [aria-label (or aria-label (tr "dashboard.pin-unpin")) class (dm/str (or class "") " " (stl/css-case :button true :button-active is-pinned)) diff --git a/frontend/src/app/main/ui/dashboard/projects.cljs b/frontend/src/app/main/ui/dashboard/projects.cljs index 04ca802352..e29b8bbdb0 100644 --- a/frontend/src/app/main/ui/dashboard/projects.cljs +++ b/frontend/src/app/main/ui/dashboard/projects.cljs @@ -48,7 +48,6 @@ (mf/defc header* {::mf/wrap [mf/memo] - ::mf/props :obj ::mf/private true} [{:keys [can-edit]}] (let [on-click (mf/use-fn #(st/emit! (dd/create-project)))] @@ -62,8 +61,7 @@ (tr "dashboard.new-project")])])) (mf/defc team-hero* - {::mf/wrap [mf/memo] - ::mf/props :obj} + {::mf/wrap [mf/memo]} [{:keys [team on-close]}] (let [on-nav-members-click (mf/use-fn #(st/emit! (dcm/go-to-dashboard-members))) @@ -102,8 +100,7 @@ close-icon]])) (mf/defc project-item* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [project is-first team files can-edit]}] (let [project-id (get project :id) team-id (get team :id) @@ -313,7 +310,6 @@ (l/derived :recent-files st/state)) (mf/defc projects-section* - {::mf/props :obj} [{:keys [team projects profile]}] (let [team-id (get team :id) diff --git a/frontend/src/app/main/ui/dashboard/search.cljs b/frontend/src/app/main/ui/dashboard/search.cljs index 126b038ca8..1c2b6ddb20 100644 --- a/frontend/src/app/main/ui/dashboard/search.cljs +++ b/frontend/src/app/main/ui/dashboard/search.cljs @@ -32,7 +32,6 @@ st/state)) (mf/defc search-page* - {::mf/props :obj} [{:keys [team search-term]}] (let [search-term (d/nilv search-term "") diff --git a/frontend/src/app/main/ui/dashboard/sidebar.cljs b/frontend/src/app/main/ui/dashboard/sidebar.cljs index 2cb4b31373..2faef9cbec 100644 --- a/frontend/src/app/main/ui/dashboard/sidebar.cljs +++ b/frontend/src/app/main/ui/dashboard/sidebar.cljs @@ -1123,8 +1123,7 @@ [:div {:class (stl/css-case :separator true :overflow-separator overflow?)}]]])) (mf/defc help-learning-menu* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [on-close on-click]}] (let [handle-click-url (mf/use-fn @@ -1168,8 +1167,7 @@ (tr "labels.give-feedback")])])) (mf/defc community-contributions-menu* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [on-close]}] (let [handle-click-url (mf/use-fn @@ -1199,8 +1197,7 @@ (tr "labels.community")]])) (mf/defc about-penpot-menu* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [on-close]}] (let [version cf/version show-release-notes @@ -1436,8 +1433,7 @@ nil))])) (mf/defc sidebar* - {::mf/props :obj - ::mf/wrap [mf/memo]} + {::mf/wrap [mf/memo]} [{:keys [team profile] :as props}] [:nav {:class (stl/css :dashboard-sidebar) :data-testid "dashboard-sidebar"} [:> sidebar-content* props] diff --git a/frontend/src/app/main/ui/dashboard/subscription.cljs b/frontend/src/app/main/ui/dashboard/subscription.cljs index ba07f40e25..5d1f16ea7c 100644 --- a/frontend/src/app/main/ui/dashboard/subscription.cljs +++ b/frontend/src/app/main/ui/dashboard/subscription.cljs @@ -118,7 +118,6 @@ :is-highlighted false}])))) (mf/defc nitrate-sidebar* - {::mf/props :obj} [{:keys [profile teams]}] (let [nitrate? (dnt/is-valid-license? profile) nitrate-license (:subscription profile) diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index 1698e8481b..871d46a11a 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -372,8 +372,7 @@ (st/emit! (dtm/update-member-role params)))) (mf/defc team-member* - {::mf/wrap [mf/memo] - ::mf/props :obj} + {::mf/wrap [mf/memo]} [{:keys [team member total-members profile]}] (let [member-id (:id member) @@ -501,8 +500,7 @@ :on-leave on-leave'}]]])) (mf/defc team-members* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [team profile]}] (let [members (get team :members) @@ -541,7 +539,6 @@ :total-members total-members}])]])) (mf/defc team-members-page* - {::mf/props :obj} [{:keys [team profile]}] (mf/with-effect [team] (dom/set-html-title @@ -570,7 +567,6 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (mf/defc invitation-role-selector* - {::mf/props :obj} [{:keys [can-invite role status on-change]}] (let [show? (mf/use-state false) label (cond @@ -616,8 +612,7 @@ (tr "labels.viewer")]]]])) (mf/defc invitation-actions* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [invitation team-id]}] (let [email (:email invitation) copied* (mf/use-state false) @@ -671,8 +666,7 @@ (mf/defc invitation-row* {::mf/wrap [mf/memo] - ::mf/private true - ::mf/props :obj} + ::mf/private true} [{:keys [invitation can-invite team-id selected on-select-change]}] (let [expired? (:expired invitation) @@ -738,8 +732,7 @@ :team-id team-id}])]])) (mf/defc empty-invitation-table* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [can-invite team]}] (let [route (mf/deref refs/route) @@ -879,8 +872,7 @@ (tr accept-key)]]]]])) (mf/defc invitation-section* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [team]}] (let [permissions (get team :permissions) invitations (mf/use-state (get team :invitations)) @@ -1064,7 +1056,6 @@ :on-select-change on-select-change}])])])) (mf/defc team-invitations-page* - {::mf/props :obj} [{:keys [team profile]}] (mf/with-effect [team] @@ -1218,7 +1209,6 @@ (tr "modals.create-webhook.submit-label"))}]]]]]])) (mf/defc webhooks-hero* - {::mf/props :obj} [] [:div {:class (stl/css :webhooks-hero-container)} [:h2 {:class (stl/css :hero-title)} @@ -1230,8 +1220,7 @@ (tr "dashboard.webhooks.create")]]) (mf/defc webhook-actions* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [on-edit on-delete can-edit]}] (let [show? (mf/use-state false) on-show (mf/use-fn #(reset! show? true)) @@ -1254,7 +1243,6 @@ (mf/defc webhook-item* {::mf/wrap [mf/memo] - ::mf/props :obj ::mf/private true} [{:keys [webhook permissions]}] (let [error-code (:error-code webhook) @@ -1320,8 +1308,7 @@ :can-edit can-edit}]]])) (mf/defc webhooks-list* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [webhooks permissions]}] [:div {:class (stl/css :table-rows :webhook-table)} (for [webhook webhooks] @@ -1331,7 +1318,6 @@ :permissions permissions}])]) (mf/defc webhooks-page* - {::mf/props :obj} [{:keys [team]}] (let [webhooks (:webhooks team)] @@ -1363,7 +1349,6 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (mf/defc team-settings-page* - {::mf/props :obj} [{:keys [team]}] (let [nitrate? (contains? cfg/flags :nitrate) finput (mf/use-ref) diff --git a/frontend/src/app/main/ui/dashboard/templates.cljs b/frontend/src/app/main/ui/dashboard/templates.cljs index d5f84c2862..8abccfc3e3 100644 --- a/frontend/src/app/main/ui/dashboard/templates.cljs +++ b/frontend/src/app/main/ui/dashboard/templates.cljs @@ -64,8 +64,7 @@ :on-finish-import on-finish})))) (mf/defc title* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [on-click is-collapsed]}] (let [on-key-down (mf/use-fn @@ -171,7 +170,6 @@ [:div {:class (stl/css :template-link-text)} (tr "dashboard.libraries-and-templates.explore")]]]]]])) (mf/defc templates-section* - {::mf/props :obj} [{:keys [default-project-id profile project-id team-id]}] (let [templates (mf/deref builtin-templates) templates (mf/with-memo [templates] diff --git a/frontend/src/app/main/ui/dashboard/templates.scss b/frontend/src/app/main/ui/dashboard/templates.scss index e48fc26de3..6b4b44600d 100644 --- a/frontend/src/app/main/ui/dashboard/templates.scss +++ b/frontend/src/app/main/ui/dashboard/templates.scss @@ -26,7 +26,7 @@ transition: inset-block-end 300ms; width: calc(100% - $sz-12); pointer-events: none; - z-index: var(--z-index-set); + z-index: var(--z-index-panels); &.collapsed { inset-block-end: calc(-1 * px2rem(228)); diff --git a/frontend/src/app/main/ui/exports/files.cljs b/frontend/src/app/main/ui/exports/files.cljs index ec937809ec..13652480c5 100644 --- a/frontend/src/app/main/ui/exports/files.cljs +++ b/frontend/src/app/main/ui/exports/files.cljs @@ -45,8 +45,7 @@ :files files})) (mf/defc export-entry* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [file]}] [:div {:class (stl/css-case :file-entry true diff --git a/frontend/src/app/main/ui/modal.cljs b/frontend/src/app/main/ui/modal.cljs index 6e9b1df7d4..4210d9ba40 100644 --- a/frontend/src/app/main/ui/modal.cljs +++ b/frontend/src/app/main/ui/modal.cljs @@ -46,8 +46,7 @@ (st/emit! (modal/hide))))) (mf/defc modal-wrapper* - {::mf/props :obj - ::mf/wrap [mf/memo]} + {::mf/wrap [mf/memo]} [{:keys [data]}] (let [wrapper-ref (mf/use-ref nil) components (mf/deref modal/components) @@ -82,7 +81,6 @@ (l/derived ::modal/modal st/state)) (mf/defc modal-container* - {::mf/props :obj} [] (let [container (hooks/use-portal-container :modal)] (when-let [modal (mf/deref ref:modal)] diff --git a/frontend/src/app/main/ui/settings/options.cljs b/frontend/src/app/main/ui/settings/options.cljs index 50b4b47424..7a2fc59413 100644 --- a/frontend/src/app/main/ui/settings/options.cljs +++ b/frontend/src/app/main/ui/settings/options.cljs @@ -7,16 +7,24 @@ (ns app.main.ui.settings.options (:require-macros [app.main.style :as stl]) (:require + [app.config :as cf] [app.main.data.notifications :as ntf] [app.main.data.profile :as du] [app.main.refs :as refs] + [app.main.router :as rt] [app.main.store :as st] [app.main.ui.components.forms :as fm] + [app.main.ui.ds.controls.switch :refer [switch*]] + [app.main.ui.ds.foundations.assets.icon :refer [icon*]] + [app.main.ui.ds.foundations.typography :as t] + [app.main.ui.ds.foundations.typography.heading :refer [heading*]] + [app.main.ui.ds.foundations.typography.text :refer [text*]] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [app.util.theme :as theme] [rumext.v2 :as mf])) + (def ^:private schema:options-form [:map {:title "OptionsForm"} [:lang {:optional true} [:string {:max 20}]] @@ -76,15 +84,45 @@ :data-testid "submit-lang-change" :class (stl/css :btn-primary)}]])) -;; --- Password Page +(defn ^:private go-settings-feedback + [event] + (dom/prevent-default event) + (st/emit! (rt/nav :settings-feedback))) + +(mf/defc webgl-settings* + [{:keys [renderer]}] + (let [wasm-renderer? (= renderer :wasm) + handle-render-change + (mf/use-fn + (fn [enabled?] + (st/emit! (du/update-profile-props {:renderer (if enabled? :wasm :svg)}) + (ntf/success (tr (if enabled? + "webgl.toast.webgl-render-enabled" + "webgl.toast.webgl-render-disabled"))))))] + [:section {:class (stl/css :webgl-container)} + [:header {:class (stl/css :webgl-header)} + [:> heading* {:class (stl/css :title) :level 2 :typography t/title-large} (tr "dashboard.webgl-switch.title")] + [:> text* {:as "span" :class (stl/css :beta) :typography t/body-small} (tr "dashboard.webgl-switch.beta")]] + [:> text* {:class (stl/css :description) :typography t/body-medium} (tr "dashboard.webgl-switch.description")] + [:form {:class (stl/css :webgl-form)} + [:> heading* {:level 3 :typography t/headline-small} (tr "dashboard.webgl-switch.status")] + [:> switch* {:label (if wasm-renderer? (tr "dashboard.webgl-switch.enabled") (tr "dashboard.webgl-switch.disabled")) + :default-checked wasm-renderer? + :on-change handle-render-change}]] + [:> text* {:typography t/body-medium :class (stl/css :feedback)} [:a {:href "#" :on-click go-settings-feedback :class (stl/css :link)} (tr "dashboard.webgl-switch.feedback") [:> icon* {:icon-id "arrow-up-right" :size "s"}]]]])) (mf/defc options-page [] - (mf/use-effect - #(dom/set-html-title (tr "title.settings.options"))) + (let [profile (mf/deref refs/profile) + renderer (or (-> profile :props :renderer) :svg)] + (mf/use-effect + #(dom/set-html-title (tr "title.settings.options"))) - [:div {:class (stl/css :dashboard-settings)} - [:div {:class (stl/css :form-container) :data-testid "settings-form"} - [:h2 (tr "labels.settings")] - [:& options-form {}]]]) + [:div {:class (stl/css :dashboard-settings)} + [:* + [:div {:class (stl/css :form-container) :data-testid "settings-form"} + [:h2 (tr "labels.settings")] + [:& options-form {}]] + (when (contains? cf/flags :render-switch) + [:> webgl-settings* {:renderer renderer}])]])) diff --git a/frontend/src/app/main/ui/settings/options.scss b/frontend/src/app/main/ui/settings/options.scss index 474e96838f..2df1d9235f 100644 --- a/frontend/src/app/main/ui/settings/options.scss +++ b/frontend/src/app/main/ui/settings/options.scss @@ -5,3 +5,59 @@ // Copyright (c) KALEIDOS INC @use "./profile" as *; +@use "ds/_sizes.scss" as *; +@use "ds/_borders.scss" as *; + +.dashboard-settings { + display: grid; +} + +.form-container { + &:first-child { + margin-block-end: var(--sp-xxxl); + } +} + +/* Copied from profile.scss .form-container, but without included nested + rules, since we want custom styles for it */ +.webgl-container { + display: grid; + grid-auto-rows: auto; + gap: var(--sp-s); + width: $sz-500; + margin-block-start: var(--sp-xxxl); + padding-block-start: var(--sp-xxxl); + margin-block-end: $sz-120; /* FIXME: this should be a proper token */ + border-block-start: $b-1 solid var(--color-background-quaternary); + color: var(--color-foreground-primary); +} + +.webgl-header { + display: flex; + flex-direction: row; + align-items: baseline; + gap: var(--sp-m); +} + +.description { + color: var(--color-foreground-secondary); +} + +.beta { + color: var(--color-accent-primary); + padding: var(--sp-xxs) var(--sp-s); + border: $b-1 solid var(--color-accent-primary); + border-radius: $br-4; +} + +.title { + color: var(--color-foreground-primary); +} + +.link { + color: var(--color-accent-primary); + text-decoration: none; + display: flex; + align-items: center; + gap: var(--sp-xs); +} diff --git a/frontend/src/app/main/ui/settings/subscription.cljs b/frontend/src/app/main/ui/settings/subscription.cljs index 687329cbf0..285f3e4c66 100644 --- a/frontend/src/app/main/ui/settings/subscription.cljs +++ b/frontend/src/app/main/ui/settings/subscription.cljs @@ -25,7 +25,6 @@ [rumext.v2 :as mf])) (mf/defc plan-card* - {::mf/props :obj} [{:keys [card-title card-title-icon price-value price-period diff --git a/frontend/src/app/main/ui/static.cljs b/frontend/src/app/main/ui/static.cljs index b5337616f6..a13d6f76d8 100644 --- a/frontend/src/app/main/ui/static.cljs +++ b/frontend/src/app/main/ui/static.cljs @@ -41,7 +41,6 @@ (def TimeoutError rxjs/TimeoutError) (mf/defc error-container* - {::mf/props :obj} [{:keys [children]}] (let [profile-id (:profile-id @st/state) on-nav-root (mf/use-fn #(st/emit! (rt/nav-root)))] @@ -186,7 +185,6 @@ [:& recovery-sent-page {:email @user-email}]])]]])) (mf/defc request-dialog* - {::mf/props :obj} [{:keys [title content button-text on-button-click cancel-text on-close]}] (let [on-click (or on-button-click on-close)] [:div {:class (stl/css :overlay)} @@ -532,7 +530,6 @@ children]) (mf/defc exception-page* - {::mf/props :obj} [{:keys [data route] :as props}] (let [type (:type data) diff --git a/frontend/src/app/main/ui/viewer.cljs b/frontend/src/app/main/ui/viewer.cljs index 8ef3e26056..47e6d2aa2b 100644 --- a/frontend/src/app/main/ui/viewer.cljs +++ b/frontend/src/app/main/ui/viewer.cljs @@ -278,7 +278,6 @@ :zoom zoom}])]]) (mf/defc viewer-content* - {::mf/props :obj} [{:keys [data page-id share-id section index interactions-mode share]}] (let [{:keys [file users project permissions]} data allowed (or diff --git a/frontend/src/app/main/ui/viewer/comments.cljs b/frontend/src/app/main/ui/viewer/comments.cljs index 9646c81f8c..ffc3992366 100644 --- a/frontend/src/app/main/ui/viewer/comments.cljs +++ b/frontend/src/app/main/ui/viewer/comments.cljs @@ -235,7 +235,6 @@ :zoom zoom}])]]])) (mf/defc comments-sidebar* - {::mf/props :obj} [{:keys [profiles frame page]}] (let [profile (mf/deref refs/profile) local (mf/deref refs/comments-local) diff --git a/frontend/src/app/main/ui/viewer/interactions.cljs b/frontend/src/app/main/ui/viewer/interactions.cljs index ae4bec1f1e..be73931031 100644 --- a/frontend/src/app/main/ui/viewer/interactions.cljs +++ b/frontend/src/app/main/ui/viewer/interactions.cljs @@ -219,8 +219,7 @@ :fixed? fixed?}])) (mf/defc flows-menu* - {::mf/wrap [mf/memo] - ::mf/props :obj} + {::mf/wrap [mf/memo]} [{:keys [page index]}] (let [flows (not-empty (:flows page)) frames (:frames page) @@ -268,7 +267,6 @@ [:span {:class (stl/css :icon)} deprecated-icon/tick])])]]]))) (mf/defc interactions-menu* - {::mf/props :obj} [{:keys [interactions-mode]}] (let [show-dropdown? (mf/use-state false) toggle-dropdown (mf/use-fn #(swap! show-dropdown? not)) diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index 7b645384bb..c70764ffd3 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -55,8 +55,7 @@ (dom/stop-propagation event)) (mf/defc menu-entry* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [title shortcut on-click on-pointer-enter on-pointer-leave on-unmount children is-selected icon disabled value]}] (let [submenu-ref (mf/use-ref nil) @@ -143,14 +142,12 @@ children])]))) (mf/defc menu-separator* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [] [:li {:class (stl/css :separator)}]) (mf/defc context-menu-edit* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [shapes]}] (let [multiple? (> (count shapes) 1) @@ -252,8 +249,7 @@ [:> menu-separator* {}]])) (mf/defc context-menu-layer-position* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [shapes]}] (let [do-bring-forward (mf/use-fn #(st/emit! (dw/vertical-order-selected :up))) do-bring-to-front (mf/use-fn #(st/emit! (dw/vertical-order-selected :top))) @@ -299,8 +295,7 @@ [:> menu-separator* {}]])) (mf/defc context-menu-flip* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [] (let [do-flip-vertical #(st/emit! (dw/flip-vertical-selected)) do-flip-horizontal #(st/emit! (dw/flip-horizontal-selected))] @@ -315,8 +310,7 @@ [:> menu-separator* {}]])) (mf/defc context-menu-thumbnail* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [shapes]}] (let [single? (= (count shapes) 1) has-frame? (some cfh/frame-shape? shapes) @@ -332,8 +326,7 @@ [:> menu-separator* {}]]))) (mf/defc context-menu-rename* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [shapes]}] (let [do-rename #(st/emit! (dw/start-rename-selected))] (when (= (count shapes) 1) @@ -344,8 +337,7 @@ :on-click do-rename}]]))) (mf/defc context-menu-group* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [shapes]}] (let [multiple? (> (count shapes) 1) single? (= (count shapes) 1) @@ -398,8 +390,7 @@ [:> menu-separator* {}]])])) (mf/defc context-focus-mode-menu* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [] (let [focus (mf/deref refs/workspace-focus-selected) do-toggle-focus-mode #(st/emit! (dw/toggle-focus-mode))] @@ -411,8 +402,7 @@ :on-click do-toggle-focus-mode}])) (mf/defc context-menu-path* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [shapes objects disable-flatten disable-booleans]}] (let [multiple? (> (count shapes) 1) single? (= (count shapes) 1) @@ -489,8 +479,7 @@ :on-click do-transform-to-path}]])])])) (mf/defc context-menu-layer-options* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [shapes]}] (let [ids (mapv :id shapes) do-show-shape #(st/emit! (dw/update-shape-flags ids {:hidden false})) @@ -515,8 +504,7 @@ :on-click do-lock-shape}])])) (mf/defc context-menu-prototype* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [shapes]}] (let [flows (mf/deref refs/workspace-page-flows) options-mode (mf/deref refs/options-mode-global) @@ -537,8 +525,7 @@ :on-click do-add-flow}])))) (mf/defc context-menu-layout* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [shapes]}] (let [single? (= (count shapes) 1) objects (deref refs/workspace-page-objects) @@ -667,8 +654,7 @@ :on-click do-remove-guides}]]))) (mf/defc context-menu-delete* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [] (let [do-delete #(st/emit! (dw/delete-selected))] [:* @@ -711,8 +697,7 @@ [:> context-menu-delete* props]]))) (mf/defc page-item-context-menu* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [mdata]}] (let [page (:page mdata) deletable? (:deletable? mdata) @@ -739,7 +724,6 @@ :on-click do-duplicate}]])) (mf/defc viewport-context-menu* - {::mf/props :obj} [] (let [focus (mf/deref refs/workspace-focus-selected) read-only? (mf/use-ctx ctx/workspace-read-only?) @@ -762,8 +746,7 @@ :on-click do-toggle-focus-mode}])])) (mf/defc grid-track-context-menu* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [mdata]}] (let [{:keys [type index grid-id]} mdata do-delete-track @@ -812,8 +795,7 @@ [:> menu-entry* {:title (tr "workspace.context-menu.grid-track.row.delete-shapes") :on-click do-delete-track-shapes}]]))) (mf/defc grid-cells-context-menu* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [mdata]}] (let [{:keys [grid cells]} mdata diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index 6b9532225a..00834db2ca 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -107,8 +107,7 @@ "\u00A0"))) (mf/defc library-description* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [summary]}] (let [components-count (get summary :components) graphics-count (get summary :graphics) @@ -133,8 +132,7 @@ (tr "workspace.libraries.typography" (c typography-count))])])) (mf/defc sample-library-entry* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [library importing]}] (let [id (:id library) importing? (deref importing) @@ -185,8 +183,7 @@ (not (ctob/empty-lib? tokens-lib)))) (mf/defc libraries-tab* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [is-shared linked-libraries shared-libraries]}] (let [file-id (mf/use-ctx ctx/current-file-id) search-term* (mf/use-state "") @@ -485,8 +482,7 @@ :typographies typographies}])) (mf/defc updates-tab* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [file-id libraries]}] ;; FIXME: naming (let [summary?* (mf/use-state true) diff --git a/frontend/src/app/main/ui/workspace/main_menu.cljs b/frontend/src/app/main/ui/workspace/main_menu.cljs index 02ba5b8bb5..ac3a44b57b 100644 --- a/frontend/src/app/main/ui/workspace/main_menu.cljs +++ b/frontend/src/app/main/ui/workspace/main_menu.cljs @@ -18,6 +18,7 @@ [app.main.data.exports.assets :as de] [app.main.data.exports.files :as fexp] [app.main.data.modal :as modal] + [app.main.data.notifications :as ntf] [app.main.data.plugins :as dp] [app.main.data.profile :as du] [app.main.data.shortcuts :as scd] @@ -226,8 +227,10 @@ (mf/defc preferences-menu* {::mf/private true ::mf/wrap [mf/memo]} - [{:keys [layout profile toggle-flag on-close toggle-theme]}] - (let [show-nudge-options + [{:keys [layout profile toggle-flag on-close toggle-theme toggle-render]}] + (let [renderer (or (-> profile :props :renderer) :svg) + + show-nudge-options (mf/use-fn #(modal/show! {:type :nudge-option}))] @@ -321,7 +324,17 @@ "light" (tr "workspace.header.menu.toggle-system-theme") "system" (tr "workspace.header.menu.toggle-dark-theme") (tr "workspace.header.menu.toggle-light-theme"))] - [:> shortcuts* {:id :toggle-theme}]]])) + [:> shortcuts* {:id :toggle-theme}]] + (when (contains? cf/flags :render-switch) + [:> dropdown-menu-item* {:on-click toggle-render + :class (stl/css :base-menu-item :submenu-item) + :on-key-down (fn [event] + (when (kbd/enter? event) + (toggle-render event)))} + [:span {:class (stl/css :item-name)} + (if (= renderer :wasm) + (tr "workspace.header.menu.disable-webgl") + (tr "workspace.header.menu.enable-webgl"))]])])) (mf/defc view-menu* {::mf/private true @@ -920,6 +933,18 @@ (dom/stop-propagation event) (st/emit! (du/toggle-theme)))) + toggle-render + (mf/use-fn + (mf/deps profile) + (fn [event] + (dom/stop-propagation event) + (let [renderer (or (-> profile :props :renderer) :svg) + next-renderer (if (= renderer :wasm) :svg :wasm)] + (st/emit! (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 (fn [event] @@ -1099,6 +1124,7 @@ :profile profile :toggle-flag toggle-flag :toggle-theme toggle-theme + :toggle-render toggle-render :on-close close-sub-menu}] :plugins diff --git a/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs index e81f841baf..6e96297ebc 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs @@ -418,9 +418,10 @@ (cond-> #js {:pointerEvents "all"} render-wasm? (obj/merge! - #js {"--editor-container-width" (dm/str width "px") - "--editor-container-height" (dm/str height "px") - "--fallback-families" (if (seq fallback-families) (dm/str (str/join ", " fallback-families)) "sourcesanspro")}) + #js {"--editor-container-width" "auto" + "--editor-container-height" "auto" + "--fallback-families" (if (seq fallback-families) (dm/str (str/join ", " fallback-families)) "sourcesanspro") + :display "flex"}) (not render-wasm?) (obj/merge! diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 206a16f8e0..72a709bfb5 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -29,7 +29,6 @@ (mf/defc assets-libraries* {::mf/wrap [mf/memo] - ::mf/props :obj ::mf/private true} [{:keys [filters]}] (let [file-id (mf/use-ctx ctx/current-file-id) diff --git a/frontend/src/app/main/ui/workspace/tokens/settings/menu.cljs b/frontend/src/app/main/ui/workspace/tokens/settings/menu.cljs index 8469d83f28..a26400d7cd 100644 --- a/frontend/src/app/main/ui/workspace/tokens/settings/menu.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/settings/menu.cljs @@ -26,7 +26,6 @@ [rumext.v2 :as mf])) (mf/defc token-settings* - {::mf/wrap-props false} [] (let [file-data (deref refs/workspace-data) base-font-size* (mf/use-state #(ctf/get-base-font-size file-data)) diff --git a/frontend/src/app/main/ui/workspace/viewport/gradients.cljs b/frontend/src/app/main/ui/workspace/viewport/gradients.cljs index cd2623fd47..525a4fd7bb 100644 --- a/frontend/src/app/main/ui/workspace/viewport/gradients.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/gradients.cljs @@ -448,7 +448,6 @@ :fill "var(--app-white)"}]))])) (mf/defc gradient-handlers-impl* - {::mf/props :obj} [{:keys [zoom stops gradient editing shape]}] (let [transform (gsh/transform-matrix shape) transform-inverse (gsh/inverse-transform-matrix shape) @@ -517,8 +516,7 @@ :on-change-width on-change-width}])) (mf/defc gradient-handlers* - {::mf/wrap [mf/memo] - ::mf/props :obj} + {::mf/wrap [mf/memo]} [{:keys [id zoom]}] (let [shape-ref (mf/use-memo (mf/deps id) #(refs/object-by-id id)) shape (mf/deref shape-ref) diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index 36d9d7e8e6..46322c0193 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -370,7 +370,7 @@ :else (show-unavailable)))))] (reset! canvas-init? false) - (->> wasm.api/module + (->> @wasm.api/module (p/fmap (fn [ready?] (when ready? (try-init 3))))) @@ -378,6 +378,7 @@ (vreset! unmounted? true) (when-let [timeout-id @timeout-id-ref] (js/clearTimeout timeout-id)) + (wasm.api/end-page-transition!) (wasm.api/clear-canvas))))) (mf/with-effect [show-text-editor? workspace-editor-state edition] diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 9a06f0bcf1..b3737206ce 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -1077,16 +1077,18 @@ (defn intersect-position-in-shape [id position] - (let [buffer (uuid/get-u32 id) - result - (h/call wasm/internal-module "_intersect_position_in_shape" - (aget buffer 0) - (aget buffer 1) - (aget buffer 2) - (aget buffer 3) - (:x position) - (:y position))] - (= result 1))) + (if (and wasm/context-initialized? (not @wasm/context-lost?)) + (let [buffer (uuid/get-u32 id) + result + (h/call wasm/internal-module "_intersect_position_in_shape" + (aget buffer 0) + (aget buffer 1) + (aget buffer 2) + (aget buffer 3) + (:x position) + (:y position))] + (= result 1)) + false)) (def render-finish (letfn [(do-render [] diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 5de7250a59..4b774cd775 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1216,6 +1216,27 @@ msgstr "Type to search results" msgid "dashboard.unpublish-shared" msgstr "Unpublish Library" +msgid "dashboard.webgl-switch.title" +msgstr "WebGL rendering" + +msgid "dashboard.webgl-switch.beta" +msgstr "Beta" + +msgid "dashboard.webgl-switch.description" +msgstr "WebGL rendering can improve performance, but it is in beta and may be less stable. Some visual differences may appear between the canvas, exports (SVG/PDF), and view mode." + +msgid "dashboard.webgl-switch.status" +msgstr "Status" + +msgid "dashboard.webgl-switch.enabled" +msgstr "Enabled" + +msgid "dashboard.webgl-switch.disabled" +msgstr "Disabled" + +msgid "dashboard.webgl-switch.feedback" +msgstr "Give feedback" + #:src/app/main/ui/workspace/tokens/import_from_library.cljs msgid "modals.import-library-tokens.title" msgstr "Import tokens from library?" @@ -5995,6 +6016,12 @@ msgstr "Switch to light theme" msgid "workspace.header.menu.toggle-system-theme" msgstr "Switch to system theme" +msgid "workspace.header.menu.enable-webgl" +msgstr "Enable WebGL rendering (beta)" + +msgid "workspace.header.menu.disable-webgl" +msgstr "Disable WebGL rendering (beta)" + #: src/app/main/ui/workspace/main_menu.cljs:492 msgid "workspace.header.menu.undo" msgstr "Undo" @@ -9196,6 +9223,12 @@ msgstr "Projects and files will remain available to team members, but the organi msgid "modals.remove-team-org.accept" msgstr "Remove from organization" +msgid "webgl.toast.webgl-render-enabled" +msgstr "WebGL rendering enabled" + +msgid "webgl.toast.webgl-render-disabled" +msgstr "WebGL rendering disabled" + #, unused msgid "workspace.viewport.click-to-close-path" msgstr "Click to close the path" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index d0da81200b..eadfd7b51d 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -1291,6 +1291,27 @@ msgstr "Tu nombre" msgid "dashboard.your-penpot" msgstr "Tu Penpot" +msgid "dashboard.webgl-switch.title" +msgstr "Renderizado WebGL" + +msgid "dashboard.webgl-switch.beta" +msgstr "Beta" + +msgid "dashboard.webgl-switch.description" +msgstr "El renderizado WebGL puede mejorar el rendimiento, pero está en beta y puede ser menos estable. Pueden aparecer diferencias visuales entre el lienzo, las exportaciones (SVG/PDF) y el modo de vista." + +msgid "dashboard.webgl-switch.status" +msgstr "Estado" + +msgid "dashboard.webgl-switch.enabled" +msgstr "Activado" + +msgid "dashboard.webgl-switch.disabled" +msgstr "Desactivado" + +msgid "dashboard.webgl-switch.feedback" +msgstr "Enviar comentarios" + #: src/app/main/ui/alert.cljs:35 msgid "ds.alert-ok" msgstr "Ok" @@ -5906,6 +5927,13 @@ msgstr "Cambiar a tema claro" msgid "workspace.header.menu.toggle-system-theme" msgstr "Cambiar a tema del sistema" +msgid "workspace.header.menu.enable-webgl" +msgstr "Activar renderizado WebGL (beta)" + +msgid "workspace.header.menu.disable-webgl" +msgstr "Desactivar renderizado WebGL (beta)" + + #: src/app/main/ui/workspace/main_menu.cljs:492 msgid "workspace.header.menu.undo" msgstr "Deshacer" @@ -8937,6 +8965,12 @@ msgstr "Los proyectos y archivos seguirán estando disponibles para los miembros msgid "modals.remove-team-org.accept" msgstr "Eliminar de la organización" +msgid "webgl.toast.webgl-render-enabled" +msgstr "Renderizado WebGL activado" + +msgid "webgl.toast.webgl-render-disabled" +msgstr "Renderizado WebGL desactivado" + #, unused msgid "workspace.viewport.click-to-close-path" msgstr "Pulsar para cerrar la ruta" diff --git a/package.json b/package.json index d2d6a9f5a8..8062f6315e 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,9 @@ "fmt": "./scripts/fmt" }, "devDependencies": { - "@github/copilot": "^1.0.35", + "@github/copilot": "^1.0.36", "@types/node": "^25.6.0", "esbuild": "^0.28.0", - "opencode-ai": "^1.14.22" + "opencode-ai": "^1.14.28" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cf5a098662..647cecdd12 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: devDependencies: '@github/copilot': - specifier: ^1.0.35 - version: 1.0.35 + specifier: ^1.0.36 + version: 1.0.36 '@types/node': specifier: ^25.6.0 version: 25.6.0 @@ -18,8 +18,8 @@ importers: specifier: ^0.28.0 version: 0.28.0 opencode-ai: - specifier: ^1.14.22 - version: 1.14.22 + specifier: ^1.14.28 + version: 1.14.28 packages: @@ -179,44 +179,44 @@ packages: cpu: [x64] os: [win32] - '@github/copilot-darwin-arm64@1.0.35': - resolution: {integrity: sha512-NNZE0TOz0HOlv7eqlh6EcQbNkhtnIHReBLieW6pfDUUTKkgsqbUu1MOitF8m+LUQk3ml1T0MQ5MOfad1HSa/MQ==} + '@github/copilot-darwin-arm64@1.0.36': + resolution: {integrity: sha512-5qkb7frTS4K/LdTDLrzKo78VR4aw/EZ6JzLz4KfmaW4UYyPiNirExDFXa/By22X0o8YMfOp4MCA2KSCAxKdgTg==} cpu: [arm64] os: [darwin] hasBin: true - '@github/copilot-darwin-x64@1.0.35': - resolution: {integrity: sha512-XCv/mfdv0rnrtrNVOluio/N/kyCge0uG2hghvtlgO/+z6EjvzFygkpXXS1gVxiXhWc3lX232cTXQU3zklC/8Ng==} + '@github/copilot-darwin-x64@1.0.36': + resolution: {integrity: sha512-AdsM8QtM5QSzMLpavLREh8HALO5G+VWzGNQqIHu4f0YQC/s1cGoiwo3wsgkpxRcLGBykFc+bDX3yK3MDQ8XvSw==} cpu: [x64] os: [darwin] hasBin: true - '@github/copilot-linux-arm64@1.0.35': - resolution: {integrity: sha512-mbaadATfJPzmXq2SD1TWocIG/GobcYC6OvNFhCG8UXMsiXY5cevhszl5ujuayhPJBxS77Yj5uvIFjNQ1Kf5V8Q==} + '@github/copilot-linux-arm64@1.0.36': + resolution: {integrity: sha512-n7K1I6r0ggOJ4A9uAMS11USTvn6BKtAwvrOkzEaeRK89VNUJzpTe6p0mE13ItzRe5eot9WLBQOxvXLtL9f6E+g==} cpu: [arm64] os: [linux] hasBin: true - '@github/copilot-linux-x64@1.0.35': - resolution: {integrity: sha512-NrZ0VjztdBbJ5qAmuUtuKsWkimOaqzjDV+ZGUv1FxSxoys40kiiakQ5WbnMFDzaIFaf47zDi++6ixgQzq7Jk5A==} + '@github/copilot-linux-x64@1.0.36': + resolution: {integrity: sha512-wBtCdR3ITZcq07BJbkwHfwI6ayiwbH5pF1ex+Ycl4UI+Lf1vP9eQD6wJppPgsrjwFcdeWRThaYTPCRTkSGHv5g==} cpu: [x64] os: [linux] hasBin: true - '@github/copilot-win32-arm64@1.0.35': - resolution: {integrity: sha512-KQN7Q7+oPyglmvUEiMp6SYWjl30VSu91T0dUpNHbUs/xRM3qgnCymLPPUyBZGWHog/FueUAsRkhisMHWQVnO+g==} + '@github/copilot-win32-arm64@1.0.36': + resolution: {integrity: sha512-0GzZUZQn07alI8BgbzK0NlR5+ta/Rd0sWmd8kbRCns7oybAIkSALy6BKVwJmVHtXUi6h4iUE8oiFhkn0spymvw==} cpu: [arm64] os: [win32] hasBin: true - '@github/copilot-win32-x64@1.0.35': - resolution: {integrity: sha512-J0XhXO2FmlFr8pGa970xEd4tr1rqFiZxoaPW5WvkJYZoZUHbBhFcGasp5/yEeJ71b3vI4PHm/mSZZebD3ALMKQ==} + '@github/copilot-win32-x64@1.0.36': + resolution: {integrity: sha512-UBX9qj0McCK/SLq93XIr1i80fj3b3XmE3befVFrzxQuTeOoxLURN35vi7W+4x+4ZfsDHQpRTlJNjZw9w0fPr+Q==} cpu: [x64] os: [win32] hasBin: true - '@github/copilot@1.0.35': - resolution: {integrity: sha512-O1nUy8DXOTE+v86b/FTkyu09EMrDy+vj+2rhmUOcmsXGe0RE5ECyESsasUTUoHK/CSgAExFTziNxbubUoiMMfg==} + '@github/copilot@1.0.36': + resolution: {integrity: sha512-x0N5wLzw+tANzb+vCFYLHn3BV3qii2oyn14wC20RO7SsS8/YeBH8olvwlDLJ4PB0mL17QOiytNCdkvjvprm28w==} hasBin: true '@types/node@25.6.0': @@ -227,67 +227,67 @@ packages: engines: {node: '>=18'} hasBin: true - opencode-ai@1.14.22: - resolution: {integrity: sha512-J+q1Ehlfg7SSXw2aIY8Mb47FHhPTN8IciKNt0/D+H/brO8RWLe67WjFzxhh/z9SSad9wPcCiLRGAc/iAn8W8wA==} + opencode-ai@1.14.28: + resolution: {integrity: sha512-ZPukJNvujSVa+LVoXvj2ciUV57UcnuxmMtzpFQBYd6fbhjeT1vMC6jCurO/5mIp76fiPmGM7ilzRXVeY6bIwPw==} hasBin: true - opencode-darwin-arm64@1.14.22: - resolution: {integrity: sha512-h9FjzNoDRsuJD0EEg535P9ul5TyrWovwx591VmuG8fp9d4PoSrAN1O3Zi07GJjkrYyrB8g3c+x5whDqJCz+qog==} + opencode-darwin-arm64@1.14.28: + resolution: {integrity: sha512-Gu2vZYACAeoewfPhgJDAaScwRo1K5YZq7tVpPKw2rpul34OpOPLk4oB4Pmr539iWiagK+DLuUxnbJIbRRYCS5g==} cpu: [arm64] os: [darwin] - opencode-darwin-x64-baseline@1.14.22: - resolution: {integrity: sha512-GgfP0wSm9/I+j3shOxfeA++7yZpXS6Y1Vis258nEFoRS9Xfv3YlHom7c/8BR9rYqeUE/+rrijP7PrGWGl+IHBw==} + opencode-darwin-x64-baseline@1.14.28: + resolution: {integrity: sha512-/KsZkZh5oh6urHWwIHJudS6sedBil59E/4o7/7TuxPy/pOdRlSlSWVkMJd20AmqM4G/qILF/GthXy3D2+f99Tg==} cpu: [x64] os: [darwin] - opencode-darwin-x64@1.14.22: - resolution: {integrity: sha512-cyKRo22sxDwu4ITOlENwXaqVM9kMGndwSaAd95gz1Rmz5NYMShUO/8eckrD2MhS2wm+QvKw9XkRVWVHWQlZw3Q==} + opencode-darwin-x64@1.14.28: + resolution: {integrity: sha512-D6BnAXlSdQDRtZgAg6OxWT6CEzbbONnlYof9hdPbaIIaNyBLjqK+Er2O1rrbiXFhSbs7YHBiDoGd+nNUymx4Ng==} cpu: [x64] os: [darwin] - opencode-linux-arm64-musl@1.14.22: - resolution: {integrity: sha512-DtSd5tbGk6R5+hGhqViSvbY8ICf+u4oVQhfvCAplQCb1UEwYVc0+oAF6PimFJ+o8i8L6x14O0rry0NaRzZ0CzA==} + opencode-linux-arm64-musl@1.14.28: + resolution: {integrity: sha512-7R1GHqSg/UuT9r77GF2skh8r66WkZcphmDWAWaV2dmptJlxEJeV9I2jbE2i8Ctp4BzPUexFqfSoBA82S9Dcf+A==} cpu: [arm64] os: [linux] - opencode-linux-arm64@1.14.22: - resolution: {integrity: sha512-ohK4LkkGvzB4ptr0nqDOVi2JEJMLROfy1s2U2A4Qrh+1Y0QimgH2b5VgTm+BjA3bC2Hm8Yf/IfkitqlUnCp7YA==} + opencode-linux-arm64@1.14.28: + resolution: {integrity: sha512-jdTrs4YpPGFGZOMLuiaSfOUzkjAA+lnIEaW6HYLvaey3WsBnu3S4utaBhXURincp20H1JPQcahDOe+jjGZH7xw==} cpu: [arm64] os: [linux] - opencode-linux-x64-baseline-musl@1.14.22: - resolution: {integrity: sha512-oZffotEbGXbA38Y0Dmj7IVq0ATl3nKbP8j91Z0zR5kBEBykOqExJIyc9pZpModgfPf86k98XBsRHiVLK4u9ARw==} + opencode-linux-x64-baseline-musl@1.14.28: + resolution: {integrity: sha512-GKxZXj8/Mbutfs1DW4v0/rEWcAQrD/RUI9kV9VhMoNA8vUt0nuA3H9UvbFXh9EJj2C+RBSPLlMGal++oCH4c4w==} cpu: [x64] os: [linux] - opencode-linux-x64-baseline@1.14.22: - resolution: {integrity: sha512-J67YAIWr3E03o9e6wNaPEqBo+9FcPKf5CzjIUSb8yNDyobWON1HHihcuu0hCJ6wF9J9awmlp2/4mO1HOoCo3QQ==} + opencode-linux-x64-baseline@1.14.28: + resolution: {integrity: sha512-Dtl+xjEAKaWNk2l3iC9ebwi79BkChHIdtx97ksZKTLjAeR424Zh3vnjuWjpMYk9YAnesVlwL8y4kHs2Y736Zpw==} cpu: [x64] os: [linux] - opencode-linux-x64-musl@1.14.22: - resolution: {integrity: sha512-r+QnqwR/OPmMm197Kb8VLD9mkZGFXz4m5QCZFxOAL34k8AhQZqn3d2mx2bfrMBVfoSiSVxa3jEjZEbNNFGlICQ==} + opencode-linux-x64-musl@1.14.28: + resolution: {integrity: sha512-XyzWl35L8N6El/hxAM28bDUHLCY0aujMtprDTCYXVckeNxBkN3idM4EfdLtJaUHkE6bqMr+m6wXQl4oYDoOtpg==} cpu: [x64] os: [linux] - opencode-linux-x64@1.14.22: - resolution: {integrity: sha512-MSUaO/Cvfb8DFRYETVrVeCnKtoIfgLflyB+O8xQOkVtjMKJ41M+1dFSMyZ3LQa2Vfp5tDskyMhj7eUxvT/owgQ==} + opencode-linux-x64@1.14.28: + resolution: {integrity: sha512-XnpQrud15bvUBvOI58tOGUBTrwqKHl6bYQ3eoy5HhGa2spUnRv3B/HU8QiS6QuNbmkPxRPR+vuTGtBYQvtRGPw==} cpu: [x64] os: [linux] - opencode-windows-arm64@1.14.22: - resolution: {integrity: sha512-8grcxLSf9BD9Bt38MIxXfkI6aOFophVgM0US5r8nAUdVU78/8TS9Flnn6D39GM5RmxzqGWMl1u10vMFrBtMwPA==} + opencode-windows-arm64@1.14.28: + resolution: {integrity: sha512-emR1oEoLe6soASahJNX6IwR9x8rJkbwBXDnXNTWQcGdSxKBMD4/cLkq84k/5zqLfB7dbUChTw7eFz7u8Sa5VQw==} cpu: [arm64] os: [win32] - opencode-windows-x64-baseline@1.14.22: - resolution: {integrity: sha512-R/o36LpmQmbv/tL2pkcmApn6030z/1oJIYmjDkW5a4K5MXmV7aq+jWrH5p6iYKp9fo9L8oCtOp/rELMBqDS3UA==} + opencode-windows-x64-baseline@1.14.28: + resolution: {integrity: sha512-ARKHTThHezib44QPLiivYI8c71iNE9iNDubwV5XxUhM2FtzMJkZGma+EgbcCsXwY5r0lAsarzzDMqYB0YfCZ1A==} cpu: [x64] os: [win32] - opencode-windows-x64@1.14.22: - resolution: {integrity: sha512-jVbZ4VA5b5MF2QhWQOE1VYBKdBE0v/ZebFjwzs6Vieazfgr6OFnGSHVP5WJbU/r6zDssbTBzzpnFxo0IY1SQWw==} + opencode-windows-x64@1.14.28: + resolution: {integrity: sha512-tEpblIEdmlJ7npo5Bq+1O7uup9jCOyqnnA63t+3JQiNQ1et3UTjNb5ruAjb7sudUer6i5MlQCwNXBjitjuU4Kg==} cpu: [x64] os: [win32] @@ -374,32 +374,32 @@ snapshots: '@esbuild/win32-x64@0.28.0': optional: true - '@github/copilot-darwin-arm64@1.0.35': + '@github/copilot-darwin-arm64@1.0.36': optional: true - '@github/copilot-darwin-x64@1.0.35': + '@github/copilot-darwin-x64@1.0.36': optional: true - '@github/copilot-linux-arm64@1.0.35': + '@github/copilot-linux-arm64@1.0.36': optional: true - '@github/copilot-linux-x64@1.0.35': + '@github/copilot-linux-x64@1.0.36': optional: true - '@github/copilot-win32-arm64@1.0.35': + '@github/copilot-win32-arm64@1.0.36': optional: true - '@github/copilot-win32-x64@1.0.35': + '@github/copilot-win32-x64@1.0.36': optional: true - '@github/copilot@1.0.35': + '@github/copilot@1.0.36': optionalDependencies: - '@github/copilot-darwin-arm64': 1.0.35 - '@github/copilot-darwin-x64': 1.0.35 - '@github/copilot-linux-arm64': 1.0.35 - '@github/copilot-linux-x64': 1.0.35 - '@github/copilot-win32-arm64': 1.0.35 - '@github/copilot-win32-x64': 1.0.35 + '@github/copilot-darwin-arm64': 1.0.36 + '@github/copilot-darwin-x64': 1.0.36 + '@github/copilot-linux-arm64': 1.0.36 + '@github/copilot-linux-x64': 1.0.36 + '@github/copilot-win32-arm64': 1.0.36 + '@github/copilot-win32-x64': 1.0.36 '@types/node@25.6.0': dependencies: @@ -434,55 +434,55 @@ snapshots: '@esbuild/win32-ia32': 0.28.0 '@esbuild/win32-x64': 0.28.0 - opencode-ai@1.14.22: + opencode-ai@1.14.28: optionalDependencies: - opencode-darwin-arm64: 1.14.22 - opencode-darwin-x64: 1.14.22 - opencode-darwin-x64-baseline: 1.14.22 - opencode-linux-arm64: 1.14.22 - opencode-linux-arm64-musl: 1.14.22 - opencode-linux-x64: 1.14.22 - opencode-linux-x64-baseline: 1.14.22 - opencode-linux-x64-baseline-musl: 1.14.22 - opencode-linux-x64-musl: 1.14.22 - opencode-windows-arm64: 1.14.22 - opencode-windows-x64: 1.14.22 - opencode-windows-x64-baseline: 1.14.22 + opencode-darwin-arm64: 1.14.28 + opencode-darwin-x64: 1.14.28 + opencode-darwin-x64-baseline: 1.14.28 + opencode-linux-arm64: 1.14.28 + opencode-linux-arm64-musl: 1.14.28 + opencode-linux-x64: 1.14.28 + opencode-linux-x64-baseline: 1.14.28 + opencode-linux-x64-baseline-musl: 1.14.28 + opencode-linux-x64-musl: 1.14.28 + opencode-windows-arm64: 1.14.28 + opencode-windows-x64: 1.14.28 + opencode-windows-x64-baseline: 1.14.28 - opencode-darwin-arm64@1.14.22: + opencode-darwin-arm64@1.14.28: optional: true - opencode-darwin-x64-baseline@1.14.22: + opencode-darwin-x64-baseline@1.14.28: optional: true - opencode-darwin-x64@1.14.22: + opencode-darwin-x64@1.14.28: optional: true - opencode-linux-arm64-musl@1.14.22: + opencode-linux-arm64-musl@1.14.28: optional: true - opencode-linux-arm64@1.14.22: + opencode-linux-arm64@1.14.28: optional: true - opencode-linux-x64-baseline-musl@1.14.22: + opencode-linux-x64-baseline-musl@1.14.28: optional: true - opencode-linux-x64-baseline@1.14.22: + opencode-linux-x64-baseline@1.14.28: optional: true - opencode-linux-x64-musl@1.14.22: + opencode-linux-x64-musl@1.14.28: optional: true - opencode-linux-x64@1.14.22: + opencode-linux-x64@1.14.28: optional: true - opencode-windows-arm64@1.14.22: + opencode-windows-arm64@1.14.28: optional: true - opencode-windows-x64-baseline@1.14.22: + opencode-windows-x64-baseline@1.14.28: optional: true - opencode-windows-x64@1.14.22: + opencode-windows-x64@1.14.28: optional: true undici-types@7.19.2: {}