diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 053dd3ff0e..c0890324c8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -63,7 +63,7 @@ jobs: echo "$PUB_DOCKER_PASSWORD" | skopeo login --username "$PUB_DOCKER_USERNAME" --password-stdin docker.io - IMAGES=("frontend" "backend" "exporter" "storybook") + IMAGES=("frontend" "backend" "exporter" "mcp" "storybook") SHORT_TAG=${TAG%.*} for image in "${IMAGES[@]}"; do diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index afcffb0ae7..e4b2d49efc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,7 +24,11 @@ jobs: if: ${{ !github.event.pull_request.draft }} name: "Linter" runs-on: penpot-runner-02 - container: penpotapp/devenv:latest + container: + image: penpotapp/devenv:latest + volumes: + - /tmp/.m2:/root/.m2 + - /tmp/.gitlibs:/root/.gitlibs steps: - name: Checkout repository @@ -84,7 +88,11 @@ jobs: if: ${{ !github.event.pull_request.draft }} name: "Common Tests" runs-on: penpot-runner-02 - container: penpotapp/devenv:latest + container: + image: penpotapp/devenv:latest + volumes: + - /tmp/.m2:/root/.m2 + - /tmp/.gitlibs:/root/.gitlibs steps: - name: Checkout repository @@ -99,7 +107,11 @@ jobs: if: ${{ !github.event.pull_request.draft }} name: Plugins Runtime Linter & Tests runs-on: penpot-runner-02 - container: penpotapp/devenv:latest + container: + image: penpotapp/devenv:latest + volumes: + - /tmp/.m2:/root/.m2 + - /tmp/.gitlibs:/root/.gitlibs steps: - uses: actions/checkout@v6 @@ -150,7 +162,11 @@ jobs: if: ${{ !github.event.pull_request.draft }} name: "Frontend Tests" runs-on: penpot-runner-02 - container: penpotapp/devenv:latest + container: + image: penpotapp/devenv:latest + volumes: + - /tmp/.m2:/root/.m2 + - /tmp/.gitlibs:/root/.gitlibs steps: - name: Checkout repository @@ -172,7 +188,11 @@ jobs: if: ${{ !github.event.pull_request.draft }} name: "Render WASM Tests" runs-on: penpot-runner-02 - container: penpotapp/devenv:latest + container: + image: penpotapp/devenv:latest + volumes: + - /tmp/.m2:/root/.m2 + - /tmp/.gitlibs:/root/.gitlibs steps: - name: Checkout repository @@ -197,7 +217,11 @@ jobs: if: ${{ !github.event.pull_request.draft }} name: "Backend Tests" runs-on: penpot-runner-02 - container: penpotapp/devenv:latest + container: + image: penpotapp/devenv:latest + volumes: + - /tmp/.m2:/root/.m2 + - /tmp/.gitlibs:/root/.gitlibs services: postgres: @@ -237,7 +261,11 @@ jobs: if: ${{ !github.event.pull_request.draft }} name: "Library Tests" runs-on: penpot-runner-02 - container: penpotapp/devenv:latest + container: + image: penpotapp/devenv:latest + volumes: + - /tmp/.m2:/root/.m2 + - /tmp/.gitlibs:/root/.gitlibs steps: - name: Checkout repository @@ -252,7 +280,11 @@ jobs: if: ${{ !github.event.pull_request.draft }} name: "Build Integration Bundle" runs-on: penpot-runner-02 - container: penpotapp/devenv:latest + container: + image: penpotapp/devenv:latest + volumes: + - /tmp/.m2:/root/.m2 + - /tmp/.gitlibs:/root/.gitlibs steps: - name: Checkout repository @@ -273,7 +305,12 @@ jobs: if: ${{ !github.event.pull_request.draft }} name: "Integration Tests 1/3" runs-on: penpot-runner-02 - container: penpotapp/devenv:latest + container: + image: penpotapp/devenv:latest + volumes: + - /tmp/.m2:/root/.m2 + - /tmp/.gitlibs:/root/.gitlibs + needs: build-integration steps: @@ -304,7 +341,12 @@ jobs: if: ${{ !github.event.pull_request.draft }} name: "Integration Tests 2/3" runs-on: penpot-runner-02 - container: penpotapp/devenv:latest + container: + image: penpotapp/devenv:latest + volumes: + - /tmp/.m2:/root/.m2 + - /tmp/.gitlibs:/root/.gitlibs + needs: build-integration steps: @@ -335,7 +377,12 @@ jobs: if: ${{ !github.event.pull_request.draft }} name: "Integration Tests 3/3" runs-on: penpot-runner-02 - container: penpotapp/devenv:latest + container: + image: penpotapp/devenv:latest + volumes: + - /tmp/.m2:/root/.m2 + - /tmp/.gitlibs:/root/.gitlibs + needs: build-integration steps: diff --git a/CHANGES.md b/CHANGES.md index 6691112759..8bf431e759 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -127,33 +127,69 @@ - Fix library updates reappear after being applied and the file is reloaded [Taiga #14040](https://tree.taiga.io/project/penpot/issue/14040) - Fix dependency libraries remaining visible in UI after unlinking main library [Taiga #14020](https://tree.taiga.io/project/penpot/issue/14020) -## 2.15.0 (Unreleased) +## 2.15.2 + +### :bug: Bugs fixed + +- Fix mcp related internal config for docker images [Github #9565](https://github.com/penpot/penpot/pull/9565) + + +## 2.15.1 + +### :sparkles: New features & Enhancements + +- Add support for chunked uploading of fonts [Github #9560](https://github.com/penpot/penpot/issues/9560) + + +## 2.15.0 ### :sparkles: New features & Enhancements - Add MCP server integration [Github #9174](https://github.com/penpot/penpot/issues/9174) -- Add chunked upload API for large media and binary files (removes previous upload size limits) [Github #8909](https://github.com/penpot/penpot/pull/8909) -- Improve team name validation [Github #9176](https://github.com/penpot/penpot/pull/9176) + (PR: [#9032](https://github.com/penpot/penpot/pull/9032), [#9321](https://github.com/penpot/penpot/pull/9321)) +- Add chunked upload API for large media and binary files (removes previous upload size limits) [Github #9516](https://github.com/penpot/penpot/issues/9516) + (PR: [#8909](https://github.com/penpot/penpot/pull/8909)) +- Add anonymous telemetry event collection [Github #9467](https://github.com/penpot/penpot/issues/9467) + (PR: [#9065](https://github.com/penpot/penpot/pull/9065), [#9483](https://github.com/penpot/penpot/pull/9483)) +- Improve team name validation [Github #9517](https://github.com/penpot/penpot/issues/9517) + (PR: [#9176](https://github.com/penpot/penpot/pull/9176)) +- Enhance readability of applied tokens in plugins API [Github #9175](https://github.com/penpot/penpot/issues/9175) + (PR: [#8607](https://github.com/penpot/penpot/pull/8607)) +- Encourage use of flex/grid layouts in designs generated via MCP [Github #9081](https://github.com/penpot/penpot/issues/9081) + (PR: [#9084](https://github.com/penpot/penpot/pull/9084)) +- Improve MCP server logging, adding Loki support [Github #9415](https://github.com/penpot/penpot/issues/9415) + (PR: [#9425](https://github.com/penpot/penpot/pull/9425)) +- Add security headers to Nginx on Docker images [Github #9519](https://github.com/penpot/penpot/issues/9519) + (PR: [#9473](https://github.com/penpot/penpot/pull/9473)) ### :bug: Bugs fixed -- Fix MCP integrations URL copy action to match the URL displayed in settings [Github #9238](https://github.com/penpot/penpot/issues/9238) -- Fix Plugin API token methods rejecting JS array of strings [Github #9162](https://github.com/penpot/penpot/issues/9162) -- Harden Nginx responses with standard security headers and hide upstream `X-Powered-By` headers -- Fix keep-alive interval leak in PluginBridge (by @opcode81) [Github #9435](https://github.com/penpot/penpot/pull/9435) -- Fix MCP "active in another tab" notification not clearing (by @Dexterity104) [Github #9321](https://github.com/penpot/penpot/pull/9321) -- Fix swapped analytics event names on MCP tab-switch dialog (by @Dexterity104) [Github #9322](https://github.com/penpot/penpot/pull/9322) -- Fix MCP ReplServer binding to all interfaces (0.0.0.0) instead of localhost, allowing unauthenticated RCE [Github #9400] (https://github.com/penpot/penpot/pull/9400) -- Fix incorrect handling of version restore operation [Github #9041](https://github.com/penpot/penpot/pull/9041) -- Fix SSRF in media URL import and restrict unauthenticated asset access to public buckets only [Github #9390](https://github.com/penpot/penpot/pull/9390) - Fix text edition mode not exited when changing selection, blocking token application [Github #9346](https://github.com/penpot/penpot/issues/9346) -- Use base64 envelope for Uint8Array task results to avoid JSON expansion (by @opcode81) [Github #9431](https://github.com/penpot/penpot/pull/9431) -- Fix empty warning on login [Github #9056](https://github.com/penpot/penpot/pull/9056) -- Fix layer hierarchy to match old and new SCSS [Github #9126](https://github.com/penpot/penpot/pull/9126) -- Fix multiple selection on shapes with token applied to stroke color [Github #9110](https://github.com/penpot/penpot/pull/9110) -- Fix onboarding modals appearing behind libraries and templates panel [Github #9178](https://github.com/penpot/penpot/pull/9178) + (PR: [#9355](https://github.com/penpot/penpot/pull/9355)) +- Reduce memory usage of MCP server when handling images (by @opcode81) [Github #9420](https://github.com/penpot/penpot/issues/9420) + (PR: [#9431](https://github.com/penpot/penpot/pull/9431)) +- Fix Plugin API token methods rejecting JS array of strings (by @boskodev790) [Github #9162](https://github.com/penpot/penpot/issues/9162) + (PR: [#9166](https://github.com/penpot/penpot/pull/9166)) - Fix release notes modal appearing behind the dashboard sidebar (by @RenzoMXD) [Github #8296](https://github.com/penpot/penpot/issues/8296) + (PR: [#9126](https://github.com/penpot/penpot/pull/9126), [#9233](https://github.com/penpot/penpot/pull/9233)) +- Fix empty warning on login [Github #9520](https://github.com/penpot/penpot/issues/9520) + (PR: [#9056](https://github.com/penpot/penpot/pull/9056)) - Fix maximum call stack size exceeded in SSE read-stream [Github #9470](https://github.com/penpot/penpot/issues/9470) + (PR: [#9484](https://github.com/penpot/penpot/pull/9484)) +- Fix incorrect handling of version restore operation [Github #9515](https://github.com/penpot/penpot/issues/9515) + (PR: [#9041](https://github.com/penpot/penpot/pull/9041)) +- Fix MCP ReplServer binding to all interfaces (0.0.0.0) instead of localhost, allowing unauthenticated RCE [Github #9518](https://github.com/penpot/penpot/issues/9518) + (PR: [#9400](https://github.com/penpot/penpot/pull/9400)) +- Fix MCP integrations URL copy action to match the URL displayed in settings [Github #9238](https://github.com/penpot/penpot/issues/9238) + (PR: [#9239](https://github.com/penpot/penpot/pull/9239)) +- Fix swapped analytics event names on MCP tab-switch dialog (by @Dexterity104) [Github #9496](https://github.com/penpot/penpot/issues/9496) + (PR: [#9322](https://github.com/penpot/penpot/pull/9322)) +- Fix multiple selection on shapes with token applied to stroke color [Github #9522](https://github.com/penpot/penpot/issues/9522) + (PR: [#9110](https://github.com/penpot/penpot/pull/9110)) +- Fix onboarding modals appearing behind libraries and templates panel [Github #9521](https://github.com/penpot/penpot/issues/9521) + (PR: [#9178](https://github.com/penpot/penpot/pull/9178)) +- Fix keep-alive interval leak in PluginBridge (by @opcode81) [Github #9430](https://github.com/penpot/penpot/issues/9430) + (PR: [#9435](https://github.com/penpot/penpot/pull/9435)) ## 2.14.5 diff --git a/backend/deps.edn b/backend/deps.edn index af73aecbc8..d53cf9a6c2 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -3,7 +3,7 @@ :deps {penpot/common {:local/root "../common"} - org.clojure/clojure {:mvn/version "1.12.4"} + org.clojure/clojure {:mvn/version "1.12.5"} org.clojure/tools.namespace {:mvn/version "1.5.0"} com.github.luben/zstd-jni {:mvn/version "1.5.7-4"} @@ -17,7 +17,7 @@ io.prometheus/simpleclient_httpserver {:mvn/version "0.16.0"} - io.lettuce/lettuce-core {:mvn/version "6.8.1.RELEASE"} + io.lettuce/lettuce-core {:mvn/version "7.5.1.RELEASE"} ;; Minimal dependencies required by lettuce, we need to include them ;; explicitly because clojure dependency management does not support ;; yet the BOM format. @@ -28,18 +28,18 @@ com.google.guava/guava {:mvn/version "33.4.8-jre"} funcool/yetti - {:git/tag "v11.9" - :git/sha "5fad7a9" + {:git/tag "v11.10" + :git/sha "88701f4" :git/url "https://github.com/funcool/yetti.git" :exclusions [org.slf4j/slf4j-api]} com.github.seancorfield/next.jdbc - {:mvn/version "1.3.1070"} + {:mvn/version "1.3.1093"} metosin/reitit-core {:mvn/version "0.9.1"} - nrepl/nrepl {:mvn/version "1.4.0"} + nrepl/nrepl {:mvn/version "1.7.0"} - org.postgresql/postgresql {:mvn/version "42.7.9"} + org.postgresql/postgresql {:mvn/version "42.7.11"} org.xerial/sqlite-jdbc {:mvn/version "3.50.3.0"} com.zaxxer/HikariCP {:mvn/version "7.0.2"} @@ -49,7 +49,7 @@ buddy/buddy-hashers {:mvn/version "2.0.167"} buddy/buddy-sign {:mvn/version "3.6.1-359"} - com.github.ben-manes.caffeine/caffeine {:mvn/version "3.2.3"} + com.github.ben-manes.caffeine/caffeine {:mvn/version "3.2.4"} org.jsoup/jsoup {:mvn/version "1.21.2"} org.im4java/im4java @@ -57,7 +57,8 @@ :git/sha "e2b3e16" :git/url "https://github.com/penpot/im4java"} - org.lz4/lz4-java {:mvn/version "1.8.0"} + at.yawk.lz4/lz4-java + {:mvn/version "1.11.0"} org.clojars.pntblnk/clj-ldap {:mvn/version "0.0.17"} @@ -66,17 +67,17 @@ ;; Pretty Print specs pretty-spec/pretty-spec {:mvn/version "0.1.4"} - software.amazon.awssdk/s3 {:mvn/version "2.41.21"}} + software.amazon.awssdk/s3 {:mvn/version "2.44.4"}} :paths ["src" "resources" "target/classes"] :aliases {:dev {:extra-deps - {com.bhauman/rebel-readline {:mvn/version "RELEASE"} + {com.bhauman/rebel-readline {:mvn/version "0.1.5"} clojure-humanize/clojure-humanize {:mvn/version "0.2.2"} - org.clojure/data.csv {:mvn/version "RELEASE"} - com.clojure-goes-fast/clj-async-profiler {:mvn/version "RELEASE"} - mockery/mockery {:mvn/version "RELEASE"}} + org.clojure/data.csv {:mvn/version "1.1.1"} + com.clojure-goes-fast/clj-async-profiler {:mvn/version "2.0.0-beta1"} + mockery/mockery {:mvn/version "0.1.4"}} :extra-paths ["test" "dev"]} :build @@ -92,7 +93,7 @@ :extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}}} :outdated - {:extra-deps {com.github.liquidz/antq {:mvn/version "RELEASE"}} + {:extra-deps {com.github.liquidz/antq {:mvn/version "2.11.1276"}} :main-opts ["-m" "antq.core"]} :jmx-remote diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index 66dd6285b9..ae461e351b 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -72,6 +72,7 @@ :telemetry-uri "https://telemetry.penpot.app/" :media-max-file-size (* 1024 1024 30) ; 30MiB + :font-max-file-size (* 1024 1024 30) ; 30MiB :ldap-user-query "(|(uid=:username)(mail=:username))" :ldap-attrs-username "uid" @@ -120,6 +121,7 @@ [:auto-file-snapshot-timeout {:optional true} ::ct/duration] [:media-max-file-size {:optional true} ::sm/int] + [:font-max-file-size {:optional true} ::sm/int] [:deletion-delay {:optional true} ::ct/duration] [:file-clean-delay {:optional true} ::ct/duration] [:telemetry-enabled {:optional true} ::sm/boolean] diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 7dac2b59f6..018e6e301d 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -61,21 +61,15 @@ ::mdef/help "A total number of bytes processed by update-file." ::mdef/type :counter} - :rpc-mutation-timing - {::mdef/name "penpot_rpc_mutation_timing" - ::mdef/help "RPC mutation method call timing." + :rpc-main-timing + {::mdef/name "penpot_rpc_main_timing" + ::mdef/help "RPC command method call timing for main" ::mdef/labels ["name"] ::mdef/type :histogram} - :rpc-command-timing - {::mdef/name "penpot_rpc_command_timing" - ::mdef/help "RPC command method call timing." - ::mdef/labels ["name"] - ::mdef/type :histogram} - - :rpc-query-timing - {::mdef/name "penpot_rpc_query_timing" - ::mdef/help "RPC query method call timing." + :rpc-management-timing + {::mdef/name "penpot_rpc_management_timing" + ::mdef/help "RPC command method call timing for management." ::mdef/labels ["name"] ::mdef/type :histogram} diff --git a/backend/src/app/media.clj b/backend/src/app/media.clj index 25bd3e0002..b91c09d38e 100644 --- a/backend/src/app/media.clj +++ b/backend/src/app/media.clj @@ -38,9 +38,6 @@ org.im4java.core.ConvertCmd org.im4java.core.IMOperation)) -(def default-max-file-size - (* 1024 1024 10)) ; 10 MiB - (def schema:upload [:map {:title "Upload"} [:filename :string] @@ -79,6 +76,20 @@ max-size))) upload)) +(defn validate-font-size! + "Validates that the font file `upload` does not exceed the configured + `:font-max-file-size` limit. Accepts the same map shape as + `validate-media-size!` — requires a `:size` key in bytes." + [upload] + (let [max-size (cf/get :font-max-file-size)] + (when (> (:size upload) max-size) + (ex/raise :type :restriction + :code :font-max-file-size-reached + :hint (str/ffmt "the uploaded font size % is greater than the maximum %" + (:size upload) + max-size))) + upload)) + (defmulti process :cmd) (defmulti process-error class) @@ -296,9 +307,7 @@ [{:keys [::http/client]} uri] (letfn [(parse-and-validate [{:keys [status headers] :as response}] (let [size (some-> (get headers "content-length") d/parse-integer) - mtype (get headers "content-type") - format (cm/mtype->format mtype) - max-size (cf/get :media-max-file-size default-max-file-size)] + mtype (get headers "content-type")] (when-not (<= 200 status 299) (ex/raise :type :validation @@ -310,19 +319,9 @@ :code :unknown-size :hint "seems like the url points to resource with unknown size")) - (when (> size max-size) - (ex/raise :type :validation - :code :file-too-large - :hint (str/ffmt "the file size % is greater than the maximum %" - size - default-max-file-size))) - - (when (nil? format) - (ex/raise :type :validation - :code :media-type-not-allowed - :hint "seems like the url points to an invalid media object")) - - {:size size :mtype mtype :format format}))] + (-> {:size size :mtype mtype} + (validate-media-type!) + (validate-media-size!))))] (let [{:keys [body] :as response} (try diff --git a/backend/src/app/rpc/commands/fonts.clj b/backend/src/app/rpc/commands/fonts.clj index e8c759fed7..a5ff63c0e1 100644 --- a/backend/src/app/rpc/commands/fonts.clj +++ b/backend/src/app/rpc/commands/fonts.clj @@ -9,7 +9,8 @@ [app.binfile.common :as bfc] [app.common.data.macros :as dm] [app.common.exceptions :as ex] - [app.common.media :as cmedia] + [app.common.logging :as l] + [app.common.media :as cm] [app.common.schema :as sm] [app.common.time :as ct] [app.common.uuid :as uuid] @@ -23,6 +24,7 @@ [app.rpc :as-alias rpc] [app.rpc.climit :as-alias climit] [app.rpc.commands.files :as files] + [app.rpc.commands.media :refer [assemble-chunks]] [app.rpc.commands.projects :as projects] [app.rpc.commands.teams :as teams] [app.rpc.doc :as-alias doc] @@ -31,6 +33,8 @@ [app.storage :as sto] [app.storage.tmp :as tmp] [app.util.services :as sv] + [cuerdas.core :as str] + [datoteka.fs :as fs] [datoteka.io :as io]) (:import java.io.InputStream @@ -91,33 +95,92 @@ (declare create-font-variant) (def ^:private schema:create-font-variant - [:map {:title "create-font-variant"} - [:team-id ::sm/uuid] - [:data [:map-of ::sm/text [:or ::sm/bytes - [::sm/vec ::sm/bytes]]]] - [:font-id ::sm/uuid] - [:font-family ::sm/text] - [:font-weight [::sm/one-of {:format "number"} valid-weight]] - [:font-style [::sm/one-of {:format "string"} valid-style]] - [:variant-name {:optional true} [:maybe ::sm/text]]]) + [:and + [:map {:title "create-font-variant"} + [:team-id ::sm/uuid] + [:font-id ::sm/uuid] + [:font-family ::sm/text] + [:font-weight [::sm/one-of {:format "number"} valid-weight]] + [:font-style [::sm/one-of {:format "string"} valid-style]] + [:data {:optional true} [:map-of ::sm/text [:or ::sm/bytes [::sm/vec ::sm/bytes]]]] + [:uploads {:optional true} [:map-of ::sm/text ::sm/uuid]]] + [:fn {:error/message "one of :data or :uploads is required"} + (fn [{:keys [data uploads]}] + (or (seq data) (seq uploads)))]]) ;; FIXME: IMPORTANT: refactor this, we should not hold a whole db ;; connection around the font creation +(defn- prepare-font-data-from-uploads + "Assembles each chunked-upload session in `uploads` (a `{mtype → + session-id}` map) into a temp file, validates the media type and + size of every entry, and returns a `{mtype → path}` data map." + [cfg {:keys [uploads] :as params}] + (let [data (reduce-kv + (fn [acc mtype session-id] + (let [assembled (assemble-chunks cfg session-id)] + (-> {:mtype mtype :size (:size assembled)} + (media/validate-media-type! cm/font-types) + (media/validate-font-size!)) + (assoc acc mtype (:path assembled)))) + {} + uploads)] + + (-> params + (assoc :data data) + (dissoc :uploads)))) + +(defn- prepare-font-data-from-legacy + "Validates the media type and size of every entry in the legacy + `:data` map (a `{mtype → bytes | [bytes]}` map). Normalises every + entry to a tempfile. Returns params with a normalised + `{mtype → path}` data map." + [{:keys [data] :as params}] + (let [data (reduce-kv + (fn [acc mtype content] + (let [tmp (tmp/tempfile :prefix "penpot.tempfont." :suffix "") + chunks (if (vector? content) content [content]) + streams (map io/input-stream chunks) + streams (Collections/enumeration streams)] + + ;; Generate the tempfile from all chunks + (with-open [^OutputStream output (io/output-stream tmp) + ^InputStream input (SequenceInputStream. streams)] + (io/copy input output)) + + ;; Validate + (-> {:mtype mtype :size (fs/size tmp)} + (media/validate-media-type! cm/font-types) + (media/validate-font-size!)) + + (assoc acc mtype tmp))) + {} + data)] + (assoc params :data data))) + (sv/defmethod ::create-font-variant + "Upload a font variant. Font data may be provided either as a + Transit-encoded `:data` map (keyed by mime-type) for small fonts, or + as an `:uploads` map (keyed by mime-type, values are upload-session + UUIDs from the chunked-upload API) for large fonts. Exactly one of + the two must be present." {::doc/added "1.18" + ::doc/changes ["2.16" "Add :uploads param for chunked upload support"] ::climit/id [[:process-font/by-profile ::rpc/profile-id] [:process-font/global]] ::webhooks/event? true ::sm/params schema:create-font-variant} - [cfg {:keys [::rpc/profile-id team-id] :as params}] + [cfg {:keys [::rpc/profile-id team-id uploads] :as params}] (db/tx-run! cfg (fn [{:keys [::db/conn] :as cfg}] (teams/check-edition-permissions! conn profile-id team-id) (quotes/check! cfg {::quotes/id ::quotes/font-variants-per-team ::quotes/profile-id profile-id ::quotes/team-id team-id}) - (create-font-variant cfg (assoc params :profile-id profile-id))))) + (let [params (if (some? uploads) + (prepare-font-data-from-uploads cfg params) + (prepare-font-data-from-legacy params))] + (create-font-variant cfg (assoc params :profile-id profile-id)))))) (defn create-font-variant [{:keys [::sto/storage ::db/conn]} {:keys [data] :as params}] @@ -132,23 +195,6 @@ :hint "invalid font upload, unable to generate missing font assets")) data)) - (process-chunks [chunks] - (let [tmp (tmp/tempfile :prefix "penpot.tempfont." :suffix "") - streams (map io/input-stream chunks) - streams (Collections/enumeration streams)] - (with-open [^OutputStream output (io/output-stream tmp) - ^InputStream input (SequenceInputStream. streams)] - (io/copy input output)) - tmp)) - - (join-chunks [data] - (reduce-kv (fn [data mtype content] - (if (vector? content) - (assoc data mtype (process-chunks content)) - data)) - data - data)) - (prepare-font [data mtype] (when-let [resource (get data mtype)] @@ -191,11 +237,38 @@ :otf-file-id (:id otf) :ttf-file-id (:id ttf)}))] - (let [data (join-chunks data) - data (generate-missing data) - assets (persist-fonts-files! data) - result (insert-font-variant! assets)] - (vary-meta result assoc ::audit/replace-props (update params :data (comp vec keys)))))) + (let [tpoint (ct/tpoint) + mtypes (vec (keys data)) + total-size (reduce-kv (fn [acc _ content] + (+ acc (if (bytes? content) + (alength ^bytes content) + (fs/size content)))) + 0 + data)] + + (l/dbg :hint "create-font-variant" + :step "init" + :font-family (:font-family params) + :font-weight (:font-weight params) + :font-style (:font-style params) + :mtypes (str/join mtypes ",") + :size total-size) + + (let [data (generate-missing data) + assets (persist-fonts-files! data) + result (insert-font-variant! assets) + elapsed (tpoint)] + + (l/dbg :hint "create-font-variant" + :step "end" + :font-family (:font-family params) + :font-weight (:font-weight params) + :font-style (:font-style params) + :mtypes (str/join mtypes ",") + :size total-size + :elapsed (ct/format-duration elapsed)) + + (vary-meta result assoc ::audit/replace-props (update params :data (comp vec keys))))))) ;; --- UPDATE FONT FAMILY @@ -326,7 +399,7 @@ [v mtype] (str (:font-family v) "-" (:font-weight v) (when-not (= "normal" (:font-style v)) (str "-" (:font-style v))) - (cmedia/mtype->extension mtype))) + (cm/mtype->extension mtype))) (def ^:private schema:download-font [:map {:title "download-font"} diff --git a/backend/src/app/rpc/commands/media.clj b/backend/src/app/rpc/commands/media.clj index 22fedd39b9..98b81a0810 100644 --- a/backend/src/app/rpc/commands/media.clj +++ b/backend/src/app/rpc/commands/media.clj @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.exceptions :as ex] + [app.common.logging :as l] [app.common.schema :as sm] [app.common.time :as ct] [app.common.uuid :as uuid] @@ -58,8 +59,8 @@ (db/run! cfg (fn [{:keys [::db/conn] :as cfg}] ;; We get the minimal file for proper checking if ;; file is not already deleted - (let [_ (files/get-minimal-file conn file-id) - mobj (create-file-media-object cfg params)] + (let [_ (files/get-minimal-file conn file-id) + mobj (create-file-media-object cfg params)] (db/update! conn :file {:modified-at (ct/now) @@ -149,20 +150,49 @@ (defn- create-file-media-object [{:keys [::sto/storage ::db/conn] :as cfg} - {:keys [id file-id is-local name content]}] - (let [result (process-image content) - image (sto/put-object! storage (::image result)) - thumb (when-let [params (::thumb result)] - (sto/put-object! storage params))] + {:keys [id file-id is-local name content from-url? from-chunks?]}] - (db/exec-one! conn [sql:create-file-media-object - (or id (uuid/next)) - file-id is-local name - (:id image) - (:id thumb) - (:width result) - (:height result) - (:mtype result)]))) + (let [tpoint (ct/tpoint) + id (or id (uuid/next)) + origin (cond + from-url? + "url" + from-chunks? + "chunks" + :else + "direct")] + + (l/dbg :hint "create file-media-object" + :step "init" + :id (str id) + :mtype (:mtype content) + :size (:size content) + :path (str (:path content)) + :origin origin) + + (let [result (process-image content) + image (sto/put-object! storage (::image result)) + thumb (when-let [params (::thumb result)] + (sto/put-object! storage params)) + elapsed (tpoint)] + + (l/dbg :hint "create file-media-object" + :step "end" + :id (str id) + :mtype (:mtype content) + :size (:size content) + :path (str (:path content)) + :origin origin + :elapsed (ct/format-duration elapsed)) + + (db/exec-one! conn [sql:create-file-media-object + id + file-id is-local name + (:id image) + (:id thumb) + (:width result) + (:height result) + (:mtype result)])))) ;; --- Create File Media Object (from URL) @@ -198,6 +228,7 @@ [cfg {:keys [url name] :as params}] (let [content (media/download-image cfg url) params (-> params + (assoc :from-url? true) (assoc :content content) (assoc :name (d/nilv name "unknown")))] @@ -305,7 +336,14 @@ :hint "chunk index is out of range for this session" :session-id session-id :total-chunks (:total-chunks session) - :index index))) + :index index)) + + + (l/trc :hint "upload-chunk" + :session-id session-id + :chunk (str index "/" (:total-chunks session)) + :size (:size content) + :path (:path content))) (let [storage (sto/resolve cfg) data (sto/content (:path content))] @@ -399,14 +437,15 @@ (db/tx-run! cfg (fn [{:keys [::db/conn] :as cfg}] - (let [{:keys [path size]} (assemble-chunks cfg session-id) - content {:filename "upload" - :size size - :path path - :mtype mtype} - _ (media/validate-media-type! content) + (let [content (assemble-chunks cfg session-id) + content (-> content + (assoc :filename (str "upload:" name)) + (assoc :mtype mtype) + (media/validate-media-type!) + (media/validate-media-size!)) mobj (create-file-media-object cfg (assoc params - :id (or id (uuid/next)) + :id id + :from-chunks? true :content content))] (db/update! conn :file diff --git a/backend/src/app/rpc/commands/profile.clj b/backend/src/app/rpc/commands/profile.clj index ed09d90586..fa4a993b77 100644 --- a/backend/src/app/rpc/commands/profile.clj +++ b/backend/src/app/rpc/commands/profile.clj @@ -265,6 +265,7 @@ [cfg {:keys [::rpc/profile-id file] :as params}] ;; Validate incoming mime type (media/validate-media-type! file #{"image/jpeg" "image/png" "image/webp"}) + (media/validate-media-size! file) (update-profile-photo cfg (assoc params :profile-id profile-id))) (defn update-profile-photo diff --git a/backend/src/app/rpc/commands/teams.clj b/backend/src/app/rpc/commands/teams.clj index 359b79c841..df0a1d8ef1 100644 --- a/backend/src/app/rpc/commands/teams.clj +++ b/backend/src/app/rpc/commands/teams.clj @@ -918,6 +918,7 @@ ;; Validate incoming mime type (media/validate-media-type! file #{"image/jpeg" "image/png" "image/webp"}) + (media/validate-media-size! file) (update-team-photo cfg (assoc params :profile-id profile-id))) (defn update-team-photo diff --git a/backend/test/backend_tests/rpc_font_test.clj b/backend/test/backend_tests/rpc_font_test.clj index 498e21ef2b..59a58466b9 100644 --- a/backend/test/backend_tests/rpc_font_test.clj +++ b/backend/test/backend_tests/rpc_font_test.clj @@ -17,7 +17,9 @@ [clojure.test :as t] [datoteka.fs :as fs] [datoteka.io :as io] - [mockery.core :refer [with-mocks]])) + [mockery.core :refer [with-mocks]]) + (:import + java.io.RandomAccessFile)) (t/use-fixtures :once th/state-init) (t/use-fixtures :each th/database-reset) @@ -327,3 +329,499 @@ (let [error (:error out) error-data (ex-data error)] (t/is (th/ex-info? error)))))) + +;; ----------------------------------------------------------------------- +;; Helpers for chunked-upload font tests +;; ----------------------------------------------------------------------- + +(defn- split-bytes-into-chunks + "Splits `data` (byte array) into chunks of at most `chunk-size` bytes. + Returns a vector of byte arrays." + [^bytes data chunk-size] + (let [length (alength data)] + (loop [offset 0 chunks []] + (if (>= offset length) + chunks + (let [remaining (- length offset) + size (min chunk-size remaining) + buf (byte-array size)] + (System/arraycopy data offset buf 0 size) + (recur (+ offset size) (conj chunks buf))))))) + +(defn- make-chunk-mfile + "Writes `data` (byte array) to a tempfile and returns a map + compatible with the upload-chunk :content parameter." + [^bytes data mtype] + (let [tmp (fs/create-tempfile :dir "/tmp/penpot" :prefix "test-font-chunk-")] + (io/write* tmp data) + {:filename "chunk" + :path tmp + :mtype mtype + :size (alength data)})) + +(defn- create-upload-session! + "Creates an upload session for `prof` with `total-chunks`. Returns the session-id UUID." + [prof total-chunks] + (let [out (th/command! {::th/type :create-upload-session + ::rpc/profile-id (:id prof) + :total-chunks total-chunks})] + (t/is (nil? (:error out))) + (:session-id (:result out)))) + +(defn- upload-font-chunked! + "Splits `font-bytes` into chunks of `chunk-size` bytes, creates an upload + session, uploads all chunks, and returns the session-id UUID." + [prof ^bytes font-bytes mtype chunk-size] + (let [chunks (split-bytes-into-chunks font-bytes chunk-size) + session-id (create-upload-session! prof (count chunks))] + (doseq [[idx chunk-data] (map-indexed vector chunks)] + (let [mfile (make-chunk-mfile chunk-data mtype) + out (th/command! {::th/type :upload-chunk + ::rpc/profile-id (:id prof) + :session-id session-id + :index idx + :content mfile})] + (t/is (nil? (:error out))))) + session-id)) + +(defn- assert-font-variant-result + "Checks that a successful create-font-variant result has valid UUIDs and + the expected scalar fields matching `params`." + [params result] + (t/is (uuid? (:id result))) + (t/is (uuid? (:ttf-file-id result))) + (t/is (uuid? (:otf-file-id result))) + (t/is (uuid? (:woff1-file-id result))) + (t/are [k] (= (get params k) (get result k)) + :team-id + :font-id + :font-family + :font-weight + :font-style)) + +;; ----------------------------------------------------------------------- +;; Path 1 – Normal (direct :data bytes) +;; ----------------------------------------------------------------------- + +(t/deftest create-font-variant-normal-ttf + (with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}] + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + font-id (uuid/custom 10 10) + data (-> (io/resource "backend_tests/test_files/font-1.ttf") (io/read*)) + params {::th/type :create-font-variant + ::rpc/profile-id (:id prof) + :team-id team-id + :font-id font-id + :font-family "chunked-test" + :font-weight 400 + :font-style "normal" + :data {"font/ttf" data}} + out (th/command! params)] + (t/is (= 1 (:call-count @mock))) + (t/is (nil? (:error out))) + (assert-font-variant-result params (:result out))))) + +(t/deftest create-font-variant-normal-otf + (with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}] + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + font-id (uuid/custom 10 11) + data (-> (io/resource "backend_tests/test_files/font-1.otf") (io/read*)) + params {::th/type :create-font-variant + ::rpc/profile-id (:id prof) + :team-id team-id + :font-id font-id + :font-family "chunked-test" + :font-weight 400 + :font-style "normal" + :data {"font/otf" data}} + out (th/command! params)] + (t/is (= 1 (:call-count @mock))) + (t/is (nil? (:error out))) + (assert-font-variant-result params (:result out))))) + +(t/deftest create-font-variant-normal-woff + (with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}] + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + font-id (uuid/custom 10 12) + data (-> (io/resource "backend_tests/test_files/font-1.woff") (io/read*)) + params {::th/type :create-font-variant + ::rpc/profile-id (:id prof) + :team-id team-id + :font-id font-id + :font-family "chunked-test" + :font-weight 400 + :font-style "normal" + :data {"font/woff" data}} + out (th/command! params)] + (t/is (= 1 (:call-count @mock))) + (t/is (nil? (:error out))) + (assert-font-variant-result params (:result out))))) + +;; ----------------------------------------------------------------------- +;; Path 2 – Legacy chunking (:data with vector of byte-arrays per mtype) +;; ----------------------------------------------------------------------- + +(t/deftest create-font-variant-legacy-chunked-ttf + "Upload a TTF via the legacy :data path where each mtype value is a + vector of byte-array chunks (4 MiB each) instead of a single byte-array." + (with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}] + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + font-id (uuid/custom 10 20) + full-bytes (-> (io/resource "backend_tests/test_files/font-1.ttf") (io/read*)) + ;; Simulate 4 MiB legacy chunks – font is small so a single chunk suffices + chunks (split-bytes-into-chunks full-bytes (* 4 1024 1024)) + params {::th/type :create-font-variant + ::rpc/profile-id (:id prof) + :team-id team-id + :font-id font-id + :font-family "legacy-chunked" + :font-weight 700 + :font-style "italic" + :data {"font/ttf" (vec chunks)}} + out (th/command! params)] + (t/is (= 1 (:call-count @mock))) + (t/is (nil? (:error out))) + (assert-font-variant-result params (:result out))))) + +(t/deftest create-font-variant-legacy-chunked-woff + "Upload a WOFF via the legacy :data path with multiple sub-4 KiB chunks + to exercise the SequenceInputStream concatenation path." + (with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}] + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + font-id (uuid/custom 10 21) + full-bytes (-> (io/resource "backend_tests/test_files/font-1.woff") (io/read*)) + ;; Split into small chunks to exercise the SequenceInputStream path + chunks (split-bytes-into-chunks full-bytes 512) + params {::th/type :create-font-variant + ::rpc/profile-id (:id prof) + :team-id team-id + :font-id font-id + :font-family "legacy-chunked-woff" + :font-weight 400 + :font-style "normal" + :data {"font/woff" (vec chunks)}} + out (th/command! params)] + (t/is (= 1 (:call-count @mock))) + (t/is (nil? (:error out))) + (assert-font-variant-result params (:result out))))) + +;; ----------------------------------------------------------------------- +;; Path 3 – New standardized chunked upload (:uploads map) +;; ----------------------------------------------------------------------- + +(t/deftest create-font-variant-chunked-upload-ttf + "Upload a TTF via the new :uploads path (chunked-upload API)." + (with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}] + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + font-id (uuid/custom 10 30) + font-bytes (-> (io/resource "backend_tests/test_files/font-1.ttf") (io/read*)) + session-id (upload-font-chunked! prof font-bytes "font/ttf" (* 4 1024 1024)) + params {::th/type :create-font-variant + ::rpc/profile-id (:id prof) + :team-id team-id + :font-id font-id + :font-family "new-chunked" + :font-weight 400 + :font-style "normal" + :uploads {"font/ttf" session-id}} + out (th/command! params)] + ;; quotes/check! is called at least once (for the font-variant quota) plus + ;; once during session creation — assert it fired at least once. + (t/is (>= (:call-count @mock) 1)) + (t/is (nil? (:error out))) + (assert-font-variant-result params (:result out))))) + +(t/deftest create-font-variant-chunked-upload-otf + "Upload an OTF via the new :uploads path." + (with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}] + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + font-id (uuid/custom 10 31) + font-bytes (-> (io/resource "backend_tests/test_files/font-1.otf") (io/read*)) + session-id (upload-font-chunked! prof font-bytes "font/otf" (* 4 1024 1024)) + params {::th/type :create-font-variant + ::rpc/profile-id (:id prof) + :team-id team-id + :font-id font-id + :font-family "new-chunked-otf" + :font-weight 400 + :font-style "normal" + :uploads {"font/otf" session-id}} + out (th/command! params)] + (t/is (>= (:call-count @mock) 1)) + (t/is (nil? (:error out))) + (assert-font-variant-result params (:result out))))) + +(t/deftest create-font-variant-chunked-upload-woff + "Upload a WOFF via the new :uploads path." + (with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}] + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + font-id (uuid/custom 10 32) + font-bytes (-> (io/resource "backend_tests/test_files/font-1.woff") (io/read*)) + session-id (upload-font-chunked! prof font-bytes "font/woff" (* 4 1024 1024)) + params {::th/type :create-font-variant + ::rpc/profile-id (:id prof) + :team-id team-id + :font-id font-id + :font-family "new-chunked-woff" + :font-weight 400 + :font-style "normal" + :uploads {"font/woff" session-id}} + out (th/command! params)] + (t/is (>= (:call-count @mock) 1)) + (t/is (nil? (:error out))) + (assert-font-variant-result params (:result out))))) + +(t/deftest create-font-variant-chunked-upload-multi-chunk + "Upload a WOFF split into many small chunks to exercise multi-chunk assembly." + (with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}] + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + font-id (uuid/custom 10 33) + font-bytes (-> (io/resource "backend_tests/test_files/font-1.woff") (io/read*)) + ;; Use a chunk-size smaller than 4 MiB to force multiple chunks while + ;; staying within the 20-chunk-per-session quota limit (29836 / 2000 = ~15 chunks). + session-id (upload-font-chunked! prof font-bytes "font/woff" 2000) + params {::th/type :create-font-variant + ::rpc/profile-id (:id prof) + :team-id team-id + :font-id font-id + :font-family "multi-chunk-woff" + :font-weight 400 + :font-style "normal" + :uploads {"font/woff" session-id}} + out (th/command! params)] + (t/is (>= (:call-count @mock) 1)) + (t/is (nil? (:error out))) + (assert-font-variant-result params (:result out))))) + +;; ----------------------------------------------------------------------- +;; Error cases +;; ----------------------------------------------------------------------- + +(t/deftest create-font-variant-missing-data-and-uploads + "Neither :data nor :uploads is present — schema validation must reject it." + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + font-id (uuid/custom 10 40) + params {::th/type :create-font-variant + ::rpc/profile-id (:id prof) + :team-id team-id + :font-id font-id + :font-family "bad" + :font-weight 400 + :font-style "normal"} + out (th/command! params)] + (t/is (some? (:error out))) + (t/is (= :validation (-> out :error ex-data :type))))) + +(t/deftest create-font-variant-chunked-upload-missing-chunks + "When only some chunks are uploaded the assembly step must fail." + (with-mocks [_mock {:target 'app.rpc.quotes/check! :return nil}] + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + font-id (uuid/custom 10 41) + font-bytes (-> (io/resource "backend_tests/test_files/font-1.ttf") (io/read*)) + ;; 5000-byte chunks → 68640/5000 = 14 chunks; declare 15 but only upload 13 + chunks (split-bytes-into-chunks font-bytes 5000) + ;; Declare one extra chunk so assembly will fail (not all chunks present) + session-id (create-upload-session! prof (inc (count chunks)))] + + ;; Upload all real chunks except the last one (omit it so the session is incomplete) + (doseq [[idx chunk-data] (map-indexed vector (butlast chunks))] + (let [mfile (make-chunk-mfile chunk-data "font/ttf") + out (th/command! {::th/type :upload-chunk + ::rpc/profile-id (:id prof) + :session-id session-id + :index idx + :content mfile})] + (t/is (nil? (:error out))))) + + (let [out (th/command! {::th/type :create-font-variant + ::rpc/profile-id (:id prof) + :team-id team-id + :font-id font-id + :font-family "missing-chunks" + :font-weight 400 + :font-style "normal" + :uploads {"font/ttf" session-id}})] + (t/is (some? (:error out))))))) + +(t/deftest create-font-variant-chunked-upload-invalid-session + "Passing a non-existent session-id must fail at assembly time." + (with-mocks [_mock {:target 'app.rpc.quotes/check! :return nil}] + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + font-id (uuid/custom 10 42) + out (th/command! {::th/type :create-font-variant + ::rpc/profile-id (:id prof) + :team-id team-id + :font-id font-id + :font-family "bad-session" + :font-weight 400 + :font-style "normal" + :uploads {"font/ttf" (uuid/next)}})] + (t/is (some? (:error out)))))) + +;; ----------------------------------------------------------------------- +;; Font size validation tests +;; ----------------------------------------------------------------------- + +(t/deftest create-font-variant-size-exceeded-normal + "Direct :data upload exceeding font-max-file-size must be rejected." + (with-mocks [_mock {:target 'app.rpc.quotes/check! :return nil}] + (with-redefs [app.config/config (assoc app.config/config :font-max-file-size 1)] + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + font-id (uuid/custom 10 50) + data (-> (io/resource "backend_tests/test_files/font-1.ttf") (io/read*)) + params {::th/type :create-font-variant + ::rpc/profile-id (:id prof) + :team-id team-id + :font-id font-id + :font-family "size-exceeded" + :font-weight 400 + :font-style "normal" + :data {"font/ttf" data}} + out (th/command! params)] + (t/is (some? (:error out))) + (t/is (= :restriction (-> out :error ex-data :type))) + (t/is (= :font-max-file-size-reached (-> out :error ex-data :code))))))) + +(t/deftest create-font-variant-size-exceeded-legacy-chunked + "Legacy :data chunk-vector upload exceeding font-max-file-size must be rejected." + (with-mocks [_mock {:target 'app.rpc.quotes/check! :return nil}] + (with-redefs [app.config/config (assoc app.config/config :font-max-file-size 1)] + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + font-id (uuid/custom 10 51) + full-bytes (-> (io/resource "backend_tests/test_files/font-1.woff") (io/read*)) + chunks (split-bytes-into-chunks full-bytes (* 4 1024 1024)) + params {::th/type :create-font-variant + ::rpc/profile-id (:id prof) + :team-id team-id + :font-id font-id + :font-family "size-exceeded-legacy" + :font-weight 400 + :font-style "normal" + :data {"font/woff" (vec chunks)}} + out (th/command! params)] + (t/is (some? (:error out))) + (t/is (= :restriction (-> out :error ex-data :type))) + (t/is (= :font-max-file-size-reached (-> out :error ex-data :code))))))) + +(t/deftest create-font-variant-size-exceeded-chunked-upload + "New :uploads path exceeding font-max-file-size must be rejected after assembly." + (with-mocks [_mock {:target 'app.rpc.quotes/check! :return nil}] + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + font-id (uuid/custom 10 52) + font-bytes (-> (io/resource "backend_tests/test_files/font-1.ttf") (io/read*)) + session-id (upload-font-chunked! prof font-bytes "font/ttf" (* 4 1024 1024))] + (with-redefs [app.config/config (assoc app.config/config :font-max-file-size 1)] + (let [out (th/command! {::th/type :create-font-variant + ::rpc/profile-id (:id prof) + :team-id team-id + :font-id font-id + :font-family "size-exceeded-chunked" + :font-weight 400 + :font-style "normal" + :uploads {"font/ttf" session-id}})] + (t/is (some? (:error out))) + (t/is (= :restriction (-> out :error ex-data :type))) + (t/is (= :font-max-file-size-reached (-> out :error ex-data :code)))))))) + +(t/deftest create-font-variant-size-within-limit + "Upload exactly at the limit must succeed." + (with-mocks [_mock {:target 'app.rpc.quotes/check! :return nil}] + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + font-id (uuid/custom 10 53) + font-bytes (-> (io/resource "backend_tests/test_files/font-1.ttf") (io/read*)) + font-size (alength ^bytes font-bytes)] + (with-redefs [app.config/config (assoc app.config/config :font-max-file-size font-size)] + (let [params {::th/type :create-font-variant + ::rpc/profile-id (:id prof) + :team-id team-id + :font-id font-id + :font-family "size-at-limit" + :font-weight 400 + :font-style "normal" + :data {"font/ttf" font-bytes}} + out (th/command! params)] + (t/is (nil? (:error out))) + (assert-font-variant-result params (:result out))))))) + +;; ----------------------------------------------------------------------- +;; Font media-type validation tests +;; ----------------------------------------------------------------------- + +(t/deftest create-font-variant-invalid-type-normal + "Direct :data upload with a disallowed mtype must be rejected." + (with-mocks [_mock {:target 'app.rpc.quotes/check! :return nil}] + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + font-id (uuid/custom 10 60) + data (-> (io/resource "backend_tests/test_files/font-1.ttf") (io/read*)) + params {::th/type :create-font-variant + ::rpc/profile-id (:id prof) + :team-id team-id + :font-id font-id + :font-family "invalid-type" + :font-weight 400 + :font-style "normal" + :data {"application/octet-stream" data}} + out (th/command! params)] + (t/is (some? (:error out))) + (t/is (= :validation (-> out :error ex-data :type))) + (t/is (= :media-type-not-allowed (-> out :error ex-data :code)))))) + +(t/deftest create-font-variant-invalid-type-legacy-chunked + "Legacy :data chunk-vector upload with a disallowed mtype must be rejected." + (with-mocks [_mock {:target 'app.rpc.quotes/check! :return nil}] + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + font-id (uuid/custom 10 61) + full-bytes (-> (io/resource "backend_tests/test_files/font-1.woff") (io/read*)) + chunks (split-bytes-into-chunks full-bytes (* 4 1024 1024)) + params {::th/type :create-font-variant + ::rpc/profile-id (:id prof) + :team-id team-id + :font-id font-id + :font-family "invalid-type-legacy" + :font-weight 400 + :font-style "normal" + :data {"image/png" (vec chunks)}} + out (th/command! params)] + (t/is (some? (:error out))) + (t/is (= :validation (-> out :error ex-data :type))) + (t/is (= :media-type-not-allowed (-> out :error ex-data :code)))))) + +(t/deftest create-font-variant-invalid-type-chunked-upload + "New :uploads path with a disallowed mtype must be rejected after assembly." + (with-mocks [_mock {:target 'app.rpc.quotes/check! :return nil}] + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + font-id (uuid/custom 10 62) + font-bytes (-> (io/resource "backend_tests/test_files/font-1.ttf") (io/read*)) + ;; Upload the bytes under a valid session but lie about the mtype + ;; when calling create-font-variant. + session-id (upload-font-chunked! prof font-bytes "font/ttf" (* 4 1024 1024)) + out (th/command! {::th/type :create-font-variant + ::rpc/profile-id (:id prof) + :team-id team-id + :font-id font-id + :font-family "invalid-type-chunked" + :font-weight 400 + :font-style "normal" + :uploads {"image/jpeg" session-id}})] + (t/is (some? (:error out))) + (t/is (= :validation (-> out :error ex-data :type))) + (t/is (= :media-type-not-allowed (-> out :error ex-data :code)))))) diff --git a/common/deps.edn b/common/deps.edn index 01b6d33df4..74fcef0f28 100644 --- a/common/deps.edn +++ b/common/deps.edn @@ -1,23 +1,23 @@ {:deps - {org.clojure/clojure {:mvn/version "1.12.4"} - org.clojure/data.json {:mvn/version "2.5.1"} + {org.clojure/clojure {:mvn/version "1.12.5"} + org.clojure/data.json {:mvn/version "2.5.2"} org.clojure/tools.cli {:mvn/version "1.1.230"} org.clojure/test.check {:mvn/version "1.1.1"} - org.clojure/data.fressian {:mvn/version "1.1.0"} + org.clojure/data.fressian {:mvn/version "1.1.1"} org.clojure/clojurescript {:mvn/version "1.12.42"} org.apache.commons/commons-pool2 {:mvn/version "2.12.1"} ;; Logging - org.apache.logging.log4j/log4j-api {:mvn/version "2.25.3"} - org.apache.logging.log4j/log4j-core {:mvn/version "2.25.3"} - org.apache.logging.log4j/log4j-web {:mvn/version "2.25.3"} - org.apache.logging.log4j/log4j-jul {:mvn/version "2.25.3"} - org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.25.3"} - org.slf4j/slf4j-api {:mvn/version "2.0.17"} + org.apache.logging.log4j/log4j-api {:mvn/version "2.26.0"} + org.apache.logging.log4j/log4j-core {:mvn/version "2.26.0"} + org.apache.logging.log4j/log4j-web {:mvn/version "2.26.0"} + org.apache.logging.log4j/log4j-jul {:mvn/version "2.26.0"} + org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.26.0"} + org.slf4j/slf4j-api {:mvn/version "2.0.18"} pl.tkowalcz.tjahzi/log4j2-appender {:mvn/version "0.9.41"} - selmer/selmer {:mvn/version "1.12.70"} + selmer/selmer {:mvn/version "1.13.1"} criterium/criterium {:mvn/version "0.4.6"} metosin/jsonista {:mvn/version "0.3.13"} @@ -55,12 +55,12 @@ :aliases {:dev {:extra-deps - {org.clojure/tools.namespace {:mvn/version "RELEASE"} + {org.clojure/tools.namespace {:mvn/version "1.5.0"} thheller/shadow-cljs {:mvn/version "3.2.0"} - com.clojure-goes-fast/clj-async-profiler {:mvn/version "RELEASE"} - com.bhauman/rebel-readline {:mvn/version "RELEASE"} + com.clojure-goes-fast/clj-async-profiler {:mvn/version "2.0.0-beta1"} + com.bhauman/rebel-readline {:mvn/version "0.1.5"} criterium/criterium {:mvn/version "0.4.6"} - mockery/mockery {:mvn/version "RELEASE"}} + mockery/mockery {:mvn/version "0.1.4"}} :extra-paths ["test" "dev"]} :build diff --git a/common/src/app/common/media.cljc b/common/src/app/common/media.cljc index 87d7b2f401..d047fb5106 100644 --- a/common/src/app/common/media.cljc +++ b/common/src/app/common/media.cljc @@ -13,8 +13,7 @@ #{"font/ttf" "font/woff" "font/woff2" - "font/otf" - "font/opentype"}) + "font/otf"}) (def image-types #{"image/jpeg" diff --git a/docker/images/Dockerfile.backend b/docker/images/Dockerfile.backend index c3d08916a6..c27d2c4363 100644 --- a/docker/images/Dockerfile.backend +++ b/docker/images/Dockerfile.backend @@ -5,7 +5,7 @@ ENV LANG='C.UTF-8' \ LC_ALL='C.UTF-8' \ JAVA_HOME="/opt/jdk" \ DEBIAN_FRONTEND=noninteractive \ - NODE_VERSION=v22.22.0 \ + NODE_VERSION=v24.15.0 \ TZ=Etc/UTC RUN set -ex; \ @@ -46,12 +46,12 @@ RUN set -eux; \ ARCH="$(dpkg --print-architecture)"; \ case "${ARCH}" in \ aarch64|arm64) \ - ESUM='9903c6b19183a33725ca1dfdae5b72400c9d00995c76fafc4a0d31c5152f33f7'; \ - BINARY_URL='https://cdn.azul.com/zulu/bin/zulu25.32.21-ca-jdk25.0.2-linux_aarch64.tar.gz'; \ + ESUM='cc1b459dc442d7422b46a3b5fe52acaea54879fa7913e29a05650cef54687f5f'; \ + BINARY_URL='https://cdn.azul.com/zulu/bin/zulu26.30.11-ca-jdk26.0.1-linux_aarch64.tar.gz'; \ ;; \ amd64|x86_64) \ - ESUM='946ad9766d98fc6ab495a1a120072197db54997f6925fb96680f1ecd5591db4e'; \ - BINARY_URL='https://cdn.azul.com/zulu/bin/zulu25.32.21-ca-jdk25.0.2-linux_x64.tar.gz'; \ + ESUM='7d6663ea8d4298df65de065e32f9f449745ff607d30ba5d13777cb92e9d4613d'; \ + BINARY_URL='https://cdn.azul.com/zulu/bin/zulu26.30.11-ca-jdk26.0.1-linux_x64.tar.gz'; \ ;; \ *) \ echo "Unsupported arch: ${ARCH}"; \ @@ -68,7 +68,7 @@ RUN set -eux; \ --no-header-files \ --no-man-pages \ --strip-debug \ - --add-modules java.base,jdk.management.agent,java.se,jdk.compiler,jdk.javadoc,jdk.attach,jdk.unsupported,jdk.jfr,jdk.jcmd \ + --add-modules java.base,jdk.net,jdk.management.agent,java.se,jdk.compiler,jdk.javadoc,jdk.attach,jdk.unsupported,jdk.jfr,jdk.jcmd \ --output /opt/jre; FROM ubuntu:24.04 AS image diff --git a/docker/images/Dockerfile.exporter b/docker/images/Dockerfile.exporter index 03be19d2f3..0049c6dd76 100644 --- a/docker/images/Dockerfile.exporter +++ b/docker/images/Dockerfile.exporter @@ -3,7 +3,7 @@ LABEL maintainer="Penpot " ENV LANG=en_US.UTF-8 \ LC_ALL=en_US.UTF-8 \ - NODE_VERSION=v22.22.0 \ + NODE_VERSION=v24.15.0 \ DEBIAN_FRONTEND=noninteractive \ PATH=/opt/node/bin:/opt/imagick/bin:$PATH \ PLAYWRIGHT_BROWSERS_PATH=/opt/penpot/browsers diff --git a/docker/images/Dockerfile.frontend b/docker/images/Dockerfile.frontend index 1b44b9c3a1..3e0edbf002 100644 --- a/docker/images/Dockerfile.frontend +++ b/docker/images/Dockerfile.frontend @@ -1,4 +1,4 @@ -FROM nginxinc/nginx-unprivileged:1.29.1 +FROM nginxinc/nginx-unprivileged:1.30.0 LABEL maintainer="Penpot " USER root diff --git a/docker/images/Dockerfile.storybook b/docker/images/Dockerfile.storybook index 24e6acc5cf..9cccbe799b 100644 --- a/docker/images/Dockerfile.storybook +++ b/docker/images/Dockerfile.storybook @@ -1,4 +1,4 @@ -FROM nginxinc/nginx-unprivileged:1.29.1 +FROM nginxinc/nginx-unprivileged:1.30.0 LABEL maintainer="Penpot " USER root diff --git a/docker/images/docker-compose.yaml b/docker/images/docker-compose.yaml index b4ecc2b41d..25d419a354 100644 --- a/docker/images/docker-compose.yaml +++ b/docker/images/docker-compose.yaml @@ -24,7 +24,7 @@ # WARNING: if you're exposing Penpot to the internet, you should remove the flags # 'disable-secure-session-cookies' and 'disable-email-verification' x-flags: &penpot-flags - PENPOT_FLAGS: disable-email-verification enable-smtp enable-prepl-server disable-secure-session-cookies + PENPOT_FLAGS: disable-email-verification enable-smtp enable-prepl-server disable-secure-session-cookies enable-mcp x-uri: &penpot-public-uri PENPOT_PUBLIC_URI: http://localhost:9001 @@ -78,7 +78,7 @@ services: # - "443:443" penpot-frontend: - image: "penpotapp/frontend:${PENPOT_VERSION:-latest}" + image: "penpotapp/frontend:${PENPOT_VERSION:-2.15}" restart: always ports: - 9001:8080 @@ -108,7 +108,7 @@ services: << : [*penpot-flags, *penpot-http-body-size, *penpot-public-uri] penpot-backend: - image: "penpotapp/backend:${PENPOT_VERSION:-latest}" + image: "penpotapp/backend:${PENPOT_VERSION:-2.15}" restart: always volumes: @@ -176,8 +176,14 @@ services: PENPOT_SMTP_TLS: false PENPOT_SMTP_SSL: false + penpot-mcp: + image: "penpotapp/mcp:${PENPOT_VERSION:-2.15}" + restart: always + networks: + - penpot + penpot-exporter: - image: "penpotapp/exporter:${PENPOT_VERSION:-latest}" + image: "penpotapp/exporter:${PENPOT_VERSION:-2.15}" restart: always depends_on: diff --git a/docker/images/files/nginx-entrypoint.sh b/docker/images/files/nginx-entrypoint.sh index 01e918ec5c..571c9f6782 100644 --- a/docker/images/files/nginx-entrypoint.sh +++ b/docker/images/files/nginx-entrypoint.sh @@ -43,9 +43,10 @@ update_oidc_name /var/www/app/js/config.js export PENPOT_BACKEND_URI=${PENPOT_BACKEND_URI:-http://penpot-backend:6060} export PENPOT_EXPORTER_URI=${PENPOT_EXPORTER_URI:-http://penpot-exporter:6061} export PENPOT_NITRATE_URI=${PENPOT_NITRATE_URI:-http://penpot-nitrate:3000} -export PENPOT_MCP_URI=${PENPOT_MCP_URI:-http://penpot-mcp} +export PENPOT_MCP_URI=${PENPOT_MCP_URI:-http://penpot-mcp:4401} +export PENPOT_MCP_URI_WS=${PENPOT_MCP_URI_WS:-http://penpot-mcp:4402} export PENPOT_HTTP_SERVER_MAX_BODY_SIZE=${PENPOT_HTTP_SERVER_MAX_BODY_SIZE:-367001600} # Default to 350MiB -envsubst "\$PENPOT_BACKEND_URI,\$PENPOT_EXPORTER_URI,\$PENPOT_NITRATE_URI,\$PENPOT_MCP_URI,\$PENPOT_HTTP_SERVER_MAX_BODY_SIZE" \ +envsubst "\$PENPOT_BACKEND_URI,\$PENPOT_EXPORTER_URI,\$PENPOT_NITRATE_URI,\$PENPOT_MCP_URI,\$PENPOT_MCP_URI_WS,\$PENPOT_HTTP_SERVER_MAX_BODY_SIZE" \ < /tmp/nginx.conf.template > /etc/nginx/nginx.conf PENPOT_DEFAULT_INTERNAL_RESOLVER="$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf)" diff --git a/docker/images/files/nginx.conf.template b/docker/images/files/nginx.conf.template index f365cbd512..c182856e6b 100644 --- a/docker/images/files/nginx.conf.template +++ b/docker/images/files/nginx.conf.template @@ -142,17 +142,17 @@ http { location /mcp/ws { proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; - proxy_pass $PENPOT_MCP_URI:4402; + proxy_pass $PENPOT_MCP_URI_WS; proxy_http_version 1.1; } location /mcp/stream { - proxy_pass $PENPOT_MCP_URI:4401/mcp; + proxy_pass $PENPOT_MCP_URI/mcp; proxy_http_version 1.1; } location /mcp/sse { - proxy_pass $PENPOT_MCP_URI:4401/sse; + proxy_pass $PENPOT_MCP_URI/sse; proxy_http_version 1.1; } diff --git a/docs/technical-guide/configuration.md b/docs/technical-guide/configuration.md index 4c70936dc7..ddbe801fd7 100644 --- a/docs/technical-guide/configuration.md +++ b/docs/technical-guide/configuration.md @@ -425,6 +425,12 @@ In a high-availability (HA) scenario, managing the state outside of replicas is - Valkey: Penpot only needs one Valkey instance to function correctly. Due to the nature of the data it manages, replication isn't even essential. - User media storage: This should not be configured with local storage but rather with centralized storage, such as Kubernetes PVC or S3. + +__Since version 2.15.0__ + +Starting with version 2.15, we have introduced the MCP server. Due to architectural constraints, using the MCP server requires running only a single instance of Penpot. +If the MCP server is not installed, then Penpot can scale normally and multiple application instances may be deployed without restrictions. + ## Backend This section enumerates the backend only configuration variables. diff --git a/exporter/deps.edn b/exporter/deps.edn index 1c5cd1fb55..08e3a3cdd8 100644 --- a/exporter/deps.edn +++ b/exporter/deps.edn @@ -2,12 +2,12 @@ :deps {penpot/common {:local/root "../common"} org.clojure/clojure {:mvn/version "1.12.2"} - binaryage/devtools {:mvn/version "RELEASE"} + binaryage/devtools {:mvn/version "1.0.7"} metosin/reitit-core {:mvn/version "0.9.1"} } :aliases {:outdated - {:extra-deps {com.github.liquidz/antq {:mvn/version "RELEASE"} + {:extra-deps {com.github.liquidz/antq {:mvn/version"2.11.1276"} ;; org.slf4j/slf4j-nop {:mvn/version "RELEASE"} } :main-opts ["-m" "antq.core"]} diff --git a/exporter/package.json b/exporter/package.json index 94e188896a..ee92e952f7 100644 --- a/exporter/package.json +++ b/exporter/package.json @@ -11,22 +11,22 @@ }, "type": "module", "dependencies": { - "archiver": "^7.0.1", + "archiver": "^8.0.0", "cookies": "^0.9.1", "date-fns": "^4.1.0", "generic-pool": "^3.9.0", "inflation": "^2.1.0", - "ioredis": "^5.8.2", - "playwright": "^1.57.0", + "ioredis": "^5.10.1", + "playwright": "^1.60.0", "raw-body": "^3.0.2", "source-map-support": "^0.5.21", "svgo": "penpot/svgo#v3.1", - "undici": "^7.16.0", + "undici": "^8.2.0", "xml-js": "^1.6.11", "xregexp": "^5.1.2" }, "devDependencies": { - "ws": "^8.18.3" + "ws": "^8.20.1" }, "scripts": { "clear:shadow-cache": "rm -rf .shadow-cljs && rm -rf target", diff --git a/exporter/pnpm-lock.yaml b/exporter/pnpm-lock.yaml index 78e28acc49..7acfafe825 100644 --- a/exporter/pnpm-lock.yaml +++ b/exporter/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: archiver: - specifier: ^7.0.1 - version: 7.0.1 + specifier: ^8.0.0 + version: 8.0.0 cookies: specifier: ^0.9.1 version: 0.9.1 @@ -24,11 +24,11 @@ importers: specifier: ^2.1.0 version: 2.1.0 ioredis: - specifier: ^5.8.2 - version: 5.8.2 + specifier: ^5.10.1 + version: 5.10.1 playwright: - specifier: ^1.57.0 - version: 1.57.0 + specifier: ^1.60.0 + version: 1.60.0 raw-body: specifier: ^3.0.2 version: 3.0.2 @@ -39,8 +39,8 @@ importers: specifier: penpot/svgo#v3.1 version: https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180 undici: - specifier: ^7.16.0 - version: 7.16.0 + specifier: ^8.2.0 + version: 8.2.0 xml-js: specifier: ^1.6.11 version: 1.6.11 @@ -49,8 +49,8 @@ importers: version: 5.1.2 devDependencies: ws: - specifier: ^8.18.3 - version: 8.18.3 + specifier: ^8.20.1 + version: 8.20.1 packages: @@ -58,16 +58,8 @@ packages: resolution: {integrity: sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==} engines: {node: '>=6.9.0'} - '@ioredis/commands@1.4.0': - resolution: {integrity: sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==} - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} + '@ioredis/commands@1.5.1': + resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==} '@trysound/sax@0.2.0': resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} @@ -77,43 +69,24 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} - - archiver-utils@5.0.2: - resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==} - engines: {node: '>= 14'} - - archiver@7.0.1: - resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} - engines: {node: '>= 14'} + archiver@8.0.0: + resolution: {integrity: sha512-fV1orZfsnPn9BaSByR/qE67rJCLJEy2Ox5bq7nJh+jquWaNh6Sfec75kJ2T6PtdGUbPQlrVoSVCEOa5SdiTQ1g==} + engines: {node: '>=18'} async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} - b4a@1.7.3: - resolution: {integrity: sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==} + b4a@1.8.1: + resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==} peerDependencies: react-native-b4a: '*' peerDependenciesMeta: react-native-b4a: optional: true - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} bare-events@2.8.2: resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} @@ -123,14 +96,48 @@ packages: bare-abort-controller: optional: true + bare-fs@4.7.1: + resolution: {integrity: sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==} + engines: {bare: '>=1.16.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-os@3.9.1: + resolution: {integrity: sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==} + engines: {bare: '>=1.14.0'} + + bare-path@3.0.0: + resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} + + bare-stream@2.13.1: + resolution: {integrity: sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==} + peerDependencies: + bare-abort-controller: '*' + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + bare-buffer: + optional: true + bare-events: + optional: true + + bare-url@2.4.3: + resolution: {integrity: sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} buffer-crc32@1.0.0: resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} @@ -150,16 +157,9 @@ packages: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - compress-commons@6.0.2: - resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} - engines: {node: '>= 14'} + compress-commons@7.0.1: + resolution: {integrity: sha512-g0S8KAD8qf4+V//pr3BfB1aBnARLXNz2Gx+jmHU0LEriUuoQUOPOulVquHKTJ8+EAIIO7fhseNDr9wK5Q9FKBQ==} + engines: {node: '>=18'} cookies@0.9.1: resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} @@ -176,13 +176,9 @@ packages: engines: {node: '>=0.8'} hasBin: true - crc32-stream@6.0.0: - resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} - engines: {node: '>= 14'} - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} + crc32-stream@7.0.1: + resolution: {integrity: sha512-IBWsY8xznyQrcHn8h4bC8/4ErNke5elzgG8GcqF4RFPw6aHkWWRc7Tgw6upjaTX/CT/yQgqYENkxYsTYN+hW2g==} + engines: {node: '>=18'} css-select@5.2.2: resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} @@ -236,15 +232,6 @@ packages: domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -263,10 +250,6 @@ packages: fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -276,13 +259,6 @@ packages: resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} engines: {node: '>= 4'} - glob@10.5.0: - resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} - hasBin: true - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} @@ -301,27 +277,17 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - ioredis@5.8.2: - resolution: {integrity: sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==} + ioredis@5.10.1: + resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==} engines: {node: '>=12.22.0'} - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - keygrip@1.1.0: resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} engines: {node: '>= 0.6'} @@ -340,26 +306,15 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - mdn-data@2.0.28: resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} mdn-data@2.12.2: resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} - minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} - engines: {node: '>=10'} - - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} - - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -371,24 +326,13 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - - playwright-core@1.57.0: - resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==} + playwright-core@1.60.0: + resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==} engines: {node: '>=18'} hasBin: true - playwright@1.57.0: - resolution: {integrity: sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==} + playwright@1.60.0: + resolution: {integrity: sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==} engines: {node: '>=18'} hasBin: true @@ -410,8 +354,9 @@ packages: resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - readdir-glob@1.1.3: - resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + readdir-glob@3.0.0: + resolution: {integrity: sha512-AhNB2KgKeVJr16nK9LLZbJNWnYoT23ZrumNKFDebHBdkC8KHSqWo871JAUhoWC/RtjEVdqNMFpM6qrwRbaUqpw==} + engines: {node: '>=18'} redis-errors@1.2.0: resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} @@ -436,18 +381,6 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -466,16 +399,8 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - streamx@2.23.0: - resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} + streamx@2.25.0: + resolution: {integrity: sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==} string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -483,24 +408,19 @@ packages: string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.1.2: - resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} - engines: {node: '>=12'} - svgo@https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180: resolution: {tarball: https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180} version: 4.0.0 engines: {node: '>=16.0.0'} - tar-stream@3.1.7: - resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + tar-stream@3.2.0: + resolution: {integrity: sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==} - text-decoder@1.2.3: - resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} + teex@1.0.1: + resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} + + text-decoder@1.2.7: + resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} @@ -510,9 +430,9 @@ packages: resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} engines: {node: '>=0.6.x'} - undici@7.16.0: - resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==} - engines: {node: '>=20.18.1'} + undici@8.2.0: + resolution: {integrity: sha512-Z+4Hx9GE26Lh9Upwfnc8C7SsrpBPGaM/Gm6kMFtiG7c+5IvQKlXi/t+9x9DrrCh29cww5TSP9YdVaBcnLDs5fQ==} + engines: {node: '>=22.19.0'} unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} @@ -521,21 +441,8 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + ws@8.20.1: + resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -553,9 +460,9 @@ packages: xregexp@5.1.2: resolution: {integrity: sha512-6hGgEMCGhqCTFEJbqmWrNIPqfpdirdGWkqshu7fFZddmTSfgv5Sn9D2SaKloR79s5VUiUlpwzg3CM3G6D3VIlw==} - zip-stream@6.0.1: - resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} - engines: {node: '>= 14'} + zip-stream@7.0.5: + resolution: {integrity: sha512-dSvYKdvLsAHCDqPOhIwk/q5CvuWtTB3Dgpoe0uVEFjTzIOAmsQpprX25InCvrvJsirEbu1OHyy67n/kAj1Sw/w==} + engines: {node: '>=18'} snapshots: @@ -563,19 +470,7 @@ snapshots: dependencies: core-js-pure: 3.47.0 - '@ioredis/commands@1.4.0': {} - - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.2 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - - '@pkgjs/parseargs@0.11.0': - optional: true + '@ioredis/commands@1.5.1': {} '@trysound/sax@0.2.0': {} @@ -583,54 +478,67 @@ snapshots: dependencies: event-target-shim: 5.0.1 - ansi-regex@5.0.1: {} - - ansi-regex@6.2.2: {} - - ansi-styles@4.3.0: + archiver@8.0.0: dependencies: - color-convert: 2.0.1 - - ansi-styles@6.2.3: {} - - archiver-utils@5.0.2: - dependencies: - glob: 10.5.0 - graceful-fs: 4.2.11 - is-stream: 2.0.1 - lazystream: 1.0.1 - lodash: 4.17.21 - normalize-path: 3.0.0 - readable-stream: 4.7.0 - - archiver@7.0.1: - dependencies: - archiver-utils: 5.0.2 async: 3.2.6 buffer-crc32: 1.0.0 + is-stream: 4.0.1 + lazystream: 1.0.1 + normalize-path: 3.0.0 readable-stream: 4.7.0 - readdir-glob: 1.1.3 - tar-stream: 3.1.7 - zip-stream: 6.0.1 + readdir-glob: 3.0.0 + tar-stream: 3.2.0 + zip-stream: 7.0.5 transitivePeerDependencies: - bare-abort-controller + - bare-buffer - react-native-b4a async@3.2.6: {} - b4a@1.7.3: {} + b4a@1.8.1: {} - balanced-match@1.0.2: {} + balanced-match@4.0.4: {} bare-events@2.8.2: {} + bare-fs@4.7.1: + dependencies: + bare-events: 2.8.2 + bare-path: 3.0.0 + bare-stream: 2.13.1(bare-events@2.8.2) + bare-url: 2.4.3 + fast-fifo: 1.3.2 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + bare-os@3.9.1: {} + + bare-path@3.0.0: + dependencies: + bare-os: 3.9.1 + + bare-stream@2.13.1(bare-events@2.8.2): + dependencies: + streamx: 2.25.0 + teex: 1.0.1 + optionalDependencies: + bare-events: 2.8.2 + transitivePeerDependencies: + - react-native-b4a + + bare-url@2.4.3: + dependencies: + bare-path: 3.0.0 + base64-js@1.5.1: {} boolbase@1.0.0: {} - brace-expansion@2.0.2: + brace-expansion@5.0.6: dependencies: - balanced-match: 1.0.2 + balanced-match: 4.0.4 buffer-crc32@1.0.0: {} @@ -645,17 +553,11 @@ snapshots: cluster-key-slot@1.1.2: {} - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - compress-commons@6.0.2: + compress-commons@7.0.1: dependencies: crc-32: 1.2.2 - crc32-stream: 6.0.0 - is-stream: 2.0.1 + crc32-stream: 7.0.1 + is-stream: 4.0.1 normalize-path: 3.0.0 readable-stream: 4.7.0 @@ -670,17 +572,11 @@ snapshots: crc-32@1.2.2: {} - crc32-stream@6.0.0: + crc32-stream@7.0.1: dependencies: crc-32: 1.2.2 readable-stream: 4.7.0 - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - css-select@5.2.2: dependencies: boolbase: 1.0.0 @@ -733,12 +629,6 @@ snapshots: domelementtype: 2.3.0 domhandler: 5.0.3 - eastasianwidth@0.2.0: {} - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - entities@4.5.0: {} event-target-shim@5.0.1: {} @@ -753,27 +643,11 @@ snapshots: fast-fifo@1.3.2: {} - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - fsevents@2.3.2: optional: true generic-pool@3.9.0: {} - glob@10.5.0: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - - graceful-fs@4.2.11: {} - http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -792,9 +666,9 @@ snapshots: inherits@2.0.4: {} - ioredis@5.8.2: + ioredis@5.10.1: dependencies: - '@ioredis/commands': 1.4.0 + '@ioredis/commands': 1.5.1 cluster-key-slot: 1.1.2 debug: 4.4.3 denque: 2.1.0 @@ -806,20 +680,10 @@ snapshots: transitivePeerDependencies: - supports-color - is-fullwidth-code-point@3.0.0: {} - - is-stream@2.0.1: {} + is-stream@4.0.1: {} isarray@1.0.0: {} - isexe@2.0.0: {} - - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - keygrip@1.1.0: dependencies: tsscmp: 1.0.6 @@ -834,21 +698,13 @@ snapshots: lodash@4.17.21: {} - lru-cache@10.4.3: {} - mdn-data@2.0.28: {} mdn-data@2.12.2: {} - minimatch@5.1.6: + minimatch@10.2.5: dependencies: - brace-expansion: 2.0.2 - - minimatch@9.0.5: - dependencies: - brace-expansion: 2.0.2 - - minipass@7.1.2: {} + brace-expansion: 5.0.6 ms@2.1.3: {} @@ -858,20 +714,11 @@ snapshots: dependencies: boolbase: 1.0.0 - package-json-from-dist@1.0.1: {} + playwright-core@1.60.0: {} - path-key@3.1.1: {} - - path-scurry@1.11.1: + playwright@1.60.0: dependencies: - lru-cache: 10.4.3 - minipass: 7.1.2 - - playwright-core@1.57.0: {} - - playwright@1.57.0: - dependencies: - playwright-core: 1.57.0 + playwright-core: 1.60.0 optionalDependencies: fsevents: 2.3.2 @@ -904,9 +751,9 @@ snapshots: process: 0.11.10 string_decoder: 1.3.0 - readdir-glob@1.1.3: + readdir-glob@3.0.0: dependencies: - minimatch: 5.1.6 + minimatch: 10.2.5 redis-errors@1.2.0: {} @@ -924,14 +771,6 @@ snapshots: setprototypeof@1.2.0: {} - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - signal-exit@4.1.0: {} - source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -945,27 +784,15 @@ snapshots: statuses@2.0.2: {} - streamx@2.23.0: + streamx@2.25.0: dependencies: events-universal: 1.0.1 fast-fifo: 1.3.2 - text-decoder: 1.2.3 + text-decoder: 1.2.7 transitivePeerDependencies: - bare-abort-controller - react-native-b4a - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.2 - string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 @@ -974,14 +801,6 @@ snapshots: dependencies: safe-buffer: 5.2.1 - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.1.2: - dependencies: - ansi-regex: 6.2.2 - svgo@https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180: dependencies: '@trysound/sax': 0.2.0 @@ -990,18 +809,27 @@ snapshots: csso: 5.0.5 lodash: 4.17.21 - tar-stream@3.1.7: + tar-stream@3.2.0: dependencies: - b4a: 1.7.3 + b4a: 1.8.1 + bare-fs: 4.7.1 fast-fifo: 1.3.2 - streamx: 2.23.0 + streamx: 2.25.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + teex@1.0.1: + dependencies: + streamx: 2.25.0 transitivePeerDependencies: - bare-abort-controller - react-native-b4a - text-decoder@1.2.3: + text-decoder@1.2.7: dependencies: - b4a: 1.7.3 + b4a: 1.8.1 transitivePeerDependencies: - react-native-b4a @@ -1009,29 +837,13 @@ snapshots: tsscmp@1.0.6: {} - undici@7.16.0: {} + undici@8.2.0: {} unpipe@1.0.0: {} util-deprecate@1.0.2: {} - which@2.0.2: - dependencies: - isexe: 2.0.0 - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 5.1.2 - strip-ansi: 7.1.2 - - ws@8.18.3: {} + ws@8.20.1: {} xml-js@1.6.11: dependencies: @@ -1041,8 +853,8 @@ snapshots: dependencies: '@babel/runtime-corejs3': 7.28.4 - zip-stream@6.0.1: + zip-stream@7.0.5: dependencies: - archiver-utils: 5.0.2 - compress-commons: 6.0.2 + compress-commons: 7.0.1 + normalize-path: 3.0.0 readable-stream: 4.7.0 diff --git a/frontend/package.json b/frontend/package.json index dfe7eb5047..d391219f87 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -90,7 +90,7 @@ "npm-run-all": "^4.1.5", "opentype.js": "^1.3.4", "p-limit": "^7.3.0", - "playwright": "1.59.1", + "playwright": "1.60.0", "postcss": "^8.5.8", "postcss-clean": "^1.2.2", "postcss-modules": "^6.0.1", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 54a8c367c0..9e735488b9 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -39,31 +39,31 @@ importers: version: 1.59.1 '@storybook/addon-docs': specifier: 10.3.5 - version: 10.3.5(@types/react@19.2.14)(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + version: 10.3.5(@types/react@19.2.14)(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) '@storybook/addon-themes': specifier: 10.3.5 version: 10.3.5(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) '@storybook/addon-vitest': specifier: 10.3.5 - version: 10.3.5(@vitest/browser-playwright@4.1.5)(@vitest/browser@4.1.3)(@vitest/runner@4.1.5)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vitest@4.1.5) + version: 10.3.5(@vitest/browser-playwright@4.1.6)(@vitest/browser@4.1.3)(@vitest/runner@4.1.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vitest@4.1.6) '@storybook/react-vite': specifier: 10.3.5 - version: 10.3.5(esbuild@0.28.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@6.0.3)(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + version: 10.3.5(esbuild@0.28.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@6.0.3)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) '@tokens-studio/sd-transforms': specifier: 1.2.11 version: 1.2.11(style-dictionary@5.0.0-rc.1(tslib@2.8.1)) '@types/node': specifier: ^25.5.2 - version: 25.6.2 + version: 25.7.0 '@vitest/browser': specifier: 4.1.3 - version: 4.1.3(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.5) + version: 4.1.3(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.6) '@vitest/browser-playwright': specifier: ^4.1.3 - version: 4.1.5(playwright@1.59.1)(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.5) + version: 4.1.6(playwright@1.60.0)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.6) '@vitest/coverage-v8': specifier: 4.1.3 - version: 4.1.3(@vitest/browser@4.1.3)(vitest@4.1.5) + version: 4.1.3(@vitest/browser@4.1.3)(vitest@4.1.6) '@zip.js/zip.js': specifier: 2.8.26 version: 2.8.26(patch_hash=7b556bbd426f152eb086f0126a53900e369a95cf64357c380b7c8d8e940c3d95) @@ -137,8 +137,8 @@ importers: specifier: ^7.3.0 version: 7.3.0 playwright: - specifier: 1.59.1 - version: 1.59.1 + specifier: 1.60.0 + version: 1.60.0 postcss: specifier: ^8.5.8 version: 8.5.14 @@ -210,7 +210,7 @@ importers: version: 17.0.0(postcss@8.5.14)(stylelint@17.11.0(typescript@6.0.3)) stylelint-scss: specifier: ^7.0.0 - version: 7.1.0(stylelint@17.11.0(typescript@6.0.3)) + version: 7.1.1(stylelint@17.11.0(typescript@6.0.3)) stylelint-use-logical-spec: specifier: ^5.0.1 version: 5.0.1(stylelint@17.11.0(typescript@6.0.3)) @@ -231,10 +231,10 @@ importers: version: 2.0.9 vite: specifier: ^8.0.7 - version: 8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) + version: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) vitest: specifier: ^4.1.3 - version: 4.1.5(@types/node@25.6.2)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.3)(jsdom@29.1.1(canvas@3.2.1))(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + version: 4.1.6(@types/node@25.7.0)(@vitest/browser-playwright@4.1.6)(@vitest/coverage-v8@4.1.3)(jsdom@29.1.1(canvas@3.2.1))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) wait-on: specifier: ^9.0.4 version: 9.0.10 @@ -298,7 +298,7 @@ importers: version: 10.3.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@6.0.3) '@storybook/react-vite': specifier: 10.3.5 - version: 10.3.5(esbuild@0.28.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@6.0.3)(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + version: 10.3.5(esbuild@0.28.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@6.0.3)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) '@testing-library/dom': specifier: 10.4.1 version: 10.4.1 @@ -313,7 +313,7 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(babel-plugin-react-compiler@1.0.0)(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + version: 6.0.1(babel-plugin-react-compiler@1.0.0)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) babel-plugin-react-compiler: specifier: ^1.0.0 version: 1.0.0 @@ -337,7 +337,7 @@ importers: version: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) vite-plugin-dts: specifier: ^4.5.4 - version: 4.5.4(@types/node@25.6.2)(rollup@4.57.1)(typescript@6.0.3)(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + version: 4.5.4(@types/node@25.7.0)(rollup@4.57.1)(typescript@6.0.3)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) text-editor: devDependencies: @@ -590,8 +590,8 @@ packages: '@csstools/css-parser-algorithms': ^4.0.0 '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-calc@3.2.0': - resolution: {integrity: sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==} + '@csstools/css-calc@3.2.1': + resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==} engines: {node: '>=20.19.0'} peerDependencies: '@csstools/css-parser-algorithms': ^4.0.0 @@ -604,8 +604,8 @@ packages: '@csstools/css-parser-algorithms': ^4.0.0 '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-color-parser@4.1.0': - resolution: {integrity: sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==} + '@csstools/css-color-parser@4.1.1': + resolution: {integrity: sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==} engines: {node: '>=20.19.0'} peerDependencies: '@csstools/css-parser-algorithms': ^4.0.0 @@ -620,8 +620,8 @@ packages: '@csstools/css-syntax-patches-for-csstree@1.0.26': resolution: {integrity: sha512-6boXK0KkzT5u5xOgF6TKB+CLq9SOpEGmkZw0g5n9/7yg85wab3UzSxB8TxhLJ31L4SGJ6BCFRw/iftTha1CJXA==} - '@csstools/css-syntax-patches-for-csstree@1.1.3': - resolution: {integrity: sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==} + '@csstools/css-syntax-patches-for-csstree@1.1.4': + resolution: {integrity: sha512-wgsqt92b7C7tQhIdPNxj0n9zuUbQlvAuI1exyzeNrOKOi62SD7ren8zqszmpVREjAOqg8cD2FqYhQfAuKjk4sw==} peerDependencies: css-tree: ^3.2.1 peerDependenciesMeta: @@ -2246,8 +2246,8 @@ packages: '@types/node@25.2.1': resolution: {integrity: sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg==} - '@types/node@25.6.2': - resolution: {integrity: sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw==} + '@types/node@25.7.0': + resolution: {integrity: sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==} '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} @@ -2276,11 +2276,11 @@ packages: babel-plugin-react-compiler: optional: true - '@vitest/browser-playwright@4.1.5': - resolution: {integrity: sha512-CWy0lBQJq97nionyJJdnaU4961IXTl43a7UCu5nHy51IoKxAt6PVIJLo+76rVl7KOOgcWHNkG4kbJu/pW7knvA==} + '@vitest/browser-playwright@4.1.6': + resolution: {integrity: sha512-4csoeyl/qwHyxU2zNL0++WaoDr8YJDXOQPwWPNJoTZ+QzcdO3INYKgF5Zfz730Io7zbkuv914aZmfQ+QE+1Hvw==} peerDependencies: playwright: '*' - vitest: 4.1.5 + vitest: 4.1.6 '@vitest/browser@1.6.1': resolution: {integrity: sha512-9ZYW6KQ30hJ+rIfJoGH4wAub/KAb4YrFzX0kVLASvTm7nJWVC5EAv5SlzlXVl3h3DaUq5aqHlZl77nmOPnALUQ==} @@ -2302,10 +2302,10 @@ packages: peerDependencies: vitest: 4.1.3 - '@vitest/browser@4.1.5': - resolution: {integrity: sha512-iCDGI8c4yg+xmjUg2VsygdAUSIIB4x5Rht/P68OXy1hPELKXHDkzh87lkuTcdYmemRChDkEpB426MmDjzC0ziA==} + '@vitest/browser@4.1.6': + resolution: {integrity: sha512-ynsspTubXGSpa58JFJ24xIQt4z4A25epSbugEyaTmmrV1//Wec9EgE/LtoaC6yxUrXi5P7erGHRrkdZIHaVQuA==} peerDependencies: - vitest: 4.1.5 + vitest: 4.1.6 '@vitest/coverage-v8@1.6.1': resolution: {integrity: sha512-6YeRZwuO4oTGKxD3bijok756oktHSIm3eczVVzNe3scqzuhLwltIF3S9ZL/vwOVIpURmU6SnZhziXXAfw8/Qlw==} @@ -2327,8 +2327,8 @@ packages: '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - '@vitest/expect@4.1.5': - resolution: {integrity: sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==} + '@vitest/expect@4.1.6': + resolution: {integrity: sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==} '@vitest/mocker@4.1.3': resolution: {integrity: sha512-XN3TrycitDQSzGRnec/YWgoofkYRhouyVQj4YNsJ5r/STCUFqMrP4+oxEv3e7ZbLi4og5kIHrZwekDJgw6hcjw==} @@ -2341,8 +2341,8 @@ packages: vite: optional: true - '@vitest/mocker@4.1.5': - resolution: {integrity: sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==} + '@vitest/mocker@4.1.6': + resolution: {integrity: sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -2358,20 +2358,20 @@ packages: '@vitest/pretty-format@4.1.3': resolution: {integrity: sha512-hYqqwuMbpkkBodpRh4k4cQSOELxXky1NfMmQvOfKvV8zQHz8x8Dla+2wzElkMkBvSAJX5TRGHJAQvK0TcOafwg==} - '@vitest/pretty-format@4.1.5': - resolution: {integrity: sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==} + '@vitest/pretty-format@4.1.6': + resolution: {integrity: sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==} '@vitest/runner@1.6.1': resolution: {integrity: sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==} - '@vitest/runner@4.1.5': - resolution: {integrity: sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==} + '@vitest/runner@4.1.6': + resolution: {integrity: sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==} '@vitest/snapshot@1.6.1': resolution: {integrity: sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==} - '@vitest/snapshot@4.1.5': - resolution: {integrity: sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==} + '@vitest/snapshot@4.1.6': + resolution: {integrity: sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==} '@vitest/spy@1.6.1': resolution: {integrity: sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==} @@ -2382,8 +2382,8 @@ packages: '@vitest/spy@4.1.3': resolution: {integrity: sha512-ujj5Uwxagg4XUIfAUyRQxAg631BP6e9joRiN99mr48Bg9fRs+5mdUElhOoZ6rP5mBr8Bs3lmrREnkrQWkrsTCw==} - '@vitest/spy@4.1.5': - resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==} + '@vitest/spy@4.1.6': + resolution: {integrity: sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==} '@vitest/ui@1.6.1': resolution: {integrity: sha512-xa57bCPGuzEFqGjPs3vVLyqareG8DX0uMkr5U/v5vLv5/ZUrBrPL7gzxzTJedEyZxFMfsozwTIbbYfEQVo3kgg==} @@ -2399,8 +2399,8 @@ packages: '@vitest/utils@4.1.3': resolution: {integrity: sha512-Pc/Oexse/khOWsGB+w3q4yzA4te7W4gpZZAvk+fr8qXfTURZUMj5i7kuxsNK5mP/dEB6ao3jfr0rs17fHhbHdw==} - '@vitest/utils@4.1.5': - resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} + '@vitest/utils@4.1.6': + resolution: {integrity: sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==} '@volar/language-core@2.4.28': resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} @@ -2472,6 +2472,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} @@ -2638,8 +2642,8 @@ packages: axios@0.26.1: resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} - axios@1.16.0: - resolution: {integrity: sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==} + axios@1.16.1: + resolution: {integrity: sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==} axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} @@ -3244,8 +3248,8 @@ packages: electron-to-chromium@1.5.286: resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==} - electron-to-chromium@1.5.353: - resolution: {integrity: sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==} + electron-to-chromium@1.5.355: + resolution: {integrity: sha512-LUPZhKzZPYSPme1jEYohpkA+ybYCJztr1quAdBd7E7h3+VOBVcKkwwtBJu41nrjawrRzfb8mtMfzWozoaK0ZIQ==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -3857,6 +3861,10 @@ packages: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -4652,8 +4660,8 @@ packages: node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} - node-releases@2.0.38: - resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} + node-releases@2.0.44: + resolution: {integrity: sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==} nodemon@3.1.14: resolution: {integrity: sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==} @@ -4915,6 +4923,11 @@ packages: engines: {node: '>=18'} hasBin: true + playwright-core@1.60.0: + resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==} + engines: {node: '>=18'} + hasBin: true + playwright@1.58.0: resolution: {integrity: sha512-2SVA0sbPktiIY/MCOPX8e86ehA/e+tDNq+e5Y8qjKYti2Z/JG7xnronT/TXTIkKbYGWlCbuucZ6dziEgkoEjQQ==} engines: {node: '>=18'} @@ -4925,6 +4938,11 @@ packages: engines: {node: '>=18'} hasBin: true + playwright@1.60.0: + resolution: {integrity: sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==} + engines: {node: '>=18'} + hasBin: true + pngjs@7.0.0: resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} engines: {node: '>=14.19.0'} @@ -5737,8 +5755,8 @@ packages: peerDependencies: stylelint: ^17.0.0 - stylelint-scss@7.1.0: - resolution: {integrity: sha512-ByiA9umUGQ8rJXi0cWpnKoGwuco7rWJRLs8XvpKNPBiafQPH8inWVWZkg/AMgkwhqApaMu0tLY8qnmA9gAWn8A==} + stylelint-scss@7.1.1: + resolution: {integrity: sha512-pLPXJZ7RtAFNLXe8gqarf3B56ScVTd1vPiL9IFgcJkIZsYPzAgLJPh2h9NHrp3BFeKrtAkIgwQ08QSmhvlv1gA==} engines: {node: '>=20.19.0'} peerDependencies: stylelint: ^16.8.2 || ^17.0.0 @@ -6024,8 +6042,8 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - undici-types@7.19.2: - resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} + undici-types@7.21.0: + resolution: {integrity: sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==} undici@7.25.0: resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} @@ -6201,20 +6219,20 @@ packages: jsdom: optional: true - vitest@4.1.5: - resolution: {integrity: sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==} + vitest@4.1.6: + resolution: {integrity: sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.5 - '@vitest/browser-preview': 4.1.5 - '@vitest/browser-webdriverio': 4.1.5 - '@vitest/coverage-istanbul': 4.1.5 - '@vitest/coverage-v8': 4.1.5 - '@vitest/ui': 4.1.5 + '@vitest/browser-playwright': 4.1.6 + '@vitest/browser-preview': 4.1.6 + '@vitest/browser-webdriverio': 4.1.6 + '@vitest/coverage-istanbul': 4.1.6 + '@vitest/coverage-v8': 4.1.6 + '@vitest/ui': 4.1.6 happy-dom: '*' jsdom: '*' vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -6452,8 +6470,8 @@ snapshots: '@asamuzakjp/css-color@5.1.11': dependencies: '@asamuzakjp/generational-cache': 1.0.1 - '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) - '@csstools/css-color-parser': 4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 @@ -6701,7 +6719,7 @@ snapshots: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-calc@3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 @@ -6713,10 +6731,10 @@ snapshots: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-color-parser@4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@csstools/css-color-parser@4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/color-helpers': 6.0.2 - '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 @@ -6726,7 +6744,7 @@ snapshots: '@csstools/css-syntax-patches-for-csstree@1.0.26': {} - '@csstools/css-syntax-patches-for-csstree@1.1.3(css-tree@3.2.1)': + '@csstools/css-syntax-patches-for-csstree@1.1.4(css-tree@3.2.1)': optionalDependencies: css-tree: 3.2.1 @@ -7173,11 +7191,11 @@ snapshots: dependencies: '@sinclair/typebox': 0.27.10 - '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@6.0.3)(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@6.0.3)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': dependencies: glob: 13.0.1 react-docgen-typescript: 2.4.0(typescript@6.0.3) - vite: 8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) + vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) optionalDependencies: typescript: 6.0.3 @@ -7341,23 +7359,23 @@ snapshots: '@types/react': 19.2.14 react: 19.2.4 - '@microsoft/api-extractor-model@7.32.2(@types/node@25.6.2)': + '@microsoft/api-extractor-model@7.32.2(@types/node@25.7.0)': dependencies: '@microsoft/tsdoc': 0.16.0 '@microsoft/tsdoc-config': 0.18.0 - '@rushstack/node-core-library': 5.19.1(@types/node@25.6.2) + '@rushstack/node-core-library': 5.19.1(@types/node@25.7.0) transitivePeerDependencies: - '@types/node' - '@microsoft/api-extractor@7.56.2(@types/node@25.6.2)': + '@microsoft/api-extractor@7.56.2(@types/node@25.7.0)': dependencies: - '@microsoft/api-extractor-model': 7.32.2(@types/node@25.6.2) + '@microsoft/api-extractor-model': 7.32.2(@types/node@25.7.0) '@microsoft/tsdoc': 0.16.0 '@microsoft/tsdoc-config': 0.18.0 - '@rushstack/node-core-library': 5.19.1(@types/node@25.6.2) + '@rushstack/node-core-library': 5.19.1(@types/node@25.7.0) '@rushstack/rig-package': 0.6.0 - '@rushstack/terminal': 0.21.0(@types/node@25.6.2) - '@rushstack/ts-command-line': 5.2.0(@types/node@25.6.2) + '@rushstack/terminal': 0.21.0(@types/node@25.7.0) + '@rushstack/ts-command-line': 5.2.0(@types/node@25.7.0) diff: 8.0.3 lodash: 4.17.23 minimatch: 10.1.2 @@ -7580,7 +7598,7 @@ snapshots: '@rollup/pluginutils@5.3.0(rollup@4.57.1)': dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 estree-walker: 2.0.2 picomatch: 4.0.3 optionalDependencies: @@ -7663,7 +7681,7 @@ snapshots: '@rtsao/scc@1.1.0': {} - '@rushstack/node-core-library@5.19.1(@types/node@25.6.2)': + '@rushstack/node-core-library@5.19.1(@types/node@25.7.0)': dependencies: ajv: 8.13.0 ajv-draft-04: 1.0.0(ajv@8.13.0) @@ -7674,28 +7692,28 @@ snapshots: resolve: 1.22.11 semver: 7.5.4 optionalDependencies: - '@types/node': 25.6.2 + '@types/node': 25.7.0 - '@rushstack/problem-matcher@0.1.1(@types/node@25.6.2)': + '@rushstack/problem-matcher@0.1.1(@types/node@25.7.0)': optionalDependencies: - '@types/node': 25.6.2 + '@types/node': 25.7.0 '@rushstack/rig-package@0.6.0': dependencies: resolve: 1.22.11 strip-json-comments: 3.1.1 - '@rushstack/terminal@0.21.0(@types/node@25.6.2)': + '@rushstack/terminal@0.21.0(@types/node@25.7.0)': dependencies: - '@rushstack/node-core-library': 5.19.1(@types/node@25.6.2) - '@rushstack/problem-matcher': 0.1.1(@types/node@25.6.2) + '@rushstack/node-core-library': 5.19.1(@types/node@25.7.0) + '@rushstack/problem-matcher': 0.1.1(@types/node@25.7.0) supports-color: 8.1.1 optionalDependencies: - '@types/node': 25.6.2 + '@types/node': 25.7.0 - '@rushstack/ts-command-line@5.2.0(@types/node@25.6.2)': + '@rushstack/ts-command-line@5.2.0(@types/node@25.7.0)': dependencies: - '@rushstack/terminal': 0.21.0(@types/node@25.6.2) + '@rushstack/terminal': 0.21.0(@types/node@25.7.0) '@types/argparse': 1.0.38 argparse: 1.0.10 string-argv: 0.3.2 @@ -7713,10 +7731,10 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@storybook/addon-docs@10.3.5(@types/react@19.2.14)(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': + '@storybook/addon-docs@10.3.5(@types/react@19.2.14)(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': dependencies: '@mdx-js/react': 3.1.1(@types/react@19.2.14)(react@19.2.4) - '@storybook/csf-plugin': 10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + '@storybook/csf-plugin': 10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) '@storybook/icons': 2.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@storybook/react-dom-shim': 10.3.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) react: 19.2.4 @@ -7735,59 +7753,59 @@ snapshots: storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) ts-dedent: 2.2.0 - '@storybook/addon-vitest@10.3.5(@vitest/browser-playwright@4.1.5)(@vitest/browser@4.1.3)(@vitest/runner@4.1.5)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vitest@4.1.5)': + '@storybook/addon-vitest@10.3.5(@vitest/browser-playwright@4.1.6)(@vitest/browser@4.1.3)(@vitest/runner@4.1.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vitest@4.1.6)': dependencies: '@storybook/global': 5.0.0 '@storybook/icons': 2.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) optionalDependencies: - '@vitest/browser': 4.1.3(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.5) - '@vitest/browser-playwright': 4.1.5(playwright@1.59.1)(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.5) - '@vitest/runner': 4.1.5 - vitest: 4.1.5(@types/node@25.6.2)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.3)(jsdom@29.1.1(canvas@3.2.1))(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + '@vitest/browser': 4.1.3(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.6) + '@vitest/browser-playwright': 4.1.6(playwright@1.60.0)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.6) + '@vitest/runner': 4.1.6 + vitest: 4.1.6(@types/node@25.7.0)(@vitest/browser-playwright@4.1.6)(@vitest/coverage-v8@4.1.3)(jsdom@29.1.1(canvas@3.2.1))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) transitivePeerDependencies: - react - react-dom - '@storybook/builder-vite@10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': + '@storybook/builder-vite@10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': dependencies: - '@storybook/csf-plugin': 10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + '@storybook/csf-plugin': 10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) ts-dedent: 2.2.0 - vite: 8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) + vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) transitivePeerDependencies: - esbuild - rollup - webpack - '@storybook/builder-vite@10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': + '@storybook/builder-vite@10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': dependencies: - '@storybook/csf-plugin': 10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + '@storybook/csf-plugin': 10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) ts-dedent: 2.2.0 - vite: 8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) + vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) transitivePeerDependencies: - esbuild - rollup - webpack - '@storybook/csf-plugin@10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': + '@storybook/csf-plugin@10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': dependencies: storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) unplugin: 2.3.11 optionalDependencies: esbuild: 0.28.0 rollup: 4.57.1 - vite: 8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) + vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) - '@storybook/csf-plugin@10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': + '@storybook/csf-plugin@10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': dependencies: storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) unplugin: 2.3.11 optionalDependencies: esbuild: 0.28.0 rollup: 4.57.1 - vite: 8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) + vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) '@storybook/global@5.0.0': {} @@ -7813,11 +7831,11 @@ snapshots: react-dom: 19.2.4(react@19.2.4) storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@storybook/react-vite@10.3.5(esbuild@0.28.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@6.0.3)(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': + '@storybook/react-vite@10.3.5(esbuild@0.28.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@6.0.3)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@6.0.3)(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@6.0.3)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) '@rollup/pluginutils': 5.3.0(rollup@4.57.1) - '@storybook/builder-vite': 10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + '@storybook/builder-vite': 10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) '@storybook/react': 10.3.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@6.0.3) empathic: 2.0.0 magic-string: 0.30.21 @@ -7827,7 +7845,7 @@ snapshots: resolve: 1.22.11 storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) tsconfig-paths: 4.2.0 - vite: 8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) + vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) transitivePeerDependencies: - esbuild - rollup @@ -7835,11 +7853,11 @@ snapshots: - typescript - webpack - '@storybook/react-vite@10.3.5(esbuild@0.28.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@6.0.3)(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': + '@storybook/react-vite@10.3.5(esbuild@0.28.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@6.0.3)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@6.0.3)(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@6.0.3)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) '@rollup/pluginutils': 5.3.0(rollup@4.57.1) - '@storybook/builder-vite': 10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + '@storybook/builder-vite': 10.3.5(esbuild@0.28.0)(rollup@4.57.1)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) '@storybook/react': 10.3.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@6.0.3) empathic: 2.0.0 magic-string: 0.30.21 @@ -7849,7 +7867,7 @@ snapshots: resolve: 1.22.11 storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) tsconfig-paths: 4.2.0 - vite: 8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) + vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) transitivePeerDependencies: - esbuild - rollup @@ -7997,9 +8015,9 @@ snapshots: dependencies: undici-types: 7.16.0 - '@types/node@25.6.2': + '@types/node@25.7.0': dependencies: - undici-types: 7.19.2 + undici-types: 7.21.0 '@types/react-dom@19.2.3(@types/react@19.2.14)': dependencies: @@ -8013,20 +8031,20 @@ snapshots: '@types/triple-beam@1.3.5': {} - '@vitejs/plugin-react@6.0.1(babel-plugin-react-compiler@1.0.0)(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': + '@vitejs/plugin-react@6.0.1(babel-plugin-react-compiler@1.0.0)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.7 - vite: 8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) + vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) optionalDependencies: babel-plugin-react-compiler: 1.0.0 - '@vitest/browser-playwright@4.1.5(playwright@1.59.1)(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.5)': + '@vitest/browser-playwright@4.1.6(playwright@1.60.0)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.6)': dependencies: - '@vitest/browser': 4.1.5(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.5) - '@vitest/mocker': 4.1.5(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) - playwright: 1.59.1 + '@vitest/browser': 4.1.6(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.6) + '@vitest/mocker': 4.1.6(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + playwright: 1.60.0 tinyrainbow: 3.1.0 - vitest: 4.1.5(@types/node@25.6.2)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.3)(jsdom@29.1.1(canvas@3.2.1))(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + vitest: 4.1.6(@types/node@25.7.0)(@vitest/browser-playwright@4.1.6)(@vitest/coverage-v8@4.1.3)(jsdom@29.1.1(canvas@3.2.1))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) transitivePeerDependencies: - bufferutil - msw @@ -8042,16 +8060,16 @@ snapshots: optionalDependencies: playwright: 1.58.0 - '@vitest/browser@4.1.3(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.5)': + '@vitest/browser@4.1.3(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.6)': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.3(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + '@vitest/mocker': 4.1.3(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) '@vitest/utils': 4.1.3 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.0 - vitest: 4.1.5(@types/node@25.6.2)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.3)(jsdom@29.1.1(canvas@3.2.1))(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + vitest: 4.1.6(@types/node@25.7.0)(@vitest/browser-playwright@4.1.6)(@vitest/coverage-v8@4.1.3)(jsdom@29.1.1(canvas@3.2.1))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) ws: 8.19.0 transitivePeerDependencies: - bufferutil @@ -8059,16 +8077,16 @@ snapshots: - utf-8-validate - vite - '@vitest/browser@4.1.5(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.5)': + '@vitest/browser@4.1.6(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.6)': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.5(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) - '@vitest/utils': 4.1.5 + '@vitest/mocker': 4.1.6(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + '@vitest/utils': 4.1.6 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.0 - vitest: 4.1.5(@types/node@25.6.2)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.3)(jsdom@29.1.1(canvas@3.2.1))(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + vitest: 4.1.6(@types/node@25.7.0)(@vitest/browser-playwright@4.1.6)(@vitest/coverage-v8@4.1.3)(jsdom@29.1.1(canvas@3.2.1))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) ws: 8.19.0 transitivePeerDependencies: - bufferutil @@ -8095,7 +8113,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@4.1.3(@vitest/browser@4.1.3)(vitest@4.1.5)': + '@vitest/coverage-v8@4.1.3(@vitest/browser@4.1.3)(vitest@4.1.6)': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.1.3 @@ -8107,9 +8125,9 @@ snapshots: obug: 2.1.1 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.5(@types/node@25.6.2)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.3)(jsdom@29.1.1(canvas@3.2.1))(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + vitest: 4.1.6(@types/node@25.7.0)(@vitest/browser-playwright@4.1.6)(@vitest/coverage-v8@4.1.3)(jsdom@29.1.1(canvas@3.2.1))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) optionalDependencies: - '@vitest/browser': 4.1.3(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.5) + '@vitest/browser': 4.1.3(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.6) '@vitest/expect@1.6.1': dependencies: @@ -8125,30 +8143,30 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/expect@4.1.5': + '@vitest/expect@4.1.6': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.1.5 - '@vitest/utils': 4.1.5 + '@vitest/spy': 4.1.6 + '@vitest/utils': 4.1.6 chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.3(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': + '@vitest/mocker@4.1.3(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': dependencies: '@vitest/spy': 4.1.3 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) + vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) - '@vitest/mocker@4.1.5(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': + '@vitest/mocker@4.1.6(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))': dependencies: - '@vitest/spy': 4.1.5 + '@vitest/spy': 4.1.6 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) + vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) '@vitest/pretty-format@3.2.4': dependencies: @@ -8158,7 +8176,7 @@ snapshots: dependencies: tinyrainbow: 3.1.0 - '@vitest/pretty-format@4.1.5': + '@vitest/pretty-format@4.1.6': dependencies: tinyrainbow: 3.1.0 @@ -8168,9 +8186,9 @@ snapshots: p-limit: 5.0.0 pathe: 1.1.2 - '@vitest/runner@4.1.5': + '@vitest/runner@4.1.6': dependencies: - '@vitest/utils': 4.1.5 + '@vitest/utils': 4.1.6 pathe: 2.0.3 '@vitest/snapshot@1.6.1': @@ -8179,10 +8197,10 @@ snapshots: pathe: 1.1.2 pretty-format: 29.7.0 - '@vitest/snapshot@4.1.5': + '@vitest/snapshot@4.1.6': dependencies: - '@vitest/pretty-format': 4.1.5 - '@vitest/utils': 4.1.5 + '@vitest/pretty-format': 4.1.6 + '@vitest/utils': 4.1.6 magic-string: 0.30.21 pathe: 2.0.3 @@ -8196,7 +8214,7 @@ snapshots: '@vitest/spy@4.1.3': {} - '@vitest/spy@4.1.5': {} + '@vitest/spy@4.1.6': {} '@vitest/ui@1.6.1(vitest@1.6.1)': dependencies: @@ -8228,9 +8246,9 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 - '@vitest/utils@4.1.5': + '@vitest/utils@4.1.6': dependencies: - '@vitest/pretty-format': 4.1.5 + '@vitest/pretty-format': 4.1.6 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 @@ -8306,6 +8324,12 @@ snapshots: acorn@8.16.0: {} + agent-base@6.0.2: + dependencies: + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + agent-base@7.1.4: {} ajv-draft-04@1.0.0(ajv@8.13.0): @@ -8504,13 +8528,15 @@ snapshots: transitivePeerDependencies: - debug - axios@1.16.0: + axios@1.16.1: dependencies: follow-redirects: 1.16.0 form-data: 4.0.5 + https-proxy-agent: 5.0.1 proxy-from-env: 2.1.0 transitivePeerDependencies: - debug + - supports-color axobject-query@4.1.0: {} @@ -8600,8 +8626,8 @@ snapshots: dependencies: baseline-browser-mapping: 2.10.29 caniuse-lite: 1.0.30001792 - electron-to-chromium: 1.5.353 - node-releases: 2.0.38 + electron-to-chromium: 1.5.355 + node-releases: 2.0.44 update-browserslist-db: 1.2.3(browserslist@4.28.2) buffer-crc32@0.2.13: {} @@ -9129,7 +9155,7 @@ snapshots: electron-to-chromium@1.5.286: {} - electron-to-chromium@1.5.353: {} + electron-to-chromium@1.5.355: {} emoji-regex@8.0.0: {} @@ -9977,6 +10003,13 @@ snapshots: transitivePeerDependencies: - supports-color + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 @@ -10315,7 +10348,7 @@ snapshots: '@asamuzakjp/css-color': 5.1.11 '@asamuzakjp/dom-selector': 7.1.1 '@bramus/specificity': 2.4.2 - '@csstools/css-syntax-patches-for-csstree': 1.1.3(css-tree@3.2.1) + '@csstools/css-syntax-patches-for-csstree': 1.1.4(css-tree@3.2.1) '@exodus/bytes': 1.15.0 css-tree: 3.2.1 data-urls: 7.0.0 @@ -10714,7 +10747,7 @@ snapshots: node-releases@2.0.27: {} - node-releases@2.0.38: {} + node-releases@2.0.44: {} nodemon@3.1.14: dependencies: @@ -11002,6 +11035,8 @@ snapshots: playwright-core@1.59.1: {} + playwright-core@1.60.0: {} + playwright@1.58.0: dependencies: playwright-core: 1.58.0 @@ -11014,6 +11049,12 @@ snapshots: optionalDependencies: fsevents: 2.3.2 + playwright@1.60.0: + dependencies: + playwright-core: 1.60.0 + optionalDependencies: + fsevents: 2.3.2 + pngjs@7.0.0: {} possible-typed-array-names@1.1.0: {} @@ -11933,7 +11974,7 @@ snapshots: postcss-scss: 4.0.9(postcss@8.5.14) stylelint: 17.11.0(typescript@6.0.3) stylelint-config-recommended: 18.0.0(stylelint@17.11.0(typescript@6.0.3)) - stylelint-scss: 7.1.0(stylelint@17.11.0(typescript@6.0.3)) + stylelint-scss: 7.1.1(stylelint@17.11.0(typescript@6.0.3)) optionalDependencies: postcss: 8.5.14 @@ -11954,11 +11995,11 @@ snapshots: stylelint: 17.11.0(typescript@6.0.3) stylelint-config-recommended: 18.0.0(stylelint@17.11.0(typescript@6.0.3)) - stylelint-scss@7.1.0(stylelint@17.11.0(typescript@6.0.3)): + stylelint-scss@7.1.1(stylelint@17.11.0(typescript@6.0.3)): dependencies: - '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) - '@csstools/css-syntax-patches-for-csstree': 1.1.3(css-tree@3.2.1) + '@csstools/css-syntax-patches-for-csstree': 1.1.4(css-tree@3.2.1) '@csstools/css-tokenizer': 4.0.0 css-tree: 3.2.1 is-plain-object: 5.0.0 @@ -11975,9 +12016,9 @@ snapshots: stylelint@17.11.0(typescript@6.0.3): dependencies: - '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) - '@csstools/css-syntax-patches-for-csstree': 1.1.3(css-tree@3.2.1) + '@csstools/css-syntax-patches-for-csstree': 1.1.4(css-tree@3.2.1) '@csstools/css-tokenizer': 4.0.0 '@csstools/media-query-list-parser': 5.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/selector-resolve-nested': 4.0.0(postcss-selector-parser@7.1.1) @@ -12301,7 +12342,7 @@ snapshots: undici-types@7.16.0: {} - undici-types@7.19.2: {} + undici-types@7.21.0: {} undici@7.25.0: {} @@ -12397,9 +12438,9 @@ snapshots: - supports-color - terser - vite-plugin-dts@4.5.4(@types/node@25.6.2)(rollup@4.57.1)(typescript@6.0.3)(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)): + vite-plugin-dts@4.5.4(@types/node@25.7.0)(rollup@4.57.1)(typescript@6.0.3)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)): dependencies: - '@microsoft/api-extractor': 7.56.2(@types/node@25.6.2) + '@microsoft/api-extractor': 7.56.2(@types/node@25.7.0) '@rollup/pluginutils': 5.3.0(rollup@4.57.1) '@volar/typescript': 2.4.28 '@vue/language-core': 2.2.0(typescript@6.0.3) @@ -12410,7 +12451,7 @@ snapshots: magic-string: 0.30.21 typescript: 6.0.3 optionalDependencies: - vite: 8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) + vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) transitivePeerDependencies: - '@types/node' - rollup @@ -12428,7 +12469,7 @@ snapshots: sass: 1.99.0 sass-embedded: 1.99.0 - vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2): + vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -12436,7 +12477,7 @@ snapshots: rolldown: 1.0.0 tinyglobby: 0.2.16 optionalDependencies: - '@types/node': 25.6.2 + '@types/node': 25.7.0 esbuild: 0.28.0 fsevents: 2.3.3 sass: 1.99.0 @@ -12480,15 +12521,15 @@ snapshots: - supports-color - terser - vitest@4.1.5(@types/node@25.6.2)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.3)(jsdom@29.1.1(canvas@3.2.1))(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)): + vitest@4.1.6(@types/node@25.7.0)(@vitest/browser-playwright@4.1.6)(@vitest/coverage-v8@4.1.3)(jsdom@29.1.1(canvas@3.2.1))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)): dependencies: - '@vitest/expect': 4.1.5 - '@vitest/mocker': 4.1.5(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) - '@vitest/pretty-format': 4.1.5 - '@vitest/runner': 4.1.5 - '@vitest/snapshot': 4.1.5 - '@vitest/spy': 4.1.5 - '@vitest/utils': 4.1.5 + '@vitest/expect': 4.1.6 + '@vitest/mocker': 4.1.6(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2)) + '@vitest/pretty-format': 4.1.6 + '@vitest/runner': 4.1.6 + '@vitest/snapshot': 4.1.6 + '@vitest/spy': 4.1.6 + '@vitest/utils': 4.1.6 es-module-lexer: 2.1.0 expect-type: 1.3.0 magic-string: 0.30.21 @@ -12500,12 +12541,12 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vite: 8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) + vite: 8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 25.6.2 - '@vitest/browser-playwright': 4.1.5(playwright@1.59.1)(vite@8.0.12(@types/node@25.6.2)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.5) - '@vitest/coverage-v8': 4.1.3(@vitest/browser@4.1.3)(vitest@4.1.5) + '@types/node': 25.7.0 + '@vitest/browser-playwright': 4.1.6(playwright@1.60.0)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.28.0)(sass-embedded@1.99.0)(sass@1.99.0)(yaml@2.8.2))(vitest@4.1.6) + '@vitest/coverage-v8': 4.1.3(@vitest/browser@4.1.3)(vitest@4.1.6) jsdom: 29.1.1(canvas@3.2.1) transitivePeerDependencies: - msw @@ -12518,13 +12559,14 @@ snapshots: wait-on@9.0.10: dependencies: - axios: 1.16.0 + axios: 1.16.1 joi: 18.2.1 lodash: 4.18.1 minimist: 1.2.8 rxjs: 7.8.2 transitivePeerDependencies: - debug + - supports-color wasm-pack@0.13.1: dependencies: diff --git a/frontend/src/app/main/data/fonts.cljs b/frontend/src/app/main/data/fonts.cljs index f6791f0a0d..ed891eb463 100644 --- a/frontend/src/app/main/data/fonts.cljs +++ b/frontend/src/app/main/data/fonts.cljs @@ -14,6 +14,7 @@ [app.common.uuid :as uuid] [app.main.data.event :as ev] [app.main.data.notifications :as ntf] + [app.main.data.uploads :as uploads] [app.main.fonts :as fonts] [app.main.repo :as rp] [app.main.store :as st] @@ -24,24 +25,14 @@ [cuerdas.core :as str] [potok.v2.core :as ptk])) -(def ^:const default-chunk-size - (* 1024 1024 4)) ;; 4MiB - -(defn- chunk-array - [data chunk-size] - (let [total-size (alength data)] - (loop [offset 0 - chunks []] - (if (< offset total-size) - (let [end (min (+ offset chunk-size) total-size) - chunk (.subarray ^js data offset end)] - (recur end (conj chunks chunk))) - chunks)))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; General purpose events & IMPL ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(def ^:private font-upload-chunk-size + "Size in bytes of each chunk when uploading font files (10 MiB)." + (* 1024 1024 10)) + (defn fonts-fetched [fonts] (letfn [;; Prepare font to the internal font database format. @@ -94,9 +85,44 @@ (->> (rp/cmd! :get-font-variants {:team-id team-id}) (rx/map fonts-fetched))))) +(defn upload-font-variant + "Uploads a single font variant item using the chunked upload API. + + For each mime-type in `data`, creates a Blob and uploads it via the + session-based chunked upload. Once all sessions are created, calls + `create-font-variant` with the resulting `:uploads` map so the server + can assemble the chunks and materialise the final font-variant record. + + Returns an observable that emits the created font-variant." + [{:keys [data team-id font-id font-family font-weight font-style] :as _item}] + ;; Upload each mtype as a separate chunked session in parallel, collect + ;; all [mtype session-id] pairs, then call create-font-variant with :uploads. + (->> (rx/from (seq data)) + (rx/mapcat (fn [[mtype buffer]] + (let [blob (js/Blob. #js [buffer] #js {:type mtype})] + (->> (uploads/upload-blob-chunked blob :chunk-size font-upload-chunk-size) + (rx/map (fn [{:keys [session-id]}] + [mtype session-id])))))) + (rx/reduce (fn [acc [mtype session-id]] + (assoc acc mtype session-id)) + {}) + (rx/mapcat (fn [uploads] + (rp/cmd! :create-font-variant + {:team-id team-id + :font-id font-id + :font-family font-family + :font-weight font-weight + :font-style font-style + :uploads uploads}))))) + (defn process-upload "Given a seq of blobs and the team id, creates a ready-to-use fonts - map with temporal ID's associated to each font entry." + map with temporal ID's associated to each font entry. + + Each font entry's `:data` is a map of `{mtype -> ArrayBuffer}`. The + raw `ArrayBuffer` is kept as-is so that `upload-font-variant` can + wrap it in a `Blob` and hand it directly to `upload-blob-chunked` + without any intermediate client-side chunking." [blobs team-id] (letfn [(prepare [{:keys [font type name data] :as params}] (if font @@ -134,7 +160,7 @@ (not= hhea-ascender os2-ascent) (not= hhea-descender os2-descent)))) data (js/Uint8Array. data)] - {:content {:data (chunk-array data default-chunk-size) + {:content {:data data :name name :type type} :font-family (or family "") @@ -152,7 +178,7 @@ (str/trim)) family-name (if (str/blank? raw-family-name) base-name raw-family-name) data (js/Uint8Array. data)] - {:content {:data (chunk-array data default-chunk-size) + {:content {:data data :name name :type type} :font-family family-name diff --git a/frontend/src/app/main/data/uploads.cljs b/frontend/src/app/main/data/uploads.cljs index 06e87f02d9..2721cd99cb 100644 --- a/frontend/src/app/main/data/uploads.cljs +++ b/frontend/src/app/main/data/uploads.cljs @@ -24,10 +24,6 @@ [app.main.repo :as rp] [beicon.v2.core :as rx])) -;; Size of each upload chunk in bytes. Reads the penpotUploadChunkSize global -;; variable at startup; defaults to 25 MiB (overridden in production). -(def ^:private chunk-size cf/upload-chunk-size) - (def ^:private max-parallel-chunk-uploads "Maximum number of chunk upload requests that may be in-flight at the same time within a single chunked upload session." @@ -44,8 +40,11 @@ Returns an observable that emits exactly one map: `{:session-id }` - The caller is responsible for the final step (assemble / import)." - [blob] + The caller is responsible for the final step (assemble / import). + + The optional `opts` map accepts: + `:chunk-size` – size in bytes of each chunk (default: `cf/upload-chunk-size`, 25 MiB)." + [blob & {:keys [chunk-size] :or {chunk-size cf/upload-chunk-size}}] (let [total-size (.-size blob) total-chunks (js/Math.ceil (/ total-size chunk-size))] (->> (rp/cmd! :create-upload-session diff --git a/frontend/src/app/main/ui/dashboard/fonts.cljs b/frontend/src/app/main/ui/dashboard/fonts.cljs index ab513250d7..16b8c7dec3 100644 --- a/frontend/src/app/main/ui/dashboard/fonts.cljs +++ b/frontend/src/app/main/ui/dashboard/fonts.cljs @@ -109,7 +109,7 @@ (mf/use-fn (fn [{:keys [id] :as item}] (swap! uploading* conj id) - (->> (rp/cmd! :create-font-variant item) + (->> (df/upload-font-variant item) (rx/delay-at-least 2000) (rx/subs! (fn [font] (swap! fonts* dissoc id) diff --git a/library/deps.edn b/library/deps.edn index 9edca59650..18861c635e 100644 --- a/library/deps.edn +++ b/library/deps.edn @@ -9,12 +9,12 @@ :aliases {:outdated - {:extra-deps {com.github.liquidz/antq {:mvn/version "RELEASE"}} + {:extra-deps {com.github.liquidz/antq {:mvn/version "2.11.1276"}} :main-opts ["-m" "antq.core"]} :jvm-repl {:extra-deps - {com.bhauman/rebel-readline {:mvn/version "RELEASE"}} + {com.bhauman/rebel-readline {:mvn/version "0.1.5"}} :main-opts ["-m" "rebel-readline.main"] :jvm-opts ["--sun-misc-unsafe-memory-access=allow"]} @@ -22,9 +22,9 @@ {:extra-paths ["dev"] :extra-deps {thheller/shadow-cljs {:mvn/version "3.2.1"} - com.bhauman/rebel-readline {:mvn/version "RELEASE"} - org.clojure/tools.namespace {:mvn/version "RELEASE"} - criterium/criterium {:mvn/version "RELEASE"}}} + com.bhauman/rebel-readline {:mvn/version "0.1.5"} + org.clojure/tools.namespace {:mvn/version "1.5.0"} + criterium/criterium {:mvn/version "0.4.6"}}} :shadow-cljs {:main-opts ["-m" "shadow.cljs.devtools.cli"] diff --git a/scripts/check-commit b/scripts/check-commit new file mode 100755 index 0000000000..478aee5156 --- /dev/null +++ b/scripts/check-commit @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 +""" +Check commit messages against Penpot's commit guidelines. + +Validates commit messages using the rules defined in: + - .github/workflows/commit-checker.yml (regex pattern) + - CONTRIBUTING.md (formatting rules, subject length, DCO) + +By default, checks HEAD. Use --commit to specify a different commit. + +Usage: + ./scripts/check-commit + ./scripts/check-commit --commit HEAD~1 + ./scripts/check-commit -c abc1234 +""" + +import argparse +import re +import subprocess +import sys + +# ── Emoji list ─────────────────────────────────────────────────────────────── +# Combined from commit-checker.yml AND CONTRIBUTING.md +VALID_EMOJIS = ( + "lipstick|globe_with_meridians|wrench|books|" + "arrow_up|arrow_down|zap|ambulance|construction|" + "boom|fire|whale|bug|sparkles|paperclip|tada|" + "recycle|rewind|construction_worker|rocket" +) + +# ── Regex from .github/workflows/commit-checker.yml ────────────────────────── +# Matches: +# 1) ":emoji: " +# 2) "Merge|Revert|Reapply ... without trailing dot" +COMMIT_PATTERN = re.compile( + r"^((:(" + VALID_EMOJIS + r"):\s[A-Z].*[^.]))$" +) + +MERGE_PATTERN = re.compile(r"^(Merge|Revert|Reapply).+[^.]$") + +# ═══════════════════════════════════════════════════════════════════════════════ +# Helpers +# ═══════════════════════════════════════════════════════════════════════════════ + +def run_git(args): + """Run a git command and return (returncode, stdout, stderr).""" + try: + result = subprocess.run( + ["git"] + args, + capture_output=True, + text=True, + check=False, + ) + return result.returncode, result.stdout, result.stderr + except FileNotFoundError: + print("ERROR: git not found. Is it installed?", file=sys.stderr) + sys.exit(1) + + +def get_commit_message(commit_ref): + """Return the full commit message for *commit_ref*.""" + rc, out, err = run_git(["log", "--format=%B", "-n", "1", commit_ref]) + if rc != 0: + print(f"ERROR: could not read commit {commit_ref}: {err.strip()}", file=sys.stderr) + sys.exit(1) + if not out.strip(): + print(f"ERROR: commit {commit_ref} has no message", file=sys.stderr) + sys.exit(1) + return out.rstrip("\n") + + +# ═══════════════════════════════════════════════════════════════════════════════ +# Validators +# ═══════════════════════════════════════════════════════════════════════════════ + +def check_regex(message): + """Check the commit message against the CI regex pattern.""" + # Normalise: strip trailing newlines for single-line matching + first_line = message.split("\n")[0] + + if MERGE_PATTERN.match(first_line): + return True, None + + if COMMIT_PATTERN.match(first_line): + return True, None + + return False, ( + "Commit subject must match one of:\n" + " :emoji: \n" + " Merge|Revert|Reapply \n" + f"Got: {first_line!r}" + ) + + +def check_subject_length(message): + """Subject line must be ≤ 90 characters.""" + first_line = message.split("\n")[0] + if len(first_line) > 90: + return False, ( + f"Subject line exceeds 90 characters ({len(first_line)} chars):\n" + f" {first_line}" + ) + return True, None + + +def check_subject_no_trailing_dot(message): + """Subject line must not end with a period ('.').""" + first_line = message.split("\n")[0] + if first_line.endswith("."): + return False, ( + "Subject line must not end with a period:\n" + f" {first_line}" + ) + return True, None + + +def check_subject_capitalized(message): + """Subject must be capitalized, but only if it's a regular commit (not Merge/Revert/Reapply).""" + first_line = message.split("\n")[0] + + # Skip check for Merge/Revert/Reapply commits + if MERGE_PATTERN.match(first_line): + return True, None + + # Strip emoji prefix before checking capitalization + emoji_match = re.match(r"^:([a-z_]+):\s+(.*)", first_line) + if emoji_match: + rest = emoji_match.group(2) + else: + rest = first_line + + if rest and not rest[0].isupper(): + return False, ( + "Subject line must start with a capital letter " + "(after the emoji prefix):\n" + f" {first_line}" + ) + return True, None + + +def check_body_blank_line(message): + """If a body exists, there must be a blank line between subject and body.""" + lines = message.split("\n") + if len(lines) >= 3 and lines[1] != "": + return False, ( + "A blank line must separate the subject from the body." + ) + return True, None + + +def check_signed_off_by(message): + """Check for the DCO Signed-off-by line (required for code changes).""" + if "Signed-off-by:" not in message: + return False, ( + "Missing 'Signed-off-by:' line in the commit footer.\n" + " Add it with 'git commit -s' or append it manually:\n" + " Signed-off-by: Your Real Name " + ) + return True, None + + +# ═══════════════════════════════════════════════════════════════════════════════ + +def main(): + parser = argparse.ArgumentParser( + description="Check a commit message against Penpot commit guidelines." + ) + parser.add_argument( + "-c", "--commit", + default="HEAD", + help="Commit to check (default: HEAD)", + ) + args = parser.parse_args() + + commit_ref = args.commit + message = get_commit_message(commit_ref) + + print(f"Checking commit {commit_ref} ...\n") + + validators = [ + ("Regex pattern", check_regex), + ("Subject ≤ 90 chars", check_subject_length), + ("No trailing period in subject", check_subject_no_trailing_dot), + ("Subject capitalized", check_subject_capitalized), + ("Blank line after subject", check_body_blank_line), + ] + + all_ok = True + + for name, validator in validators: + ok, error_msg = validator(message) + status = "✓" if ok else "✗" + print(f" [{status}] {name}") + if not ok: + all_ok = False + print(f" {error_msg}", file=sys.stderr) + + print() + if all_ok: + print("All checks passed.") + sys.exit(0) + else: + print("Some checks FAILED. See messages above.", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main()