@1agh/maude 1.4.5 → 1.5.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/annotations/ai-read.ts +288 -0
- package/apps/studio/annotations/ai-write.ts +533 -0
- package/apps/studio/annotations/board-io.ts +94 -0
- package/apps/studio/annotations/board-text.ts +45 -0
- package/apps/studio/annotations/constants.ts +58 -0
- package/apps/studio/annotations/elements/_shared.ts +125 -0
- package/apps/studio/annotations/elements/arrow.model.ts +196 -0
- package/apps/studio/annotations/elements/media.model.ts +79 -0
- package/apps/studio/annotations/elements/pen.model.ts +82 -0
- package/apps/studio/annotations/elements/section.model.ts +70 -0
- package/apps/studio/annotations/elements/shape.model.ts +141 -0
- package/apps/studio/annotations/elements/sticky.model.ts +48 -0
- package/apps/studio/annotations/elements/text.model.ts +52 -0
- package/apps/studio/annotations/fields.ts +347 -0
- package/apps/studio/annotations/fractional-index.ts +234 -0
- package/apps/studio/annotations/legacy/mini-dom.ts +207 -0
- package/apps/studio/annotations/migrate-boot.ts +172 -0
- package/apps/studio/annotations/migrate-cli.ts +37 -0
- package/apps/studio/annotations/migrate-v1.ts +383 -0
- package/apps/studio/annotations/ops.ts +474 -0
- package/apps/studio/annotations/registry.ts +159 -0
- package/apps/studio/annotations/replica.ts +264 -0
- package/apps/studio/annotations/scene.ts +235 -0
- package/apps/studio/annotations/schema.ts +188 -0
- package/apps/studio/annotations/types.ts +77 -0
- package/apps/studio/annotations/ui/board.ts +126 -0
- package/apps/studio/annotations/ui/containment.ts +118 -0
- package/apps/studio/annotations/ui/edit-actions.ts +551 -0
- package/apps/studio/annotations/ui/editor-channel.ts +62 -0
- package/apps/studio/annotations/ui/element-node.tsx +993 -0
- package/apps/studio/annotations/ui/pipeline-context.ts +17 -0
- package/apps/studio/annotations/ui/pointer-pipeline.ts +150 -0
- package/apps/studio/annotations/ui/render-model.ts +264 -0
- package/apps/studio/annotations/ui/scene.tsx +63 -0
- package/apps/studio/annotations/ui/text-editor.tsx +420 -0
- package/apps/studio/annotations/ui/text-session.ts +140 -0
- package/apps/studio/annotations/ui/text-style.ts +111 -0
- package/apps/studio/annotations/ui/world.ts +47 -0
- package/apps/studio/annotations/v1-adapter.ts +428 -0
- package/apps/studio/annotations-align.ts +21 -6
- package/apps/studio/annotations-bindings.ts +2 -0
- package/apps/studio/annotations-context-toolbar.tsx +9 -13
- package/apps/studio/annotations-groups.ts +3 -0
- package/apps/studio/annotations-layer.tsx +923 -1925
- package/apps/studio/annotations-model.ts +97 -4
- package/apps/studio/annotations-sync.ts +4 -47
- package/apps/studio/api.ts +262 -68
- package/apps/studio/bin/_import-figma.mjs +22 -12
- package/apps/studio/bin/annotate.mjs +331 -838
- package/apps/studio/bin/annotate.sh +4 -4
- package/apps/studio/bin/perf.sh +21 -7
- package/apps/studio/bin/read-annotations.mjs +184 -666
- package/apps/studio/bin/read-annotations.sh +9 -5
- package/apps/studio/canvas-artifacts.ts +9 -0
- package/apps/studio/canvas-comment-mount.tsx +35 -2
- package/apps/studio/canvas-lib.tsx +18 -1
- package/apps/studio/canvas-shell.tsx +54 -1
- package/apps/studio/client/app.jsx +101 -35
- package/apps/studio/client/comments-overlay.css +10 -0
- package/apps/studio/client/hmr.mjs +1 -1
- package/apps/studio/client/panels/git-grouping.js +2 -2
- package/apps/studio/client/tree-expansion.js +217 -0
- package/apps/studio/collab/index.ts +58 -5
- package/apps/studio/collab/persistence.ts +80 -11
- package/apps/studio/collab/registry.ts +63 -26
- package/apps/studio/commands/annotation-ops-command.ts +72 -0
- package/apps/studio/comment-anchor.ts +116 -0
- package/apps/studio/comments-overlay.tsx +78 -35
- package/apps/studio/context.ts +1 -0
- package/apps/studio/cursors-overlay.tsx +337 -109
- package/apps/studio/dist/client.bundle.js +850 -850
- package/apps/studio/dist/comment-mount.js +2 -2
- package/apps/studio/figma/to-strokes.ts +31 -10
- package/apps/studio/git/endpoints.ts +1 -1
- package/apps/studio/git/service.ts +1 -1
- package/apps/studio/git/watch.ts +1 -1
- package/apps/studio/http.ts +90 -15
- package/apps/studio/server.ts +16 -0
- package/apps/studio/sync/accepted-cold-start.ts +72 -8
- package/apps/studio/sync/agent.ts +36 -6
- package/apps/studio/sync/codec.ts +95 -40
- package/apps/studio/sync/comment-ledger.ts +229 -0
- package/apps/studio/sync/file-membership.ts +12 -2
- package/apps/studio/sync/file-plane.ts +78 -2
- package/apps/studio/sync/index.ts +75 -16
- package/apps/studio/sync/journal-client.ts +5 -0
- package/apps/studio/sync/limits.ts +7 -2
- package/apps/studio/sync/migrate-seed.ts +17 -7
- package/apps/studio/sync/projection.ts +7 -2
- package/apps/studio/sync/remote-docs.ts +34 -0
- package/apps/studio/sync/writer-registry.ts +10 -0
- package/apps/studio/text-caret.ts +11 -2
- package/apps/studio/tree-state.ts +45 -0
- package/apps/studio/undo-stack.ts +2 -2
- package/apps/studio/use-annotation-resize.tsx +48 -23
- package/apps/studio/use-annotation-selection.tsx +9 -2
- package/apps/studio/use-collab.tsx +104 -1
- package/apps/studio/use-selection-set.tsx +4 -0
- package/apps/studio/whats-new.json +63 -0
- package/cli/lib/design-link.mjs +5 -1
- package/cli/lib/gitignore-block.mjs +1 -1
- package/cli/lib/gitignore-drift.mjs +2 -1
- package/package.json +9 -8
- package/plugins/design/templates/brief-board.tsx.template +1 -1
- package/apps/studio/annotation-edit-base.ts +0 -36
- package/apps/studio/commands/annotation-strokes-command.ts +0 -137
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
* - sticky / polygon / image / link — see the per-tool serializers below.
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
+
import { defOf } from './annotations/registry.ts';
|
|
29
|
+
import type { AnnotationElement, GeomCtx } from './annotations/types.ts';
|
|
28
30
|
import {
|
|
29
31
|
ARROW_HEADS,
|
|
30
32
|
type ArrowHead,
|
|
@@ -318,7 +320,21 @@ export interface SectionStroke extends StrokeBase {
|
|
|
318
320
|
color: string;
|
|
319
321
|
}
|
|
320
322
|
|
|
323
|
+
/**
|
|
324
|
+
* DDR-242 (Task 25) — an element of a registered type with no stroke form of
|
|
325
|
+
* its own. Its world-space record rides along and every geometry question is
|
|
326
|
+
* answered by its registry definition, so a new element type is one model file:
|
|
327
|
+
* the whiteboard draws, selects, moves, syncs and hands it to the AI verbs
|
|
328
|
+
* without another `tool ===` branch.
|
|
329
|
+
*/
|
|
330
|
+
export interface ElementStroke extends StrokeBase {
|
|
331
|
+
tool: 'element';
|
|
332
|
+
/** The element in WORLD coordinates, without `parent`. */
|
|
333
|
+
el: AnnotationElement;
|
|
334
|
+
}
|
|
335
|
+
|
|
321
336
|
export type Stroke =
|
|
337
|
+
| ElementStroke
|
|
322
338
|
| PenStroke
|
|
323
339
|
| RectStroke
|
|
324
340
|
| EllipseStroke
|
|
@@ -654,6 +670,37 @@ export function rid(): string {
|
|
|
654
670
|
return `s_${Math.random().toString(36).slice(2, 10)}`;
|
|
655
671
|
}
|
|
656
672
|
|
|
673
|
+
/**
|
|
674
|
+
* The id of an annotation element that was written WITHOUT a `data-id` (a
|
|
675
|
+
* hand-edited or externally generated SVG). It used to be `rid()` — a new
|
|
676
|
+
* random id on every parse — so the same element had a different id in every
|
|
677
|
+
* tab, on every peer and after every reload, and nothing could point at it: a
|
|
678
|
+
* comment anchored to it (#134/#136), a peer's selection halo, an agent's
|
|
679
|
+
* `annotate update`. Now it is derived from the element's own markup, plus its
|
|
680
|
+
* occurrence among identical elements, so every parse of the same SVG gives
|
|
681
|
+
* every element the same id everywhere. The first edit writes it back as a real
|
|
682
|
+
* `data-id` (strokesToSvg always emits one), after which it never changes.
|
|
683
|
+
*
|
|
684
|
+
* Contract for the annotations-v2 element model
|
|
685
|
+
* (.ai/plans/feature-annotations-v2-element-model.md): an annotation's id is
|
|
686
|
+
* stable for the life of the element — across edits, moves, undo/redo, sync
|
|
687
|
+
* and reload — and is what external references (comments' `annotationId`)
|
|
688
|
+
* hold. A migration must carry ids over unchanged.
|
|
689
|
+
*/
|
|
690
|
+
export function stableAnnotationId(el: SvgElLike, seen: Map<string, number>): string {
|
|
691
|
+
const content = el.outerHTML;
|
|
692
|
+
const n = seen.get(content) ?? 0;
|
|
693
|
+
seen.set(content, n + 1);
|
|
694
|
+
// FNV-1a, 32-bit — deterministic, dependency-free, plenty for a per-board id.
|
|
695
|
+
let h = 0x811c9dc5;
|
|
696
|
+
const key = `${content}#${n}`;
|
|
697
|
+
for (let i = 0; i < key.length; i++) {
|
|
698
|
+
h ^= key.charCodeAt(i);
|
|
699
|
+
h = Math.imul(h, 0x01000193) >>> 0;
|
|
700
|
+
}
|
|
701
|
+
return `s_h${h.toString(36)}`;
|
|
702
|
+
}
|
|
703
|
+
|
|
657
704
|
/** FigJam v3 — group ids mirror the stroke id scheme (`g_` prefix). */
|
|
658
705
|
export function gid(): string {
|
|
659
706
|
return `g_${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -1016,6 +1063,8 @@ export function strokeToSvgEl(s: Stroke): string {
|
|
|
1016
1063
|
}
|
|
1017
1064
|
|
|
1018
1065
|
function strokeToSvgElBase(s: Stroke): string {
|
|
1066
|
+
// No legacy SVG form: such an element only ever lives in a v2 board.
|
|
1067
|
+
if (s.tool === 'element') return '';
|
|
1019
1068
|
if (s.tool === 'text') {
|
|
1020
1069
|
// Phase 21 — anchored text keeps the byte-identical Phase 5.1 form;
|
|
1021
1070
|
// standalone text (no anchorId) writes its own world x/y and omits
|
|
@@ -1317,7 +1366,7 @@ function parseFill(raw: string | null): string | null {
|
|
|
1317
1366
|
* between them, so first-pair = start, last-pair = end recovers the ends
|
|
1318
1367
|
* exactly → idempotent re-serialize).
|
|
1319
1368
|
*/
|
|
1320
|
-
function arrowEndpoints(el:
|
|
1369
|
+
function arrowEndpoints(el: SvgElLike): { x1: number; y1: number; x2: number; y2: number } | null {
|
|
1321
1370
|
const line = el.querySelector('line');
|
|
1322
1371
|
if (line) {
|
|
1323
1372
|
return {
|
|
@@ -1390,7 +1439,7 @@ function sanitizeAuthorName(raw: string): string {
|
|
|
1390
1439
|
}
|
|
1391
1440
|
|
|
1392
1441
|
/** FigJam v3 — read the cross-tool root attrs back onto a parsed stroke. */
|
|
1393
|
-
function readSharedAttrs(el:
|
|
1442
|
+
function readSharedAttrs(el: SvgElLike, s: Stroke): void {
|
|
1394
1443
|
const g = el.getAttribute('data-group-ids');
|
|
1395
1444
|
if (g) {
|
|
1396
1445
|
const ids = g.split(/\s+/).filter(Boolean);
|
|
@@ -1416,17 +1465,45 @@ function readSharedAttrs(el: Element, s: Stroke): void {
|
|
|
1416
1465
|
}
|
|
1417
1466
|
}
|
|
1418
1467
|
|
|
1468
|
+
/**
|
|
1469
|
+
* The slice of the DOM `Element` API the parser reads. A browser `Element`
|
|
1470
|
+
* satisfies it; so does the DOM-free tree in `annotations/legacy/mini-dom.ts`,
|
|
1471
|
+
* which lets the hub (Node, no DOMParser) upconvert legacy SVG history for the
|
|
1472
|
+
* annotations-v2 migration (DDR-242 §6).
|
|
1473
|
+
*/
|
|
1474
|
+
export interface SvgElLike {
|
|
1475
|
+
getAttribute(name: string): string | null;
|
|
1476
|
+
querySelector(selector: string): SvgElLike | null;
|
|
1477
|
+
querySelectorAll(selector: string): ArrayLike<SvgElLike>;
|
|
1478
|
+
readonly textContent: string | null;
|
|
1479
|
+
readonly outerHTML: string;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
export interface SvgDocLike {
|
|
1483
|
+
querySelector(selector: string): SvgElLike | null;
|
|
1484
|
+
querySelectorAll(selector: string): ArrayLike<SvgElLike>;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1419
1487
|
export function svgToStrokes(svgText: string): Stroke[] {
|
|
1420
1488
|
const text = (svgText ?? '').trim();
|
|
1421
1489
|
if (!text) return [];
|
|
1422
1490
|
if (typeof DOMParser === 'undefined') return [];
|
|
1423
1491
|
try {
|
|
1424
|
-
|
|
1492
|
+
return strokesFromDocument(new DOMParser().parseFromString(text, 'image/svg+xml'));
|
|
1493
|
+
} catch {
|
|
1494
|
+
return [];
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
/** The parser proper, over any document exposing `SvgDocLike`. Never throws. */
|
|
1499
|
+
export function strokesFromDocument(doc: SvgDocLike): Stroke[] {
|
|
1500
|
+
try {
|
|
1425
1501
|
if (doc.querySelector('parsererror')) return [];
|
|
1426
1502
|
const out: Stroke[] = [];
|
|
1503
|
+
const seenContent = new Map<string, number>();
|
|
1427
1504
|
for (const el of Array.from(doc.querySelectorAll('[data-tool]'))) {
|
|
1428
1505
|
const tool = el.getAttribute('data-tool');
|
|
1429
|
-
const id = el.getAttribute('data-id') ||
|
|
1506
|
+
const id = el.getAttribute('data-id') || stableAnnotationId(el, seenContent);
|
|
1430
1507
|
const color = el.getAttribute('stroke') || el.getAttribute('fill') || DEFAULT_COLOR;
|
|
1431
1508
|
const width = Number.parseFloat(el.getAttribute('stroke-width') || '2') || 2;
|
|
1432
1509
|
// FigJam v3 — every branch funnels through push() so the shared attrs
|
|
@@ -1741,6 +1818,7 @@ function pointSegmentDist(
|
|
|
1741
1818
|
|
|
1742
1819
|
/** FigJam v3 — strokes whose `rotation` is honoured (see StrokeBase doc). */
|
|
1743
1820
|
export function canRotate(s: Stroke): boolean {
|
|
1821
|
+
if (s.tool === 'element') return false;
|
|
1744
1822
|
if (s.tool === 'pen' || s.tool === 'arrow' || s.tool === 'section') return false;
|
|
1745
1823
|
if (s.tool === 'text') return s.anchorId == null || s.anchorId === '';
|
|
1746
1824
|
return true;
|
|
@@ -1808,6 +1886,9 @@ export function strokeHitTest(s: Stroke, wx: number, wy: number, tol: number): b
|
|
|
1808
1886
|
wx >= bb.x - tol && wx <= bb.x + bb.w + tol && wy >= bb.y - tol && wy <= bb.y + bb.h + tol
|
|
1809
1887
|
);
|
|
1810
1888
|
}
|
|
1889
|
+
if (s.tool === 'element') {
|
|
1890
|
+
return defOf(s.el.type)?.hitTest(s.el, wx, wy, tol, WORLD_CTX) ?? false;
|
|
1891
|
+
}
|
|
1811
1892
|
if (s.tool === 'section') {
|
|
1812
1893
|
// FigJam — a section is grabbed by its BORDER or its label chip; the
|
|
1813
1894
|
// interior stays click-through so content on the section selects normally.
|
|
@@ -2013,6 +2094,7 @@ export function normalizeSticky(s: StickyStroke): StickyStroke {
|
|
|
2013
2094
|
}
|
|
2014
2095
|
|
|
2015
2096
|
export function isStrokeMeaningful(s: Stroke): boolean {
|
|
2097
|
+
if (s.tool === 'element') return defOf(s.el.type)?.meaningful(s.el) ?? false;
|
|
2016
2098
|
if (s.tool === 'pen') return s.points.length >= 2;
|
|
2017
2099
|
if (s.tool === 'rect') return Math.abs(s.w) >= 4 && Math.abs(s.h) >= 4;
|
|
2018
2100
|
if (s.tool === 'polygon') return Math.abs(s.w) >= 4 && Math.abs(s.h) >= 4;
|
|
@@ -2031,10 +2113,17 @@ export function isStrokeMeaningful(s: Stroke): boolean {
|
|
|
2031
2113
|
return Math.hypot(s.x2 - s.x1, s.y2 - s.y1) >= 4;
|
|
2032
2114
|
}
|
|
2033
2115
|
|
|
2116
|
+
const WORLD_CTX: GeomCtx = { origin: { x: 0, y: 0 }, resolve: () => null };
|
|
2117
|
+
|
|
2118
|
+
function elementBox(s: ElementStroke): { x: number; y: number; w: number; h: number } | null {
|
|
2119
|
+
return defOf(s.el.type)?.bounds(s.el, WORLD_CTX) ?? null;
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2034
2122
|
export function strokeBBox(
|
|
2035
2123
|
s: Stroke,
|
|
2036
2124
|
anchors?: Map<string, AnchorHost>
|
|
2037
2125
|
): { x: number; y: number; w: number; h: number } | null {
|
|
2126
|
+
if (s.tool === 'element') return elementBox(s);
|
|
2038
2127
|
if (s.tool === 'pen') {
|
|
2039
2128
|
if (!s.points.length) return null;
|
|
2040
2129
|
let xMin = Number.POSITIVE_INFINITY;
|
|
@@ -2109,6 +2198,10 @@ export function strokeBBox(
|
|
|
2109
2198
|
}
|
|
2110
2199
|
|
|
2111
2200
|
export function translateOne(s: Stroke, dx: number, dy: number): Stroke {
|
|
2201
|
+
if (s.tool === 'element') {
|
|
2202
|
+
const def = defOf(s.el.type);
|
|
2203
|
+
return def ? { ...s, el: { ...s.el, ...def.translate(s.el, dx, dy) } } : s;
|
|
2204
|
+
}
|
|
2112
2205
|
if (s.tool === 'pen') {
|
|
2113
2206
|
return { ...s, points: s.points.map(([x, y]) => [x + dx, y + dy] as WorldPoint) };
|
|
2114
2207
|
}
|
|
@@ -1,50 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Write/action ids travel with annotation writes as echo metadata (DDR-242:
|
|
3
|
+
* the replica's `~action`), never as authorization or a durable ACK.
|
|
4
|
+
*/
|
|
6
5
|
export function validAnnotationWriteId(value: unknown): value is string {
|
|
7
6
|
return typeof value === 'string' && /^[a-zA-Z0-9_-]{1,96}$/.test(value);
|
|
8
7
|
}
|
|
9
|
-
|
|
10
|
-
/** Remember authored operations, not previously rendered content (A → B → A). */
|
|
11
|
-
export function createAnnotationEchoGuard() {
|
|
12
|
-
const authored = new Map<string, string>();
|
|
13
|
-
return {
|
|
14
|
-
remember(id: string, svg: string) {
|
|
15
|
-
authored.set(id, svg);
|
|
16
|
-
if (authored.size > 64) {
|
|
17
|
-
const oldest = authored.keys().next().value;
|
|
18
|
-
if (oldest !== undefined) authored.delete(oldest);
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
forget(id: string) {
|
|
22
|
-
authored.delete(id);
|
|
23
|
-
},
|
|
24
|
-
isOwn(svg: string, id: unknown) {
|
|
25
|
-
return validAnnotationWriteId(id) && authored.get(id) === svg;
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** Old clients/importers may change svg without changing its old writeId. */
|
|
31
|
-
export function observeAnnotationSnapshots(
|
|
32
|
-
doc: Y.Doc,
|
|
33
|
-
receive: (svg: string, writeId: unknown) => void
|
|
34
|
-
): () => void {
|
|
35
|
-
const map = doc.getMap<unknown>('annotations');
|
|
36
|
-
const apply = (event?: Y.YMapEvent<unknown>) => {
|
|
37
|
-
if (event && !event.keysChanged.has('svg') && !event.keysChanged.has(ANNOTATION_WRITE_ID))
|
|
38
|
-
return;
|
|
39
|
-
const svg = map.get('svg');
|
|
40
|
-
if (typeof svg !== 'string' && !event?.keysChanged.has('svg')) return;
|
|
41
|
-
const id =
|
|
42
|
-
!event || event.keysChanged.has(ANNOTATION_WRITE_ID)
|
|
43
|
-
? map.get(ANNOTATION_WRITE_ID)
|
|
44
|
-
: undefined;
|
|
45
|
-
receive(typeof svg === 'string' ? svg : '', id);
|
|
46
|
-
};
|
|
47
|
-
map.observe(apply);
|
|
48
|
-
apply();
|
|
49
|
-
return () => map.unobserve(apply);
|
|
50
|
-
}
|