mirror of
https://github.com/penpot/penpot.git
synced 2026-06-15 11:52:10 +00:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
commit
d44c6250ea
81
.opencode/skills/create-pr/SKILL.md
Normal file
81
.opencode/skills/create-pr/SKILL.md
Normal file
@ -0,0 +1,81 @@
|
||||
---
|
||||
name: create-pr
|
||||
description: Create a GitHub PR following Penpot conventions, with a concise engineer-focused description
|
||||
---
|
||||
|
||||
# Create Pull Request
|
||||
|
||||
Create a GitHub PR with proper title format and a concise description that explains reasoning, not implementation details.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Opening a new pull request
|
||||
- The user asks to create a PR
|
||||
- Code changes are ready and committed
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Verify Prerequisites
|
||||
|
||||
```bash
|
||||
git branch --show-current
|
||||
git log --oneline main..HEAD
|
||||
```
|
||||
|
||||
### 2. Check if Branch is Pushed
|
||||
|
||||
```bash
|
||||
BRANCH=$(git branch --show-current)
|
||||
if git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then
|
||||
echo "Branch is pushed, proceeding with PR creation"
|
||||
else
|
||||
echo "ERROR: Branch '$BRANCH' is not pushed to remote. Please push the branch first."
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
**If the branch is not pushed, STOP here and ask the user to push it. The LLM does not have push permissions.**
|
||||
|
||||
### 3. Create PR Body
|
||||
|
||||
Write to `/tmp/pr-body.md` to avoid shell quoting issues:
|
||||
|
||||
```bash
|
||||
cat > /tmp/pr-body.md << 'EOF'
|
||||
**Note:** This PR was created with AI assistance.
|
||||
|
||||
## What
|
||||
|
||||
<one paragraph: the problem or feature, user-facing impact>
|
||||
|
||||
## Why
|
||||
|
||||
<root cause or motivation, why this change was necessary>
|
||||
|
||||
## How
|
||||
|
||||
<high-level approach, key technical decisions>
|
||||
EOF
|
||||
```
|
||||
|
||||
### 4. Create the PR
|
||||
|
||||
Follow title and description format from `mem:workflow/creating-prs` and `mem:workflow/creating-commits`.
|
||||
|
||||
```bash
|
||||
gh pr create --base main --project "Main" --title "<title>" --body-file /tmp/pr-body.md
|
||||
```
|
||||
|
||||
### 5. What NOT to Include
|
||||
|
||||
- ❌ List of files changed (visible in diff)
|
||||
- ❌ Testing steps (CI handles this)
|
||||
- ❌ Screenshots unless UI-visible
|
||||
- ❌ Migration notes unless breaking changes
|
||||
- ❌ Regression fixes introduced during the PR (they're part of the development process, not the feature)
|
||||
|
||||
## Key Principles
|
||||
|
||||
- **Write for humans.** The diff shows what changed. The description explains why.
|
||||
- **Be concise.** Focus on reasoning: What was the problem? Why did it happen? How did you solve it?
|
||||
- **Skip the obvious.** Don't explain what `git diff` already shows.
|
||||
@ -112,7 +112,7 @@
|
||||
- Fix exported path with strokes being cut off in SVG file [#9995](https://github.com/penpot/penpot/issues/9995) (PR: [#9996](https://github.com/penpot/penpot/pull/9996))
|
||||
- Fix French Canada locale falling back to French translations instead of French Canadian (by @alexismo) [#10017](https://github.com/penpot/penpot/issues/10017) (PR: [#10027](https://github.com/penpot/penpot/pull/10027))
|
||||
|
||||
## 2.16.0 (Unreleased)
|
||||
## 2.16.0
|
||||
|
||||
### :boom: Breaking changes & Deprecations
|
||||
|
||||
|
||||
@ -311,11 +311,30 @@
|
||||
:object-id object-id
|
||||
:tag tag})))
|
||||
|
||||
(defn- delete-file-object-thumbnails!
|
||||
"Soft-deletes multiple object thumbnails in a single UPDATE statement
|
||||
with RETURNING, then touches all returned media objects."
|
||||
[{:keys [::db/conn ::sto/storage]} object-ids]
|
||||
(let [ids (db/create-array conn "text" (seq object-ids))
|
||||
sql (str/concat
|
||||
"UPDATE file_tagged_object_thumbnail"
|
||||
" SET deleted_at = now()"
|
||||
" WHERE object_id = ANY(?)"
|
||||
" AND deleted_at IS NULL"
|
||||
" RETURNING media_id")
|
||||
rows (db/exec! conn [sql ids])]
|
||||
(doseq [{:keys [media-id]} rows]
|
||||
(sto/touch-object! storage media-id))))
|
||||
|
||||
(def ^:private schema:delete-file-object-thumbnail
|
||||
[:map {:title "delete-file-object-thumbnail"}
|
||||
[:file-id ::sm/uuid]
|
||||
[:object-id [:string {:max 250}]]])
|
||||
|
||||
(def ^:private schema:delete-file-object-thumbnails
|
||||
[:map {:title "delete-file-object-thumbnails"}
|
||||
[:object-ids [:vector {:max 200} [:string {:max 250}]]]])
|
||||
|
||||
(sv/defmethod ::delete-file-object-thumbnail
|
||||
{::doc/added "1.19"
|
||||
::doc/module :files
|
||||
@ -329,6 +348,30 @@
|
||||
(delete-file-object-thumbnail! file-id object-id))
|
||||
nil)))
|
||||
|
||||
(sv/defmethod ::delete-file-object-thumbnails
|
||||
{::doc/added "1.19"
|
||||
::doc/module :files
|
||||
::climit/id [[:file-thumbnail-ops/by-profile ::rpc/profile-id]
|
||||
[:file-thumbnail-ops/global]]
|
||||
::sm/params schema:delete-file-object-thumbnails
|
||||
::audit/skip true}
|
||||
[cfg {:keys [::rpc/profile-id object-ids]}]
|
||||
(when (seq object-ids)
|
||||
;; Extract unique file-ids from object-ids for permission checks
|
||||
(let [file-ids (->> object-ids
|
||||
(map thc/get-file-id)
|
||||
(into #{}))]
|
||||
;; Check permissions for each unique file using a single connection
|
||||
(db/run! cfg (fn [{:keys [::db/conn]}]
|
||||
(doseq [file-id file-ids]
|
||||
(files/check-edition-permissions! conn profile-id file-id))))
|
||||
;; Delete all matching thumbnails in one transaction
|
||||
(db/tx-run! cfg (fn [{:keys [::db/conn] :as cfg}]
|
||||
(-> cfg
|
||||
(update ::sto/storage sto/configure conn)
|
||||
(delete-file-object-thumbnails! object-ids))
|
||||
nil)))))
|
||||
|
||||
;; --- MUTATION COMMAND: create-file-thumbnail
|
||||
|
||||
(defn- create-file-thumbnail
|
||||
|
||||
@ -380,3 +380,538 @@
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (map? (:result out))))))
|
||||
|
||||
;; --- delete-file-object-thumbnails (batch)
|
||||
|
||||
(t/deftest delete-file-object-thumbnails-basic
|
||||
(let [profile (th/create-profile* 1)
|
||||
file (th/create-file* 1 {:profile-id (:id profile)
|
||||
:project-id (:default-project-id profile)
|
||||
:is-shared false})
|
||||
page-id (first (get-in file [:data :pages]))
|
||||
oid1 (thc/fmt-object-id (:id file) page-id (uuid/random) "frame")
|
||||
oid2 (thc/fmt-object-id (:id file) page-id (uuid/random) "frame")
|
||||
oid3 (thc/fmt-object-id (:id file) page-id (uuid/random) "component")]
|
||||
|
||||
;; Create three thumbnails
|
||||
(doseq [oid [oid1 oid2 oid3]]
|
||||
(let [data {::th/type :create-file-object-thumbnail
|
||||
::rpc/profile-id (:id profile)
|
||||
:file-id (:id file)
|
||||
:object-id oid
|
||||
:media {:filename "sample.jpg"
|
||||
:size 7923
|
||||
:path (th/tempfile "backend_tests/test_files/sample2.jpg")
|
||||
:mtype "image/jpeg"}}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (map? (:result out)))))
|
||||
|
||||
;; Verify all three exist and are not soft-deleted
|
||||
(let [rows (th/db-query :file-tagged-object-thumbnail
|
||||
{:file-id (:id file)}
|
||||
{::db/remove-deleted false
|
||||
:order-by [[:created-at :asc]]})]
|
||||
(t/is (= 3 (count rows)))
|
||||
(doseq [row rows]
|
||||
(t/is (nil? (:deleted-at row)))))
|
||||
|
||||
;; Batch delete all three
|
||||
(let [data {::th/type :delete-file-object-thumbnails
|
||||
::rpc/profile-id (:id profile)
|
||||
:object-ids [oid1 oid2 oid3]}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (nil? (:result out))))
|
||||
|
||||
;; Verify all three are now soft-deleted
|
||||
(let [rows (th/db-query :file-tagged-object-thumbnail
|
||||
{:file-id (:id file)}
|
||||
{::db/remove-deleted false
|
||||
:order-by [[:created-at :asc]]})]
|
||||
(t/is (= 3 (count rows)))
|
||||
(doseq [row rows]
|
||||
(t/is (some? (:deleted-at row)))))))
|
||||
|
||||
(t/deftest delete-file-object-thumbnails-empty
|
||||
(let [profile (th/create-profile* 1)
|
||||
file (th/create-file* 1 {:profile-id (:id profile)
|
||||
:project-id (:default-project-id profile)
|
||||
:is-shared false})
|
||||
data {::th/type :delete-file-object-thumbnails
|
||||
::rpc/profile-id (:id profile)
|
||||
:object-ids []}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (nil? (:result out)))))
|
||||
|
||||
(t/deftest delete-file-object-thumbnails-non-existent
|
||||
(let [profile (th/create-profile* 1)
|
||||
file (th/create-file* 1 {:profile-id (:id profile)
|
||||
:project-id (:default-project-id profile)
|
||||
:is-shared false})
|
||||
page-id (first (get-in file [:data :pages]))
|
||||
oid1 (thc/fmt-object-id (:id file) page-id (uuid/random) "frame")
|
||||
oid2 (thc/fmt-object-id (:id file) page-id (uuid/random) "frame")]
|
||||
|
||||
;; Batch delete non-existent object-ids (no thumbnails were created)
|
||||
(let [data {::th/type :delete-file-object-thumbnails
|
||||
::rpc/profile-id (:id profile)
|
||||
:object-ids [oid1 oid2]}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (nil? (:result out))))))
|
||||
|
||||
(t/deftest delete-file-object-thumbnails-mixed-exists
|
||||
(let [profile (th/create-profile* 1)
|
||||
file (th/create-file* 1 {:profile-id (:id profile)
|
||||
:project-id (:default-project-id profile)
|
||||
:is-shared false})
|
||||
page-id (first (get-in file [:data :pages]))
|
||||
oid1 (thc/fmt-object-id (:id file) page-id (uuid/random) "frame")
|
||||
oid2 (thc/fmt-object-id (:id file) page-id (uuid/random) "frame")
|
||||
oid3 (thc/fmt-object-id (:id file) page-id (uuid/random) "frame")]
|
||||
|
||||
;; Create only one thumbnail
|
||||
(let [data {::th/type :create-file-object-thumbnail
|
||||
::rpc/profile-id (:id profile)
|
||||
:file-id (:id file)
|
||||
:object-id oid1
|
||||
:media {:filename "sample.jpg"
|
||||
:size 7923
|
||||
:path (th/tempfile "backend_tests/test_files/sample2.jpg")
|
||||
:mtype "image/jpeg"}}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (map? (:result out))))
|
||||
|
||||
;; Batch delete mix of existing and non-existing
|
||||
(let [data {::th/type :delete-file-object-thumbnails
|
||||
::rpc/profile-id (:id profile)
|
||||
:object-ids [oid1 oid2 oid3]}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (nil? (:result out))))
|
||||
|
||||
;; Verify oid1 is soft-deleted, others don't exist
|
||||
(let [rows (th/db-query :file-tagged-object-thumbnail
|
||||
{:file-id (:id file)}
|
||||
{::db/remove-deleted false})]
|
||||
(t/is (= 1 (count rows)))
|
||||
(t/is (= oid1 (:object-id (first rows))))
|
||||
(t/is (some? (:deleted-at (first rows)))))))
|
||||
|
||||
(t/deftest delete-file-object-thumbnails-already-deleted
|
||||
(let [profile (th/create-profile* 1)
|
||||
file (th/create-file* 1 {:profile-id (:id profile)
|
||||
:project-id (:default-project-id profile)
|
||||
:is-shared false})
|
||||
page-id (first (get-in file [:data :pages]))
|
||||
oid (thc/fmt-object-id (:id file) page-id (uuid/random) "frame")]
|
||||
|
||||
;; Create a thumbnail
|
||||
(let [data {::th/type :create-file-object-thumbnail
|
||||
::rpc/profile-id (:id profile)
|
||||
:file-id (:id file)
|
||||
:object-id oid
|
||||
:media {:filename "sample.jpg"
|
||||
:size 7923
|
||||
:path (th/tempfile "backend_tests/test_files/sample2.jpg")
|
||||
:mtype "image/jpeg"}}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (map? (:result out))))
|
||||
|
||||
;; First batch delete
|
||||
(let [data {::th/type :delete-file-object-thumbnails
|
||||
::rpc/profile-id (:id profile)
|
||||
:object-ids [oid]}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (nil? (:result out))))
|
||||
|
||||
;; Second batch delete (idempotent — no rows match deleted_at IS NULL)
|
||||
(let [data {::th/type :delete-file-object-thumbnails
|
||||
::rpc/profile-id (:id profile)
|
||||
:object-ids [oid]}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (nil? (:result out))))
|
||||
|
||||
;; Verify still 1 row, still soft-deleted, not duplicated
|
||||
(let [rows (th/db-query :file-tagged-object-thumbnail
|
||||
{:file-id (:id file)}
|
||||
{::db/remove-deleted false})]
|
||||
(t/is (= 1 (count rows)))
|
||||
(t/is (= oid (:object-id (first rows))))
|
||||
(t/is (some? (:deleted-at (first rows)))))))
|
||||
|
||||
(t/deftest delete-file-object-thumbnails-unauthorized
|
||||
(let [profile1 (th/create-profile* 1)
|
||||
profile2 (th/create-profile* 2)
|
||||
file (th/create-file* 1 {:profile-id (:id profile1)
|
||||
:project-id (:default-project-id profile1)
|
||||
:is-shared false})
|
||||
page-id (first (get-in file [:data :pages]))
|
||||
oid (thc/fmt-object-id (:id file) page-id (uuid/random) "frame")]
|
||||
|
||||
;; profile1 creates a thumbnail on their file
|
||||
(let [data {::th/type :create-file-object-thumbnail
|
||||
::rpc/profile-id (:id profile1)
|
||||
:file-id (:id file)
|
||||
:object-id oid
|
||||
:media {:filename "sample.jpg"
|
||||
:size 7923
|
||||
:path (th/tempfile "backend_tests/test_files/sample2.jpg")
|
||||
:mtype "image/jpeg"}}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (map? (:result out))))
|
||||
|
||||
;; profile2 tries to batch delete thumbnails from profile1's file
|
||||
(let [data {::th/type :delete-file-object-thumbnails
|
||||
::rpc/profile-id (:id profile2)
|
||||
:object-ids [oid]}
|
||||
out (th/command! data)]
|
||||
(t/is (some? (:error out)))
|
||||
(t/is (th/ex-info? (:error out)))
|
||||
(t/is (= :not-found (th/ex-type (:error out)))))
|
||||
|
||||
;; Verify the thumbnail is NOT deleted
|
||||
(let [rows (th/db-query :file-tagged-object-thumbnail
|
||||
{:file-id (:id file)}
|
||||
{::db/remove-deleted false})]
|
||||
(t/is (= 1 (count rows)))
|
||||
(t/is (nil? (:deleted-at (first rows)))))))
|
||||
|
||||
(t/deftest delete-file-object-thumbnails-cross-file
|
||||
(let [profile (th/create-profile* 1)
|
||||
file1 (th/create-file* 1 {:profile-id (:id profile)
|
||||
:project-id (:default-project-id profile)
|
||||
:is-shared false})
|
||||
file2 (th/create-file* 2 {:profile-id (:id profile)
|
||||
:project-id (:default-project-id profile)
|
||||
:is-shared false})
|
||||
page1-id (first (get-in file1 [:data :pages]))
|
||||
page2-id (first (get-in file2 [:data :pages]))
|
||||
oid1 (thc/fmt-object-id (:id file1) page1-id (uuid/random) "frame")
|
||||
oid2 (thc/fmt-object-id (:id file2) page2-id (uuid/random) "frame")]
|
||||
|
||||
;; Create thumbnails on both files
|
||||
(doseq [[oid fid] [[oid1 (:id file1)] [oid2 (:id file2)]]]
|
||||
(let [data {::th/type :create-file-object-thumbnail
|
||||
::rpc/profile-id (:id profile)
|
||||
:file-id fid
|
||||
:object-id oid
|
||||
:media {:filename "sample.jpg"
|
||||
:size 7923
|
||||
:path (th/tempfile "backend_tests/test_files/sample2.jpg")
|
||||
:mtype "image/jpeg"}}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (map? (:result out)))))
|
||||
|
||||
;; Batch delete from both files in one call
|
||||
(let [data {::th/type :delete-file-object-thumbnails
|
||||
::rpc/profile-id (:id profile)
|
||||
:object-ids [oid1 oid2]}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (nil? (:result out))))
|
||||
|
||||
;; Verify both are soft-deleted
|
||||
(let [rows1 (th/db-query :file-tagged-object-thumbnail
|
||||
{:file-id (:id file1)}
|
||||
{::db/remove-deleted false})
|
||||
rows2 (th/db-query :file-tagged-object-thumbnail
|
||||
{:file-id (:id file2)}
|
||||
{::db/remove-deleted false})]
|
||||
(t/is (= 1 (count rows1)))
|
||||
(t/is (some? (:deleted-at (first rows1))))
|
||||
(t/is (= 1 (count rows2)))
|
||||
(t/is (some? (:deleted-at (first rows2)))))))
|
||||
|
||||
(t/deftest delete-file-object-thumbnails-cross-file-unauthorized
|
||||
(let [profile1 (th/create-profile* 1)
|
||||
profile2 (th/create-profile* 2)
|
||||
file1 (th/create-file* 1 {:profile-id (:id profile1)
|
||||
:project-id (:default-project-id profile1)
|
||||
:is-shared false})
|
||||
file2 (th/create-file* 2 {:profile-id (:id profile2)
|
||||
:project-id (:default-project-id profile2)
|
||||
:is-shared false})
|
||||
page1-id (first (get-in file1 [:data :pages]))
|
||||
page2-id (first (get-in file2 [:data :pages]))
|
||||
oid1 (thc/fmt-object-id (:id file1) page1-id (uuid/random) "frame")
|
||||
oid2 (thc/fmt-object-id (:id file2) page2-id (uuid/random) "frame")]
|
||||
|
||||
;; Create thumbnails on both files (by their respective owners)
|
||||
(let [data {::th/type :create-file-object-thumbnail
|
||||
::rpc/profile-id (:id profile1)
|
||||
:file-id (:id file1)
|
||||
:object-id oid1
|
||||
:media {:filename "sample.jpg"
|
||||
:size 7923
|
||||
:path (th/tempfile "backend_tests/test_files/sample2.jpg")
|
||||
:mtype "image/jpeg"}}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (map? (:result out))))
|
||||
|
||||
(let [data {::th/type :create-file-object-thumbnail
|
||||
::rpc/profile-id (:id profile2)
|
||||
:file-id (:id file2)
|
||||
:object-id oid2
|
||||
:media {:filename "sample.jpg"
|
||||
:size 7923
|
||||
:path (th/tempfile "backend_tests/test_files/sample2.jpg")
|
||||
:mtype "image/jpeg"}}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (map? (:result out))))
|
||||
|
||||
;; profile1 tries to batch delete thumbnails from both files
|
||||
;; (profile1 does NOT have access to file2)
|
||||
(let [data {::th/type :delete-file-object-thumbnails
|
||||
::rpc/profile-id (:id profile1)
|
||||
:object-ids [oid1 oid2]}
|
||||
out (th/command! data)]
|
||||
(t/is (some? (:error out)))
|
||||
(t/is (th/ex-info? (:error out)))
|
||||
(t/is (= :not-found (th/ex-type (:error out)))))
|
||||
|
||||
;; Verify NEITHER thumbnail was deleted (all-or-nothing)
|
||||
(let [rows1 (th/db-query :file-tagged-object-thumbnail
|
||||
{:file-id (:id file1)}
|
||||
{::db/remove-deleted false})
|
||||
rows2 (th/db-query :file-tagged-object-thumbnail
|
||||
{:file-id (:id file2)}
|
||||
{::db/remove-deleted false})]
|
||||
(t/is (= 1 (count rows1)))
|
||||
(t/is (nil? (:deleted-at (first rows1))))
|
||||
(t/is (= 1 (count rows2)))
|
||||
(t/is (nil? (:deleted-at (first rows2)))))))
|
||||
|
||||
(t/deftest delete-file-object-thumbnails-media-touch
|
||||
(let [profile (th/create-profile* 1)
|
||||
file (th/create-file* 1 {:profile-id (:id profile)
|
||||
:project-id (:default-project-id profile)
|
||||
:is-shared false})
|
||||
page-id (first (get-in file [:data :pages]))
|
||||
oid1 (thc/fmt-object-id (:id file) page-id (uuid/random) "frame")
|
||||
oid2 (thc/fmt-object-id (:id file) page-id (uuid/random) "frame")]
|
||||
|
||||
;; Create two thumbnails
|
||||
(let [data {::th/type :create-file-object-thumbnail
|
||||
::rpc/profile-id (:id profile)
|
||||
:file-id (:id file)
|
||||
:object-id oid1
|
||||
:media {:filename "sample.jpg"
|
||||
:size 7923
|
||||
:path (th/tempfile "backend_tests/test_files/sample2.jpg")
|
||||
:mtype "image/jpeg"}}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (map? (:result out))))
|
||||
|
||||
(let [data {::th/type :create-file-object-thumbnail
|
||||
::rpc/profile-id (:id profile)
|
||||
:file-id (:id file)
|
||||
:object-id oid2
|
||||
:media {:filename "sample.jpg"
|
||||
:size 312043
|
||||
:path (th/tempfile "backend_tests/test_files/sample.jpg")
|
||||
:mtype "image/jpeg"}}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (map? (:result out))))
|
||||
|
||||
;; Get media-ids for both thumbnails
|
||||
(let [rows (th/db-query :file-tagged-object-thumbnail
|
||||
{:file-id (:id file)}
|
||||
{:order-by [[:created-at :asc]]})
|
||||
mid1 (:media-id (first rows))
|
||||
mid2 (:media-id (second rows))]
|
||||
|
||||
;; Verify storage objects exist (they are created with touched-at already set)
|
||||
(t/is (some? (th/db-get :storage-object {:id mid1})))
|
||||
(t/is (some? (th/db-get :storage-object {:id mid2})))
|
||||
|
||||
;; Batch delete both thumbnails
|
||||
(let [data {::th/type :delete-file-object-thumbnails
|
||||
::rpc/profile-id (:id profile)
|
||||
:object-ids [oid1 oid2]}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (nil? (:result out))))
|
||||
|
||||
;; After soft-delete, storage objects should STILL exist
|
||||
;; (they are only garbage-collected later by storage-gc-touched task)
|
||||
(t/is (some? (th/db-get :storage-object {:id mid1})))
|
||||
(t/is (some? (th/db-get :storage-object {:id mid2}))))))
|
||||
|
||||
(t/deftest delete-file-object-thumbnails-max-batch
|
||||
(let [profile (th/create-profile* 1)
|
||||
file (th/create-file* 1 {:profile-id (:id profile)
|
||||
:project-id (:default-project-id profile)
|
||||
:is-shared false})
|
||||
page-id (first (get-in file [:data :pages]))
|
||||
cnt 200
|
||||
oids (vec (repeatedly cnt
|
||||
#(thc/fmt-object-id (:id file) page-id
|
||||
(uuid/random) "frame")))]
|
||||
|
||||
;; Create 200 thumbnails
|
||||
(doseq [oid oids]
|
||||
(let [data {::th/type :create-file-object-thumbnail
|
||||
::rpc/profile-id (:id profile)
|
||||
:file-id (:id file)
|
||||
:object-id oid
|
||||
:media {:filename "sample.jpg"
|
||||
:size 7923
|
||||
:path (th/tempfile "backend_tests/test_files/sample2.jpg")
|
||||
:mtype "image/jpeg"}}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (map? (:result out)))))
|
||||
|
||||
;; Verify all 200 exist
|
||||
(let [rows (th/db-query :file-tagged-object-thumbnail
|
||||
{:file-id (:id file)}
|
||||
{::db/remove-deleted false})]
|
||||
(t/is (= cnt (count rows))))
|
||||
|
||||
;; Batch delete all 200 in one call
|
||||
(let [data {::th/type :delete-file-object-thumbnails
|
||||
::rpc/profile-id (:id profile)
|
||||
:object-ids oids}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (nil? (:result out))))
|
||||
|
||||
;; Verify all 200 are now soft-deleted
|
||||
(let [rows (th/db-query :file-tagged-object-thumbnail
|
||||
{:file-id (:id file)}
|
||||
{::db/remove-deleted false})]
|
||||
(t/is (= cnt (count rows)))
|
||||
(doseq [row rows]
|
||||
(t/is (some? (:deleted-at row)))))))
|
||||
|
||||
(t/deftest delete-file-object-thumbnails-single
|
||||
(let [profile (th/create-profile* 1)
|
||||
file (th/create-file* 1 {:profile-id (:id profile)
|
||||
:project-id (:default-project-id profile)
|
||||
:is-shared false})
|
||||
page-id (first (get-in file [:data :pages]))
|
||||
oid (thc/fmt-object-id (:id file) page-id (uuid/random) "frame")]
|
||||
|
||||
;; Create a single thumbnail
|
||||
(let [data {::th/type :create-file-object-thumbnail
|
||||
::rpc/profile-id (:id profile)
|
||||
:file-id (:id file)
|
||||
:object-id oid
|
||||
:media {:filename "sample.jpg"
|
||||
:size 7923
|
||||
:path (th/tempfile "backend_tests/test_files/sample2.jpg")
|
||||
:mtype "image/jpeg"}}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (map? (:result out))))
|
||||
|
||||
;; Batch delete just one
|
||||
(let [data {::th/type :delete-file-object-thumbnails
|
||||
::rpc/profile-id (:id profile)
|
||||
:object-ids [oid]}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (nil? (:result out))))
|
||||
|
||||
;; Verify it's soft-deleted
|
||||
(let [rows (th/db-query :file-tagged-object-thumbnail
|
||||
{:file-id (:id file)}
|
||||
{::db/remove-deleted false})]
|
||||
(t/is (= 1 (count rows)))
|
||||
(t/is (some? (:deleted-at (first rows)))))))
|
||||
|
||||
(t/deftest delete-file-object-thumbnails-same-object-twice-in-batch
|
||||
(let [profile (th/create-profile* 1)
|
||||
file (th/create-file* 1 {:profile-id (:id profile)
|
||||
:project-id (:default-project-id profile)
|
||||
:is-shared false})
|
||||
page-id (first (get-in file [:data :pages]))
|
||||
oid (thc/fmt-object-id (:id file) page-id (uuid/random) "frame")]
|
||||
|
||||
;; Create one thumbnail
|
||||
(let [data {::th/type :create-file-object-thumbnail
|
||||
::rpc/profile-id (:id profile)
|
||||
:file-id (:id file)
|
||||
:object-id oid
|
||||
:media {:filename "sample.jpg"
|
||||
:size 7923
|
||||
:path (th/tempfile "backend_tests/test_files/sample2.jpg")
|
||||
:mtype "image/jpeg"}}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (map? (:result out))))
|
||||
|
||||
;; Batch delete with the same object-id listed twice
|
||||
(let [data {::th/type :delete-file-object-thumbnails
|
||||
::rpc/profile-id (:id profile)
|
||||
:object-ids [oid oid]}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (nil? (:result out))))
|
||||
|
||||
;; Verify it's soft-deleted (only one row)
|
||||
(let [rows (th/db-query :file-tagged-object-thumbnail
|
||||
{:file-id (:id file)}
|
||||
{::db/remove-deleted false})]
|
||||
(t/is (= 1 (count rows)))
|
||||
(t/is (some? (:deleted-at (first rows)))))))
|
||||
|
||||
(t/deftest delete-file-object-thumbnails-keeps-other-files-intact
|
||||
(let [profile (th/create-profile* 1)
|
||||
file1 (th/create-file* 1 {:profile-id (:id profile)
|
||||
:project-id (:default-project-id profile)
|
||||
:is-shared false})
|
||||
file2 (th/create-file* 2 {:profile-id (:id profile)
|
||||
:project-id (:default-project-id profile)
|
||||
:is-shared false})
|
||||
page1-id (first (get-in file1 [:data :pages]))
|
||||
page2-id (first (get-in file2 [:data :pages]))
|
||||
oid1 (thc/fmt-object-id (:id file1) page1-id (uuid/random) "frame")
|
||||
oid2 (thc/fmt-object-id (:id file2) page2-id (uuid/random) "frame")]
|
||||
|
||||
;; Create thumbnails on both files
|
||||
(doseq [[oid fid] [[oid1 (:id file1)] [oid2 (:id file2)]]]
|
||||
(let [data {::th/type :create-file-object-thumbnail
|
||||
::rpc/profile-id (:id profile)
|
||||
:file-id fid
|
||||
:object-id oid
|
||||
:media {:filename "sample.jpg"
|
||||
:size 7923
|
||||
:path (th/tempfile "backend_tests/test_files/sample2.jpg")
|
||||
:mtype "image/jpeg"}}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (map? (:result out)))))
|
||||
|
||||
;; Delete only thumbnail from file1
|
||||
(let [data {::th/type :delete-file-object-thumbnails
|
||||
::rpc/profile-id (:id profile)
|
||||
:object-ids [oid1]}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (nil? (:result out))))
|
||||
|
||||
;; Verify file1's thumbnail is deleted, file2's is not
|
||||
(let [rows1 (th/db-query :file-tagged-object-thumbnail
|
||||
{:file-id (:id file1)}
|
||||
{::db/remove-deleted false})
|
||||
rows2 (th/db-query :file-tagged-object-thumbnail
|
||||
{:file-id (:id file2)}
|
||||
{::db/remove-deleted false})]
|
||||
(t/is (= 1 (count rows1)))
|
||||
(t/is (some? (:deleted-at (first rows1))))
|
||||
(t/is (= 1 (count rows2)))
|
||||
(t/is (nil? (:deleted-at (first rows2)))))))
|
||||
|
||||
|
||||
@ -32,7 +32,7 @@ RUN set -ex; \
|
||||
|
||||
FROM base AS setup-node
|
||||
|
||||
ENV NODE_VERSION=v24.15.0 \
|
||||
ENV NODE_VERSION=v24.16.0 \
|
||||
PATH=/opt/node/bin:$PATH
|
||||
|
||||
RUN set -eux; \
|
||||
|
||||
@ -5,7 +5,7 @@ ENV LANG='C.UTF-8' \
|
||||
LC_ALL='C.UTF-8' \
|
||||
JAVA_HOME="/opt/jdk" \
|
||||
DEBIAN_FRONTEND=noninteractive \
|
||||
NODE_VERSION=v24.15.0 \
|
||||
NODE_VERSION=v24.16.0 \
|
||||
TZ=Etc/UTC
|
||||
|
||||
RUN set -ex; \
|
||||
@ -16,6 +16,7 @@ RUN set -ex; \
|
||||
ca-certificates \
|
||||
curl \
|
||||
; \
|
||||
apt-get clean; \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN set -eux; \
|
||||
@ -115,13 +116,13 @@ RUN set -ex; \
|
||||
woff2 \
|
||||
; \
|
||||
find tmp/usr/share/zoneinfo/* -type d ! -name 'Etc' |xargs rm -rf; \
|
||||
apt-get clean; \
|
||||
rm -rf /var/lib /var/cache; \
|
||||
rm -rf /usr/include; \
|
||||
mkdir -p /opt/data/assets; \
|
||||
mkdir -p /opt/penpot; \
|
||||
chown -R penpot:penpot /opt/penpot; \
|
||||
chown -R penpot:penpot /opt/data; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
chown -R penpot:penpot /opt/data;
|
||||
|
||||
COPY --from=build /opt/jre /opt/jre
|
||||
COPY --from=build /opt/node /opt/node
|
||||
|
||||
@ -3,7 +3,7 @@ LABEL maintainer="Penpot <docker@penpot.app>"
|
||||
|
||||
ENV LANG=en_US.UTF-8 \
|
||||
LC_ALL=en_US.UTF-8 \
|
||||
NODE_VERSION=v24.15.0 \
|
||||
NODE_VERSION=v24.16.0 \
|
||||
DEBIAN_FRONTEND=noninteractive \
|
||||
PATH=/opt/node/bin:/opt/imagick/bin:$PATH \
|
||||
PLAYWRIGHT_BROWSERS_PATH=/opt/penpot/browsers
|
||||
@ -20,6 +20,7 @@ RUN set -ex; \
|
||||
locales \
|
||||
ca-certificates \
|
||||
; \
|
||||
apt-get clean; \
|
||||
rm -rf /var/lib/apt/lists/*; \
|
||||
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen; \
|
||||
locale-gen; \
|
||||
@ -81,6 +82,7 @@ RUN set -ex; \
|
||||
libzip4t64 \
|
||||
libzstd1 \
|
||||
; \
|
||||
apt-get clean; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
|
||||
RUN set -eux; \
|
||||
|
||||
@ -1,10 +1,15 @@
|
||||
FROM nginxinc/nginx-unprivileged:1.30.0
|
||||
FROM nginxinc/nginx-unprivileged:1.30.2-alpine
|
||||
LABEL maintainer="Penpot <docker@penpot.app>"
|
||||
|
||||
USER root
|
||||
|
||||
RUN set -ex; \
|
||||
useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \
|
||||
apk update; \
|
||||
apk upgrade; \
|
||||
apk add --no-cache bash gettext; \
|
||||
rm -rf /var/cache/apk/*; \
|
||||
addgroup -g 1001 penpot; \
|
||||
adduser -D -H -u 1001 -s /bin/false -h /opt/penpot -G penpot penpot; \
|
||||
mkdir -p /opt/data/assets; \
|
||||
chown -R penpot:penpot /opt/data; \
|
||||
mkdir -p /etc/nginx/overrides/main.d/; \
|
||||
|
||||
@ -20,6 +20,7 @@ RUN set -ex; \
|
||||
locales \
|
||||
ca-certificates \
|
||||
; \
|
||||
apt-get clean; \
|
||||
rm -rf /var/lib/apt/lists/*; \
|
||||
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen; \
|
||||
locale-gen; \
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
FROM nginxinc/nginx-unprivileged:1.30.0
|
||||
FROM nginxinc/nginx-unprivileged:1.30.2-alpine
|
||||
LABEL maintainer="Penpot <docker@penpot.app>"
|
||||
|
||||
USER root
|
||||
|
||||
RUN set -ex; \
|
||||
useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot;
|
||||
apk update; \
|
||||
apk upgrade; \
|
||||
rm -rf /var/cache/apk/*; \
|
||||
addgroup -g 1001 penpot; \
|
||||
adduser -D -H -u 1001 -s /bin/false -h /opt/penpot -G penpot penpot;
|
||||
|
||||
ARG BUNDLE_PATH="./bundle-storybook/"
|
||||
COPY $BUNDLE_PATH /var/www/
|
||||
|
||||
@ -206,6 +206,12 @@ server {
|
||||
proxy_pass http://localhost:9001/ws/notifications;
|
||||
}
|
||||
|
||||
location /mcp/ws {
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_pass http://localhost:9001/mcp/ws;
|
||||
}
|
||||
|
||||
# Proxy pass
|
||||
location / {
|
||||
proxy_set_header Host $http_host;
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
"playwright": "^1.60.0",
|
||||
"raw-body": "^3.0.2",
|
||||
"source-map-support": "^0.5.21",
|
||||
"svgo": "penpot/svgo#v3.1",
|
||||
"@penpot/svgo": "penpot/svgo#3.3.0",
|
||||
"undici": "^8.4.1",
|
||||
"xml-js": "^1.6.11",
|
||||
"xregexp": "^5.1.2"
|
||||
|
||||
184
exporter/pnpm-lock.yaml
generated
184
exporter/pnpm-lock.yaml
generated
@ -4,10 +4,18 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
lodash@<=4.17.23: ^4.17.24
|
||||
lodash@>=4.0.0 <=4.17.22: ^4.17.23
|
||||
lodash@>=4.0.0 <=4.17.23: ^4.17.24
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@penpot/svgo':
|
||||
specifier: penpot/svgo#3.3.0
|
||||
version: https://codeload.github.com/penpot/svgo/tar.gz/65b3a645df9edbe3c00acf4267dd06c2ca736021
|
||||
archiver:
|
||||
specifier: 8.0.0
|
||||
version: 8.0.0
|
||||
@ -35,9 +43,6 @@ importers:
|
||||
source-map-support:
|
||||
specifier: ^0.5.21
|
||||
version: 0.5.21
|
||||
svgo:
|
||||
specifier: penpot/svgo#v3.1
|
||||
version: https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180
|
||||
undici:
|
||||
specifier: ^8.4.1
|
||||
version: 8.4.1
|
||||
@ -54,16 +59,16 @@ importers:
|
||||
|
||||
packages:
|
||||
|
||||
'@babel/runtime-corejs3@7.28.4':
|
||||
resolution: {integrity: sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==}
|
||||
'@babel/runtime-corejs3@7.29.7':
|
||||
resolution: {integrity: sha512-ppj9ouYku+RX0ljtgZd+KMO5mkM2bCqg8H2PYAFWnLsHEIKIdRojqbJ2i3eVHrisuxy7nOFCmngTDdWtUCdXUQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@ioredis/commands@1.10.0':
|
||||
resolution: {integrity: sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q==}
|
||||
|
||||
'@trysound/sax@0.2.0':
|
||||
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
'@penpot/svgo@https://codeload.github.com/penpot/svgo/tar.gz/65b3a645df9edbe3c00acf4267dd06c2ca736021':
|
||||
resolution: {gitHosted: true, integrity: sha512-hG/pgVEWhmHEFMU+evGZkB5kHauff5Zo6ZO+Ro7HY0efsQTJft6svM4isH5jDISeSVrZ1CDGnhWBXuqkztsTWw==, tarball: https://codeload.github.com/penpot/svgo/tar.gz/65b3a645df9edbe3c00acf4267dd06c2ca736021}
|
||||
version: 3.3.0
|
||||
|
||||
abort-controller@3.0.0:
|
||||
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
|
||||
@ -132,8 +137,9 @@ packages:
|
||||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
boolbase@1.0.0:
|
||||
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
|
||||
boolbase@2.0.0:
|
||||
resolution: {integrity: sha512-DkVaaQHymRhpYEYo9x1oo7Q7B0Y6KJUsjm3c9eTyFDby4MHLBTwZ6ZDWBel5zrYxj1WsZgC5oLpiz+93MluXeA==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
brace-expansion@5.0.6:
|
||||
resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==}
|
||||
@ -165,8 +171,8 @@ packages:
|
||||
resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
core-js-pure@3.47.0:
|
||||
resolution: {integrity: sha512-BcxeDbzUrRnXGYIVAGFtcGQVNpFcUhVjr6W7F8XktvQW2iJP9e66GP6xdKotCRFlrxBvNIBrhwKteRXqMV86Nw==}
|
||||
core-js-pure@3.49.0:
|
||||
resolution: {integrity: sha512-XM4RFka59xATyJv/cS3O3Kml72hQXUeGRuuTmMYFxwzc9/7C8OYTaIR/Ji+Yt8DXzsFLNhat15cE/JP15HrCgw==}
|
||||
|
||||
core-util-is@1.0.3:
|
||||
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
||||
@ -180,20 +186,21 @@ packages:
|
||||
resolution: {integrity: sha512-IBWsY8xznyQrcHn8h4bC8/4ErNke5elzgG8GcqF4RFPw6aHkWWRc7Tgw6upjaTX/CT/yQgqYENkxYsTYN+hW2g==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
css-select@5.2.2:
|
||||
resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==}
|
||||
css-select@7.0.0:
|
||||
resolution: {integrity: sha512-snmjEVXy+1LnwXdxhYvTMj1d9tOh4HxkA1YmoayVBeeyR2C14Pum7fcxJIm4SswYspVy866eYNwlH6xC3/VH5g==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
css-tree@2.2.1:
|
||||
resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==}
|
||||
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
|
||||
|
||||
css-tree@3.1.0:
|
||||
resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==}
|
||||
css-tree@3.2.1:
|
||||
resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==}
|
||||
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
|
||||
|
||||
css-what@6.2.2:
|
||||
resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==}
|
||||
engines: {node: '>= 6'}
|
||||
css-what@8.0.0:
|
||||
resolution: {integrity: sha512-DH0Bqq3DNp5tdOReuNyAA+Ev4Y2GS5FMbZpeTLP6C4CDi0h5nL0BmUPChXw3o/qbHLDWHl49sbNqQVY7bMSDdw==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
csso@5.0.5:
|
||||
resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==}
|
||||
@ -219,22 +226,25 @@ packages:
|
||||
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
dom-serializer@2.0.0:
|
||||
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
|
||||
dom-serializer@3.1.1:
|
||||
resolution: {integrity: sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
domelementtype@2.3.0:
|
||||
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
|
||||
domelementtype@3.0.0:
|
||||
resolution: {integrity: sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
domhandler@5.0.3:
|
||||
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
|
||||
engines: {node: '>= 4'}
|
||||
domhandler@6.0.1:
|
||||
resolution: {integrity: sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
domutils@3.2.2:
|
||||
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
|
||||
domutils@4.0.2:
|
||||
resolution: {integrity: sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
entities@4.5.0:
|
||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
engines: {node: '>=0.12'}
|
||||
entities@8.0.0:
|
||||
resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
event-target-shim@5.0.1:
|
||||
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
|
||||
@ -263,8 +273,8 @@ packages:
|
||||
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
iconv-lite@0.7.1:
|
||||
resolution: {integrity: sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==}
|
||||
iconv-lite@0.7.2:
|
||||
resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
ieee754@1.2.1:
|
||||
@ -291,20 +301,19 @@ packages:
|
||||
keygrip@1.1.0:
|
||||
resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
|
||||
lazystream@1.0.1:
|
||||
resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==}
|
||||
engines: {node: '>= 0.6.3'}
|
||||
|
||||
lodash@4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
lodash@4.18.1:
|
||||
resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==}
|
||||
|
||||
mdn-data@2.0.28:
|
||||
resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==}
|
||||
|
||||
mdn-data@2.12.2:
|
||||
resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==}
|
||||
mdn-data@2.27.1:
|
||||
resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==}
|
||||
|
||||
minimatch@10.2.5:
|
||||
resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
|
||||
@ -317,8 +326,9 @@ packages:
|
||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
nth-check@2.1.1:
|
||||
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
|
||||
nth-check@3.0.1:
|
||||
resolution: {integrity: sha512-GX0gsdbGVCgnRgbeGaubfjpBXyYRWOOCVeYh08bSQvDZqxz5ndXs1OTfAt/h36G1xvI94YIspsI0sVFqAV9+RQ==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
playwright-core@1.60.0:
|
||||
resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==}
|
||||
@ -369,8 +379,9 @@ packages:
|
||||
safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
sax@1.4.3:
|
||||
resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==}
|
||||
sax@1.6.0:
|
||||
resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==}
|
||||
engines: {node: '>=11.0.0'}
|
||||
|
||||
setprototypeof@1.2.0:
|
||||
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
|
||||
@ -402,11 +413,6 @@ packages:
|
||||
string_decoder@1.3.0:
|
||||
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
||||
|
||||
svgo@https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180:
|
||||
resolution: {gitHosted: true, tarball: https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180}
|
||||
version: 4.0.0
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
tar-stream@3.2.0:
|
||||
resolution: {integrity: sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==}
|
||||
|
||||
@ -460,13 +466,19 @@ packages:
|
||||
|
||||
snapshots:
|
||||
|
||||
'@babel/runtime-corejs3@7.28.4':
|
||||
'@babel/runtime-corejs3@7.29.7':
|
||||
dependencies:
|
||||
core-js-pure: 3.47.0
|
||||
core-js-pure: 3.49.0
|
||||
|
||||
'@ioredis/commands@1.10.0': {}
|
||||
|
||||
'@trysound/sax@0.2.0': {}
|
||||
'@penpot/svgo@https://codeload.github.com/penpot/svgo/tar.gz/65b3a645df9edbe3c00acf4267dd06c2ca736021':
|
||||
dependencies:
|
||||
css-select: 7.0.0
|
||||
css-tree: 3.2.1
|
||||
csso: 5.0.5
|
||||
lodash: 4.18.1
|
||||
sax: 1.6.0
|
||||
|
||||
abort-controller@3.0.0:
|
||||
dependencies:
|
||||
@ -528,7 +540,7 @@ snapshots:
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
boolbase@1.0.0: {}
|
||||
boolbase@2.0.0: {}
|
||||
|
||||
brace-expansion@5.0.6:
|
||||
dependencies:
|
||||
@ -560,7 +572,7 @@ snapshots:
|
||||
depd: 2.0.0
|
||||
keygrip: 1.1.0
|
||||
|
||||
core-js-pure@3.47.0: {}
|
||||
core-js-pure@3.49.0: {}
|
||||
|
||||
core-util-is@1.0.3: {}
|
||||
|
||||
@ -571,25 +583,25 @@ snapshots:
|
||||
crc-32: 1.2.2
|
||||
readable-stream: 4.7.0
|
||||
|
||||
css-select@5.2.2:
|
||||
css-select@7.0.0:
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
css-what: 6.2.2
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.2.2
|
||||
nth-check: 2.1.1
|
||||
boolbase: 2.0.0
|
||||
css-what: 8.0.0
|
||||
domhandler: 6.0.1
|
||||
domutils: 4.0.2
|
||||
nth-check: 3.0.1
|
||||
|
||||
css-tree@2.2.1:
|
||||
dependencies:
|
||||
mdn-data: 2.0.28
|
||||
source-map-js: 1.2.1
|
||||
|
||||
css-tree@3.1.0:
|
||||
css-tree@3.2.1:
|
||||
dependencies:
|
||||
mdn-data: 2.12.2
|
||||
mdn-data: 2.27.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
css-what@6.2.2: {}
|
||||
css-what@8.0.0: {}
|
||||
|
||||
csso@5.0.5:
|
||||
dependencies:
|
||||
@ -605,25 +617,25 @@ snapshots:
|
||||
|
||||
depd@2.0.0: {}
|
||||
|
||||
dom-serializer@2.0.0:
|
||||
dom-serializer@3.1.1:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
entities: 4.5.0
|
||||
domelementtype: 3.0.0
|
||||
domhandler: 6.0.1
|
||||
entities: 8.0.0
|
||||
|
||||
domelementtype@2.3.0: {}
|
||||
domelementtype@3.0.0: {}
|
||||
|
||||
domhandler@5.0.3:
|
||||
domhandler@6.0.1:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
domelementtype: 3.0.0
|
||||
|
||||
domutils@3.2.2:
|
||||
domutils@4.0.2:
|
||||
dependencies:
|
||||
dom-serializer: 2.0.0
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
dom-serializer: 3.1.1
|
||||
domelementtype: 3.0.0
|
||||
domhandler: 6.0.1
|
||||
|
||||
entities@4.5.0: {}
|
||||
entities@8.0.0: {}
|
||||
|
||||
event-target-shim@5.0.1: {}
|
||||
|
||||
@ -650,7 +662,7 @@ snapshots:
|
||||
statuses: 2.0.2
|
||||
toidentifier: 1.0.1
|
||||
|
||||
iconv-lite@0.7.1:
|
||||
iconv-lite@0.7.2:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
|
||||
@ -684,11 +696,11 @@ snapshots:
|
||||
dependencies:
|
||||
readable-stream: 2.3.8
|
||||
|
||||
lodash@4.17.21: {}
|
||||
lodash@4.18.1: {}
|
||||
|
||||
mdn-data@2.0.28: {}
|
||||
|
||||
mdn-data@2.12.2: {}
|
||||
mdn-data@2.27.1: {}
|
||||
|
||||
minimatch@10.2.5:
|
||||
dependencies:
|
||||
@ -698,9 +710,9 @@ snapshots:
|
||||
|
||||
normalize-path@3.0.0: {}
|
||||
|
||||
nth-check@2.1.1:
|
||||
nth-check@3.0.1:
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
boolbase: 2.0.0
|
||||
|
||||
playwright-core@1.60.0: {}
|
||||
|
||||
@ -718,7 +730,7 @@ snapshots:
|
||||
dependencies:
|
||||
bytes: 3.1.2
|
||||
http-errors: 2.0.1
|
||||
iconv-lite: 0.7.1
|
||||
iconv-lite: 0.7.2
|
||||
unpipe: 1.0.0
|
||||
|
||||
readable-stream@2.3.8:
|
||||
@ -755,7 +767,7 @@ snapshots:
|
||||
|
||||
safer-buffer@2.1.2: {}
|
||||
|
||||
sax@1.4.3: {}
|
||||
sax@1.6.0: {}
|
||||
|
||||
setprototypeof@1.2.0: {}
|
||||
|
||||
@ -789,14 +801,6 @@ snapshots:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
svgo@https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180:
|
||||
dependencies:
|
||||
'@trysound/sax': 0.2.0
|
||||
css-select: 5.2.2
|
||||
css-tree: 3.1.0
|
||||
csso: 5.0.5
|
||||
lodash: 4.17.21
|
||||
|
||||
tar-stream@3.2.0:
|
||||
dependencies:
|
||||
b4a: 1.8.1
|
||||
@ -835,11 +839,11 @@ snapshots:
|
||||
|
||||
xml-js@1.6.11:
|
||||
dependencies:
|
||||
sax: 1.4.3
|
||||
sax: 1.6.0
|
||||
|
||||
xregexp@5.1.2:
|
||||
dependencies:
|
||||
'@babel/runtime-corejs3': 7.28.4
|
||||
'@babel/runtime-corejs3': 7.29.7
|
||||
|
||||
zip-stream@7.0.5:
|
||||
dependencies:
|
||||
|
||||
@ -1,2 +1,9 @@
|
||||
allowBuilds:
|
||||
core-js-pure: false
|
||||
minimumReleaseAgeExclude:
|
||||
- lodash@4.17.24
|
||||
- lodash@4.17.23
|
||||
overrides:
|
||||
lodash@<=4.17.23: ^4.17.24
|
||||
lodash@>=4.0.0 <=4.17.22: ^4.17.23
|
||||
lodash@>=4.0.0 <=4.17.23: ^4.17.24
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
|
||||
(ns app.renderer.svg
|
||||
(:require
|
||||
["svgo" :as svgo]
|
||||
["@penpot/svgo" :as svgo]
|
||||
["xml-js" :as xml]
|
||||
[app.browser :as bw]
|
||||
[app.common.data :as d]
|
||||
|
||||
@ -53,18 +53,18 @@
|
||||
"@penpot/draft-js": "link:packages/draft-js",
|
||||
"@penpot/mousetrap": "link:packages/mousetrap",
|
||||
"@penpot/plugins-runtime": "link:../plugins/libs/plugins-runtime",
|
||||
"@penpot/svgo": "github:penpot/svgo#v3.2",
|
||||
"@penpot/svgo": "penpot/svgo#3.3.0",
|
||||
"@penpot/text-editor": "link:text-editor",
|
||||
"@penpot/tokenscript": "link:packages/tokenscript",
|
||||
"@penpot/ui": "link:packages/ui",
|
||||
"@penpot/ua-parser": "penpot/ua-parser#1.0.0",
|
||||
"@playwright/test": "1.60.0",
|
||||
"@storybook/addon-docs": "10.4.3",
|
||||
"@storybook/addon-themes": "10.4.3",
|
||||
"@storybook/addon-vitest": "10.4.3",
|
||||
"@storybook/react-vite": "10.4.3",
|
||||
"@storybook/addon-docs": "10.4.4",
|
||||
"@storybook/addon-themes": "10.4.4",
|
||||
"@storybook/addon-vitest": "10.4.4",
|
||||
"@storybook/react-vite": "10.4.4",
|
||||
"@tokens-studio/sd-transforms": "2.0.3",
|
||||
"@types/node": "^25.9.2",
|
||||
"@types/node": "^25.9.3",
|
||||
"@vitest/browser": "4.1.8",
|
||||
"@vitest/browser-playwright": "^4.1.8",
|
||||
"@vitest/coverage-v8": "4.1.8",
|
||||
@ -73,7 +73,7 @@
|
||||
"compression": "^1.8.1",
|
||||
"concurrently": "^10.0.3",
|
||||
"date-fns": "^4.4.0",
|
||||
"esbuild": "^0.28.0",
|
||||
"esbuild": "^0.28.1",
|
||||
"eventsource-parser": "^3.1.0",
|
||||
"express": "^5.1.0",
|
||||
"fancy-log": "^2.0.0",
|
||||
@ -112,7 +112,7 @@
|
||||
"sax": "^1.6.0",
|
||||
"scheduler": "^0.27.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"storybook": "10.4.3",
|
||||
"storybook": "10.4.4",
|
||||
"style-dictionary": "5.4.4",
|
||||
"stylelint": "^17.13.0",
|
||||
"stylelint-config-standard-scss": "^17.0.0",
|
||||
|
||||
441
frontend/pnpm-lock.yaml
generated
441
frontend/pnpm-lock.yaml
generated
@ -35,8 +35,8 @@ importers:
|
||||
specifier: link:../plugins/libs/plugins-runtime
|
||||
version: link:../plugins/libs/plugins-runtime
|
||||
'@penpot/svgo':
|
||||
specifier: github:penpot/svgo#v3.2
|
||||
version: svgo@https://codeload.github.com/penpot/svgo/tar.gz/8c9b0e32e9cb5f106085260bd9375f3c91a5010b
|
||||
specifier: penpot/svgo#3.3.0
|
||||
version: https://codeload.github.com/penpot/svgo/tar.gz/65b3a645df9edbe3c00acf4267dd06c2ca736021
|
||||
'@penpot/text-editor':
|
||||
specifier: link:text-editor
|
||||
version: link:text-editor
|
||||
@ -53,22 +53,22 @@ importers:
|
||||
specifier: 1.60.0
|
||||
version: 1.60.0
|
||||
'@storybook/addon-docs':
|
||||
specifier: 10.4.3
|
||||
version: 10.4.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(storybook@10.4.3(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))
|
||||
specifier: 10.4.4
|
||||
version: 10.4.4(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(storybook@10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))
|
||||
'@storybook/addon-themes':
|
||||
specifier: 10.4.3
|
||||
version: 10.4.3(storybook@10.4.3(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))
|
||||
specifier: 10.4.4
|
||||
version: 10.4.4(storybook@10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))
|
||||
'@storybook/addon-vitest':
|
||||
specifier: 10.4.3
|
||||
version: 10.4.3(@vitest/browser-playwright@4.1.8)(@vitest/browser@4.1.8)(@vitest/runner@4.1.8)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.3(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vitest@4.1.8)
|
||||
specifier: 10.4.4
|
||||
version: 10.4.4(@vitest/browser-playwright@4.1.8)(@vitest/browser@4.1.8)(@vitest/runner@4.1.8)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vitest@4.1.8)
|
||||
'@storybook/react-vite':
|
||||
specifier: 10.4.3
|
||||
version: 10.4.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.3(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))
|
||||
specifier: 10.4.4
|
||||
version: 10.4.4(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))
|
||||
'@tokens-studio/sd-transforms':
|
||||
specifier: 2.0.3
|
||||
version: 2.0.3(style-dictionary@5.4.4(tslib@2.8.1))
|
||||
'@types/node':
|
||||
specifier: ^25.9.2
|
||||
specifier: ^25.9.3
|
||||
version: 25.9.3
|
||||
'@vitest/browser':
|
||||
specifier: 4.1.8
|
||||
@ -95,14 +95,14 @@ importers:
|
||||
specifier: ^4.4.0
|
||||
version: 4.4.0
|
||||
esbuild:
|
||||
specifier: ^0.28.0
|
||||
specifier: ^0.28.1
|
||||
version: 0.28.1
|
||||
eventsource-parser:
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.0
|
||||
express:
|
||||
specifier: ^5.1.0
|
||||
version: 5.2.1
|
||||
version: 5.2.1(supports-color@5.5.0)
|
||||
fancy-log:
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
@ -212,23 +212,23 @@ importers:
|
||||
specifier: ^0.5.21
|
||||
version: 0.5.21
|
||||
storybook:
|
||||
specifier: 10.4.3
|
||||
version: 10.4.3(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
specifier: 10.4.4
|
||||
version: 10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
style-dictionary:
|
||||
specifier: 5.4.4
|
||||
version: 5.4.4(tslib@2.8.1)
|
||||
stylelint:
|
||||
specifier: ^17.13.0
|
||||
version: 17.13.0(typescript@6.0.3)
|
||||
version: 17.13.0(supports-color@5.5.0)(typescript@6.0.3)
|
||||
stylelint-config-standard-scss:
|
||||
specifier: ^17.0.0
|
||||
version: 17.0.0(postcss@8.5.15)(stylelint@17.13.0(typescript@6.0.3))
|
||||
version: 17.0.0(postcss@8.5.15)(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3))
|
||||
stylelint-scss:
|
||||
specifier: ^7.2.0
|
||||
version: 7.2.0(stylelint@17.13.0(typescript@6.0.3))
|
||||
version: 7.2.0(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3))
|
||||
stylelint-use-logical-spec:
|
||||
specifier: ^5.0.1
|
||||
version: 5.0.1(stylelint@17.13.0(typescript@6.0.3))
|
||||
version: 5.0.1(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3))
|
||||
svg-sprite:
|
||||
specifier: ^2.0.4
|
||||
version: 2.0.4
|
||||
@ -249,7 +249,7 @@ importers:
|
||||
version: 4.1.8(@types/node@25.9.3)(@vitest/browser-playwright@4.1.8)(@vitest/coverage-v8@4.1.8)(@vitest/ui@4.1.8)(jsdom@29.1.1(canvas@3.2.3))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))
|
||||
wait-on:
|
||||
specifier: ^9.0.4
|
||||
version: 9.0.10
|
||||
version: 9.0.10(supports-color@5.5.0)
|
||||
watcher:
|
||||
specifier: ^2.3.1
|
||||
version: 2.3.1
|
||||
@ -346,7 +346,7 @@ importers:
|
||||
version: 10.4.3(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
vite-plugin-dts:
|
||||
specifier: ^5.0.2
|
||||
version: 5.0.2(esbuild@0.28.1)(rolldown@1.0.3)(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))
|
||||
version: 5.0.2(esbuild@0.28.1)(rolldown@1.0.3)(supports-color@5.5.0)(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))
|
||||
|
||||
text-editor:
|
||||
devDependencies:
|
||||
@ -1549,6 +1549,10 @@ packages:
|
||||
resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
||||
'@penpot/svgo@https://codeload.github.com/penpot/svgo/tar.gz/65b3a645df9edbe3c00acf4267dd06c2ca736021':
|
||||
resolution: {gitHosted: true, tarball: https://codeload.github.com/penpot/svgo/tar.gz/65b3a645df9edbe3c00acf4267dd06c2ca736021}
|
||||
version: 3.3.0
|
||||
|
||||
'@penpot/ua-parser@https://codeload.github.com/penpot/ua-parser/tar.gz/90b970f39f2dc08378b975a0f01045b4ec8e89a4':
|
||||
resolution: {gitHosted: true, integrity: sha512-BxcjiWGtCbGBT+dsOnEODk1jASZLNYp27BuGQaJR7fxU4gLws3251r90Sp9seubcpRhGrfRdsA5WU0ExRdPOgg==, tarball: https://codeload.github.com/penpot/ua-parser/tar.gz/90b970f39f2dc08378b975a0f01045b4ec8e89a4}
|
||||
version: 1.0.0
|
||||
@ -1765,27 +1769,27 @@ packages:
|
||||
'@standard-schema/spec@1.1.0':
|
||||
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
||||
|
||||
'@storybook/addon-docs@10.4.3':
|
||||
resolution: {integrity: sha512-CJGEXSo0zpIy7gvEeeUi09ZbjQUSNDi4YipAeb+lZGGEn8ShZUr2Pk330yd2ZO+ngNWJXD4ZxOb0e3/aIlxb3Q==}
|
||||
'@storybook/addon-docs@10.4.4':
|
||||
resolution: {integrity: sha512-yPshCvtmQTq52T2sXuXgjy7B/QbhA/WIZxLYggptNjBL8BJMvbOfp9bAfCKh7+KpRWGqDZ6Y6tWL1Q48Wj3vtw==}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
storybook: ^10.4.3
|
||||
storybook: ^10.4.4
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@storybook/addon-themes@10.4.3':
|
||||
resolution: {integrity: sha512-pL73C5h6uyIpeLXjptefjo52Kjhq6ZQh+XgTs8b93ytYy5fkupvQeoGjUnMknTjoIabNPSOhvb0Mcj5OUA+XCw==}
|
||||
'@storybook/addon-themes@10.4.4':
|
||||
resolution: {integrity: sha512-VH443z7o/JO5K9QFVuB9IzwaMu0jEiq4ybpzTlAmt0ZUEqNBuM+ESBvkVMkZ5QeNghKrs/J9yvum2g2t94YR4Q==}
|
||||
peerDependencies:
|
||||
storybook: ^10.4.3
|
||||
storybook: ^10.4.4
|
||||
|
||||
'@storybook/addon-vitest@10.4.3':
|
||||
resolution: {integrity: sha512-np5qbyc/A7bZTvRlap9eaNmp9ix9yBBhMc3ClF4u2NkyI9MNLRH2xh66mI9lsShTyUZ6NAD8Uj72YJXkcigP6w==}
|
||||
'@storybook/addon-vitest@10.4.4':
|
||||
resolution: {integrity: sha512-VPpBwf1Elr+0g33am8ZE6aHhLB+r1TPxUsnDuCVNhxGjRxMFyQkAE8+jPJFPvS/YIUGMbVXarzaV7PcI/sJuVQ==}
|
||||
peerDependencies:
|
||||
'@vitest/browser': ^3.0.0 || ^4.0.0
|
||||
'@vitest/browser-playwright': ^4.0.0
|
||||
'@vitest/runner': ^3.0.0 || ^4.0.0
|
||||
storybook: ^10.4.3
|
||||
storybook: ^10.4.4
|
||||
vitest: ^3.0.0 || ^4.0.0
|
||||
peerDependenciesMeta:
|
||||
'@vitest/browser':
|
||||
@ -1803,6 +1807,12 @@ packages:
|
||||
storybook: ^10.4.3
|
||||
vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
|
||||
'@storybook/builder-vite@10.4.4':
|
||||
resolution: {integrity: sha512-VyuZ4mEvhhVXjJa1qXMWKH8ohnas0rgEuJDf6u4aJ54XeENFebPUEAHde1Qo2PflJ4rUdVdXieOZzKbYwP5RAQ==}
|
||||
peerDependencies:
|
||||
storybook: ^10.4.4
|
||||
vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
|
||||
'@storybook/csf-plugin@10.4.3':
|
||||
resolution: {integrity: sha512-D+XF5CVhZmIOI0uhfTKxlQr+gR1z8X9djPy9phiA1USLPAOHagBAucp/PhLwlFVUxrKzEIf8yImrvkCv50IcDg==}
|
||||
peerDependencies:
|
||||
@ -1821,6 +1831,24 @@ packages:
|
||||
webpack:
|
||||
optional: true
|
||||
|
||||
'@storybook/csf-plugin@10.4.4':
|
||||
resolution: {integrity: sha512-1mzZyAwVUmAcw4WEUsJDVdSupkJf+Kf/f5uNAs4RzlBXA75P8YRkDKAb2EoMwsB5URiXFi9XoeAN/vWke0G6+w==}
|
||||
peerDependencies:
|
||||
esbuild: '*'
|
||||
rollup: '*'
|
||||
storybook: ^10.4.4
|
||||
vite: '*'
|
||||
webpack: '*'
|
||||
peerDependenciesMeta:
|
||||
esbuild:
|
||||
optional: true
|
||||
rollup:
|
||||
optional: true
|
||||
vite:
|
||||
optional: true
|
||||
webpack:
|
||||
optional: true
|
||||
|
||||
'@storybook/global@5.0.0':
|
||||
resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==}
|
||||
|
||||
@ -1844,6 +1872,20 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@storybook/react-dom-shim@10.4.4':
|
||||
resolution: {integrity: sha512-y6SObmoW78AydE6VfKQSUmCkuqiaMPy9LgMpMdMEyWfJ/pSxBDMIKycr9dlRMJP1cvNgByaJgrusWtA46ndSQw==}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
'@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
storybook: ^10.4.4
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@storybook/react-vite@10.4.3':
|
||||
resolution: {integrity: sha512-Pk/hi10JFuwJ5sj/HAapWrgaa9Z83oT4MJPbHqeKzMt3A3jkIru4L9ibnt82bzV3crOaiErprvOlAFDsjxhrrQ==}
|
||||
peerDependencies:
|
||||
@ -1852,6 +1894,14 @@ packages:
|
||||
storybook: ^10.4.3
|
||||
vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
|
||||
'@storybook/react-vite@10.4.4':
|
||||
resolution: {integrity: sha512-hXw1c9Jq2eFzwmJ3u9phmszbHoPjwPLYjcR1Grd6Xbe2g3bReGH35urm/fTZ0HNdjXAgQlUaXp2bWw6vz0BHQw==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
storybook: ^10.4.4
|
||||
vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
|
||||
'@storybook/react@10.4.3':
|
||||
resolution: {integrity: sha512-Td+Zoi8ylJTPC1jg5vHw8OK7U2kJgqc5kuAn92UvD4IbAkcpMTBRPHDziK1piv6q7r8yNLVah+ku6IKHpTLeXA==}
|
||||
peerDependencies:
|
||||
@ -1869,6 +1919,23 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
'@storybook/react@10.4.4':
|
||||
resolution: {integrity: sha512-6K5/uHrvjswrueyVpUt6IWGuSgYCMtMOYyVs86XJZYqKBV3Pv7nGsGNH7YSMLAVQBZW4CQqm2etd5Op0GHY9Kg==}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
'@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
storybook: ^10.4.4
|
||||
typescript: '>= 4.9.x'
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
'@testing-library/dom@10.4.1':
|
||||
resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==}
|
||||
engines: {node: '>=18'}
|
||||
@ -1912,10 +1979,6 @@ packages:
|
||||
'@tokens-studio/types@0.5.2':
|
||||
resolution: {integrity: sha512-rzMcZP0bj2E5jaa7Fj0LGgYHysoCrbrxILVbT0ohsCUH5uCHY/u6J7Qw/TE0n6gR9Js/c9ZO9T8mOoz0HdLMbA==}
|
||||
|
||||
'@trysound/sax@0.2.0':
|
||||
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
'@tybys/wasm-util@0.10.2':
|
||||
resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==}
|
||||
|
||||
@ -2269,6 +2332,10 @@ packages:
|
||||
boolbase@1.0.0:
|
||||
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
|
||||
|
||||
boolbase@2.0.0:
|
||||
resolution: {integrity: sha512-DkVaaQHymRhpYEYo9x1oo7Q7B0Y6KJUsjm3c9eTyFDby4MHLBTwZ6ZDWBel5zrYxj1WsZgC5oLpiz+93MluXeA==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
brace-expansion@1.1.15:
|
||||
resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==}
|
||||
|
||||
@ -2546,8 +2613,9 @@ packages:
|
||||
css-select@4.3.0:
|
||||
resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==}
|
||||
|
||||
css-select@5.2.2:
|
||||
resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==}
|
||||
css-select@7.0.0:
|
||||
resolution: {integrity: sha512-snmjEVXy+1LnwXdxhYvTMj1d9tOh4HxkA1YmoayVBeeyR2C14Pum7fcxJIm4SswYspVy866eYNwlH6xC3/VH5g==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
css-selector-parser@1.4.1:
|
||||
resolution: {integrity: sha512-HYPSb7y/Z7BNDCOrakL4raGO2zltZkbeXyAd6Tg9obzix6QhzxCotdBl6VT0Dv4vZfJGVz3WL/xaEI9Ly3ul0g==}
|
||||
@ -2568,6 +2636,10 @@ packages:
|
||||
resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
css-what@8.0.0:
|
||||
resolution: {integrity: sha512-DH0Bqq3DNp5tdOReuNyAA+Ev4Y2GS5FMbZpeTLP6C4CDi0h5nL0BmUPChXw3o/qbHLDWHl49sbNqQVY7bMSDdw==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
css.escape@1.5.1:
|
||||
resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
|
||||
|
||||
@ -2718,25 +2790,31 @@ packages:
|
||||
dom-serializer@1.4.1:
|
||||
resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==}
|
||||
|
||||
dom-serializer@2.0.0:
|
||||
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
|
||||
dom-serializer@3.1.1:
|
||||
resolution: {integrity: sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
domelementtype@2.3.0:
|
||||
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
|
||||
|
||||
domelementtype@3.0.0:
|
||||
resolution: {integrity: sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
domhandler@4.3.1:
|
||||
resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
domhandler@5.0.3:
|
||||
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
|
||||
engines: {node: '>= 4'}
|
||||
domhandler@6.0.1:
|
||||
resolution: {integrity: sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
domutils@2.8.0:
|
||||
resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
|
||||
|
||||
domutils@3.2.2:
|
||||
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
|
||||
domutils@4.0.2:
|
||||
resolution: {integrity: sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
draft-js@https://codeload.github.com/penpot/draft-js/tar.gz/4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0:
|
||||
resolution: {gitHosted: true, tarball: https://codeload.github.com/penpot/draft-js/tar.gz/4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0}
|
||||
@ -2792,10 +2870,6 @@ packages:
|
||||
entities@2.2.0:
|
||||
resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
|
||||
|
||||
entities@4.5.0:
|
||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
entities@8.0.0:
|
||||
resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
@ -4015,6 +4089,10 @@ packages:
|
||||
nth-check@2.1.1:
|
||||
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
|
||||
|
||||
nth-check@3.0.1:
|
||||
resolution: {integrity: sha512-GX0gsdbGVCgnRgbeGaubfjpBXyYRWOOCVeYh08bSQvDZqxz5ndXs1OTfAt/h36G1xvI94YIspsI0sVFqAV9+RQ==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
object-assign@4.1.1:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -4858,6 +4936,21 @@ packages:
|
||||
vite-plus:
|
||||
optional: true
|
||||
|
||||
storybook@10.4.4:
|
||||
resolution: {integrity: sha512-Nn0qFRxU5fyABa6dGRftfL3lz0Y+HkKOaAkfytF8S4Q2K6Szwwq7TwPAEs3Wsj8hBQbYhsobrKADcPsyXQpJaA==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
prettier: ^2 || ^3
|
||||
vite-plus: ^0.1.15
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
prettier:
|
||||
optional: true
|
||||
vite-plus:
|
||||
optional: true
|
||||
|
||||
stream@0.0.3:
|
||||
resolution: {integrity: sha512-aMsbn7VKrl4A2T7QAQQbzgN7NVc70vgF5INQrBXqn4dCXN1zy3L9HGgLO5s7PExmdrzTJ8uR/27aviW8or8/+A==}
|
||||
|
||||
@ -5035,11 +5128,6 @@ packages:
|
||||
engines: {node: '>=10.13.0'}
|
||||
hasBin: true
|
||||
|
||||
svgo@https://codeload.github.com/penpot/svgo/tar.gz/8c9b0e32e9cb5f106085260bd9375f3c91a5010b:
|
||||
resolution: {gitHosted: true, tarball: https://codeload.github.com/penpot/svgo/tar.gz/8c9b0e32e9cb5f106085260bd9375f3c91a5010b}
|
||||
version: 4.0.0
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
symbol-tree@3.2.4:
|
||||
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
|
||||
|
||||
@ -6519,6 +6607,14 @@ snapshots:
|
||||
'@parcel/watcher-win32-x64': 2.5.6
|
||||
optional: true
|
||||
|
||||
'@penpot/svgo@https://codeload.github.com/penpot/svgo/tar.gz/65b3a645df9edbe3c00acf4267dd06c2ca736021':
|
||||
dependencies:
|
||||
css-select: 7.0.0
|
||||
css-tree: 3.2.1
|
||||
csso: 5.0.5
|
||||
lodash: 4.18.1
|
||||
sax: 1.6.0
|
||||
|
||||
'@penpot/ua-parser@https://codeload.github.com/penpot/ua-parser/tar.gz/90b970f39f2dc08378b975a0f01045b4ec8e89a4': {}
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
@ -6649,15 +6745,15 @@ snapshots:
|
||||
|
||||
'@standard-schema/spec@1.1.0': {}
|
||||
|
||||
'@storybook/addon-docs@10.4.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(storybook@10.4.3(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))':
|
||||
'@storybook/addon-docs@10.4.4(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(storybook@10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))':
|
||||
dependencies:
|
||||
'@mdx-js/react': 3.1.1(@types/react@19.2.17)(react@19.2.7)
|
||||
'@storybook/csf-plugin': 10.4.3(esbuild@0.28.1)(storybook@10.4.3(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))
|
||||
'@storybook/csf-plugin': 10.4.4(esbuild@0.28.1)(storybook@10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))
|
||||
'@storybook/icons': 2.0.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@storybook/react-dom-shim': 10.4.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.3(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))
|
||||
'@storybook/react-dom-shim': 10.4.4(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))
|
||||
react: 19.2.7
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
storybook: 10.4.3(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
storybook: 10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
ts-dedent: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
@ -6668,16 +6764,16 @@ snapshots:
|
||||
- vite
|
||||
- webpack
|
||||
|
||||
'@storybook/addon-themes@10.4.3(storybook@10.4.3(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))':
|
||||
'@storybook/addon-themes@10.4.4(storybook@10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))':
|
||||
dependencies:
|
||||
storybook: 10.4.3(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
storybook: 10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
ts-dedent: 2.3.0
|
||||
|
||||
'@storybook/addon-vitest@10.4.3(@vitest/browser-playwright@4.1.8)(@vitest/browser@4.1.8)(@vitest/runner@4.1.8)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.3(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vitest@4.1.8)':
|
||||
'@storybook/addon-vitest@10.4.4(@vitest/browser-playwright@4.1.8)(@vitest/browser@4.1.8)(@vitest/runner@4.1.8)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vitest@4.1.8)':
|
||||
dependencies:
|
||||
'@storybook/global': 5.0.0
|
||||
'@storybook/icons': 2.0.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
storybook: 10.4.3(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
storybook: 10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
optionalDependencies:
|
||||
'@vitest/browser': 4.1.8(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))(vitest@4.1.8)
|
||||
'@vitest/browser-playwright': 4.1.8(playwright@1.60.0)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))(vitest@4.1.8)
|
||||
@ -6698,6 +6794,17 @@ snapshots:
|
||||
- rollup
|
||||
- webpack
|
||||
|
||||
'@storybook/builder-vite@10.4.4(esbuild@0.28.1)(storybook@10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))':
|
||||
dependencies:
|
||||
'@storybook/csf-plugin': 10.4.4(esbuild@0.28.1)(storybook@10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))
|
||||
storybook: 10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
ts-dedent: 2.3.0
|
||||
vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)
|
||||
transitivePeerDependencies:
|
||||
- esbuild
|
||||
- rollup
|
||||
- webpack
|
||||
|
||||
'@storybook/csf-plugin@10.4.3(esbuild@0.28.1)(storybook@10.4.3(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))':
|
||||
dependencies:
|
||||
storybook: 10.4.3(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
@ -6706,6 +6813,14 @@ snapshots:
|
||||
esbuild: 0.28.1
|
||||
vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)
|
||||
|
||||
'@storybook/csf-plugin@10.4.4(esbuild@0.28.1)(storybook@10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))':
|
||||
dependencies:
|
||||
storybook: 10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
unplugin: 2.3.11
|
||||
optionalDependencies:
|
||||
esbuild: 0.28.1
|
||||
vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)
|
||||
|
||||
'@storybook/global@5.0.0': {}
|
||||
|
||||
'@storybook/icons@2.0.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
|
||||
@ -6722,6 +6837,15 @@ snapshots:
|
||||
'@types/react': 19.2.17
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.17)
|
||||
|
||||
'@storybook/react-dom-shim@10.4.4(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))':
|
||||
dependencies:
|
||||
react: 19.2.7
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
storybook: 10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.17)
|
||||
|
||||
'@storybook/react-vite@10.4.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.3(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))':
|
||||
dependencies:
|
||||
'@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))
|
||||
@ -6746,6 +6870,30 @@ snapshots:
|
||||
- typescript
|
||||
- webpack
|
||||
|
||||
'@storybook/react-vite@10.4.4(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))':
|
||||
dependencies:
|
||||
'@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))
|
||||
'@rollup/pluginutils': 5.4.0
|
||||
'@storybook/builder-vite': 10.4.4(esbuild@0.28.1)(storybook@10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))
|
||||
'@storybook/react': 10.4.4(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)
|
||||
empathic: 2.0.1
|
||||
magic-string: 0.30.21
|
||||
react: 19.2.7
|
||||
react-docgen: 8.0.3
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
resolve: 1.22.12
|
||||
storybook: 10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
tsconfig-paths: 4.2.0
|
||||
vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
- '@types/react-dom'
|
||||
- esbuild
|
||||
- rollup
|
||||
- supports-color
|
||||
- typescript
|
||||
- webpack
|
||||
|
||||
'@storybook/react@10.4.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.3(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@storybook/global': 5.0.0
|
||||
@ -6762,6 +6910,22 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@storybook/react@10.4.4(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@storybook/global': 5.0.0
|
||||
'@storybook/react-dom-shim': 10.4.4(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))
|
||||
react: 19.2.7
|
||||
react-docgen: 8.0.3
|
||||
react-docgen-typescript: 2.4.0(typescript@6.0.3)
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
storybook: 10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.17)
|
||||
typescript: 6.0.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@testing-library/dom@10.4.1':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.29.7
|
||||
@ -6815,8 +6979,6 @@ snapshots:
|
||||
|
||||
'@tokens-studio/types@0.5.2': {}
|
||||
|
||||
'@trysound/sax@0.2.0': {}
|
||||
|
||||
'@tybys/wasm-util@0.10.2':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
@ -7036,7 +7198,7 @@ snapshots:
|
||||
|
||||
acorn@8.17.0: {}
|
||||
|
||||
agent-base@6.0.2:
|
||||
agent-base@6.0.2(supports-color@5.5.0):
|
||||
dependencies:
|
||||
debug: 4.4.3(supports-color@5.5.0)
|
||||
transitivePeerDependencies:
|
||||
@ -7209,11 +7371,11 @@ snapshots:
|
||||
|
||||
axe-core@4.12.1: {}
|
||||
|
||||
axios@1.17.0:
|
||||
axios@1.17.0(supports-color@5.5.0):
|
||||
dependencies:
|
||||
follow-redirects: 1.16.0
|
||||
form-data: 4.0.5
|
||||
https-proxy-agent: 5.0.1
|
||||
https-proxy-agent: 5.0.1(supports-color@5.5.0)
|
||||
proxy-from-env: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
@ -7247,7 +7409,7 @@ snapshots:
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
|
||||
body-parser@2.2.2:
|
||||
body-parser@2.2.2(supports-color@5.5.0):
|
||||
dependencies:
|
||||
bytes: 3.1.2
|
||||
content-type: 1.0.5
|
||||
@ -7263,6 +7425,8 @@ snapshots:
|
||||
|
||||
boolbase@1.0.0: {}
|
||||
|
||||
boolbase@2.0.0: {}
|
||||
|
||||
brace-expansion@1.1.15:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
@ -7555,13 +7719,13 @@ snapshots:
|
||||
domutils: 2.8.0
|
||||
nth-check: 2.1.1
|
||||
|
||||
css-select@5.2.2:
|
||||
css-select@7.0.0:
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
css-what: 6.2.2
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.2.2
|
||||
nth-check: 2.1.1
|
||||
boolbase: 2.0.0
|
||||
css-what: 8.0.0
|
||||
domhandler: 6.0.1
|
||||
domutils: 4.0.2
|
||||
nth-check: 3.0.1
|
||||
|
||||
css-selector-parser@1.4.1: {}
|
||||
|
||||
@ -7582,6 +7746,8 @@ snapshots:
|
||||
|
||||
css-what@6.2.2: {}
|
||||
|
||||
css-what@8.0.0: {}
|
||||
|
||||
css.escape@1.5.1: {}
|
||||
|
||||
cssesc@3.0.0: {}
|
||||
@ -7709,21 +7875,23 @@ snapshots:
|
||||
domhandler: 4.3.1
|
||||
entities: 2.2.0
|
||||
|
||||
dom-serializer@2.0.0:
|
||||
dom-serializer@3.1.1:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
entities: 4.5.0
|
||||
domelementtype: 3.0.0
|
||||
domhandler: 6.0.1
|
||||
entities: 8.0.0
|
||||
|
||||
domelementtype@2.3.0: {}
|
||||
|
||||
domelementtype@3.0.0: {}
|
||||
|
||||
domhandler@4.3.1:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
|
||||
domhandler@5.0.3:
|
||||
domhandler@6.0.1:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
domelementtype: 3.0.0
|
||||
|
||||
domutils@2.8.0:
|
||||
dependencies:
|
||||
@ -7731,11 +7899,11 @@ snapshots:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 4.3.1
|
||||
|
||||
domutils@3.2.2:
|
||||
domutils@4.0.2:
|
||||
dependencies:
|
||||
dom-serializer: 2.0.0
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
dom-serializer: 3.1.1
|
||||
domelementtype: 3.0.0
|
||||
domhandler: 6.0.1
|
||||
|
||||
draft-js@https://codeload.github.com/penpot/draft-js/tar.gz/4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0(encoding@0.1.13)(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
||||
dependencies:
|
||||
@ -7788,8 +7956,6 @@ snapshots:
|
||||
|
||||
entities@2.2.0: {}
|
||||
|
||||
entities@4.5.0: {}
|
||||
|
||||
entities@8.0.0: {}
|
||||
|
||||
env-paths@2.2.1: {}
|
||||
@ -8149,10 +8315,10 @@ snapshots:
|
||||
|
||||
expr-eval-fork@3.0.3: {}
|
||||
|
||||
express@5.2.1:
|
||||
express@5.2.1(supports-color@5.5.0):
|
||||
dependencies:
|
||||
accepts: 2.0.0
|
||||
body-parser: 2.2.2
|
||||
body-parser: 2.2.2(supports-color@5.5.0)
|
||||
content-disposition: 1.1.0
|
||||
content-type: 1.0.5
|
||||
cookie: 0.7.2
|
||||
@ -8162,7 +8328,7 @@ snapshots:
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
finalhandler: 2.1.1
|
||||
finalhandler: 2.1.1(supports-color@5.5.0)
|
||||
fresh: 2.0.0
|
||||
http-errors: 2.0.1
|
||||
merge-descriptors: 2.0.0
|
||||
@ -8173,8 +8339,8 @@ snapshots:
|
||||
proxy-addr: 2.0.7
|
||||
qs: 6.15.2
|
||||
range-parser: 1.2.1
|
||||
router: 2.2.0
|
||||
send: 1.2.1
|
||||
router: 2.2.0(supports-color@5.5.0)
|
||||
send: 1.2.1(supports-color@5.5.0)
|
||||
serve-static: 2.2.1
|
||||
statuses: 2.0.2
|
||||
type-is: 2.1.0
|
||||
@ -8244,7 +8410,7 @@ snapshots:
|
||||
dependencies:
|
||||
to-regex-range: 5.0.1
|
||||
|
||||
finalhandler@2.1.1:
|
||||
finalhandler@2.1.1(supports-color@5.5.0):
|
||||
dependencies:
|
||||
debug: 4.4.3(supports-color@5.5.0)
|
||||
encodeurl: 2.0.0
|
||||
@ -8501,9 +8667,9 @@ snapshots:
|
||||
statuses: 2.0.2
|
||||
toidentifier: 1.0.1
|
||||
|
||||
https-proxy-agent@5.0.1:
|
||||
https-proxy-agent@5.0.1(supports-color@5.5.0):
|
||||
dependencies:
|
||||
agent-base: 6.0.2
|
||||
agent-base: 6.0.2(supports-color@5.5.0)
|
||||
debug: 4.4.3(supports-color@5.5.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@ -9155,6 +9321,10 @@ snapshots:
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
|
||||
nth-check@3.0.1:
|
||||
dependencies:
|
||||
boolbase: 2.0.0
|
||||
|
||||
object-assign@4.1.1: {}
|
||||
|
||||
object-inspect@1.13.4: {}
|
||||
@ -9736,7 +9906,7 @@ snapshots:
|
||||
'@rolldown/binding-win32-arm64-msvc': 1.0.3
|
||||
'@rolldown/binding-win32-x64-msvc': 1.0.3
|
||||
|
||||
router@2.2.0:
|
||||
router@2.2.0(supports-color@5.5.0):
|
||||
dependencies:
|
||||
debug: 4.4.3(supports-color@5.5.0)
|
||||
depd: 2.0.0
|
||||
@ -9903,7 +10073,7 @@ snapshots:
|
||||
|
||||
semver@7.8.4: {}
|
||||
|
||||
send@1.2.1:
|
||||
send@1.2.1(supports-color@5.5.0):
|
||||
dependencies:
|
||||
debug: 4.4.3(supports-color@5.5.0)
|
||||
encodeurl: 2.0.0
|
||||
@ -9924,7 +10094,7 @@ snapshots:
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
parseurl: 1.3.3
|
||||
send: 1.2.1
|
||||
send: 1.2.1(supports-color@5.5.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@ -10091,6 +10261,33 @@ snapshots:
|
||||
- react-dom
|
||||
- utf-8-validate
|
||||
|
||||
storybook@10.4.4(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
||||
dependencies:
|
||||
'@storybook/global': 5.0.0
|
||||
'@storybook/icons': 2.0.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@testing-library/jest-dom': 6.9.1
|
||||
'@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1)
|
||||
'@vitest/expect': 3.2.4
|
||||
'@vitest/spy': 3.2.4
|
||||
'@webcontainer/env': 1.1.1
|
||||
esbuild: 0.27.7
|
||||
open: 10.2.0
|
||||
oxc-parser: 0.127.0
|
||||
oxc-resolver: 11.20.0
|
||||
recast: 0.23.11
|
||||
semver: 7.8.4
|
||||
use-sync-external-store: 1.6.0(react@19.2.7)
|
||||
ws: 8.21.0
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
prettier: 3.8.4
|
||||
transitivePeerDependencies:
|
||||
- '@testing-library/dom'
|
||||
- bufferutil
|
||||
- react
|
||||
- react-dom
|
||||
- utf-8-validate
|
||||
|
||||
stream@0.0.3:
|
||||
dependencies:
|
||||
component-emitter: 2.0.0
|
||||
@ -10226,33 +10423,33 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- tslib
|
||||
|
||||
stylelint-config-recommended-scss@17.0.1(postcss@8.5.15)(stylelint@17.13.0(typescript@6.0.3)):
|
||||
stylelint-config-recommended-scss@17.0.1(postcss@8.5.15)(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3)):
|
||||
dependencies:
|
||||
postcss-scss: 4.0.9(postcss@8.5.15)
|
||||
stylelint: 17.13.0(typescript@6.0.3)
|
||||
stylelint-config-recommended: 18.0.0(stylelint@17.13.0(typescript@6.0.3))
|
||||
stylelint-scss: 7.2.0(stylelint@17.13.0(typescript@6.0.3))
|
||||
stylelint: 17.13.0(supports-color@5.5.0)(typescript@6.0.3)
|
||||
stylelint-config-recommended: 18.0.0(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3))
|
||||
stylelint-scss: 7.2.0(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3))
|
||||
optionalDependencies:
|
||||
postcss: 8.5.15
|
||||
|
||||
stylelint-config-recommended@18.0.0(stylelint@17.13.0(typescript@6.0.3)):
|
||||
stylelint-config-recommended@18.0.0(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3)):
|
||||
dependencies:
|
||||
stylelint: 17.13.0(typescript@6.0.3)
|
||||
stylelint: 17.13.0(supports-color@5.5.0)(typescript@6.0.3)
|
||||
|
||||
stylelint-config-standard-scss@17.0.0(postcss@8.5.15)(stylelint@17.13.0(typescript@6.0.3)):
|
||||
stylelint-config-standard-scss@17.0.0(postcss@8.5.15)(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3)):
|
||||
dependencies:
|
||||
stylelint: 17.13.0(typescript@6.0.3)
|
||||
stylelint-config-recommended-scss: 17.0.1(postcss@8.5.15)(stylelint@17.13.0(typescript@6.0.3))
|
||||
stylelint-config-standard: 40.0.0(stylelint@17.13.0(typescript@6.0.3))
|
||||
stylelint: 17.13.0(supports-color@5.5.0)(typescript@6.0.3)
|
||||
stylelint-config-recommended-scss: 17.0.1(postcss@8.5.15)(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3))
|
||||
stylelint-config-standard: 40.0.0(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3))
|
||||
optionalDependencies:
|
||||
postcss: 8.5.15
|
||||
|
||||
stylelint-config-standard@40.0.0(stylelint@17.13.0(typescript@6.0.3)):
|
||||
stylelint-config-standard@40.0.0(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3)):
|
||||
dependencies:
|
||||
stylelint: 17.13.0(typescript@6.0.3)
|
||||
stylelint-config-recommended: 18.0.0(stylelint@17.13.0(typescript@6.0.3))
|
||||
stylelint: 17.13.0(supports-color@5.5.0)(typescript@6.0.3)
|
||||
stylelint-config-recommended: 18.0.0(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3))
|
||||
|
||||
stylelint-scss@7.2.0(stylelint@17.13.0(typescript@6.0.3)):
|
||||
stylelint-scss@7.2.0(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3)):
|
||||
dependencies:
|
||||
'@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)
|
||||
@ -10265,13 +10462,13 @@ snapshots:
|
||||
postcss-resolve-nested-selector: 0.1.6
|
||||
postcss-selector-parser: 7.1.4
|
||||
postcss-value-parser: 4.2.0
|
||||
stylelint: 17.13.0(typescript@6.0.3)
|
||||
stylelint: 17.13.0(supports-color@5.5.0)(typescript@6.0.3)
|
||||
|
||||
stylelint-use-logical-spec@5.0.1(stylelint@17.13.0(typescript@6.0.3)):
|
||||
stylelint-use-logical-spec@5.0.1(stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3)):
|
||||
dependencies:
|
||||
stylelint: 17.13.0(typescript@6.0.3)
|
||||
stylelint: 17.13.0(supports-color@5.5.0)(typescript@6.0.3)
|
||||
|
||||
stylelint@17.13.0(typescript@6.0.3):
|
||||
stylelint@17.13.0(supports-color@5.5.0)(typescript@6.0.3):
|
||||
dependencies:
|
||||
'@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)
|
||||
@ -10365,14 +10562,6 @@ snapshots:
|
||||
sax: 1.6.0
|
||||
stable: 0.1.8
|
||||
|
||||
svgo@https://codeload.github.com/penpot/svgo/tar.gz/8c9b0e32e9cb5f106085260bd9375f3c91a5010b:
|
||||
dependencies:
|
||||
'@trysound/sax': 0.2.0
|
||||
css-select: 5.2.2
|
||||
css-tree: 3.2.1
|
||||
csso: 5.0.5
|
||||
lodash: 4.18.1
|
||||
|
||||
symbol-tree@3.2.4: {}
|
||||
|
||||
sync-child-process@1.0.2:
|
||||
@ -10558,7 +10747,7 @@ snapshots:
|
||||
|
||||
unpipe@1.0.0: {}
|
||||
|
||||
unplugin-dts@1.0.2(esbuild@0.28.1)(rolldown@1.0.3)(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)):
|
||||
unplugin-dts@1.0.2(esbuild@0.28.1)(rolldown@1.0.3)(supports-color@5.5.0)(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)):
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.4.0
|
||||
'@volar/typescript': 2.4.28
|
||||
@ -10634,9 +10823,9 @@ snapshots:
|
||||
remove-trailing-separator: 1.1.0
|
||||
replace-ext: 1.0.1
|
||||
|
||||
vite-plugin-dts@5.0.2(esbuild@0.28.1)(rolldown@1.0.3)(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)):
|
||||
vite-plugin-dts@5.0.2(esbuild@0.28.1)(rolldown@1.0.3)(supports-color@5.5.0)(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)):
|
||||
dependencies:
|
||||
unplugin-dts: 1.0.2(esbuild@0.28.1)(rolldown@1.0.3)(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))
|
||||
unplugin-dts: 1.0.2(esbuild@0.28.1)(rolldown@1.0.3)(supports-color@5.5.0)(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0))
|
||||
optionalDependencies:
|
||||
vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(sass-embedded@1.100.0)(sass@1.101.0)
|
||||
transitivePeerDependencies:
|
||||
@ -10699,9 +10888,9 @@ snapshots:
|
||||
dependencies:
|
||||
xml-name-validator: 5.0.0
|
||||
|
||||
wait-on@9.0.10:
|
||||
wait-on@9.0.10(supports-color@5.5.0):
|
||||
dependencies:
|
||||
axios: 1.17.0
|
||||
axios: 1.17.0(supports-color@5.5.0)
|
||||
joi: 18.2.1
|
||||
lodash: 4.18.1
|
||||
minimist: 1.2.8
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.logging :as log]
|
||||
[app.common.time :as ct]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.event :as ev]
|
||||
@ -57,45 +58,57 @@
|
||||
|
||||
(defn start-plugin!
|
||||
[{:keys [plugin-id name version description host code permissions allow-background]} ^js extensions]
|
||||
(-> (.ɵloadPlugin
|
||||
^js ug/global
|
||||
#js {:pluginId plugin-id
|
||||
:name name
|
||||
:version version
|
||||
:description description
|
||||
:host host
|
||||
:code code
|
||||
:allowBackground (boolean allow-background)
|
||||
:permissions (apply array permissions)}
|
||||
nil
|
||||
extensions)
|
||||
(let [load-plugin (unchecked-get ug/global "ɵloadPlugin")]
|
||||
(if (fn? load-plugin)
|
||||
(-> (load-plugin
|
||||
#js {:pluginId plugin-id
|
||||
:name name
|
||||
:version version
|
||||
:description description
|
||||
:host host
|
||||
:code code
|
||||
:allowBackground (boolean allow-background)
|
||||
:permissions (apply array permissions)}
|
||||
nil
|
||||
extensions)
|
||||
|
||||
(p/catch (fn [cause]
|
||||
(ex/print-throwable cause :prefix "Plugin Error")
|
||||
(errors/flash :cause cause :type :handled)))))
|
||||
(p/catch (fn [cause]
|
||||
(ex/print-throwable cause :prefix "Plugin Error")
|
||||
(errors/flash :cause cause :type :handled))))
|
||||
|
||||
(log/warn :hint "Plugin runtime not initialized yet"
|
||||
:plugin-id plugin-id
|
||||
:action "start-plugin!"))))
|
||||
|
||||
(defn- load-plugin!
|
||||
[{:keys [plugin-id name version description host code icon permissions]}]
|
||||
(st/emit! (pflag/clear plugin-id)
|
||||
(save-current-plugin plugin-id))
|
||||
|
||||
(-> (.ɵloadPlugin
|
||||
^js ug/global
|
||||
#js {:pluginId plugin-id
|
||||
:name name
|
||||
:description description
|
||||
:version version
|
||||
:host host
|
||||
:code code
|
||||
:icon icon
|
||||
:permissions (apply array permissions)}
|
||||
(fn []
|
||||
(st/emit! (remove-current-plugin plugin-id))))
|
||||
(let [load-plugin (unchecked-get ug/global "ɵloadPlugin")]
|
||||
(if (fn? load-plugin)
|
||||
(-> (load-plugin
|
||||
#js {:pluginId plugin-id
|
||||
:name name
|
||||
:description description
|
||||
:version version
|
||||
:host host
|
||||
:code code
|
||||
:icon icon
|
||||
:permissions (apply array permissions)}
|
||||
(fn []
|
||||
(st/emit! (remove-current-plugin plugin-id))))
|
||||
|
||||
(p/catch (fn [cause]
|
||||
(st/emit! (remove-current-plugin plugin-id))
|
||||
(ex/print-throwable cause :prefix "Plugin Error")
|
||||
(errors/flash :cause cause :type :handled)))))
|
||||
(p/catch (fn [cause]
|
||||
(st/emit! (remove-current-plugin plugin-id))
|
||||
(ex/print-throwable cause :prefix "Plugin Error")
|
||||
(errors/flash :cause cause :type :handled))))
|
||||
|
||||
(do
|
||||
(log/warn :hint "Plugin runtime not initialized yet"
|
||||
:plugin-id plugin-id
|
||||
:action "load-plugin!")
|
||||
(st/emit! (remove-current-plugin plugin-id))))))
|
||||
|
||||
(defn open-plugin!
|
||||
[{:keys [url] :as manifest} user-can-edit?]
|
||||
@ -135,10 +148,15 @@
|
||||
|
||||
(defn close-plugin!
|
||||
[{:keys [plugin-id]}]
|
||||
(try
|
||||
(.ɵunloadPlugin ^js ug/global plugin-id)
|
||||
(catch :default e
|
||||
(.error js/console "Error" e))))
|
||||
(let [unload-plugin (unchecked-get ug/global "ɵunloadPlugin")]
|
||||
(if (fn? unload-plugin)
|
||||
(try
|
||||
(unload-plugin plugin-id)
|
||||
(catch :default e
|
||||
(.error js/console "Error" e)))
|
||||
(log/warn :hint "Plugin runtime not initialized yet"
|
||||
:plugin-id plugin-id
|
||||
:action "close-plugin!"))))
|
||||
|
||||
(defn close-current-plugin
|
||||
[& {:keys [close-only-edition-plugins?]}]
|
||||
|
||||
@ -504,8 +504,7 @@
|
||||
(rx/of (dwu/append-undo entry stack-undo?)))
|
||||
(rx/empty))))))
|
||||
|
||||
(rx/take-until stoper-s))
|
||||
(rx/of (mcp/notify-other-tabs-disconnect)))))
|
||||
(rx/take-until stoper-s)))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ _ _]
|
||||
|
||||
@ -10,13 +10,10 @@
|
||||
[app.common.uri :as u]
|
||||
[app.config :as cf]
|
||||
[app.main.broadcast :as mbc]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.plugins :as dp]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.plugins.register :refer [mcp-plugin-id]]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.plugins.register :as preg]
|
||||
[app.util.timers :as ts]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
@ -29,7 +26,7 @@
|
||||
{:code "plugin.js"
|
||||
:name "Penpot MCP Plugin"
|
||||
:version 2
|
||||
:plugin-id mcp-plugin-id
|
||||
:plugin-id preg/mcp-plugin-id
|
||||
:description "This plugin enables interaction with the Penpot MCP server"
|
||||
:allow-background true
|
||||
:permissions
|
||||
@ -39,6 +36,14 @@
|
||||
|
||||
(defonce interval-sub (atom nil))
|
||||
|
||||
(defn connect-mcp
|
||||
[]
|
||||
(ptk/reify ::connect-mcp
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (mbc/event :mcp/force-disconnect {})
|
||||
(ptk/data-event ::connect)))))
|
||||
|
||||
(defn finalize-workspace?
|
||||
[event]
|
||||
(= (ptk/type event) :app.main.data.workspace/finalize-workspace))
|
||||
@ -72,45 +77,6 @@
|
||||
(rx/dispose! @interval-sub)
|
||||
(reset! interval-sub nil)))
|
||||
|
||||
(declare manage-mcp-notification)
|
||||
|
||||
(defn handle-pong
|
||||
[{:keys [id data]}]
|
||||
(ptk/reify ::handle-pong
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [mcp-state (get state :mcp)]
|
||||
(cond
|
||||
(= "connected" (:connection-status data))
|
||||
(update state :mcp assoc :connected-tab id)
|
||||
|
||||
(and (= "disconnected" (:connection-status data))
|
||||
(= id (:connected-tab mcp-state)))
|
||||
(update state :mcp dissoc :connected-tab)
|
||||
|
||||
:else
|
||||
state)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (manage-mcp-notification)))))
|
||||
|
||||
;; This event will arrive when a new workspace is open in another tab
|
||||
(defn handle-ping
|
||||
[]
|
||||
(ptk/reify ::handle-ping
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [conn-status (get-in state [:mcp :connection-status])]
|
||||
(rx/of (mbc/event :mcp/pong {:connection-status conn-status}))))))
|
||||
|
||||
(defn notify-other-tabs-disconnect
|
||||
[]
|
||||
(ptk/reify ::notify-other-tabs-disconnect
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (mbc/event :mcp/pong {:connection-status "disconnected"})))))
|
||||
|
||||
;; This event will arrive when the mcp is enabled in the dashboard
|
||||
(defn update-mcp-status
|
||||
[value]
|
||||
@ -121,12 +87,10 @@
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/merge
|
||||
(rx/of (manage-mcp-notification))
|
||||
(case value
|
||||
true (rx/of (ptk/data-event ::connect))
|
||||
false (rx/of (ptk/data-event ::disconnect))
|
||||
nil)))))
|
||||
(case value
|
||||
true (rx/of (connect-mcp))
|
||||
false (rx/of (ptk/data-event ::disconnect))
|
||||
nil))))
|
||||
|
||||
(defn update-mcp-connection-status
|
||||
[value]
|
||||
@ -137,20 +101,13 @@
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (manage-mcp-notification)
|
||||
(mbc/event :mcp/pong {:connection-status value})))))
|
||||
|
||||
(defn connect-mcp
|
||||
[]
|
||||
(ptk/reify ::connect-mcp
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :mcp assoc :connected-tab (:session-id state)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (mbc/event :mcp/force-disconect {})
|
||||
(ptk/data-event ::connect)))))
|
||||
;; Only one MCP plugin instance may be active across browser tabs.
|
||||
;; When this tab becomes connected, tell every other tab to
|
||||
;; disconnect (which also stops their reconnect watcher). Otherwise
|
||||
;; several tabs stay connected at once and the MCP server reports
|
||||
;; "multiple instances connected" and the agent fails.
|
||||
(when (= "connected" value)
|
||||
(rx/of (mbc/event :mcp/force-disconnect {}))))))
|
||||
|
||||
;; This event will arrive when the user selects disconnect on the menu
|
||||
;; or there is a broadcast message for disconnection
|
||||
@ -166,77 +123,54 @@
|
||||
(effect [_ _ _]
|
||||
(stop-reconnect-watcher!))))
|
||||
|
||||
(defn- manage-mcp-notification
|
||||
[]
|
||||
(ptk/reify ::manage-mcp-notification
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [mcp-state (get state :mcp)
|
||||
|
||||
mcp-enabled? (-> state :profile :props :mcp-enabled)
|
||||
|
||||
current-tab-id (get state :session-id)
|
||||
connected-tab-id (get mcp-state :connected-tab)]
|
||||
|
||||
(if mcp-enabled?
|
||||
(if (= connected-tab-id current-tab-id)
|
||||
(rx/of (ntf/hide))
|
||||
(rx/of (ntf/dialog
|
||||
{:content (tr "notifications.mcp.active-in-another-tab")
|
||||
:cancel {:label (tr "labels.dismiss")
|
||||
:callback #(st/emit! (ntf/hide)
|
||||
(ev/event {::ev/name "dismiss-mcp-tab-switch"
|
||||
::ev/origin "workspace-notification"}))}
|
||||
:accept {:label (tr "labels.switch")
|
||||
:callback #(st/emit! (connect-mcp)
|
||||
(ev/event {::ev/name "confirm-mcp-tab-switch"
|
||||
::ev/origin "workspace-notification"}))}})))
|
||||
(rx/of (ntf/hide)))))))
|
||||
|
||||
(defn init-mcp
|
||||
[stream]
|
||||
(->> (rp/cmd! :get-current-mcp-token)
|
||||
(rx/tap
|
||||
(fn [{:keys [token]}]
|
||||
(when token
|
||||
(dp/start-plugin!
|
||||
(assoc default-manifest
|
||||
:url (str (u/join cf/public-uri "plugins/mcp/manifest.json"))
|
||||
:host (str (u/join cf/public-uri "plugins/mcp/")))
|
||||
;; Wait for plugins runtime to be initialized before starting the MCP plugin.
|
||||
;; This ensures global.ɵloadPlugin is available when start-plugin! is called.
|
||||
(->> (rx/from (preg/wait-for-runtime))
|
||||
(rx/mapcat
|
||||
(fn [_]
|
||||
(->> (rp/cmd! :get-current-mcp-token)
|
||||
(rx/tap
|
||||
(fn [{:keys [token]}]
|
||||
(when token
|
||||
(dp/start-plugin!
|
||||
(assoc default-manifest
|
||||
:url (str (u/join cf/public-uri "plugins/mcp/manifest.json"))
|
||||
:host (str (u/join cf/public-uri "plugins/mcp/")))
|
||||
|
||||
;; API extension for MCP server
|
||||
#js {:mcp
|
||||
#js
|
||||
{:getToken (constantly token)
|
||||
:getServerUrl #(str cf/mcp-ws-uri)
|
||||
:setMcpStatus
|
||||
(fn [status]
|
||||
(when (= status "connected")
|
||||
(start-reconnect-watcher!))
|
||||
(st/emit! (update-mcp-connection-status status))
|
||||
(log/info :hint "MCP STATUS" :status status))
|
||||
;; API extension for MCP server
|
||||
#js {:mcp
|
||||
#js
|
||||
{:getToken (constantly token)
|
||||
:getServerUrl #(str cf/mcp-ws-uri)
|
||||
:setMcpStatus
|
||||
(fn [status]
|
||||
(when (= status "connected")
|
||||
(start-reconnect-watcher!))
|
||||
(st/emit! (update-mcp-connection-status status))
|
||||
(log/info :hint "MCP STATUS" :status status))
|
||||
|
||||
:on
|
||||
(fn [event cb]
|
||||
(when-let [event
|
||||
(case event
|
||||
"disconnect" ::disconnect
|
||||
"connect" ::connect
|
||||
nil)]
|
||||
:on
|
||||
(fn [event cb]
|
||||
(when-let [event
|
||||
(case event
|
||||
"disconnect" ::disconnect
|
||||
"connect" ::connect
|
||||
nil)]
|
||||
|
||||
(let [stopper (rx/filter finalize-workspace? stream)]
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? event))
|
||||
(rx/take-until stopper)
|
||||
(rx/subs! #(cb))))))}}))))
|
||||
(rx/ignore)))
|
||||
(let [stopper (rx/filter finalize-workspace? stream)]
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? event))
|
||||
(rx/take-until stopper)
|
||||
(rx/subs! #(cb))))))}})))))))))
|
||||
|
||||
(defn init
|
||||
[]
|
||||
(ptk/reify ::init
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :mcp assoc :connected-tab (:session-id state) :active true))
|
||||
(update state :mcp assoc :active true))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
@ -251,22 +185,8 @@
|
||||
(rx/merge
|
||||
(init-mcp stream)
|
||||
|
||||
(rx/of (mbc/event :mcp/ping {}))
|
||||
|
||||
(->> mbc/stream
|
||||
(rx/filter (mbc/type? :mcp/ping))
|
||||
(rx/filter (fn [{:keys [id]}]
|
||||
(not= session-id id)))
|
||||
(rx/map handle-ping))
|
||||
|
||||
(->> mbc/stream
|
||||
(rx/filter (mbc/type? :mcp/pong))
|
||||
(rx/filter (fn [{:keys [id]}]
|
||||
(not= session-id id)))
|
||||
(rx/map handle-pong))
|
||||
|
||||
(->> mbc/stream
|
||||
(rx/filter (mbc/type? :mcp/force-disconect))
|
||||
(rx/filter (mbc/type? :mcp/force-disconnect))
|
||||
(rx/filter (fn [{:keys [id]}]
|
||||
(not= session-id id)))
|
||||
(rx/map deref)
|
||||
@ -276,9 +196,9 @@
|
||||
(->> mbc/stream
|
||||
(rx/filter (mbc/type? :mcp/enable))
|
||||
(rx/mapcat (fn [_]
|
||||
;; NOTE: we don't need an explicit
|
||||
;; connect because the plugin has
|
||||
;; auto-connect
|
||||
;; Re-init so the force-disconnect
|
||||
;; listener is set up now that MCP
|
||||
;; is enabled.
|
||||
(rx/of (update-mcp-status true)
|
||||
(init)))))
|
||||
|
||||
@ -286,7 +206,6 @@
|
||||
(rx/filter (mbc/type? :mcp/disable))
|
||||
(rx/mapcat (fn [_]
|
||||
(rx/of (update-mcp-status false)
|
||||
(init)
|
||||
(user-disconnect-mcp))))))
|
||||
|
||||
(rx/take-until stoper-s))))))
|
||||
|
||||
@ -85,40 +85,70 @@
|
||||
(let [request (create-request file-id page-id shape-id tag)]
|
||||
(q/enqueue-unique queue request (partial render-thumbnail state file-id page-id shape-id tag))))
|
||||
|
||||
(defn- clear-thumbnail-batch
|
||||
[]
|
||||
(let [pending (volatile! nil)]
|
||||
(ptk/reify ::clear-thumbnail-batch
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [items (get state ::thumbnails-deletion-queue)]
|
||||
(when (seq items)
|
||||
(vreset! pending items))
|
||||
(dissoc state ::thumbnails-deletion-queue)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [items (reduce-kv (fn [acc object-id uri]
|
||||
(when (str/starts-with? uri "blob:")
|
||||
(tm/schedule-on-idle (partial wapi/revoke-uri uri)))
|
||||
(conj acc object-id))
|
||||
[]
|
||||
@pending)]
|
||||
(l/dbg :hint "clear-thumbnail-batch" :total (count items))
|
||||
(->> (rx/from (partition-all 200 items))
|
||||
(rx/mapcat
|
||||
(fn [batch]
|
||||
(l/dbg :hint "clear-thumbnail-batch" :batch-size (count batch))
|
||||
(->> (rp/cmd! :delete-file-object-thumbnails
|
||||
{:object-ids (vec batch)})
|
||||
(rx/catch rx/empty)
|
||||
(rx/ignore))))))))))
|
||||
|
||||
(defn remove-from-deletion-queue
|
||||
"Removes an object-id from the pending deletion queue in state.
|
||||
Used by update-thumbnail to cancel a pending batched delete before
|
||||
creating a new thumbnail for the same object."
|
||||
[object-id]
|
||||
(ptk/reify ::remove-from-deletion-queue
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state ::thumbnails-deletion-queue dissoc object-id))))
|
||||
|
||||
(defn clear-thumbnail
|
||||
([file-id page-id frame-id tag]
|
||||
(clear-thumbnail file-id (thc/fmt-object-id file-id page-id frame-id tag)))
|
||||
([file-id object-id]
|
||||
(let [pending (volatile! false)]
|
||||
(ptk/reify ::clear-thumbnail
|
||||
cljs.core/IDeref
|
||||
(-deref [_] object-id)
|
||||
([_file-id object-id]
|
||||
(ptk/reify ::clear-thumbnail
|
||||
cljs.core/IDeref
|
||||
(-deref [_] object-id)
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [uri (dm/get-in state [:thumbnails object-id])]
|
||||
(l/dbg :hint "clear-thumbnail" :object-id object-id :uri uri)
|
||||
(-> state
|
||||
(update :thumbnails
|
||||
(fn [thumbs]
|
||||
(if-let [uri (get thumbs object-id)]
|
||||
(do (vreset! pending uri)
|
||||
(dissoc thumbs object-id))
|
||||
thumbs)))
|
||||
(update :thumbnails-meta dissoc object-id)))
|
||||
(update ::thumbnails-deletion-queue assoc object-id uri)
|
||||
(update :thumbnails dissoc object-id)
|
||||
(update :thumbnails-meta dissoc object-id))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(if-let [uri @pending]
|
||||
(do
|
||||
(l/trc :hint "clear-thumbnail" :uri uri)
|
||||
(when (str/starts-with? uri "blob:")
|
||||
(tm/schedule-on-idle (partial wapi/revoke-uri uri)))
|
||||
|
||||
(let [params {:file-id file-id
|
||||
:object-id object-id}]
|
||||
(->> (rp/cmd! :delete-file-object-thumbnail params)
|
||||
(rx/catch rx/empty)
|
||||
(rx/ignore))))
|
||||
(rx/empty)))))))
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(let [stopper-s (->> stream
|
||||
(rx/filter (ptk/type? ::clear-thumbnail)))]
|
||||
(->> (rx/timer 200)
|
||||
(rx/take 1)
|
||||
(rx/map (fn [_] (clear-thumbnail-batch)))
|
||||
(rx/take-until stopper-s)))))))
|
||||
|
||||
(defn assoc-thumbnail
|
||||
[object-id uri]
|
||||
@ -173,7 +203,8 @@
|
||||
:tag (or tag "frame")}]
|
||||
|
||||
(rx/merge
|
||||
(rx/of (assoc-thumbnail object-id uri))
|
||||
(rx/of (assoc-thumbnail object-id uri)
|
||||
(remove-from-deletion-queue object-id))
|
||||
(->> (rp/cmd! :create-file-object-thumbnail params)
|
||||
(rx/catch rx/empty)
|
||||
(rx/ignore))))))
|
||||
@ -305,6 +336,14 @@
|
||||
;; and interrupt any ongoing update-thumbnail process
|
||||
;; related to current frame-id
|
||||
(->> all-commits-s
|
||||
;; Ensure each clear-thumbnail event is dispatched in its
|
||||
;; own macrotask tick. Without this, multiple changes
|
||||
;; arriving on the same synchronous tick would emit
|
||||
;; several clear-thumbnail events back-to-back, causing
|
||||
;; their debounce timers (rx/take-until stopper-s) to
|
||||
;; race and potentially leave multiple clear-thumbnail-batch
|
||||
;; timers alive simultaneously.
|
||||
(rx/observe-on :async)
|
||||
(rx/mapcat (fn [[tag frame-id]]
|
||||
(rx/of (clear-thumbnail file-id page-id frame-id tag)))))
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cph]
|
||||
[app.common.time :as ct]
|
||||
[app.common.types.shape-tree :as ctt]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
@ -155,6 +156,14 @@
|
||||
(def mcp
|
||||
(l/derived :mcp st/state))
|
||||
|
||||
(def mcp-key-expired?
|
||||
(l/derived (fn [state]
|
||||
(when-let [expires-at (some->> (:access-tokens state)
|
||||
(some #(when (= (:type %) "mcp") %))
|
||||
:expires-at)]
|
||||
(> (ct/now) expires-at)))
|
||||
st/state))
|
||||
|
||||
(def workspace-drawing
|
||||
(l/derived :workspace-drawing st/state))
|
||||
|
||||
|
||||
@ -37,8 +37,20 @@
|
||||
;; If the error is a stale-asset error (cross-build
|
||||
;; module mismatch), force a hard page reload instead
|
||||
;; of showing the error page to the user.
|
||||
(if (errors/stale-asset-error? error)
|
||||
(cond
|
||||
(errors/stale-asset-error? error)
|
||||
(cf/throttled-reload :reason (ex-message error))
|
||||
|
||||
;; If the error is known to be harmless (browser
|
||||
;; extensions, React DOM conflicts, etc.), ignore it
|
||||
;; silently — the global uncaught-error-handler
|
||||
;; already does this, but react-error-boundary's
|
||||
;; onError fires independently of the window.onerror
|
||||
;; pipeline, so we must also filter here.
|
||||
(errors/is-ignorable-exception? error)
|
||||
nil
|
||||
|
||||
:else
|
||||
(do
|
||||
(set! errors/last-exception error)
|
||||
(ex/print-throwable error)
|
||||
|
||||
@ -54,16 +54,15 @@
|
||||
;; The content can arrive in markdown format, in these cases
|
||||
;; we will use the prop is-html to true to indicate it and
|
||||
;; that the html injection is performed and the necessary css classes are applied.
|
||||
[:div {:class (stl/css :context-text)
|
||||
:dangerouslySetInnerHTML (when is-html #js {:__html content})}
|
||||
(when-not is-html
|
||||
[:*
|
||||
content
|
||||
(when (some? links)
|
||||
(for [[index link] (d/enumerate links)]
|
||||
;; TODO Review this component
|
||||
[:> lb/link-button* {:class (stl/css :link)
|
||||
:on-click (:callback link)
|
||||
:value (:label link)
|
||||
:key (dm/str "link-" index)}]))])]])
|
||||
|
||||
(if is-html
|
||||
[:div {:class (stl/css :context-text)
|
||||
:dangerouslySetInnerHTML #js {:__html content}}]
|
||||
[:div {:class (stl/css :context-text)}
|
||||
content
|
||||
(when (some? links)
|
||||
(for [[index link] (d/enumerate links)]
|
||||
;; TODO Review this component
|
||||
[:> lb/link-button* {:class (stl/css :link)
|
||||
:on-click (:callback link)
|
||||
:value (:label link)
|
||||
:key (dm/str "link-" index)}]))])])
|
||||
|
||||
@ -406,18 +406,16 @@
|
||||
(mf/defc mcp-server-section*
|
||||
{::mf/private true}
|
||||
[]
|
||||
(let [tokens (mf/deref refs/access-tokens)
|
||||
profile (mf/deref refs/profile)
|
||||
(let [tokens (mf/deref refs/access-tokens)
|
||||
profile (mf/deref refs/profile)
|
||||
mcp-key-expired? (mf/deref refs/mcp-key-expired?)
|
||||
|
||||
mcp-key (some #(when (= (:type %) "mcp") %) tokens)
|
||||
mcp-token (:token mcp-key "")
|
||||
mcp-url (dm/str cf/mcp-server-url "?userToken=" mcp-token)
|
||||
mcp-enabled? (true? (-> profile :props :mcp-enabled))
|
||||
|
||||
expires-at (:expires-at mcp-key)
|
||||
expired? (and (some? expires-at) (> (ct/now) expires-at))
|
||||
|
||||
show-enabled? (and mcp-enabled? (false? expired?))
|
||||
show-enabled? (and mcp-enabled? (false? mcp-key-expired?))
|
||||
|
||||
tooltip-id
|
||||
(mf/use-id)
|
||||
@ -494,7 +492,7 @@
|
||||
(tr "integrations.mcp-server.status")]
|
||||
|
||||
[:div {:class (stl/css :mcp-server-block)}
|
||||
(when expired?
|
||||
(when mcp-key-expired?
|
||||
[:> notification-pill* {:level :error
|
||||
:type :context}
|
||||
[:div {:class (stl/css :mcp-server-notification)}
|
||||
@ -517,7 +515,7 @@
|
||||
(when (and (false? mcp-enabled?) (nil? mcp-key))
|
||||
[:div {:class (stl/css :mcp-server-switch-cover)
|
||||
:on-click handle-generate-mcp-key}])
|
||||
(when (true? expired?)
|
||||
(when (true? mcp-key-expired?)
|
||||
[:div {:class (stl/css :mcp-server-switch-cover)
|
||||
:on-click handle-regenerate-mcp-key}])]]]
|
||||
|
||||
|
||||
@ -10,7 +10,6 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.time :as ct]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.data.common :as dcm]
|
||||
@ -791,20 +790,15 @@
|
||||
[{:keys [on-close]}]
|
||||
(let [plugins? (features/active-feature? @st/state "plugins/runtime")
|
||||
|
||||
profile (mf/deref refs/profile)
|
||||
mcp (mf/deref refs/mcp)
|
||||
tokens (mf/deref refs/access-tokens)
|
||||
|
||||
expires-at (some->> tokens
|
||||
(some #(when (= (:type %) "mcp") %))
|
||||
:expires-at)
|
||||
expired? (and (some? expires-at) (> (ct/now) expires-at))
|
||||
profile (mf/deref refs/profile)
|
||||
mcp (mf/deref refs/mcp)
|
||||
mcp-key-expired? (mf/deref refs/mcp-key-expired?)
|
||||
|
||||
mcp-enabled? (true? (-> profile :props :mcp-enabled))
|
||||
mcp-connection (get mcp :connection-status)
|
||||
mcp-connected? (= mcp-connection "connected")
|
||||
|
||||
show-enabled? (and mcp-enabled? (false? expired?))
|
||||
show-enabled? (and mcp-enabled? (false? mcp-key-expired?))
|
||||
|
||||
on-nav-to-integrations
|
||||
(mf/use-fn
|
||||
@ -843,7 +837,7 @@
|
||||
:pos-6 plugins?)
|
||||
:on-close on-close}
|
||||
|
||||
(when (and show-enabled? (not expired?))
|
||||
(when (and show-enabled? (not mcp-key-expired?))
|
||||
[:> dropdown-menu-item* {:id "mcp-menu-toggle-mcp-plugin"
|
||||
:class (stl/css :base-menu-item :submenu-item)
|
||||
:on-click on-toggle-mcp-plugin
|
||||
@ -865,7 +859,6 @@
|
||||
(mf/defc menu*
|
||||
[{:keys [layout file]}]
|
||||
(let [profile (mf/deref refs/profile)
|
||||
mcp (mf/deref refs/mcp)
|
||||
|
||||
show-menu* (mf/use-state false)
|
||||
show-menu? (deref show-menu*)
|
||||
@ -1062,11 +1055,8 @@
|
||||
:class (stl/css :item-arrow)}]])
|
||||
|
||||
(when (contains? cf/flags :mcp)
|
||||
(let [tokens (mf/deref refs/access-tokens)
|
||||
expires-at (some->> tokens
|
||||
(some #(when (= (:type %) "mcp") %))
|
||||
:expires-at)
|
||||
expired? (and (some? expires-at) (> (ct/now) expires-at))
|
||||
(let [mcp (mf/deref refs/mcp)
|
||||
mcp-key-expired? (mf/deref refs/mcp-key-expired?)
|
||||
|
||||
mcp-enabled? (true? (-> profile :props :mcp-enabled))
|
||||
mcp-connection (get mcp :connection-status)
|
||||
@ -1075,7 +1065,7 @@
|
||||
|
||||
active? (and mcp-enabled? mcp-connected?)
|
||||
failed? (or (and mcp-enabled? mcp-error?)
|
||||
(true? expired?))]
|
||||
(true? mcp-key-expired?))]
|
||||
|
||||
[:> dropdown-menu-item* {:class (stl/css :base-menu-item :menu-item)
|
||||
:on-click on-menu-click
|
||||
|
||||
@ -8,15 +8,18 @@
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.config :as cf]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.mcp :as mcp]
|
||||
[app.main.data.workspace.media :as dwm]
|
||||
[app.main.data.workspace.shortcuts :as sc]
|
||||
[app.main.features :as features]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown-menu :refer [dropdown-menu* dropdown-menu-item*]]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
@ -26,6 +29,61 @@
|
||||
[okulary.core :as l]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc mcp-indicator*
|
||||
[]
|
||||
(let [profile (mf/deref refs/profile)
|
||||
mcp (mf/deref refs/mcp)
|
||||
mcp-key-expired? (mf/deref refs/mcp-key-expired?)
|
||||
|
||||
mcp-enabled? (true? (-> profile :props :mcp-enabled))
|
||||
mcp-connected? (= "connected" (:connection-status mcp))
|
||||
show-indicator? (and mcp-enabled? (false? mcp-key-expired?))
|
||||
|
||||
mcp-menu-open* (mf/use-state false)
|
||||
mcp-menu-open? (deref mcp-menu-open*)
|
||||
|
||||
toggle-mcp-menu
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(swap! mcp-menu-open* not)))
|
||||
|
||||
close-mcp-menu
|
||||
(mf/use-fn
|
||||
#(reset! mcp-menu-open* false))
|
||||
|
||||
connect-mcp
|
||||
(mf/use-fn
|
||||
#(st/emit! (mcp/connect-mcp)
|
||||
(ev/event {::ev/name "connect-mcp-plugin"
|
||||
::ev/origin "workspace:toolbar"})))]
|
||||
(when show-indicator?
|
||||
[:li
|
||||
[:button
|
||||
{:title (tr "workspace.toolbar.mcp")
|
||||
:aria-label (tr "workspace.toolbar.mcp")
|
||||
:class (stl/css-case :main-toolbar-options-button true
|
||||
:mcp-button true
|
||||
:selected mcp-menu-open?)
|
||||
:on-click toggle-mcp-menu
|
||||
:data-tool "mcp"
|
||||
:data-testid "mcp-btn"}
|
||||
[:span {:class (stl/css-case :mcp-status-dot true
|
||||
:connected mcp-connected?)}]
|
||||
[:span {:class (stl/css-case :mcp-button-label true
|
||||
:connected mcp-connected?)}
|
||||
(tr "workspace.toolbar.mcp")]]
|
||||
[:> dropdown-menu* {:show mcp-menu-open?
|
||||
:on-close close-mcp-menu
|
||||
:class (stl/css :mcp-menu)}
|
||||
(if mcp-connected?
|
||||
[:li {:class (stl/css :mcp-menu-info)
|
||||
:role "presentation"}
|
||||
(tr "workspace.toolbar.mcp-connected")]
|
||||
[:> dropdown-menu-item* {:class (stl/css :mcp-menu-item)
|
||||
:on-click connect-mcp}
|
||||
(tr "workspace.toolbar.mcp-connect-here")])]])))
|
||||
|
||||
(mf/defc image-upload*
|
||||
{::mf/wrap [mf/memo]}
|
||||
[]
|
||||
@ -221,12 +279,13 @@
|
||||
{:title "Debugging tool"
|
||||
:class (stl/css-case :main-toolbar-options-button true :selected (contains? layout :debug-panel))
|
||||
:on-click toggle-debug-panel}
|
||||
deprecated-icon/bug]])]]
|
||||
deprecated-icon/bug]])
|
||||
|
||||
(when (contains? cf/flags :mcp)
|
||||
[:> mcp-indicator*])]]
|
||||
|
||||
[:button {:title (tr "workspace.toolbar.toggle-toolbar")
|
||||
:aria-label (tr "workspace.toolbar.toggle-toolbar")
|
||||
:class (stl/css :toolbar-handler)
|
||||
:on-click toggle-toolbar}
|
||||
[:div {:class (stl/css :toolbar-handler-btn)}]]])))
|
||||
|
||||
|
||||
|
||||
@ -5,6 +5,9 @@
|
||||
// Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
@use "ds/_borders.scss" as *;
|
||||
@use "ds/_utils.scss" as *;
|
||||
@use "ds/typography.scss" as t;
|
||||
|
||||
.main-toolbar {
|
||||
cursor: initial;
|
||||
@ -83,6 +86,102 @@
|
||||
}
|
||||
}
|
||||
|
||||
.mcp-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-xs);
|
||||
width: fit-content;
|
||||
margin-inline-start: var(--sp-xs);
|
||||
padding-inline: var(--sp-s);
|
||||
}
|
||||
|
||||
.mcp-button-label {
|
||||
@include t.use-typography("body-small");
|
||||
|
||||
&.connected {
|
||||
color: var(--color-accent-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.mcp-status-dot {
|
||||
// Connection indicator placed before the label, vertically centered:
|
||||
// a muted gray when disconnected, the primary accent when connected.
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
inline-size: px2rem(6);
|
||||
block-size: px2rem(6);
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-background-disabled);
|
||||
|
||||
&.connected {
|
||||
background-color: var(--color-accent-primary);
|
||||
}
|
||||
|
||||
// One-shot "blob" ripple that confirms the moment the tab becomes
|
||||
// connected (e.g. after using "Switch here"). Triggered purely by the
|
||||
// `.connected` class appearing, in sync with the color change.
|
||||
&.connected::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-accent-primary);
|
||||
opacity: 0;
|
||||
animation: mcp-status-blob 0.5s ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes mcp-status-blob {
|
||||
from {
|
||||
opacity: 0.5;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(2.5);
|
||||
}
|
||||
}
|
||||
|
||||
.mcp-menu {
|
||||
@include deprecated.menu-shadow;
|
||||
|
||||
position: absolute;
|
||||
inset-block-start: calc(100% + var(--sp-xs));
|
||||
inset-inline-start: var(--sp-xs);
|
||||
z-index: var(--z-index-dropdown);
|
||||
margin: 0;
|
||||
padding: var(--sp-xs);
|
||||
list-style: none;
|
||||
border: $b-1 solid var(--panel-border-color);
|
||||
border-radius: $br-8;
|
||||
background-color: var(--menu-background-color);
|
||||
}
|
||||
|
||||
// Non-interactive informational text inside the menu (no hover, not focusable).
|
||||
.mcp-menu-info {
|
||||
@include t.use-typography("body-small");
|
||||
|
||||
padding: var(--sp-s) var(--sp-m);
|
||||
color: var(--color-foreground-secondary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mcp-menu-item {
|
||||
@include t.use-typography("body-small");
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--sp-s) var(--sp-m);
|
||||
border-radius: $br-8;
|
||||
color: var(--menu-foreground-color);
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--menu-background-color-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar-handler {
|
||||
@include deprecated.flex-center;
|
||||
@include deprecated.button-style;
|
||||
|
||||
@ -17,14 +17,17 @@
|
||||
[app.plugins.grid :as grid]
|
||||
[app.plugins.library :as library]
|
||||
[app.plugins.public-utils]
|
||||
[app.plugins.register :as preg]
|
||||
[app.plugins.ruler-guides :as rg]
|
||||
[app.plugins.shape :as shape]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(defn init-plugins-runtime!
|
||||
(defn init-plugins-runtime
|
||||
[]
|
||||
(runtime/initPluginsRuntime (fn [plugin-id] (api/create-context plugin-id))))
|
||||
(runtime/initPluginsRuntime (fn [plugin-id] (api/create-context plugin-id)))
|
||||
;; Signal that runtime is ready
|
||||
(preg/signal-runtime-ready))
|
||||
|
||||
(defn initialize
|
||||
[]
|
||||
@ -38,7 +41,7 @@
|
||||
(rx/observe-on :async)
|
||||
(rx/filter #(features/active-feature? @st/state "plugins/runtime"))
|
||||
(rx/take 1)
|
||||
(rx/tap init-plugins-runtime!)
|
||||
(rx/tap init-plugins-runtime)
|
||||
(rx/ignore)))))
|
||||
|
||||
;; Prevent circular dependency
|
||||
|
||||
@ -15,12 +15,28 @@
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.util.object :as obj]
|
||||
[beicon.v2.core :as rx]))
|
||||
[beicon.v2.core :as rx]
|
||||
[promesa.core :as p]))
|
||||
|
||||
;; Needs to be here because moving it to `app.main.data.workspace.mcp` will
|
||||
;; cause a circular dependency
|
||||
(def mcp-plugin-id "96dfa740-005d-8020-8007-55ede24a2bae")
|
||||
|
||||
;; Promise that resolves when plugins runtime is initialized.
|
||||
;; Lives here to avoid circular dependency: workspace.mcp -> app.plugins -> app.plugins.api -> workspace
|
||||
(defonce ^:private runtime-ready-promise (p/deferred))
|
||||
|
||||
(defn wait-for-runtime
|
||||
"Returns a promise that resolves when plugins runtime is initialized."
|
||||
[]
|
||||
runtime-ready-promise)
|
||||
|
||||
(defn signal-runtime-ready
|
||||
"Signals that plugins runtime has been initialized. Called by app.plugins/init-plugins-runtime."
|
||||
[]
|
||||
(when (p/pending? runtime-ready-promise)
|
||||
(p/resolve! runtime-ready-promise true)))
|
||||
|
||||
;; Stores the installed plugins information
|
||||
(defonce ^:private registry (atom {}))
|
||||
|
||||
|
||||
@ -49,7 +49,14 @@
|
||||
:hint "operation aborted")))
|
||||
(obj/set! image "src" uri)
|
||||
(fn []
|
||||
(obj/set! image "src" "")
|
||||
;; NOTE: We intentionally do NOT set `image.src = ""` here.
|
||||
;; Doing so discards the decoded pixel data immediately when this
|
||||
;; observable completes, but downstream operators (e.g. drawImage /
|
||||
;; createImageBitmap in `render-image-bitmap`) still need to read
|
||||
;; the pixel data after the image is emitted. Clearing `src` before
|
||||
;; the downstream pipeline finishes causes a NotReadableError
|
||||
;; ("The requested file could not be read..."). The image element
|
||||
;; will be garbage collected naturally when no references remain.
|
||||
(obj/set! image "onload" nil)
|
||||
(obj/set! image "onerror" nil)
|
||||
(obj/set! image "onabort" nil))))))
|
||||
@ -57,10 +64,13 @@
|
||||
(defn- svg-get-adjusted-size
|
||||
"Returns the adjusted size of an SVG."
|
||||
[width height max]
|
||||
(let [ratio (/ width height)]
|
||||
(if (< width height)
|
||||
[max (* max (/ 1 ratio))]
|
||||
[(* max ratio) max])))
|
||||
;; Guard against zero/NaN dimensions that would cause division by zero
|
||||
;; or produce invalid sizes, leading to createImageBitmap failures.
|
||||
(when (and (pos? width) (pos? height))
|
||||
(let [ratio (/ width height)]
|
||||
(if (< width height)
|
||||
[max (* max (/ 1 ratio))]
|
||||
[(* max ratio) max]))))
|
||||
|
||||
(defn- svg-get-size-from-viewbox
|
||||
"Returns the size of an SVG from its viewbox."
|
||||
@ -101,7 +111,10 @@
|
||||
"Sets the intrinsic size of an SVG to the given max size."
|
||||
[^js svg max]
|
||||
(let [doc (get-document-element svg)
|
||||
[w h] (svg-get-size svg max)]
|
||||
[w h] (or (svg-get-size svg max)
|
||||
;; Fallback: if we can't determine size from viewBox or
|
||||
;; intrinsic attributes, use the max size as a square.
|
||||
[max max])]
|
||||
(dom/set-attribute! doc "width" (dm/str w))
|
||||
(dom/set-attribute! doc "height" (dm/str h)))
|
||||
svg)
|
||||
|
||||
@ -24,5 +24,5 @@
|
||||
|
||||
(defn ^:export plugins []
|
||||
(st/emit! (features/enable-feature "plugins/runtime"))
|
||||
(plugins/init-plugins-runtime!)
|
||||
(plugins/init-plugins-runtime)
|
||||
nil)
|
||||
|
||||
50
frontend/test/frontend_tests/data/workspace_mcp_test.cljs
Normal file
50
frontend/test/frontend_tests/data/workspace_mcp_test.cljs
Normal file
@ -0,0 +1,50 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns frontend-tests.data.workspace-mcp-test
|
||||
(:require
|
||||
[app.main.data.workspace.mcp :as mcp]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(t/deftest test-set-mcp-active
|
||||
(t/testing "sets :active to true"
|
||||
(let [state {:mcp {:active false}}
|
||||
result (ptk/update (mcp/set-mcp-active true) state)]
|
||||
(t/is (true? (get-in result [:mcp :active])))))
|
||||
|
||||
(t/testing "sets :active to false"
|
||||
(let [state {:mcp {:active true}}
|
||||
result (ptk/update (mcp/set-mcp-active false) state)]
|
||||
(t/is (false? (get-in result [:mcp :active]))))))
|
||||
|
||||
(t/deftest test-update-mcp-status
|
||||
(t/testing "enables MCP in profile props"
|
||||
(let [state {:profile {:props {:mcp-enabled false}}}
|
||||
result (ptk/update (mcp/update-mcp-status true) state)]
|
||||
(t/is (true? (get-in result [:profile :props :mcp-enabled])))))
|
||||
|
||||
(t/testing "disables MCP in profile props"
|
||||
(let [state {:profile {:props {:mcp-enabled true}}}
|
||||
result (ptk/update (mcp/update-mcp-status false) state)]
|
||||
(t/is (false? (get-in result [:profile :props :mcp-enabled]))))))
|
||||
|
||||
(t/deftest test-update-mcp-connection-status
|
||||
(t/testing "sets connection status to connected"
|
||||
(let [state {:mcp {:connection-status "disconnected"}}
|
||||
result (ptk/update (mcp/update-mcp-connection-status "connected") state)]
|
||||
(t/is (= "connected" (get-in result [:mcp :connection-status])))))
|
||||
|
||||
(t/testing "sets connection status to disconnected"
|
||||
(let [state {:mcp {:connection-status "connected"}}
|
||||
result (ptk/update (mcp/update-mcp-connection-status "disconnected") state)]
|
||||
(t/is (= "disconnected" (get-in result [:mcp :connection-status]))))))
|
||||
|
||||
(t/deftest test-init-sets-active
|
||||
(t/testing "init sets :mcp :active to true"
|
||||
(let [state {:mcp {:active false}}
|
||||
result (ptk/update (mcp/init) state)]
|
||||
(t/is (true? (get-in result [:mcp :active]))))))
|
||||
@ -6,9 +6,18 @@
|
||||
|
||||
(ns frontend-tests.data.workspace-thumbnails-test
|
||||
(:require
|
||||
[app.common.thumbnails :as thc]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace.thumbnails :as thumbnails]
|
||||
[cljs.test :as t :include-macros true]))
|
||||
[beicon.v2.core :as rx]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[frontend-tests.helpers.mock :as mock]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
;; The qualified keyword used internally by app.main.data.workspace.thumbnails
|
||||
;; for tracking the pending deletion queue in application state.
|
||||
(def ^:private deletion-queue-key
|
||||
:app.main.data.workspace.thumbnails/thumbnails-deletion-queue)
|
||||
|
||||
(t/deftest extract-frame-changes-handles-cyclic-frame-links
|
||||
(let [page-id (uuid/next)
|
||||
@ -33,4 +42,257 @@
|
||||
:component-root true}}}}}]
|
||||
(t/is (= #{["frame" root-id]
|
||||
["component" shape-b-id]}
|
||||
(#'thumbnails/extract-frame-changes page-id [event [old-data new-data]])))))
|
||||
(#'thumbnails/extract-frame-changes page-id [event [old-data new-data]]))))
|
||||
|
||||
;; --- Batch deletion queue state management ---
|
||||
|
||||
(t/deftest clear-thumbnail-adds-to-deletion-queue
|
||||
(let [file-id (uuid/next)
|
||||
object-id (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||
uri "blob:http://localhost/test-thumb"
|
||||
event (thumbnails/clear-thumbnail file-id object-id)
|
||||
state {:thumbnails {object-id uri}}
|
||||
result (ptk/update event state)]
|
||||
;; Thumbnail removed from the map
|
||||
(t/is (nil? (get-in result [:thumbnails object-id])))
|
||||
;; Object-id added to the deletion queue with its URI
|
||||
(t/is (= uri (get-in result [deletion-queue-key object-id])))))
|
||||
|
||||
(t/deftest clear-thumbnail-keeps-other-thumbnails
|
||||
(let [file-id (uuid/next)
|
||||
object-id1 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||
object-id2 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||
uri1 "blob:http://localhost/thumb-1"
|
||||
uri2 "blob:http://localhost/thumb-2"
|
||||
event (thumbnails/clear-thumbnail file-id object-id1)
|
||||
state {:thumbnails {object-id1 uri1 object-id2 uri2}}
|
||||
result (ptk/update event state)]
|
||||
;; Only the cleared thumbnail is removed
|
||||
(t/is (nil? (get-in result [:thumbnails object-id1])))
|
||||
(t/is (= uri2 (get-in result [:thumbnails object-id2])))
|
||||
;; Only the cleared thumbnail is queued
|
||||
(t/is (= uri1 (get-in result [deletion-queue-key object-id1])))
|
||||
(t/is (nil? (get-in result [deletion-queue-key object-id2])))))
|
||||
|
||||
(t/deftest clear-thumbnail-accumulates-in-queue
|
||||
(let [file-id (uuid/next)
|
||||
object-id1 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||
object-id2 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||
uri1 "blob:http://localhost/thumb-1"
|
||||
uri2 "blob:http://localhost/thumb-2"
|
||||
event1 (thumbnails/clear-thumbnail file-id object-id1)
|
||||
event2 (thumbnails/clear-thumbnail file-id object-id2)
|
||||
state {:thumbnails {object-id1 uri1 object-id2 uri2}}
|
||||
state1 (ptk/update event1 state)
|
||||
state2 (ptk/update event2 state1)]
|
||||
;; Both removed from thumbnails
|
||||
(t/is (nil? (get-in state2 [:thumbnails object-id1])))
|
||||
(t/is (nil? (get-in state2 [:thumbnails object-id2])))
|
||||
;; Both accumulated in the queue
|
||||
(t/is (= uri1 (get-in state2 [deletion-queue-key object-id1])))
|
||||
(t/is (= uri2 (get-in state2 [deletion-queue-key object-id2])))
|
||||
(t/is (= 2 (count (get state2 deletion-queue-key))))))
|
||||
|
||||
(t/deftest remove-from-deletion-queue-removes-entry
|
||||
(let [file-id (uuid/next)
|
||||
object-id (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||
event (thumbnails/remove-from-deletion-queue object-id)
|
||||
state {deletion-queue-key {object-id "blob:http://localhost/thumb"}}
|
||||
result (ptk/update event state)]
|
||||
(t/is (nil? (get-in result [deletion-queue-key object-id])))
|
||||
(t/is (empty? (get result deletion-queue-key)))))
|
||||
|
||||
(t/deftest remove-from-deletion-queue-keeps-other-entries
|
||||
(let [file-id (uuid/next)
|
||||
object-id1 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||
object-id2 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||
uri1 "blob:http://localhost/thumb-1"
|
||||
uri2 "blob:http://localhost/thumb-2"
|
||||
event (thumbnails/remove-from-deletion-queue object-id1)
|
||||
state {deletion-queue-key {object-id1 uri1
|
||||
object-id2 uri2}}
|
||||
result (ptk/update event state)]
|
||||
;; Only the specified entry is removed
|
||||
(t/is (nil? (get-in result [deletion-queue-key object-id1])))
|
||||
(t/is (= uri2 (get-in result [deletion-queue-key object-id2])))
|
||||
(t/is (= 1 (count (get result deletion-queue-key))))))
|
||||
|
||||
(t/deftest remove-before-clear-cancels-pending-delete
|
||||
(let [file-id (uuid/next)
|
||||
object-id (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||
uri "blob:http://localhost/thumb"
|
||||
;; Step 1: clear-thumbnail queues the delete
|
||||
state1 (ptk/update (thumbnails/clear-thumbnail file-id object-id)
|
||||
{:thumbnails {object-id uri}})
|
||||
;; Step 2: remove-from-deletion-queue cancels the pending delete
|
||||
state2 (ptk/update (thumbnails/remove-from-deletion-queue object-id)
|
||||
state1)]
|
||||
;; Thumbnail was removed from :thumbnails map by clear-thumbnail
|
||||
(t/is (nil? (get-in state2 [:thumbnails object-id])))
|
||||
;; But the deletion queue entry was cancelled by remove-from-deletion-queue
|
||||
(t/is (nil? (get-in state2 [deletion-queue-key object-id])))
|
||||
(t/is (empty? (get state2 deletion-queue-key)))))
|
||||
|
||||
(t/deftest clear-thumbnail-batch-drains-queue
|
||||
(let [file-id (uuid/next)
|
||||
object-id1 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||
object-id2 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||
uri1 "blob:http://localhost/thumb-1"
|
||||
uri2 "blob:http://localhost/thumb-2"
|
||||
;; Build up the queue state manually (simulating accumulated clear-thumbnails)
|
||||
state {deletion-queue-key {object-id1 uri1 object-id2 uri2}}
|
||||
event (#'thumbnails/clear-thumbnail-batch)
|
||||
result (ptk/update event state)]
|
||||
;; The queue is drained from application state
|
||||
(t/is (empty? (get result deletion-queue-key)))))
|
||||
|
||||
(t/deftest clear-thumbnail-batch-empty-queue-noop
|
||||
(let [state {deletion-queue-key {}}
|
||||
event (#'thumbnails/clear-thumbnail-batch)
|
||||
result (ptk/update event state)]
|
||||
;; State unchanged when queue is already empty
|
||||
(t/is (empty? (get result deletion-queue-key)))
|
||||
(t/is (= state (dissoc result deletion-queue-key)))))
|
||||
|
||||
(t/deftest assoc-thumbnail-adds-to-map
|
||||
(let [object-id (thc/fmt-object-id (uuid/next) (uuid/next) (uuid/next) "frame")
|
||||
uri "blob:http://localhost/new-thumb"
|
||||
event (#'thumbnails/assoc-thumbnail object-id uri)
|
||||
state {:thumbnails {}}
|
||||
result (ptk/update event state)]
|
||||
(t/is (= uri (get-in result [:thumbnails object-id])))))
|
||||
|
||||
(t/deftest duplicate-thumbnail-copies-entry
|
||||
(let [old-id (thc/fmt-object-id (uuid/next) (uuid/next) (uuid/next) "frame")
|
||||
new-id (thc/fmt-object-id (uuid/next) (uuid/next) (uuid/next) "frame")
|
||||
uri "blob:http://localhost/dup-thumb"
|
||||
event (thumbnails/duplicate-thumbnail old-id new-id)
|
||||
state {:thumbnails {old-id uri}}
|
||||
result (ptk/update event state)]
|
||||
(t/is (= uri (get-in result [:thumbnails old-id])))
|
||||
(t/is (= uri (get-in result [:thumbnails new-id])))))
|
||||
|
||||
;; --- Async WatchEvent tests ---
|
||||
|
||||
(defn- make-obj-ids
|
||||
"Helper to create n properly-formatted object-ids for a single file."
|
||||
[file-id n]
|
||||
(vec (repeatedly n #(thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame"))))
|
||||
|
||||
(t/deftest clear-thumbnail-batch-watch-calls-rpc-with-object-ids
|
||||
(t/async done
|
||||
(let [file-id (uuid/next)
|
||||
oids (make-obj-ids file-id 3)
|
||||
state {deletion-queue-key (zipmap oids (repeat "blob:http://test"))}
|
||||
event (#'thumbnails/clear-thumbnail-batch)]
|
||||
(ptk/update event state)
|
||||
(mock/with-mocks
|
||||
{#'app.main.repo/cmd! mock/rpc-cmd!-mock
|
||||
#'app.util.timers/schedule-on-idle mock/schedule-on-idle-mock}
|
||||
(fn [done']
|
||||
(->> (ptk/watch event state nil)
|
||||
(rx/reduce conj [])
|
||||
(rx/subs!
|
||||
(fn [_] nil)
|
||||
(fn [err] (t/is (nil? err)) (done'))
|
||||
(fn [_]
|
||||
(t/is (= 1 (count @mock/rpc-calls)))
|
||||
(let [[{:keys [cmd params]}] @mock/rpc-calls]
|
||||
(t/is (= :delete-file-object-thumbnails cmd))
|
||||
(t/is (= (vec oids) (:object-ids params))))
|
||||
(done')))))
|
||||
done))))
|
||||
|
||||
(t/deftest clear-thumbnail-batch-watch-partitions-large-batch
|
||||
(t/async done
|
||||
(let [file-id (uuid/next)
|
||||
oids (make-obj-ids file-id 250)
|
||||
state {deletion-queue-key (zipmap oids (repeat "blob:http://test"))}
|
||||
event (#'thumbnails/clear-thumbnail-batch)]
|
||||
(ptk/update event state)
|
||||
(mock/with-mocks
|
||||
{#'app.main.repo/cmd! mock/rpc-cmd!-mock
|
||||
#'app.util.timers/schedule-on-idle mock/schedule-on-idle-mock}
|
||||
(fn [done']
|
||||
(->> (ptk/watch event state nil)
|
||||
(rx/reduce conj [])
|
||||
(rx/subs!
|
||||
(fn [_] nil)
|
||||
(fn [err] (t/is (nil? err)) (done'))
|
||||
(fn [_]
|
||||
(t/is (= 2 (count @mock/rpc-calls)))
|
||||
(let [[c1 c2] @mock/rpc-calls]
|
||||
(t/is (= :delete-file-object-thumbnails (:cmd c1)))
|
||||
(t/is (= :delete-file-object-thumbnails (:cmd c2)))
|
||||
(t/is (= 200 (count (:object-ids (:params c1)))))
|
||||
(t/is (= 50 (count (:object-ids (:params c2)))))
|
||||
(t/is (= (set oids)
|
||||
(set (concat (:object-ids (:params c1))
|
||||
(:object-ids (:params c2)))))))
|
||||
(done')))))
|
||||
done))))
|
||||
|
||||
(t/deftest clear-thumbnail-batch-watch-revokes-blob-uris
|
||||
(t/async done
|
||||
(let [file-id (uuid/next)
|
||||
oids (make-obj-ids file-id 2)
|
||||
uris ["blob:http://localhost/thumb-1"
|
||||
"blob:http://localhost/thumb-2"]
|
||||
state {deletion-queue-key (zipmap oids uris)}
|
||||
event (#'thumbnails/clear-thumbnail-batch)]
|
||||
(ptk/update event state)
|
||||
(mock/with-mocks
|
||||
{#'app.main.repo/cmd! (fn [_ _] (rx/of nil))
|
||||
#'app.util.webapi/revoke-uri mock/revoke-uri-mock
|
||||
#'app.util.timers/schedule-on-idle mock/schedule-on-idle-mock}
|
||||
(fn [done']
|
||||
(->> (ptk/watch event state nil)
|
||||
(rx/reduce conj [])
|
||||
(rx/subs!
|
||||
(fn [_] nil)
|
||||
(fn [err] (t/is (nil? err)) (done'))
|
||||
(fn [_]
|
||||
(t/is (= (set uris) (set @mock/revoked-uris)))
|
||||
(done')))))
|
||||
done))))
|
||||
|
||||
(t/deftest clear-thumbnail-batch-watch-empty-queue-no-rpc
|
||||
(t/async done
|
||||
(let [event (#'thumbnails/clear-thumbnail-batch)
|
||||
state {}]
|
||||
(ptk/update event state)
|
||||
(mock/with-mocks
|
||||
{#'app.main.repo/cmd! mock/rpc-cmd!-mock
|
||||
#'app.util.timers/schedule-on-idle mock/schedule-on-idle-mock}
|
||||
(fn [done']
|
||||
(->> (ptk/watch event state nil)
|
||||
(rx/reduce conj [])
|
||||
(rx/subs!
|
||||
(fn [_] nil)
|
||||
(fn [err] (t/is (nil? err)) (done'))
|
||||
(fn [_]
|
||||
(t/is (empty? @mock/rpc-calls))
|
||||
(done')))))
|
||||
done))))
|
||||
|
||||
(t/deftest clear-thumbnail-watch-emits-batch-after-debounce
|
||||
(t/async done
|
||||
(let [file-id (uuid/next)
|
||||
object-id (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||
uri "blob:http://localhost/thumb"
|
||||
state {:thumbnails {object-id uri}}
|
||||
event (thumbnails/clear-thumbnail file-id object-id)]
|
||||
(ptk/update event state)
|
||||
(mock/with-mocks
|
||||
{#'beicon.v2.core/timer mock/timer-mock}
|
||||
(fn [done']
|
||||
(->> (ptk/watch event state nil)
|
||||
(rx/reduce conj [])
|
||||
(rx/subs!
|
||||
(fn [_] nil)
|
||||
(fn [err] (t/is (nil? err)) (done'))
|
||||
(fn [events]
|
||||
(t/is (= 1 (count events)))
|
||||
(t/is (ptk/event? (first events)))
|
||||
(done')))))
|
||||
done)))))
|
||||
|
||||
103
frontend/test/frontend_tests/helpers/mock.cljs
Normal file
103
frontend/test/frontend_tests/helpers/mock.cljs
Normal file
@ -0,0 +1,103 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns frontend-tests.helpers.mock
|
||||
"Async-first mocking primitives for ClojureScript tests.
|
||||
|
||||
Uses `with-redefs` — the standard CLJS mechanism for rebinding Vars
|
||||
within a dynamic scope. Recording atoms (`rpc-calls`, `revoked-uris`)
|
||||
persist across the entire test lifecycle, making captured data
|
||||
inspectable regardless of whether callbacks fire synchronously or
|
||||
asynchronously.
|
||||
|
||||
The `with-mocks` helper wraps the lifecycle:
|
||||
1. Reset recording atoms
|
||||
2. Install mocks via `with-redefs`
|
||||
3. Execute `(test-fn inner-done)`
|
||||
4. `inner-done` calls `outer-done` (typically `cljs.test/async`'s done)
|
||||
|
||||
Since all mock functions return synchronous `rx/of` observables,
|
||||
callbacks always fire within the `with-redefs` body."
|
||||
(:require
|
||||
[beicon.v2.core :as rx]))
|
||||
|
||||
;; ═══════════════════════════════════════════════════════════════
|
||||
;; Recording atoms
|
||||
;; ═══════════════════════════════════════════════════════════════
|
||||
|
||||
(def rpc-calls
|
||||
"Atom accumulating mocked `rp/cmd!` calls as `{:cmd kw :params map}`."
|
||||
(atom []))
|
||||
|
||||
(def revoked-uris
|
||||
"Atom accumulating URIs passed to `wapi/revoke-uri`."
|
||||
(atom []))
|
||||
|
||||
;; ═══════════════════════════════════════════════════════════════
|
||||
;; Mock implementations
|
||||
;; ═══════════════════════════════════════════════════════════════
|
||||
|
||||
(defn rpc-cmd!-mock
|
||||
"Records [cmd params] in [[rpc-calls]], returns `(rx/of nil)`."
|
||||
[cmd params]
|
||||
(swap! rpc-calls conj {:cmd cmd :params params})
|
||||
(rx/of nil))
|
||||
|
||||
(defn revoke-uri-mock
|
||||
"Records `uri` in [[revoked-uris]]."
|
||||
[uri]
|
||||
(swap! revoked-uris conj uri))
|
||||
|
||||
(defn schedule-on-idle-mock
|
||||
"Calls `f` immediately instead of deferring to the idle queue."
|
||||
[f]
|
||||
(f))
|
||||
|
||||
(defn timer-mock
|
||||
"Returns `(rx/of :immediate)` so debounce timers fire instantly
|
||||
during tests."
|
||||
[_ms]
|
||||
(rx/of :immediate))
|
||||
|
||||
;; ═══════════════════════════════════════════════════════════════
|
||||
;; Lifecycle
|
||||
;; ═══════════════════════════════════════════════════════════════
|
||||
|
||||
(defn reset!
|
||||
"Clear all recording atoms. Called automatically by [[with-mocks]]."
|
||||
[]
|
||||
(reset! rpc-calls [])
|
||||
(reset! revoked-uris []))
|
||||
|
||||
;; ═══════════════════════════════════════════════════════════════
|
||||
;; Public API
|
||||
;; ═══════════════════════════════════════════════════════════════
|
||||
|
||||
(defn with-mocks
|
||||
"Resets recording atoms, installs `mocks` via `with-redefs`, then
|
||||
calls `(test-fn inner-done)`.
|
||||
|
||||
`mocks` is a map of `Var → mock-fn` (e.g. `{#'rp/cmd! mock-fn}`).
|
||||
`inner-done` tears down the `with-redefs` (by returning) and calls
|
||||
`outer-done` (the `cljs.test/async` `done` callback).
|
||||
|
||||
Example:
|
||||
|
||||
(t/deftest my-async-test
|
||||
(t/async done
|
||||
(mock/with-mocks
|
||||
{#'rp/cmd! mock/rpc-cmd!-mock}
|
||||
(fn [done']
|
||||
(->> (some-async-flow)
|
||||
(rx/subs!
|
||||
(fn [v] ...)
|
||||
(fn [err] (done'))
|
||||
(fn [] (done'))))))))"
|
||||
[mocks test-fn outer-done]
|
||||
(reset!)
|
||||
(apply with-redefs (mapcat identity mocks)
|
||||
(test-fn (fn inner-done []
|
||||
(outer-done)))))
|
||||
@ -11,6 +11,7 @@
|
||||
[frontend-tests.data.uploads-test]
|
||||
[frontend-tests.data.viewer-test]
|
||||
[frontend-tests.data.workspace-colors-test]
|
||||
[frontend-tests.data.workspace-mcp-test]
|
||||
[frontend-tests.data.workspace-media-test]
|
||||
[frontend-tests.data.workspace-shortcuts-test]
|
||||
[frontend-tests.data.workspace-texts-test]
|
||||
@ -63,6 +64,7 @@
|
||||
frontend-tests.data.uploads-test
|
||||
frontend-tests.data.viewer-test
|
||||
frontend-tests.data.workspace-colors-test
|
||||
frontend-tests.data.workspace-mcp-test
|
||||
frontend-tests.data.workspace-media-test
|
||||
frontend-tests.data.workspace-shortcuts-test
|
||||
frontend-tests.data.workspace-texts-test
|
||||
|
||||
@ -4888,16 +4888,16 @@ msgid "onboarding.questions.team-size.just-me"
|
||||
msgstr "Just me"
|
||||
|
||||
msgid "onboarding.questions.team-size.2-100"
|
||||
msgstr "2-100"
|
||||
msgstr "2 - 100"
|
||||
|
||||
msgid "onboarding.questions.team-size.101-500"
|
||||
msgstr "101-500"
|
||||
msgstr "101 - 500"
|
||||
|
||||
msgid "onboarding.questions.team-size.501-1000"
|
||||
msgstr "501-1,000"
|
||||
msgstr "501 - 1,000"
|
||||
|
||||
msgid "onboarding.questions.team-size.1001-5000"
|
||||
msgstr "1,001-5,000"
|
||||
msgstr "1,001 - 5,000"
|
||||
|
||||
msgid "onboarding.questions.team-size.more-than-5001"
|
||||
msgstr "5,001+"
|
||||
@ -9684,7 +9684,16 @@ msgstr "Create board. Click and drag to define its size. (%s)"
|
||||
msgid "workspace.toolbar.image"
|
||||
msgstr "Image (%s)"
|
||||
|
||||
#: src/app/main/ui/workspace/top_toolbar.cljs:139, src/app/main/ui/workspace/top_toolbar.cljs:140
|
||||
msgid "workspace.toolbar.mcp"
|
||||
msgstr "MCP"
|
||||
|
||||
msgid "workspace.toolbar.mcp-connected"
|
||||
msgstr "MCP connected"
|
||||
|
||||
msgid "workspace.toolbar.mcp-connect-here"
|
||||
msgstr "Connect here"
|
||||
|
||||
#: src/app/main/ui/workspace/top_toolbar.cljs:143
|
||||
msgid "workspace.toolbar.move"
|
||||
msgstr "Move (%s)"
|
||||
|
||||
|
||||
@ -4757,16 +4757,16 @@ msgid "onboarding.questions.team-size.just-me"
|
||||
msgstr "Sólo yo"
|
||||
|
||||
msgid "onboarding.questions.team-size.2-100"
|
||||
msgstr "2-100"
|
||||
msgstr "2 - 100"
|
||||
|
||||
msgid "onboarding.questions.team-size.101-500"
|
||||
msgstr "101-500"
|
||||
msgstr "101 - 500"
|
||||
|
||||
msgid "onboarding.questions.team-size.501-1000"
|
||||
msgstr "501-1000"
|
||||
msgstr "501 - 1000"
|
||||
|
||||
msgid "onboarding.questions.team-size.1001-5000"
|
||||
msgstr "1001-5000"
|
||||
msgstr "1001 - 5000"
|
||||
|
||||
msgid "onboarding.questions.team-size.more-than-5001"
|
||||
msgstr "5001+"
|
||||
@ -9357,7 +9357,16 @@ msgstr "Crear tablero. Click y arrastrar para definir el tamaño. (%s)"
|
||||
msgid "workspace.toolbar.image"
|
||||
msgstr "Imagen (%s)"
|
||||
|
||||
#: src/app/main/ui/workspace/top_toolbar.cljs:139, src/app/main/ui/workspace/top_toolbar.cljs:140
|
||||
msgid "workspace.toolbar.mcp"
|
||||
msgstr "MCP"
|
||||
|
||||
msgid "workspace.toolbar.mcp-connected"
|
||||
msgstr "MCP conectado"
|
||||
|
||||
msgid "workspace.toolbar.mcp-connect-here"
|
||||
msgstr "Conectar aquí"
|
||||
|
||||
#: src/app/main/ui/workspace/top_toolbar.cljs:143
|
||||
msgid "workspace.toolbar.move"
|
||||
msgstr "Mover (%s)"
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@penpot/mcp",
|
||||
"version": "2.16.0-rc.9.11",
|
||||
"version": "2.16.0",
|
||||
"description": "MCP server for Penpot integration",
|
||||
"license": "MPL-2.0",
|
||||
"bin": {
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC Sucursal en España SL",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319",
|
||||
"packageManager": "pnpm@11.6.0+sha512.9a36518224080c6fe5165afdcfe79bfa118c29be703f3f462b1e32efe1e98e47e8750b148e08286250aad4113cc7993ca413c4e2cd447752708c2ee5751bc95f",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/penpot/penpot"
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
allowBuilds:
|
||||
esbuild: true
|
||||
opencode-ai: true
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user