@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.
Files changed (236) hide show
  1. package/apps/studio/.ai/cache/_stats.json +7 -0
  2. package/apps/studio/acp/bootstrap-brief.ts +12 -2
  3. package/apps/studio/annotations-layer.tsx +1055 -278
  4. package/apps/studio/annotations-model.ts +58 -0
  5. package/apps/studio/api.ts +173 -22
  6. package/apps/studio/bin/_ingest-footage.mjs +386 -0
  7. package/apps/studio/bin/_probe-footage-playwright.mjs +269 -0
  8. package/apps/studio/bin/_video-playwright.mjs +141 -71
  9. package/apps/studio/bin/ingest-footage.sh +27 -0
  10. package/apps/studio/bin/photo-adjust.sh +163 -0
  11. package/apps/studio/bin/photo-bg-remove.sh +160 -0
  12. package/apps/studio/bin/probe-footage.sh +28 -0
  13. package/apps/studio/bin/read-annotations.mjs +125 -2
  14. package/apps/studio/canvas-edit.ts +459 -10
  15. package/apps/studio/canvas-lib.tsx +523 -5
  16. package/apps/studio/canvas-pipeline.ts +65 -7
  17. package/apps/studio/canvas-shell.tsx +156 -16
  18. package/apps/studio/client/app.jsx +451 -34
  19. package/apps/studio/client/export-center.jsx +35 -1
  20. package/apps/studio/client/github.js +4 -0
  21. package/apps/studio/client/panels/GitPanel.jsx +8 -0
  22. package/apps/studio/client/panels/RepoBranchSwitcher.jsx +133 -21
  23. package/apps/studio/client/panels/StickerPicker.jsx +160 -0
  24. package/apps/studio/client/panels/TimelinePanel.jsx +15 -2
  25. package/apps/studio/client/photo-knobs.jsx +437 -0
  26. package/apps/studio/client/styles/3-shell-maude.css +42 -0
  27. package/apps/studio/commands/edit-source-command.ts +31 -4
  28. package/apps/studio/context-menu.tsx +31 -6
  29. package/apps/studio/dist/client.bundle.js +5561 -19
  30. package/apps/studio/dist/comment-mount.js +2 -2
  31. package/apps/studio/dist/runtime/.min-sizes.json +1 -0
  32. package/apps/studio/dist/runtime/@imgly_background-removal.js +5543 -0
  33. package/apps/studio/dist/runtime/pixi-js.js +519 -519
  34. package/apps/studio/dist/styles.css +1 -1
  35. package/apps/studio/dom-selection.ts +25 -0
  36. package/apps/studio/exporters/jobs.ts +47 -5
  37. package/apps/studio/exporters/video.ts +24 -4
  38. package/apps/studio/footage/schema.test.ts +195 -0
  39. package/apps/studio/footage/schema.ts +484 -0
  40. package/apps/studio/footage-store.ts +235 -0
  41. package/apps/studio/fs-watch.ts +10 -0
  42. package/apps/studio/git/endpoints.ts +68 -7
  43. package/apps/studio/git/service.ts +196 -20
  44. package/apps/studio/github/service.ts +67 -0
  45. package/apps/studio/handoff.ts +3 -2
  46. package/apps/studio/http.ts +173 -14
  47. package/apps/studio/input-router.tsx +10 -0
  48. package/apps/studio/inspect.ts +53 -1
  49. package/apps/studio/paths.ts +10 -0
  50. package/apps/studio/photo/filters.ts +285 -0
  51. package/apps/studio/photo/pipeline.ts +607 -0
  52. package/apps/studio/photo/schema.ts +367 -0
  53. package/apps/studio/photo-store.ts +154 -0
  54. package/apps/studio/runtime-bundle.ts +29 -6
  55. package/apps/studio/server.ts +5 -1
  56. package/apps/studio/stickers/figjam-doodle/manifest.json +104 -0
  57. package/apps/studio/stickers/figjam-doodle/slice1.png +0 -0
  58. package/apps/studio/stickers/figjam-doodle/slice10.png +0 -0
  59. package/apps/studio/stickers/figjam-doodle/slice11.png +0 -0
  60. package/apps/studio/stickers/figjam-doodle/slice12.png +0 -0
  61. package/apps/studio/stickers/figjam-doodle/slice13.png +0 -0
  62. package/apps/studio/stickers/figjam-doodle/slice14.png +0 -0
  63. package/apps/studio/stickers/figjam-doodle/slice15.png +0 -0
  64. package/apps/studio/stickers/figjam-doodle/slice16.png +0 -0
  65. package/apps/studio/stickers/figjam-doodle/slice17.png +0 -0
  66. package/apps/studio/stickers/figjam-doodle/slice18.png +0 -0
  67. package/apps/studio/stickers/figjam-doodle/slice19.png +0 -0
  68. package/apps/studio/stickers/figjam-doodle/slice2.png +0 -0
  69. package/apps/studio/stickers/figjam-doodle/slice20.png +0 -0
  70. package/apps/studio/stickers/figjam-doodle/slice21.png +0 -0
  71. package/apps/studio/stickers/figjam-doodle/slice22.png +0 -0
  72. package/apps/studio/stickers/figjam-doodle/slice23.png +0 -0
  73. package/apps/studio/stickers/figjam-doodle/slice24.png +0 -0
  74. package/apps/studio/stickers/figjam-doodle/slice3.png +0 -0
  75. package/apps/studio/stickers/figjam-doodle/slice4.png +0 -0
  76. package/apps/studio/stickers/figjam-doodle/slice5.png +0 -0
  77. package/apps/studio/stickers/figjam-doodle/slice6.png +0 -0
  78. package/apps/studio/stickers/figjam-doodle/slice7.png +0 -0
  79. package/apps/studio/stickers/figjam-doodle/slice8.png +0 -0
  80. package/apps/studio/stickers/figjam-doodle/slice9.png +0 -0
  81. package/apps/studio/stickers/life-style/best.png +0 -0
  82. package/apps/studio/stickers/life-style/come-on.png +0 -0
  83. package/apps/studio/stickers/life-style/cut.png +0 -0
  84. package/apps/studio/stickers/life-style/detox.png +0 -0
  85. package/apps/studio/stickers/life-style/just.png +0 -0
  86. package/apps/studio/stickers/life-style/manifest.json +68 -0
  87. package/apps/studio/stickers/life-style/mirror.png +0 -0
  88. package/apps/studio/stickers/life-style/nice.png +0 -0
  89. package/apps/studio/stickers/life-style/objects.png +0 -0
  90. package/apps/studio/stickers/life-style/ok.png +0 -0
  91. package/apps/studio/stickers/life-style/queen.png +0 -0
  92. package/apps/studio/stickers/life-style/star.png +0 -0
  93. package/apps/studio/stickers/life-style/sun.png +0 -0
  94. package/apps/studio/stickers/life-style/water.png +0 -0
  95. package/apps/studio/stickers/life-style/yeah.png +0 -0
  96. package/apps/studio/stickers/life-style/you-can.png +0 -0
  97. package/apps/studio/stickers/opposing-thoughts/check-the-deets.png +0 -0
  98. package/apps/studio/stickers/opposing-thoughts/cut-it.png +0 -0
  99. package/apps/studio/stickers/opposing-thoughts/dope.png +0 -0
  100. package/apps/studio/stickers/opposing-thoughts/fresh.png +0 -0
  101. package/apps/studio/stickers/opposing-thoughts/hot.png +0 -0
  102. package/apps/studio/stickers/opposing-thoughts/idea.png +0 -0
  103. package/apps/studio/stickers/opposing-thoughts/keep-exploring.png +0 -0
  104. package/apps/studio/stickers/opposing-thoughts/killed-it.png +0 -0
  105. package/apps/studio/stickers/opposing-thoughts/love-it.png +0 -0
  106. package/apps/studio/stickers/opposing-thoughts/manifest.json +88 -0
  107. package/apps/studio/stickers/opposing-thoughts/not-sure.png +0 -0
  108. package/apps/studio/stickers/opposing-thoughts/ok.png +0 -0
  109. package/apps/studio/stickers/opposing-thoughts/pure-gold.png +0 -0
  110. package/apps/studio/stickers/opposing-thoughts/save-for-later.png +0 -0
  111. package/apps/studio/stickers/opposing-thoughts/steal-this.png +0 -0
  112. package/apps/studio/stickers/opposing-thoughts/take-a-peek.png +0 -0
  113. package/apps/studio/stickers/opposing-thoughts/this-or-that.png +0 -0
  114. package/apps/studio/stickers/opposing-thoughts/thoughts.png +0 -0
  115. package/apps/studio/stickers/opposing-thoughts/vibes.png +0 -0
  116. package/apps/studio/stickers/opposing-thoughts/winner.png +0 -0
  117. package/apps/studio/stickers/opposing-thoughts/wip.png +0 -0
  118. package/apps/studio/stickers/project-status/group-100.png +0 -0
  119. package/apps/studio/stickers/project-status/group-101.png +0 -0
  120. package/apps/studio/stickers/project-status/group-102.png +0 -0
  121. package/apps/studio/stickers/project-status/group-103.png +0 -0
  122. package/apps/studio/stickers/project-status/group-104.png +0 -0
  123. package/apps/studio/stickers/project-status/group-105.png +0 -0
  124. package/apps/studio/stickers/project-status/group-106.png +0 -0
  125. package/apps/studio/stickers/project-status/group-107.png +0 -0
  126. package/apps/studio/stickers/project-status/group-108.png +0 -0
  127. package/apps/studio/stickers/project-status/group-109.png +0 -0
  128. package/apps/studio/stickers/project-status/group-110.png +0 -0
  129. package/apps/studio/stickers/project-status/group-111.png +0 -0
  130. package/apps/studio/stickers/project-status/group-112.png +0 -0
  131. package/apps/studio/stickers/project-status/group-113.png +0 -0
  132. package/apps/studio/stickers/project-status/group-114.png +0 -0
  133. package/apps/studio/stickers/project-status/group-115.png +0 -0
  134. package/apps/studio/stickers/project-status/group-117.png +0 -0
  135. package/apps/studio/stickers/project-status/group-118.png +0 -0
  136. package/apps/studio/stickers/project-status/group-119.png +0 -0
  137. package/apps/studio/stickers/project-status/group-120.png +0 -0
  138. package/apps/studio/stickers/project-status/group-121.png +0 -0
  139. package/apps/studio/stickers/project-status/group-122.png +0 -0
  140. package/apps/studio/stickers/project-status/group-41.png +0 -0
  141. package/apps/studio/stickers/project-status/group-42.png +0 -0
  142. package/apps/studio/stickers/project-status/group-43.png +0 -0
  143. package/apps/studio/stickers/project-status/group-44.png +0 -0
  144. package/apps/studio/stickers/project-status/group-45.png +0 -0
  145. package/apps/studio/stickers/project-status/group-46.png +0 -0
  146. package/apps/studio/stickers/project-status/group-47.png +0 -0
  147. package/apps/studio/stickers/project-status/group-48.png +0 -0
  148. package/apps/studio/stickers/project-status/group-49.png +0 -0
  149. package/apps/studio/stickers/project-status/group-50.png +0 -0
  150. package/apps/studio/stickers/project-status/group-51.png +0 -0
  151. package/apps/studio/stickers/project-status/group-52.png +0 -0
  152. package/apps/studio/stickers/project-status/group-53.png +0 -0
  153. package/apps/studio/stickers/project-status/group-54.png +0 -0
  154. package/apps/studio/stickers/project-status/group-55.png +0 -0
  155. package/apps/studio/stickers/project-status/group-56.png +0 -0
  156. package/apps/studio/stickers/project-status/group-57.png +0 -0
  157. package/apps/studio/stickers/project-status/group-58.png +0 -0
  158. package/apps/studio/stickers/project-status/group-59.png +0 -0
  159. package/apps/studio/stickers/project-status/group-60.png +0 -0
  160. package/apps/studio/stickers/project-status/group-61.png +0 -0
  161. package/apps/studio/stickers/project-status/group-62.png +0 -0
  162. package/apps/studio/stickers/project-status/group-63.png +0 -0
  163. package/apps/studio/stickers/project-status/group-64.png +0 -0
  164. package/apps/studio/stickers/project-status/group-65.png +0 -0
  165. package/apps/studio/stickers/project-status/group-66.png +0 -0
  166. package/apps/studio/stickers/project-status/group-67.png +0 -0
  167. package/apps/studio/stickers/project-status/group-68.png +0 -0
  168. package/apps/studio/stickers/project-status/group-69.png +0 -0
  169. package/apps/studio/stickers/project-status/group-70.png +0 -0
  170. package/apps/studio/stickers/project-status/group-71.png +0 -0
  171. package/apps/studio/stickers/project-status/group-72.png +0 -0
  172. package/apps/studio/stickers/project-status/group-73.png +0 -0
  173. package/apps/studio/stickers/project-status/group-74.png +0 -0
  174. package/apps/studio/stickers/project-status/group-75.png +0 -0
  175. package/apps/studio/stickers/project-status/group-76.png +0 -0
  176. package/apps/studio/stickers/project-status/group-77.png +0 -0
  177. package/apps/studio/stickers/project-status/group-78.png +0 -0
  178. package/apps/studio/stickers/project-status/group-79.png +0 -0
  179. package/apps/studio/stickers/project-status/group-80.png +0 -0
  180. package/apps/studio/stickers/project-status/group-81.png +0 -0
  181. package/apps/studio/stickers/project-status/group-82.png +0 -0
  182. package/apps/studio/stickers/project-status/group-83.png +0 -0
  183. package/apps/studio/stickers/project-status/group-84.png +0 -0
  184. package/apps/studio/stickers/project-status/group-85.png +0 -0
  185. package/apps/studio/stickers/project-status/group-86.png +0 -0
  186. package/apps/studio/stickers/project-status/group-87.png +0 -0
  187. package/apps/studio/stickers/project-status/group-88.png +0 -0
  188. package/apps/studio/stickers/project-status/group-89.png +0 -0
  189. package/apps/studio/stickers/project-status/group-90.png +0 -0
  190. package/apps/studio/stickers/project-status/group-91.png +0 -0
  191. package/apps/studio/stickers/project-status/group-92.png +0 -0
  192. package/apps/studio/stickers/project-status/group-93.png +0 -0
  193. package/apps/studio/stickers/project-status/group-94.png +0 -0
  194. package/apps/studio/stickers/project-status/group-96.png +0 -0
  195. package/apps/studio/stickers/project-status/group-97.png +0 -0
  196. package/apps/studio/stickers/project-status/group-98.png +0 -0
  197. package/apps/studio/stickers/project-status/group-99.png +0 -0
  198. package/apps/studio/stickers/project-status/manifest.json +328 -0
  199. package/apps/studio/test/acp-bootstrap-brief.test.ts +11 -0
  200. package/apps/studio/test/acp-commands.test.ts +2 -1
  201. package/apps/studio/test/asset-api.test.ts +3 -3
  202. package/apps/studio/test/canvas-edit.test.ts +17 -6
  203. package/apps/studio/test/canvas-origin-gate.test.ts +13 -0
  204. package/apps/studio/test/dynamic-text-edit.test.ts +162 -0
  205. package/apps/studio/test/edit-source-command.test.ts +23 -0
  206. package/apps/studio/test/element-structural-edit.test.ts +46 -0
  207. package/apps/studio/test/footage-store.test.ts +100 -0
  208. package/apps/studio/test/git-branches.test.ts +97 -0
  209. package/apps/studio/test/github-api.test.ts +56 -1
  210. package/apps/studio/test/input-router.test.ts +33 -0
  211. package/apps/studio/test/inspect-script-syntax.test.ts +53 -0
  212. package/apps/studio/test/photo-canvas-bundle.test.ts +61 -0
  213. package/apps/studio/test/photo-edit-api.test.ts +201 -0
  214. package/apps/studio/test/photo-filters.test.ts +149 -0
  215. package/apps/studio/test/photo-pipeline.test.ts +106 -0
  216. package/apps/studio/test/photo-taxonomy.test.ts +96 -0
  217. package/apps/studio/test/read-annotations.test.ts +164 -0
  218. package/apps/studio/test/shell-importmap.test.ts +49 -0
  219. package/apps/studio/test/text-editability-stamp.test.ts +131 -0
  220. package/apps/studio/test/timeline-parse.test.ts +57 -0
  221. package/apps/studio/test/tool-palette-insert-anchor.test.ts +5 -3
  222. package/apps/studio/test/undo-stack.test.ts +33 -0
  223. package/apps/studio/test/video-asset.test.ts +5 -5
  224. package/apps/studio/test/video-render-bridge.test.ts +23 -1
  225. package/apps/studio/text-caret.ts +239 -0
  226. package/apps/studio/tool-palette.tsx +43 -20
  227. package/apps/studio/undo-stack.ts +94 -2
  228. package/apps/studio/use-annotation-resize.tsx +4 -4
  229. package/apps/studio/use-canvas-media-drop.tsx +38 -1
  230. package/apps/studio/use-selection-set.tsx +21 -0
  231. package/apps/studio/video-comp.tsx +48 -7
  232. package/apps/studio/whats-new.json +53 -0
  233. package/cli/commands/design.mjs +34 -3
  234. package/cli/lib/gitignore-block.mjs +1 -0
  235. package/package.json +10 -10
  236. 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
- expect((replay?.commands as Array<{ name: string }>).length).toBe(2);
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 (> 10 MB) with 413', async () => {
110
+ test('rejects an oversize body (> ASSET_MAX_BYTES) with 413', async () => {
111
111
  await withServer(async (port) => {
112
- const big = new Uint8Array(10 * 1024 * 1024 + 1);
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
- test('refuses an element whose only child is a JSX expression', () => {
381
- const src = 'function Demo() { const t = "x"; return <h1>{t}</h1>; }';
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
- expect(() => applyTextEdit(CANVAS, src, id, 'x')).toThrow(CanvasEditError);
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 dynamic {identifier} child (routes to /design:edit)', () => {
416
- const src = 'function Demo() { const title = "x"; return <h1>{title}</h1>; }';
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 });