@1agh/maude 0.29.0 → 0.31.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 (162) hide show
  1. package/README.md +6 -6
  2. package/apps/studio/acp/bridge.ts +285 -0
  3. package/apps/studio/acp/env.ts +48 -0
  4. package/apps/studio/acp/index.ts +132 -0
  5. package/apps/studio/acp/probe.ts +112 -0
  6. package/apps/studio/acp/transcript.ts +149 -0
  7. package/apps/studio/ai-banner.tsx +0 -1
  8. package/apps/studio/annotations-align.ts +149 -0
  9. package/apps/studio/annotations-bindings.ts +197 -0
  10. package/apps/studio/annotations-context-toolbar.tsx +436 -186
  11. package/apps/studio/annotations-groups.ts +270 -0
  12. package/apps/studio/annotations-layer.tsx +1714 -1661
  13. package/apps/studio/annotations-model.ts +2077 -0
  14. package/apps/studio/annotations-snap.ts +125 -0
  15. package/apps/studio/api.ts +433 -182
  16. package/apps/studio/bin/_png-playwright.mjs +1 -1
  17. package/apps/studio/bin/annotate.mjs +732 -0
  18. package/apps/studio/bin/annotate.sh +17 -0
  19. package/apps/studio/bin/chat-open.sh +44 -0
  20. package/apps/studio/bin/read-annotations.mjs +152 -17
  21. package/apps/studio/build.ts +1 -1
  22. package/apps/studio/canvas-arrowheads.ts +78 -9
  23. package/apps/studio/canvas-cursors.ts +2 -0
  24. package/apps/studio/canvas-edit.ts +257 -7
  25. package/apps/studio/canvas-icons.tsx +105 -0
  26. package/apps/studio/canvas-lib.tsx +112 -19
  27. package/apps/studio/canvas-list-watch.ts +177 -0
  28. package/apps/studio/canvas-shell.tsx +326 -9
  29. package/apps/studio/client/app.jsx +3579 -250
  30. package/apps/studio/client/canvas-url.js +5 -0
  31. package/apps/studio/client/github.js +99 -0
  32. package/apps/studio/client/index.html +1 -1
  33. package/apps/studio/client/panels/ChatPanel.jsx +770 -0
  34. package/apps/studio/client/panels/CollabModelInfographic.jsx +71 -0
  35. package/apps/studio/client/panels/CreateProject.jsx +334 -0
  36. package/apps/studio/client/panels/DiffView.jsx +590 -0
  37. package/apps/studio/client/panels/GitPanel.jsx +767 -0
  38. package/apps/studio/client/panels/IdentityBar.jsx +294 -0
  39. package/apps/studio/client/panels/OnboardingWizard.jsx +563 -0
  40. package/apps/studio/client/panels/RepoBranchSwitcher.jsx +349 -0
  41. package/apps/studio/client/panels/acp-runtime.js +286 -0
  42. package/apps/studio/client/panels/chat-markdown.jsx +138 -0
  43. package/apps/studio/client/panels/git-grouping.js +86 -0
  44. package/apps/studio/client/styles/0-reset.css +4 -0
  45. package/apps/studio/client/styles/3-shell-maude.css +1326 -14
  46. package/apps/studio/client/styles/4-components.css +39 -0
  47. package/apps/studio/client/styles/5-maude-overrides.css +41 -2
  48. package/apps/studio/client/styles/6-acp-chat.css +771 -0
  49. package/apps/studio/client/styles/_index.css +2 -0
  50. package/apps/studio/client/tour/collab-tour.js +61 -0
  51. package/apps/studio/client/tour/overlay.jsx +116 -18
  52. package/apps/studio/client/tour/usage-tour.js +23 -10
  53. package/apps/studio/client/whats-new.jsx +25 -10
  54. package/apps/studio/collab/registry.ts +13 -0
  55. package/apps/studio/collab/room.ts +36 -0
  56. package/apps/studio/commands/annotation-strokes-command.ts +1 -1
  57. package/apps/studio/commands/move-artboards-command.ts +1 -1
  58. package/apps/studio/comments-overlay.tsx +7 -5
  59. package/apps/studio/context-menu.tsx +4 -3
  60. package/apps/studio/contextual-toolbar.tsx +14 -0
  61. package/apps/studio/cursors-overlay.tsx +37 -21
  62. package/apps/studio/dist/client.bundle.js +31795 -1829
  63. package/apps/studio/dist/comment-mount.js +97 -9
  64. package/apps/studio/dist/styles.css +7938 -1599
  65. package/apps/studio/dom-selection.ts +115 -0
  66. package/apps/studio/export-dialog.tsx +3 -3
  67. package/apps/studio/git/endpoints.ts +338 -0
  68. package/apps/studio/git/service.ts +1334 -0
  69. package/apps/studio/git/watch.ts +97 -0
  70. package/apps/studio/github/endpoints.ts +358 -0
  71. package/apps/studio/github/service.ts +231 -0
  72. package/apps/studio/github/token.ts +53 -0
  73. package/apps/studio/history.ts +20 -3
  74. package/apps/studio/hmr-broadcast.ts +9 -2
  75. package/apps/studio/http.ts +508 -6
  76. package/apps/studio/input-router.tsx +7 -0
  77. package/apps/studio/inspect.ts +37 -8
  78. package/apps/studio/participants-chrome.tsx +70 -16
  79. package/apps/studio/paths.ts +12 -0
  80. package/apps/studio/scaffold-design.ts +57 -0
  81. package/apps/studio/server.ts +70 -4
  82. package/apps/studio/sync/agent.ts +311 -57
  83. package/apps/studio/sync/codec.ts +69 -0
  84. package/apps/studio/sync/cold-start.ts +198 -0
  85. package/apps/studio/sync/connection-state.ts +58 -2
  86. package/apps/studio/sync/hub-link.ts +137 -0
  87. package/apps/studio/sync/index.ts +563 -221
  88. package/apps/studio/sync/journal.ts +190 -0
  89. package/apps/studio/sync/migrate-seed.ts +202 -44
  90. package/apps/studio/sync/projection.ts +22 -3
  91. package/apps/studio/sync/status.ts +15 -4
  92. package/apps/studio/test/acp-bridge.test.ts +127 -0
  93. package/apps/studio/test/acp-env.test.ts +65 -0
  94. package/apps/studio/test/acp-origin-gate.test.ts +78 -0
  95. package/apps/studio/test/acp-transcript.test.ts +112 -0
  96. package/apps/studio/test/activity.test.ts +1 -7
  97. package/apps/studio/test/annotate-write.test.ts +184 -0
  98. package/apps/studio/test/annotations-align.test.ts +88 -0
  99. package/apps/studio/test/annotations-bindings.test.ts +124 -0
  100. package/apps/studio/test/annotations-groups.test.ts +231 -0
  101. package/apps/studio/test/annotations-snap.test.ts +79 -0
  102. package/apps/studio/test/canvas-create-api.test.ts +72 -0
  103. package/apps/studio/test/canvas-edit.test.ts +181 -1
  104. package/apps/studio/test/canvas-list-watch.test.ts +322 -0
  105. package/apps/studio/test/canvas-meta-api.test.ts +161 -27
  106. package/apps/studio/test/canvas-origin-gate.test.ts +43 -0
  107. package/apps/studio/test/chat-markdown.test.tsx +58 -0
  108. package/apps/studio/test/collab-bridge.test.ts +0 -2
  109. package/apps/studio/test/collab-room.test.ts +2 -7
  110. package/apps/studio/test/collab-session-survival.test.tsx +176 -0
  111. package/apps/studio/test/csp-canvas-shell.test.ts +5 -0
  112. package/apps/studio/test/csrf-write-guard.test.ts +78 -0
  113. package/apps/studio/test/editing-presence.test.ts +103 -0
  114. package/apps/studio/test/figjam-v3-model.test.ts +342 -0
  115. package/apps/studio/test/fixtures/figjam-v3-groups-bindings.svg +1 -0
  116. package/apps/studio/test/fixtures/mock-acp-agent.mjs +45 -0
  117. package/apps/studio/test/git-api.test.ts +0 -0
  118. package/apps/studio/test/git-branches.test.ts +106 -0
  119. package/apps/studio/test/git-grouping.test.ts +106 -0
  120. package/apps/studio/test/git-watch.test.ts +97 -0
  121. package/apps/studio/test/github-api.test.ts +465 -0
  122. package/apps/studio/test/hub-link.test.ts +69 -0
  123. package/apps/studio/test/participants-chrome.test.ts +36 -1
  124. package/apps/studio/test/shared-doc-migrate.test.ts +224 -14
  125. package/apps/studio/test/sync-agent.test.ts +272 -2
  126. package/apps/studio/test/sync-codec.test.ts +65 -0
  127. package/apps/studio/test/sync-cold-seed-dedup.test.ts +187 -0
  128. package/apps/studio/test/sync-cold-start.test.ts +304 -0
  129. package/apps/studio/test/sync-connection-state.test.ts +68 -0
  130. package/apps/studio/test/sync-hardening.test.ts +0 -8
  131. package/apps/studio/test/sync-hubs-config.test.ts +0 -1
  132. package/apps/studio/test/sync-incident-replay.test.ts +211 -0
  133. package/apps/studio/test/sync-journal.test.ts +176 -0
  134. package/apps/studio/test/sync-runtime.test.ts +360 -5
  135. package/apps/studio/test/sync-status.test.ts +66 -0
  136. package/apps/studio/test/tour-overlay.test.tsx +18 -0
  137. package/apps/studio/test/use-annotation-resize.test.ts +159 -0
  138. package/apps/studio/test/use-artboard-drag.test.ts +0 -1
  139. package/apps/studio/test/use-collab.test.ts +0 -0
  140. package/apps/studio/test/use-tool-mode.test.tsx +5 -2
  141. package/apps/studio/tool-palette.tsx +30 -11
  142. package/apps/studio/undo-hud.tsx +0 -1
  143. package/apps/studio/use-agent-presence.tsx +4 -2
  144. package/apps/studio/use-annotation-resize.tsx +484 -54
  145. package/apps/studio/use-annotation-selection.tsx +16 -4
  146. package/apps/studio/use-canvas-activity.tsx +2 -2
  147. package/apps/studio/use-chrome-visibility.tsx +66 -0
  148. package/apps/studio/use-collab.tsx +434 -202
  149. package/apps/studio/use-selection-set.tsx +9 -0
  150. package/apps/studio/use-tool-mode.tsx +5 -1
  151. package/apps/studio/whats-new.json +208 -10
  152. package/apps/studio/whats-new.schema.json +18 -2
  153. package/apps/studio/ws.ts +44 -1
  154. package/cli/commands/design-link.test.mjs +84 -0
  155. package/cli/commands/design.mjs +3 -1
  156. package/cli/commands/design.test.mjs +5 -1
  157. package/cli/lib/design-link.mjs +51 -1
  158. package/cli/lib/gitignore-block.mjs +16 -3
  159. package/cli/lib/gitignore-block.test.mjs +13 -1
  160. package/package.json +11 -9
  161. package/plugins/design/dependencies.json +17 -0
  162. package/plugins/design/templates/_shell.html +58 -12
@@ -0,0 +1,270 @@
1
+ /**
2
+ * @file annotations-groups.ts — FigJam v3 grouping + duplicate + z-order
3
+ * @scope apps/studio/annotations-groups.ts
4
+ * @purpose Pure helpers over the Stroke model (no React, no DOM) for the
5
+ * group/ungroup, duplicate (Cmd+D / Alt+drag / paste), and z-order
6
+ * manipulations. Group model = Excalidraw-style flat tag-array:
7
+ * every stroke carries `groupIds` ordered DEEPEST → SHALLOWEST and
8
+ * the OUTERMOST group id is the LAST element. There is no group
9
+ * node — selection logic expands a clicked stroke to everything
10
+ * sharing its outermost group id. tldraw's lifecycle rules are
11
+ * copied as `normalizeGroups` (empty group self-deletes,
12
+ * single-member group auto-dissolves).
13
+ */
14
+
15
+ import { gid, rid, type Stroke, translateOne } from './annotations-model.ts';
16
+
17
+ /** The stroke's outermost (shallowest) group id, or null when ungrouped. */
18
+ export function outermostGroupOf(s: Stroke): string | null {
19
+ const ids = s.groupIds;
20
+ if (!ids || ids.length === 0) return null;
21
+ return ids[ids.length - 1] ?? null;
22
+ }
23
+
24
+ /** Shallow-copy a stroke with its `groupIds` key dropped entirely (so an
25
+ * ungrouped stroke round-trips byte-identically — no `groupIds: []`). */
26
+ function stripGroupIds(s: Stroke): Stroke {
27
+ const { groupIds: _drop, ...rest } = s;
28
+ return rest as Stroke;
29
+ }
30
+
31
+ /**
32
+ * Selection-expansion (FigJam: clicking a group member selects the whole
33
+ * group). Expands every id whose stroke belongs to a group to ALL strokes
34
+ * sharing that outermost group id. Result is in document (z) order, deduped.
35
+ */
36
+ export function expandIdsToGroups(ids: readonly string[], strokes: readonly Stroke[]): string[] {
37
+ const want = new Set(ids);
38
+ const groups = new Set<string>();
39
+ for (const s of strokes) {
40
+ if (!want.has(s.id)) continue;
41
+ const g = outermostGroupOf(s);
42
+ if (g) groups.add(g);
43
+ }
44
+ const out: string[] = [];
45
+ for (const s of strokes) {
46
+ const g = outermostGroupOf(s);
47
+ if (want.has(s.id) || (g != null && groups.has(g))) out.push(s.id);
48
+ }
49
+ // Preserve ids that don't resolve to a live stroke (defensive — a stale
50
+ // selection entry must not silently disappear from the caller's set).
51
+ for (const id of ids) if (!out.includes(id)) out.push(id);
52
+ return out;
53
+ }
54
+
55
+ export interface GroupResult {
56
+ strokes: Stroke[];
57
+ groupId: string;
58
+ /** Document-order ids of every member of the new group. */
59
+ memberIds: string[];
60
+ }
61
+
62
+ /**
63
+ * Group the (expanded) selection under a fresh outermost group id. Members are
64
+ * reordered CONTIGUOUS — the block lands where the topmost member sat, so the
65
+ * group's z-position matches FigJam (group adopts the front-most member's
66
+ * layer; relative order inside is preserved). Returns null when fewer than two
67
+ * members resolve (nothing to group).
68
+ */
69
+ export function groupStrokes(
70
+ strokes: readonly Stroke[],
71
+ ids: readonly string[],
72
+ newId: string = gid()
73
+ ): GroupResult | null {
74
+ const memberIds = new Set(
75
+ expandIdsToGroups(ids, strokes).filter((id) => strokes.some((s) => s.id === id))
76
+ );
77
+ if (memberIds.size < 2) return null;
78
+ const members: Stroke[] = [];
79
+ const rest: Stroke[] = [];
80
+ let lastMemberIdx = -1;
81
+ strokes.forEach((s, i) => {
82
+ if (memberIds.has(s.id)) {
83
+ members.push({ ...s, groupIds: [...(s.groupIds ?? []), newId] });
84
+ lastMemberIdx = i;
85
+ } else {
86
+ rest.push(s);
87
+ }
88
+ });
89
+ // Insert the contiguous block after every non-member that sat below the
90
+ // topmost member — the group keeps the front-most member's z position.
91
+ let insertAt = 0;
92
+ for (let i = 0; i < lastMemberIdx; i++) {
93
+ const s = strokes[i];
94
+ if (s && !memberIds.has(s.id)) insertAt++;
95
+ }
96
+ const out = [...rest.slice(0, insertAt), ...members, ...rest.slice(insertAt)];
97
+ return { strokes: out, groupId: newId, memberIds: members.map((m) => m.id) };
98
+ }
99
+
100
+ /**
101
+ * Dissolve the outermost group of every selected stroke (Cmd+Shift+G). Nested
102
+ * groups surface one level per invocation — FigJam/tldraw semantics.
103
+ */
104
+ export function ungroupStrokes(strokes: readonly Stroke[], ids: readonly string[]): Stroke[] {
105
+ const sel = new Set(ids);
106
+ const dissolve = new Set<string>();
107
+ for (const s of strokes) {
108
+ if (!sel.has(s.id)) continue;
109
+ const g = outermostGroupOf(s);
110
+ if (g) dissolve.add(g);
111
+ }
112
+ if (dissolve.size === 0) return [...strokes];
113
+ return strokes.map((s) => {
114
+ const g = outermostGroupOf(s);
115
+ if (!g || !dissolve.has(g)) return s;
116
+ const remaining = (s.groupIds ?? []).slice(0, -1);
117
+ return remaining.length ? { ...s, groupIds: remaining } : stripGroupIds(s);
118
+ });
119
+ }
120
+
121
+ /**
122
+ * tldraw lifecycle rules — drop every group id with fewer than two members
123
+ * (covers both the empty group after a delete and the singleton left behind by
124
+ * a partial erase). Run after any deletion. Returns the input array when
125
+ * nothing changed (referential no-op).
126
+ */
127
+ export function normalizeGroups(strokes: readonly Stroke[]): Stroke[] {
128
+ const counts = new Map<string, number>();
129
+ for (const s of strokes) {
130
+ for (const g of s.groupIds ?? []) counts.set(g, (counts.get(g) ?? 0) + 1);
131
+ }
132
+ const dead = new Set<string>();
133
+ for (const [g, n] of counts) if (n < 2) dead.add(g);
134
+ if (dead.size === 0) return strokes as Stroke[];
135
+ return strokes.map((s) => {
136
+ if (!s.groupIds?.some((g) => dead.has(g))) return s;
137
+ const keep = s.groupIds.filter((g) => !dead.has(g));
138
+ return keep.length ? { ...s, groupIds: keep } : stripGroupIds(s);
139
+ });
140
+ }
141
+
142
+ export interface DuplicateResult {
143
+ strokes: Stroke[];
144
+ /** Ids of the selectable clones (anchored-text clones excluded — they're
145
+ * selected through their host, FigJam semantics). */
146
+ newIds: string[];
147
+ }
148
+
149
+ /**
150
+ * Clone the (expanded) selection with fresh ids, remapping every internal
151
+ * reference: `groupIds` (each old group → one fresh id, so the copy is its own
152
+ * group), anchored-text `anchorId`, and arrow binds between co-duplicated
153
+ * strokes. A bind to a NON-duplicated host is kept pointing at the original
154
+ * (Excalidraw/FigJam: duplicating a bound arrow keeps the attachment). Clones
155
+ * append at the end of the array (top of z) offset by (dx, dy).
156
+ */
157
+ export function duplicateStrokes(
158
+ strokes: readonly Stroke[],
159
+ ids: readonly string[],
160
+ dx: number,
161
+ dy: number,
162
+ makeId: () => string = rid,
163
+ makeGroupId: () => string = gid
164
+ ): DuplicateResult {
165
+ const dupSet = new Set(expandIdsToGroups(ids, strokes));
166
+ // Anchored text travels with its host even though it's never independently
167
+ // selected — clone it alongside a duplicated host.
168
+ for (const s of strokes) {
169
+ if (s.tool === 'text' && s.anchorId && dupSet.has(s.anchorId)) dupSet.add(s.id);
170
+ }
171
+ const idMap = new Map<string, string>();
172
+ const groupMap = new Map<string, string>();
173
+ const clones: Stroke[] = [];
174
+ for (const s of strokes) {
175
+ if (!dupSet.has(s.id)) continue;
176
+ const nid = makeId();
177
+ idMap.set(s.id, nid);
178
+ const moved = translateOne(structuredClone(s) as Stroke, dx, dy);
179
+ const clone: Stroke = { ...moved, id: nid };
180
+ if (clone.groupIds) {
181
+ clone.groupIds = clone.groupIds.map((g) => {
182
+ let mapped = groupMap.get(g);
183
+ if (!mapped) {
184
+ mapped = makeGroupId();
185
+ groupMap.set(g, mapped);
186
+ }
187
+ return mapped;
188
+ });
189
+ }
190
+ clones.push(clone);
191
+ }
192
+ for (const c of clones) {
193
+ if (c.tool === 'text' && c.anchorId) {
194
+ const mapped = idMap.get(c.anchorId);
195
+ if (mapped) c.anchorId = mapped;
196
+ }
197
+ if (c.tool === 'arrow') {
198
+ if (c.startBind) {
199
+ const mapped = idMap.get(c.startBind.hostId);
200
+ if (mapped) c.startBind = { ...c.startBind, hostId: mapped };
201
+ }
202
+ if (c.endBind) {
203
+ const mapped = idMap.get(c.endBind.hostId);
204
+ if (mapped) c.endBind = { ...c.endBind, hostId: mapped };
205
+ }
206
+ }
207
+ }
208
+ return {
209
+ strokes: [...strokes, ...clones],
210
+ newIds: clones.filter((c) => !(c.tool === 'text' && c.anchorId)).map((c) => c.id),
211
+ };
212
+ }
213
+
214
+ export type ZOrderOp = 'front' | 'back' | 'forward' | 'backward';
215
+
216
+ /**
217
+ * Z-order = array order (render order; no fractional index needed under the
218
+ * whole-SVG LWW sync). Units move together: a maximal run of strokes sharing
219
+ * one outermost group id is one unit, an ungrouped stroke is its own unit —
220
+ * so `]` on a group hops the whole NEIGHBOURING group, never interleaves.
221
+ */
222
+ export function reorderStrokes(
223
+ strokes: readonly Stroke[],
224
+ ids: readonly string[],
225
+ op: ZOrderOp
226
+ ): Stroke[] {
227
+ const sel = new Set(expandIdsToGroups(ids, strokes));
228
+ if (sel.size === 0) return [...strokes];
229
+ const units: Stroke[][] = [];
230
+ for (const s of strokes) {
231
+ const g = outermostGroupOf(s);
232
+ const last = units[units.length - 1];
233
+ const lastFirst = last?.[0];
234
+ if (last && lastFirst && g != null && outermostGroupOf(lastFirst) === g) last.push(s);
235
+ else units.push([s]);
236
+ }
237
+ const isSel = (u: Stroke[]): boolean => u.some((s) => sel.has(s.id));
238
+ if (op === 'front') {
239
+ const rest = units.filter((u) => !isSel(u));
240
+ const selUnits = units.filter(isSel);
241
+ return [...rest, ...selUnits].flat();
242
+ }
243
+ if (op === 'back') {
244
+ const rest = units.filter((u) => !isSel(u));
245
+ const selUnits = units.filter(isSel);
246
+ return [...selUnits, ...rest].flat();
247
+ }
248
+ const arr = [...units];
249
+ if (op === 'forward') {
250
+ for (let i = arr.length - 2; i >= 0; i--) {
251
+ const cur = arr[i];
252
+ const above = arr[i + 1];
253
+ if (cur && above && isSel(cur) && !isSel(above)) {
254
+ arr[i] = above;
255
+ arr[i + 1] = cur;
256
+ }
257
+ }
258
+ } else {
259
+ // backward
260
+ for (let i = 1; i < arr.length; i++) {
261
+ const cur = arr[i];
262
+ const below = arr[i - 1];
263
+ if (cur && below && isSel(cur) && !isSel(below)) {
264
+ arr[i] = below;
265
+ arr[i - 1] = cur;
266
+ }
267
+ }
268
+ }
269
+ return arr.flat();
270
+ }