@1agh/maude 0.42.0 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/studio/.ai/cache/_stats.json +7 -0
- package/apps/studio/acp/bootstrap-brief.ts +12 -2
- package/apps/studio/annotations-layer.tsx +1055 -278
- package/apps/studio/annotations-model.ts +58 -0
- package/apps/studio/api.ts +173 -22
- package/apps/studio/bin/_ingest-footage.mjs +386 -0
- package/apps/studio/bin/_probe-footage-playwright.mjs +269 -0
- package/apps/studio/bin/_video-playwright.mjs +141 -71
- package/apps/studio/bin/ingest-footage.sh +27 -0
- package/apps/studio/bin/photo-adjust.sh +163 -0
- package/apps/studio/bin/photo-bg-remove.sh +160 -0
- package/apps/studio/bin/probe-footage.sh +28 -0
- package/apps/studio/bin/read-annotations.mjs +125 -2
- package/apps/studio/canvas-edit.ts +459 -10
- package/apps/studio/canvas-lib.tsx +523 -5
- package/apps/studio/canvas-pipeline.ts +65 -7
- package/apps/studio/canvas-shell.tsx +156 -16
- package/apps/studio/client/app.jsx +451 -34
- package/apps/studio/client/export-center.jsx +35 -1
- package/apps/studio/client/github.js +4 -0
- package/apps/studio/client/panels/GitPanel.jsx +8 -0
- package/apps/studio/client/panels/RepoBranchSwitcher.jsx +133 -21
- package/apps/studio/client/panels/StickerPicker.jsx +160 -0
- package/apps/studio/client/panels/TimelinePanel.jsx +15 -2
- package/apps/studio/client/photo-knobs.jsx +437 -0
- package/apps/studio/client/styles/3-shell-maude.css +42 -0
- package/apps/studio/commands/edit-source-command.ts +31 -4
- package/apps/studio/context-menu.tsx +31 -6
- package/apps/studio/dist/client.bundle.js +5561 -19
- package/apps/studio/dist/comment-mount.js +2 -2
- package/apps/studio/dist/runtime/.min-sizes.json +1 -0
- package/apps/studio/dist/runtime/@imgly_background-removal.js +5543 -0
- package/apps/studio/dist/runtime/pixi-js.js +519 -519
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/dom-selection.ts +25 -0
- package/apps/studio/exporters/jobs.ts +47 -5
- package/apps/studio/exporters/video.ts +24 -4
- package/apps/studio/footage/schema.test.ts +195 -0
- package/apps/studio/footage/schema.ts +484 -0
- package/apps/studio/footage-store.ts +235 -0
- package/apps/studio/fs-watch.ts +10 -0
- package/apps/studio/git/endpoints.ts +68 -7
- package/apps/studio/git/service.ts +196 -20
- package/apps/studio/github/service.ts +67 -0
- package/apps/studio/handoff.ts +3 -2
- package/apps/studio/http.ts +173 -14
- package/apps/studio/input-router.tsx +10 -0
- package/apps/studio/inspect.ts +53 -1
- package/apps/studio/paths.ts +10 -0
- package/apps/studio/photo/filters.ts +285 -0
- package/apps/studio/photo/pipeline.ts +607 -0
- package/apps/studio/photo/schema.ts +367 -0
- package/apps/studio/photo-store.ts +154 -0
- package/apps/studio/runtime-bundle.ts +29 -6
- package/apps/studio/server.ts +5 -1
- package/apps/studio/stickers/figjam-doodle/manifest.json +104 -0
- package/apps/studio/stickers/figjam-doodle/slice1.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice10.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice11.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice12.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice13.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice14.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice15.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice16.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice17.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice18.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice19.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice2.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice20.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice21.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice22.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice23.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice24.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice3.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice4.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice5.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice6.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice7.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice8.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice9.png +0 -0
- package/apps/studio/stickers/life-style/best.png +0 -0
- package/apps/studio/stickers/life-style/come-on.png +0 -0
- package/apps/studio/stickers/life-style/cut.png +0 -0
- package/apps/studio/stickers/life-style/detox.png +0 -0
- package/apps/studio/stickers/life-style/just.png +0 -0
- package/apps/studio/stickers/life-style/manifest.json +68 -0
- package/apps/studio/stickers/life-style/mirror.png +0 -0
- package/apps/studio/stickers/life-style/nice.png +0 -0
- package/apps/studio/stickers/life-style/objects.png +0 -0
- package/apps/studio/stickers/life-style/ok.png +0 -0
- package/apps/studio/stickers/life-style/queen.png +0 -0
- package/apps/studio/stickers/life-style/star.png +0 -0
- package/apps/studio/stickers/life-style/sun.png +0 -0
- package/apps/studio/stickers/life-style/water.png +0 -0
- package/apps/studio/stickers/life-style/yeah.png +0 -0
- package/apps/studio/stickers/life-style/you-can.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/check-the-deets.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/cut-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/dope.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/fresh.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/hot.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/idea.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/keep-exploring.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/killed-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/love-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/manifest.json +88 -0
- package/apps/studio/stickers/opposing-thoughts/not-sure.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/ok.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/pure-gold.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/save-for-later.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/steal-this.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/take-a-peek.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/this-or-that.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/thoughts.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/vibes.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/winner.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/wip.png +0 -0
- package/apps/studio/stickers/project-status/group-100.png +0 -0
- package/apps/studio/stickers/project-status/group-101.png +0 -0
- package/apps/studio/stickers/project-status/group-102.png +0 -0
- package/apps/studio/stickers/project-status/group-103.png +0 -0
- package/apps/studio/stickers/project-status/group-104.png +0 -0
- package/apps/studio/stickers/project-status/group-105.png +0 -0
- package/apps/studio/stickers/project-status/group-106.png +0 -0
- package/apps/studio/stickers/project-status/group-107.png +0 -0
- package/apps/studio/stickers/project-status/group-108.png +0 -0
- package/apps/studio/stickers/project-status/group-109.png +0 -0
- package/apps/studio/stickers/project-status/group-110.png +0 -0
- package/apps/studio/stickers/project-status/group-111.png +0 -0
- package/apps/studio/stickers/project-status/group-112.png +0 -0
- package/apps/studio/stickers/project-status/group-113.png +0 -0
- package/apps/studio/stickers/project-status/group-114.png +0 -0
- package/apps/studio/stickers/project-status/group-115.png +0 -0
- package/apps/studio/stickers/project-status/group-117.png +0 -0
- package/apps/studio/stickers/project-status/group-118.png +0 -0
- package/apps/studio/stickers/project-status/group-119.png +0 -0
- package/apps/studio/stickers/project-status/group-120.png +0 -0
- package/apps/studio/stickers/project-status/group-121.png +0 -0
- package/apps/studio/stickers/project-status/group-122.png +0 -0
- package/apps/studio/stickers/project-status/group-41.png +0 -0
- package/apps/studio/stickers/project-status/group-42.png +0 -0
- package/apps/studio/stickers/project-status/group-43.png +0 -0
- package/apps/studio/stickers/project-status/group-44.png +0 -0
- package/apps/studio/stickers/project-status/group-45.png +0 -0
- package/apps/studio/stickers/project-status/group-46.png +0 -0
- package/apps/studio/stickers/project-status/group-47.png +0 -0
- package/apps/studio/stickers/project-status/group-48.png +0 -0
- package/apps/studio/stickers/project-status/group-49.png +0 -0
- package/apps/studio/stickers/project-status/group-50.png +0 -0
- package/apps/studio/stickers/project-status/group-51.png +0 -0
- package/apps/studio/stickers/project-status/group-52.png +0 -0
- package/apps/studio/stickers/project-status/group-53.png +0 -0
- package/apps/studio/stickers/project-status/group-54.png +0 -0
- package/apps/studio/stickers/project-status/group-55.png +0 -0
- package/apps/studio/stickers/project-status/group-56.png +0 -0
- package/apps/studio/stickers/project-status/group-57.png +0 -0
- package/apps/studio/stickers/project-status/group-58.png +0 -0
- package/apps/studio/stickers/project-status/group-59.png +0 -0
- package/apps/studio/stickers/project-status/group-60.png +0 -0
- package/apps/studio/stickers/project-status/group-61.png +0 -0
- package/apps/studio/stickers/project-status/group-62.png +0 -0
- package/apps/studio/stickers/project-status/group-63.png +0 -0
- package/apps/studio/stickers/project-status/group-64.png +0 -0
- package/apps/studio/stickers/project-status/group-65.png +0 -0
- package/apps/studio/stickers/project-status/group-66.png +0 -0
- package/apps/studio/stickers/project-status/group-67.png +0 -0
- package/apps/studio/stickers/project-status/group-68.png +0 -0
- package/apps/studio/stickers/project-status/group-69.png +0 -0
- package/apps/studio/stickers/project-status/group-70.png +0 -0
- package/apps/studio/stickers/project-status/group-71.png +0 -0
- package/apps/studio/stickers/project-status/group-72.png +0 -0
- package/apps/studio/stickers/project-status/group-73.png +0 -0
- package/apps/studio/stickers/project-status/group-74.png +0 -0
- package/apps/studio/stickers/project-status/group-75.png +0 -0
- package/apps/studio/stickers/project-status/group-76.png +0 -0
- package/apps/studio/stickers/project-status/group-77.png +0 -0
- package/apps/studio/stickers/project-status/group-78.png +0 -0
- package/apps/studio/stickers/project-status/group-79.png +0 -0
- package/apps/studio/stickers/project-status/group-80.png +0 -0
- package/apps/studio/stickers/project-status/group-81.png +0 -0
- package/apps/studio/stickers/project-status/group-82.png +0 -0
- package/apps/studio/stickers/project-status/group-83.png +0 -0
- package/apps/studio/stickers/project-status/group-84.png +0 -0
- package/apps/studio/stickers/project-status/group-85.png +0 -0
- package/apps/studio/stickers/project-status/group-86.png +0 -0
- package/apps/studio/stickers/project-status/group-87.png +0 -0
- package/apps/studio/stickers/project-status/group-88.png +0 -0
- package/apps/studio/stickers/project-status/group-89.png +0 -0
- package/apps/studio/stickers/project-status/group-90.png +0 -0
- package/apps/studio/stickers/project-status/group-91.png +0 -0
- package/apps/studio/stickers/project-status/group-92.png +0 -0
- package/apps/studio/stickers/project-status/group-93.png +0 -0
- package/apps/studio/stickers/project-status/group-94.png +0 -0
- package/apps/studio/stickers/project-status/group-96.png +0 -0
- package/apps/studio/stickers/project-status/group-97.png +0 -0
- package/apps/studio/stickers/project-status/group-98.png +0 -0
- package/apps/studio/stickers/project-status/group-99.png +0 -0
- package/apps/studio/stickers/project-status/manifest.json +328 -0
- package/apps/studio/test/acp-bootstrap-brief.test.ts +11 -0
- package/apps/studio/test/acp-commands.test.ts +2 -1
- package/apps/studio/test/asset-api.test.ts +3 -3
- package/apps/studio/test/canvas-edit.test.ts +17 -6
- package/apps/studio/test/canvas-origin-gate.test.ts +13 -0
- package/apps/studio/test/dynamic-text-edit.test.ts +162 -0
- package/apps/studio/test/edit-source-command.test.ts +23 -0
- package/apps/studio/test/element-structural-edit.test.ts +46 -0
- package/apps/studio/test/footage-store.test.ts +100 -0
- package/apps/studio/test/git-branches.test.ts +97 -0
- package/apps/studio/test/github-api.test.ts +56 -1
- package/apps/studio/test/input-router.test.ts +33 -0
- package/apps/studio/test/inspect-script-syntax.test.ts +53 -0
- package/apps/studio/test/photo-canvas-bundle.test.ts +61 -0
- package/apps/studio/test/photo-edit-api.test.ts +201 -0
- package/apps/studio/test/photo-filters.test.ts +149 -0
- package/apps/studio/test/photo-pipeline.test.ts +106 -0
- package/apps/studio/test/photo-taxonomy.test.ts +96 -0
- package/apps/studio/test/read-annotations.test.ts +164 -0
- package/apps/studio/test/shell-importmap.test.ts +49 -0
- package/apps/studio/test/text-editability-stamp.test.ts +131 -0
- package/apps/studio/test/timeline-parse.test.ts +57 -0
- package/apps/studio/test/tool-palette-insert-anchor.test.ts +5 -3
- package/apps/studio/test/undo-stack.test.ts +33 -0
- package/apps/studio/test/video-asset.test.ts +5 -5
- package/apps/studio/test/video-render-bridge.test.ts +23 -1
- package/apps/studio/text-caret.ts +239 -0
- package/apps/studio/tool-palette.tsx +43 -20
- package/apps/studio/undo-stack.ts +94 -2
- package/apps/studio/use-annotation-resize.tsx +4 -4
- package/apps/studio/use-canvas-media-drop.tsx +38 -1
- package/apps/studio/use-selection-set.tsx +21 -0
- package/apps/studio/video-comp.tsx +48 -7
- package/apps/studio/whats-new.json +53 -0
- package/cli/commands/design.mjs +34 -3
- package/cli/lib/gitignore-block.mjs +1 -0
- package/package.json +10 -10
- package/plugins/design/templates/_shell.html +1 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Project status stickers",
|
|
3
|
+
"author": "Iconfinder",
|
|
4
|
+
"attributionUrl": "https://www.figma.com/community/file/1128224635870836270",
|
|
5
|
+
"license": "Figma Community (see attributionUrl for terms)",
|
|
6
|
+
"stickers": [
|
|
7
|
+
{
|
|
8
|
+
"file": "group-100.png",
|
|
9
|
+
"keywords": ["thumbs-up", "awesome", "approve"]
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"file": "group-101.png",
|
|
13
|
+
"keywords": ["super-star", "star", "cool"]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"file": "group-102.png",
|
|
17
|
+
"keywords": ["off", "launch", "rocket"]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"file": "group-103.png",
|
|
21
|
+
"keywords": ["star", "sparkle", "favorite"]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"file": "group-104.png",
|
|
25
|
+
"keywords": ["hot", "fire", "skull"]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"file": "group-105.png",
|
|
29
|
+
"keywords": ["yeah", "rock-on", "excited"]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"file": "group-106.png",
|
|
33
|
+
"keywords": ["plus-one", "+1", "agree"]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"file": "group-107.png",
|
|
37
|
+
"keywords": ["no", "reject"]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"file": "group-108.png",
|
|
41
|
+
"keywords": ["hate-it", "dislike", "broken-heart"]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"file": "group-109.png",
|
|
45
|
+
"keywords": ["fresh", "new"]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"file": "group-110.png",
|
|
49
|
+
"keywords": ["sleepy", "zzz", "tired"]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"file": "group-111.png",
|
|
53
|
+
"keywords": ["meh", "unimpressed", "neutral"]
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"file": "group-112.png",
|
|
57
|
+
"keywords": ["ok", "thumbs-up"]
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"file": "group-113.png",
|
|
61
|
+
"keywords": ["nope", "no", "reject"]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"file": "group-114.png",
|
|
65
|
+
"keywords": ["star", "sparkle", "favorite"]
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"file": "group-115.png",
|
|
69
|
+
"keywords": ["party-time", "celebrate", "party"]
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"file": "group-117.png",
|
|
73
|
+
"keywords": ["peace", "peace-sign"]
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"file": "group-118.png",
|
|
77
|
+
"keywords": ["omg", "surprised", "shocked"]
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"file": "group-119.png",
|
|
81
|
+
"keywords": ["love-it", "heart", "like"]
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"file": "group-120.png",
|
|
85
|
+
"keywords": ["ok", "okay-hand"]
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"file": "group-121.png",
|
|
89
|
+
"keywords": ["great-idea", "idea", "lightbulb"]
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"file": "group-122.png",
|
|
93
|
+
"keywords": ["hmm", "thinking", "question"]
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"file": "group-41.png",
|
|
97
|
+
"keywords": ["lol", "funny", "laugh"]
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"file": "group-42.png",
|
|
101
|
+
"keywords": ["sparkle", "shiny", "star"]
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"file": "group-43.png",
|
|
105
|
+
"keywords": ["good-idea", "idea", "lightbulb"]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"file": "group-44.png",
|
|
109
|
+
"keywords": ["thanks", "thank-you"]
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"file": "group-45.png",
|
|
113
|
+
"keywords": ["ok", "okay"]
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"file": "group-46.png",
|
|
117
|
+
"keywords": ["done", "finished", "complete"]
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"file": "group-47.png",
|
|
121
|
+
"keywords": ["flagged", "flag", "important"]
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"file": "group-48.png",
|
|
125
|
+
"keywords": ["awesome", "great"]
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"file": "group-49.png",
|
|
129
|
+
"keywords": ["dead", "skull", "killed-it"]
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"file": "group-50.png",
|
|
133
|
+
"keywords": ["hot", "fire", "trending"]
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"file": "group-51.png",
|
|
137
|
+
"keywords": ["done", "thumbs-up", "approved"]
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"file": "group-52.png",
|
|
141
|
+
"keywords": ["meh", "unimpressed", "neutral"]
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"file": "group-53.png",
|
|
145
|
+
"keywords": ["feedback", "question"]
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"file": "group-54.png",
|
|
149
|
+
"keywords": ["work-busy", "busy", "clock"]
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"file": "group-55.png",
|
|
153
|
+
"keywords": ["chill", "relax", "cool"]
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"file": "group-56.png",
|
|
157
|
+
"keywords": ["almost-done", "nearly-finished", "hourglass"]
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"file": "group-57.png",
|
|
161
|
+
"keywords": ["no", "reject"]
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"file": "group-58.png",
|
|
165
|
+
"keywords": ["nope", "no", "reject"]
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"file": "group-59.png",
|
|
169
|
+
"keywords": ["star", "favorite"]
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"file": "group-60.png",
|
|
173
|
+
"keywords": ["peace", "peace-sign"]
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"file": "group-61.png",
|
|
177
|
+
"keywords": ["hmm", "thinking", "question"]
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"file": "group-62.png",
|
|
181
|
+
"keywords": ["thumbs-up", "awesome", "approve"]
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"file": "group-63.png",
|
|
185
|
+
"keywords": ["ok", "okay-hand"]
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"file": "group-64.png",
|
|
189
|
+
"keywords": ["love-it", "heart", "like"]
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"file": "group-65.png",
|
|
193
|
+
"keywords": ["fresh", "new"]
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"file": "group-66.png",
|
|
197
|
+
"keywords": ["off", "launch", "rocket"]
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"file": "group-67.png",
|
|
201
|
+
"keywords": ["hate-it", "dislike", "broken-heart"]
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"file": "group-68.png",
|
|
205
|
+
"keywords": ["wow", "heart", "surprised"]
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"file": "group-69.png",
|
|
209
|
+
"keywords": ["yes", "agree"]
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"file": "group-70.png",
|
|
213
|
+
"keywords": ["plus-one", "+1", "agree"]
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"file": "group-71.png",
|
|
217
|
+
"keywords": ["party-time", "celebrate", "party"]
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"file": "group-72.png",
|
|
221
|
+
"keywords": ["omg", "surprised", "shocked"]
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"file": "group-73.png",
|
|
225
|
+
"keywords": ["hot", "fire", "skull"]
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"file": "group-74.png",
|
|
229
|
+
"keywords": ["sleepy", "zzz", "tired"]
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"file": "group-75.png",
|
|
233
|
+
"keywords": ["wow", "surprised"]
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"file": "group-76.png",
|
|
237
|
+
"keywords": ["yeah", "rock-on", "excited"]
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"file": "group-77.png",
|
|
241
|
+
"keywords": ["high-alert", "warning", "urgent"]
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
"file": "group-78.png",
|
|
245
|
+
"keywords": ["thumbs-down", "dislike"]
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"file": "group-79.png",
|
|
249
|
+
"keywords": ["great-idea", "idea", "lightbulb"]
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"file": "group-80.png",
|
|
253
|
+
"keywords": ["super-star", "star", "cool"]
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"file": "group-81.png",
|
|
257
|
+
"keywords": ["wow", "heart", "surprised"]
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"file": "group-82.png",
|
|
261
|
+
"keywords": ["done", "thumbs-up", "approved"]
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
"file": "group-83.png",
|
|
265
|
+
"keywords": ["wow", "surprised"]
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
"file": "group-84.png",
|
|
269
|
+
"keywords": ["work-busy", "busy", "clock"]
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
"file": "group-85.png",
|
|
273
|
+
"keywords": ["thumbs-down", "dislike"]
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
"file": "group-86.png",
|
|
277
|
+
"keywords": ["thanks", "thank-you"]
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"file": "group-87.png",
|
|
281
|
+
"keywords": ["flagged", "flag", "important"]
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
"file": "group-88.png",
|
|
285
|
+
"keywords": ["dead", "skull"]
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
"file": "group-89.png",
|
|
289
|
+
"keywords": ["high-alert", "warning", "urgent"]
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"file": "group-90.png",
|
|
293
|
+
"keywords": ["good-idea", "idea", "lightbulb"]
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
"file": "group-91.png",
|
|
297
|
+
"keywords": ["lol", "funny", "laugh"]
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
"file": "group-92.png",
|
|
301
|
+
"keywords": ["awesome", "great"]
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
"file": "group-93.png",
|
|
305
|
+
"keywords": ["hot", "fire"]
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
"file": "group-94.png",
|
|
309
|
+
"keywords": ["chill", "relax", "cool"]
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
"file": "group-96.png",
|
|
313
|
+
"keywords": ["done", "finished", "complete"]
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
"file": "group-97.png",
|
|
317
|
+
"keywords": ["feedback", "question"]
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
"file": "group-98.png",
|
|
321
|
+
"keywords": ["yes", "agree"]
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
"file": "group-99.png",
|
|
325
|
+
"keywords": ["almost-done", "nearly-finished", "hourglass"]
|
|
326
|
+
}
|
|
327
|
+
]
|
|
328
|
+
}
|
|
@@ -164,4 +164,15 @@ describe('buildStudioBrief — native session commands available (DDR-143)', ()
|
|
|
164
164
|
buildStudioBrief({ designRel: '.design', projectLabel: 'maude', commandsAvailable: true })
|
|
165
165
|
).toBe(brief);
|
|
166
166
|
});
|
|
167
|
+
|
|
168
|
+
test('names board + the whiteboard capability so it is reached for unprompted (Phase 5, whiteboard-improvements)', () => {
|
|
169
|
+
expect(brief).toContain('board');
|
|
170
|
+
expect(brief.toLowerCase()).toContain('whiteboard');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test('web path (commandsAvailable falsy) names board in the command family but skips the whiteboard capability fact (no in-session claim)', () => {
|
|
174
|
+
const web = buildStudioBrief({ designRel: '.design', projectLabel: 'maude' });
|
|
175
|
+
expect(web).toContain('board');
|
|
176
|
+
expect(web.toLowerCase()).not.toContain('whiteboard');
|
|
177
|
+
});
|
|
167
178
|
});
|
|
@@ -101,7 +101,8 @@ describe('Acp manager — warm frame broadcasts + open replays commands', () =>
|
|
|
101
101
|
acp.onOpen(b.ws);
|
|
102
102
|
const replay = b.frames.find((f) => f.t === 'commands');
|
|
103
103
|
expect(replay).toBeDefined();
|
|
104
|
-
|
|
104
|
+
const replayCommands = replay?.commands as Array<{ name: string }>;
|
|
105
|
+
expect(replayCommands.length).toBe(2);
|
|
105
106
|
acp.onClose(b.ws);
|
|
106
107
|
} finally {
|
|
107
108
|
acp.onClose(a.ws);
|
|
@@ -11,7 +11,7 @@ import { describe, expect, test } from 'bun:test';
|
|
|
11
11
|
import { existsSync, readdirSync } from 'node:fs';
|
|
12
12
|
import { join } from 'node:path';
|
|
13
13
|
|
|
14
|
-
import { sniffImageType } from '../api.ts';
|
|
14
|
+
import { ASSET_MAX_BYTES, sniffImageType } from '../api.ts';
|
|
15
15
|
import { bootServer, killProc, makeSandbox, nextPort } from './_helpers.ts';
|
|
16
16
|
|
|
17
17
|
// ── Minimal valid magic-byte headers (padded so length checks pass). ──────────
|
|
@@ -107,9 +107,9 @@ describe('asset-api / POST /_api/asset (endpoint round-trip)', () => {
|
|
|
107
107
|
});
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
-
test('rejects an oversize body (>
|
|
110
|
+
test('rejects an oversize body (> ASSET_MAX_BYTES) with 413', async () => {
|
|
111
111
|
await withServer(async (port) => {
|
|
112
|
-
const big = new Uint8Array(
|
|
112
|
+
const big = new Uint8Array(ASSET_MAX_BYTES + 1);
|
|
113
113
|
big.set([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], 0); // valid PNG header
|
|
114
114
|
const res = await postAsset(port, big);
|
|
115
115
|
expect(res.status).toBe(413);
|
|
@@ -377,10 +377,13 @@ describe('canvas-edit / applyTextEdit', () => {
|
|
|
377
377
|
expect(() => applyTextEdit(CANVAS, src, id, 'x')).toThrow(CanvasEditError);
|
|
378
378
|
});
|
|
379
379
|
|
|
380
|
-
|
|
381
|
-
|
|
380
|
+
// unified-text-editing follow-up — a `{identifier}` bound to a const string
|
|
381
|
+
// is now traced back to that const and edited THERE (was a hard refusal).
|
|
382
|
+
test('edits an {identifier} child bound to a const string (traces to the const)', () => {
|
|
383
|
+
const src = 'const t = "x";\nfunction Demo() { return <h1>{t}</h1>; }';
|
|
382
384
|
const id = idsOf(src).h1 as string;
|
|
383
|
-
|
|
385
|
+
const out = applyTextEdit(CANVAS, src, id, 'New', { before: 'x' });
|
|
386
|
+
expect(out.source).toContain('const t = "New"');
|
|
384
387
|
});
|
|
385
388
|
|
|
386
389
|
test('refuses a self-closing / empty element (no text to edit)', () => {
|
|
@@ -412,10 +415,18 @@ describe('canvas-edit / applyTextEdit', () => {
|
|
|
412
415
|
expect(() => transpileCanvasSource(CANVAS, out.source)).not.toThrow();
|
|
413
416
|
});
|
|
414
417
|
|
|
415
|
-
test('still refuses a
|
|
416
|
-
|
|
418
|
+
test('still refuses a genuinely-computed expression child (routes to /design:edit)', () => {
|
|
419
|
+
// Call expression — no single source string to rewrite → hard refusal.
|
|
420
|
+
const src = 'const price = 9;\nfunction Demo() { return <h1>{price.toFixed(2)}</h1>; }';
|
|
417
421
|
const id = idsOf(src).h1 as string;
|
|
418
|
-
expect(() => applyTextEdit(CANVAS, src, id, 'x')).toThrow(CanvasEditError);
|
|
422
|
+
expect(() => applyTextEdit(CANVAS, src, id, 'x', { before: '9.00' })).toThrow(CanvasEditError);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test('refuses an {identifier} that does not resolve to a string const', () => {
|
|
426
|
+
// `count` is a number const — no string to trace to → refuse.
|
|
427
|
+
const src = 'const count = 3;\nfunction Demo() { return <h1>{count}</h1>; }';
|
|
428
|
+
const id = idsOf(src).h1 as string;
|
|
429
|
+
expect(() => applyTextEdit(CANVAS, src, id, 'x', { before: '3' })).toThrow(CanvasEditError);
|
|
419
430
|
});
|
|
420
431
|
});
|
|
421
432
|
|
|
@@ -162,6 +162,19 @@ describe('canvas-origin gate — A1/A2 traversal + privilege containment', () =>
|
|
|
162
162
|
// untrusted canvas origin must never read (or write) the user's pasted
|
|
163
163
|
// chat images; a GET here 403s at the gate, not 404 from the handler.
|
|
164
164
|
'/_api/acp/attachment',
|
|
165
|
+
// Phase 4 (feature-whiteboard-annotation-improvements) — the sticker
|
|
166
|
+
// catalogue + bundled sticker PNGs are MAIN-ORIGIN ONLY, same posture
|
|
167
|
+
// as /_api/assets above: the StickerPicker is shell UI, absent from
|
|
168
|
+
// CANVAS_SAFE_API + startCanvasServer's routes. A GET here 403s at
|
|
169
|
+
// the gate on both — the catalogue route and the static PNG serve.
|
|
170
|
+
'/_api/stickers',
|
|
171
|
+
'/_stickers/some-pack/some-sticker.png',
|
|
172
|
+
// feature-footage-analysis-director — the footage-analysis + EDL sidecar
|
|
173
|
+
// route is MAIN-ORIGIN ONLY (written by the analyst/director agents over
|
|
174
|
+
// loopback; absent from CANVAS_SAFE_API + startCanvasServer's routes). The
|
|
175
|
+
// untrusted canvas origin must never read/write the director's analysis —
|
|
176
|
+
// a GET here 403s at the gate, not 405 from a reached handler.
|
|
177
|
+
'/_api/footage',
|
|
165
178
|
'/package.json',
|
|
166
179
|
]) {
|
|
167
180
|
expect(await code(p)).toBe(403);
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { applyTextEdit } from '../canvas-edit.ts';
|
|
3
|
+
import { transpileCanvasSource } from '../canvas-pipeline.ts';
|
|
4
|
+
|
|
5
|
+
// unified-text-editing follow-up — editing text that comes from a `{variable}`
|
|
6
|
+
// or `{item.prop}` by tracing it back to its source string (a local const or a
|
|
7
|
+
// `.map()`ed array element). The safety contract: never rewrite the wrong item
|
|
8
|
+
// — occurrence picks the `.map()` slot, `before` verifies/rescues it, and an
|
|
9
|
+
// unresolvable expression still throws (routes to /design:edit).
|
|
10
|
+
|
|
11
|
+
const CANVAS = '/virtual/Cards.tsx';
|
|
12
|
+
const SRC = `const TOPICS = [
|
|
13
|
+
{ key: "a", body: "Alpha body text." },
|
|
14
|
+
{ key: "b", body: "Beta body text." },
|
|
15
|
+
{ key: "c", body: "Gamma body text." },
|
|
16
|
+
];
|
|
17
|
+
const TAGS = ["first", "second", "third"];
|
|
18
|
+
const HEADLINE = "The one headline";
|
|
19
|
+
export default function Cards() {
|
|
20
|
+
return (
|
|
21
|
+
<div>
|
|
22
|
+
<h1>{HEADLINE}</h1>
|
|
23
|
+
<section>
|
|
24
|
+
{TOPICS.map((t) => (
|
|
25
|
+
<article key={t.key}>
|
|
26
|
+
<p>{t.body}</p>
|
|
27
|
+
</article>
|
|
28
|
+
))}
|
|
29
|
+
</section>
|
|
30
|
+
<ul>
|
|
31
|
+
{TAGS.map((tag) => (
|
|
32
|
+
<li key={tag}>{tag}</li>
|
|
33
|
+
))}
|
|
34
|
+
</ul>
|
|
35
|
+
<p>Total: {TOPICS.length} topics</p>
|
|
36
|
+
<span>{price.toFixed(2)}</span>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
const price = 9;
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
function ids(withIds: string, tag: string, inner: string): string {
|
|
44
|
+
// Grab the data-cd-id of the <tag …>inner… element.
|
|
45
|
+
const re = new RegExp(`<${tag}\\s+data-cd-id="([^"]+)"[^>]*>\\s*\\{?${inner}`);
|
|
46
|
+
const m = withIds.match(re);
|
|
47
|
+
if (!m) throw new Error(`no ${tag} matching ${inner}`);
|
|
48
|
+
return m[1] as string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe('dynamic text edit — resolveDynamicTextSpan via applyTextEdit', () => {
|
|
52
|
+
const { withIds } = transpileCanvasSource(CANVAS, SRC);
|
|
53
|
+
|
|
54
|
+
test('{item.prop} in a .map — occurrence picks the array item', () => {
|
|
55
|
+
const pId = ids(withIds, 'p', 't\\.body');
|
|
56
|
+
// occurrence 1 → TOPICS[1].body, verified by `before`.
|
|
57
|
+
const { source } = applyTextEdit(CANVAS, withIds, pId, 'Beta EDITED', {
|
|
58
|
+
occurrence: 1,
|
|
59
|
+
before: 'Beta body text.',
|
|
60
|
+
});
|
|
61
|
+
expect(source).toContain('body: "Beta EDITED"');
|
|
62
|
+
expect(source).toContain('body: "Alpha body text."'); // sibling untouched
|
|
63
|
+
expect(source).toContain('body: "Gamma body text."');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('{item.prop} — index drift rescued by unique before-match', () => {
|
|
67
|
+
const pId = ids(withIds, 'p', 't\\.body');
|
|
68
|
+
// Wrong occurrence, but `before` uniquely identifies Gamma → rewrite it.
|
|
69
|
+
const { source } = applyTextEdit(CANVAS, withIds, pId, 'Gamma EDITED', {
|
|
70
|
+
occurrence: 0,
|
|
71
|
+
before: 'Gamma body text.',
|
|
72
|
+
});
|
|
73
|
+
expect(source).toContain('body: "Gamma EDITED"');
|
|
74
|
+
expect(source).toContain('body: "Alpha body text."');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('{item} in a .map over a string array', () => {
|
|
78
|
+
const liId = ids(withIds, 'li', 'tag');
|
|
79
|
+
const { source } = applyTextEdit(CANVAS, withIds, liId, 'SECOND', {
|
|
80
|
+
occurrence: 1,
|
|
81
|
+
before: 'second',
|
|
82
|
+
});
|
|
83
|
+
expect(source).toContain('"first", "SECOND", "third"');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('{localConst} — rewrites the const string', () => {
|
|
87
|
+
const h1Id = ids(withIds, 'h1', 'HEADLINE');
|
|
88
|
+
const { source } = applyTextEdit(CANVAS, withIds, h1Id, 'A new headline', {
|
|
89
|
+
before: 'The one headline',
|
|
90
|
+
});
|
|
91
|
+
expect(source).toContain('HEADLINE = "A new headline"');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('{prop.field} — component fed BEATS[k] resolves to BEATS[k].field', () => {
|
|
95
|
+
// A reusable component whose `beat` prop is fed `BEATS[0..2]` at each usage
|
|
96
|
+
// (the Remotion-style pattern). Editing the 2nd usage's caption must hit
|
|
97
|
+
// BEATS[1].caption via occurrence, not card 0 or 2.
|
|
98
|
+
const SRC2 = `const BEATS = [
|
|
99
|
+
{ n: "01", caption: "Beat one caption." },
|
|
100
|
+
{ n: "02", caption: "Beat two caption." },
|
|
101
|
+
{ n: "03", caption: "Beat three caption." },
|
|
102
|
+
];
|
|
103
|
+
function GuideBeat({ beat }: { beat: (typeof BEATS)[number] }) {
|
|
104
|
+
return <p>{beat.caption}</p>;
|
|
105
|
+
}
|
|
106
|
+
export default function Reel() {
|
|
107
|
+
return (
|
|
108
|
+
<div>
|
|
109
|
+
<GuideBeat beat={BEATS[0]} />
|
|
110
|
+
<GuideBeat beat={BEATS[1]} />
|
|
111
|
+
<GuideBeat beat={BEATS[2]} />
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}`;
|
|
115
|
+
const built = transpileCanvasSource(CANVAS, SRC2).withIds;
|
|
116
|
+
const pId = /<p\s+data-cd-id="([^"]+)"[^>]*>\s*\{beat\.caption\}/.exec(built)?.[1] as string;
|
|
117
|
+
const { source } = applyTextEdit(CANVAS, built, pId, 'Beat two EDITED', {
|
|
118
|
+
occurrence: 1,
|
|
119
|
+
before: 'Beat two caption.',
|
|
120
|
+
});
|
|
121
|
+
expect(source).toContain('caption: "Beat two EDITED"');
|
|
122
|
+
expect(source).toContain('caption: "Beat one caption."');
|
|
123
|
+
expect(source).toContain('caption: "Beat three caption."');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('genuinely computed text still refuses (routes to /design:edit)', () => {
|
|
127
|
+
const spanId = ids(withIds, 'span', 'price\\.toFixed');
|
|
128
|
+
expect(() =>
|
|
129
|
+
applyTextEdit(CANVAS, withIds, spanId, 'nope', { occurrence: 0, before: '9.00' })
|
|
130
|
+
).toThrow(/dynamic content/);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('mixed literal+expression (Total: {n} topics) refuses', () => {
|
|
134
|
+
// This <p> has text + expression + text → mixed, never offered.
|
|
135
|
+
const m = withIds.match(/<p[^>]*data-cd-id="([^"]+)"[^>]*>Total:/);
|
|
136
|
+
expect(m).toBeTruthy();
|
|
137
|
+
expect(() => applyTextEdit(CANVAS, withIds, (m as RegExpMatchArray)[1] as string, 'x')).toThrow(
|
|
138
|
+
/mixed or expression/
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('no occurrence + no before on an ambiguous .map refuses (never guesses)', () => {
|
|
143
|
+
const pId = ids(withIds, 'p', 't\\.body');
|
|
144
|
+
// Without occurrence or before there is no way to know which card → refuse.
|
|
145
|
+
expect(() => applyTextEdit(CANVAS, withIds, pId, 'x')).toThrow(/dynamic content/);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('stamp marks var-editable text', () => {
|
|
150
|
+
const { withIds } = transpileCanvasSource(CANVAS, SRC);
|
|
151
|
+
test('{item.prop}, {item}, {const} get data-cd-editable="var"', () => {
|
|
152
|
+
expect(withIds).toMatch(/<p[^>]*data-cd-editable="var"[^>]*>\s*\{t\.body\}/);
|
|
153
|
+
expect(withIds).toMatch(/<li[^>]*data-cd-editable="var"[^>]*>\s*\{tag\}/);
|
|
154
|
+
expect(withIds).toMatch(/<h1[^>]*data-cd-editable="var"[^>]*>\s*\{HEADLINE\}/);
|
|
155
|
+
});
|
|
156
|
+
test('computed + mixed text are NOT marked', () => {
|
|
157
|
+
const span = withIds.match(/<span[^>]*>\{price\.toFixed/);
|
|
158
|
+
expect((span as RegExpMatchArray)[0].includes('data-cd-editable')).toBe(false);
|
|
159
|
+
const p = withIds.match(/<p[^>]*>Total:/);
|
|
160
|
+
expect((p as RegExpMatchArray)[0].includes('data-cd-editable')).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -30,9 +30,32 @@ describe('createEditSourceCommand', () => {
|
|
|
30
30
|
id: 'cd-1',
|
|
31
31
|
key: 'color',
|
|
32
32
|
value: 'blue',
|
|
33
|
+
// do() writes `after` over `before`, so `from` = before; css has no occurrence.
|
|
34
|
+
from: 'red',
|
|
35
|
+
occurrence: undefined,
|
|
33
36
|
});
|
|
34
37
|
});
|
|
35
38
|
|
|
39
|
+
test('do()/undo() carry occurrence + from for a text edit (variable re-target)', async () => {
|
|
40
|
+
const applyFn = mock(() => {});
|
|
41
|
+
const textEdit: EditSourcePayload = {
|
|
42
|
+
op: 'text',
|
|
43
|
+
canvas: '.design/ui/Foo.tsx',
|
|
44
|
+
id: 'cd-9',
|
|
45
|
+
key: '',
|
|
46
|
+
before: 'Old',
|
|
47
|
+
after: 'New',
|
|
48
|
+
occurrence: 2,
|
|
49
|
+
};
|
|
50
|
+
const cmd = createEditSourceCommand({ payload: textEdit, applyFn });
|
|
51
|
+
await cmd.do();
|
|
52
|
+
// redo/do: write New over Old → from = Old.
|
|
53
|
+
expect(applyFn.mock.calls[0]?.[0]).toMatchObject({ value: 'New', from: 'Old', occurrence: 2 });
|
|
54
|
+
await cmd.undo();
|
|
55
|
+
// undo: write Old over New → from = New.
|
|
56
|
+
expect(applyFn.mock.calls[1]?.[0]).toMatchObject({ value: 'Old', from: 'New', occurrence: 2 });
|
|
57
|
+
});
|
|
58
|
+
|
|
36
59
|
test('undo() applies the BEFORE value', async () => {
|
|
37
60
|
const applyFn = mock(() => {});
|
|
38
61
|
const cmd = createEditSourceCommand({ payload: cssEdit, applyFn });
|