@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.
Files changed (106) hide show
  1. package/apps/studio/annotations/ai-read.ts +288 -0
  2. package/apps/studio/annotations/ai-write.ts +533 -0
  3. package/apps/studio/annotations/board-io.ts +94 -0
  4. package/apps/studio/annotations/board-text.ts +45 -0
  5. package/apps/studio/annotations/constants.ts +58 -0
  6. package/apps/studio/annotations/elements/_shared.ts +125 -0
  7. package/apps/studio/annotations/elements/arrow.model.ts +196 -0
  8. package/apps/studio/annotations/elements/media.model.ts +79 -0
  9. package/apps/studio/annotations/elements/pen.model.ts +82 -0
  10. package/apps/studio/annotations/elements/section.model.ts +70 -0
  11. package/apps/studio/annotations/elements/shape.model.ts +141 -0
  12. package/apps/studio/annotations/elements/sticky.model.ts +48 -0
  13. package/apps/studio/annotations/elements/text.model.ts +52 -0
  14. package/apps/studio/annotations/fields.ts +347 -0
  15. package/apps/studio/annotations/fractional-index.ts +234 -0
  16. package/apps/studio/annotations/legacy/mini-dom.ts +207 -0
  17. package/apps/studio/annotations/migrate-boot.ts +172 -0
  18. package/apps/studio/annotations/migrate-cli.ts +37 -0
  19. package/apps/studio/annotations/migrate-v1.ts +383 -0
  20. package/apps/studio/annotations/ops.ts +474 -0
  21. package/apps/studio/annotations/registry.ts +159 -0
  22. package/apps/studio/annotations/replica.ts +264 -0
  23. package/apps/studio/annotations/scene.ts +235 -0
  24. package/apps/studio/annotations/schema.ts +188 -0
  25. package/apps/studio/annotations/types.ts +77 -0
  26. package/apps/studio/annotations/ui/board.ts +126 -0
  27. package/apps/studio/annotations/ui/containment.ts +118 -0
  28. package/apps/studio/annotations/ui/edit-actions.ts +551 -0
  29. package/apps/studio/annotations/ui/editor-channel.ts +62 -0
  30. package/apps/studio/annotations/ui/element-node.tsx +993 -0
  31. package/apps/studio/annotations/ui/pipeline-context.ts +17 -0
  32. package/apps/studio/annotations/ui/pointer-pipeline.ts +150 -0
  33. package/apps/studio/annotations/ui/render-model.ts +264 -0
  34. package/apps/studio/annotations/ui/scene.tsx +63 -0
  35. package/apps/studio/annotations/ui/text-editor.tsx +420 -0
  36. package/apps/studio/annotations/ui/text-session.ts +140 -0
  37. package/apps/studio/annotations/ui/text-style.ts +111 -0
  38. package/apps/studio/annotations/ui/world.ts +47 -0
  39. package/apps/studio/annotations/v1-adapter.ts +428 -0
  40. package/apps/studio/annotations-align.ts +21 -6
  41. package/apps/studio/annotations-bindings.ts +2 -0
  42. package/apps/studio/annotations-context-toolbar.tsx +9 -13
  43. package/apps/studio/annotations-groups.ts +3 -0
  44. package/apps/studio/annotations-layer.tsx +923 -1925
  45. package/apps/studio/annotations-model.ts +97 -4
  46. package/apps/studio/annotations-sync.ts +4 -47
  47. package/apps/studio/api.ts +262 -68
  48. package/apps/studio/bin/_import-figma.mjs +22 -12
  49. package/apps/studio/bin/annotate.mjs +331 -838
  50. package/apps/studio/bin/annotate.sh +4 -4
  51. package/apps/studio/bin/perf.sh +21 -7
  52. package/apps/studio/bin/read-annotations.mjs +184 -666
  53. package/apps/studio/bin/read-annotations.sh +9 -5
  54. package/apps/studio/canvas-artifacts.ts +9 -0
  55. package/apps/studio/canvas-comment-mount.tsx +35 -2
  56. package/apps/studio/canvas-lib.tsx +18 -1
  57. package/apps/studio/canvas-shell.tsx +54 -1
  58. package/apps/studio/client/app.jsx +101 -35
  59. package/apps/studio/client/comments-overlay.css +10 -0
  60. package/apps/studio/client/hmr.mjs +1 -1
  61. package/apps/studio/client/panels/git-grouping.js +2 -2
  62. package/apps/studio/client/tree-expansion.js +217 -0
  63. package/apps/studio/collab/index.ts +58 -5
  64. package/apps/studio/collab/persistence.ts +80 -11
  65. package/apps/studio/collab/registry.ts +63 -26
  66. package/apps/studio/commands/annotation-ops-command.ts +72 -0
  67. package/apps/studio/comment-anchor.ts +116 -0
  68. package/apps/studio/comments-overlay.tsx +78 -35
  69. package/apps/studio/context.ts +1 -0
  70. package/apps/studio/cursors-overlay.tsx +337 -109
  71. package/apps/studio/dist/client.bundle.js +850 -850
  72. package/apps/studio/dist/comment-mount.js +2 -2
  73. package/apps/studio/figma/to-strokes.ts +31 -10
  74. package/apps/studio/git/endpoints.ts +1 -1
  75. package/apps/studio/git/service.ts +1 -1
  76. package/apps/studio/git/watch.ts +1 -1
  77. package/apps/studio/http.ts +90 -15
  78. package/apps/studio/server.ts +16 -0
  79. package/apps/studio/sync/accepted-cold-start.ts +72 -8
  80. package/apps/studio/sync/agent.ts +36 -6
  81. package/apps/studio/sync/codec.ts +95 -40
  82. package/apps/studio/sync/comment-ledger.ts +229 -0
  83. package/apps/studio/sync/file-membership.ts +12 -2
  84. package/apps/studio/sync/file-plane.ts +78 -2
  85. package/apps/studio/sync/index.ts +75 -16
  86. package/apps/studio/sync/journal-client.ts +5 -0
  87. package/apps/studio/sync/limits.ts +7 -2
  88. package/apps/studio/sync/migrate-seed.ts +17 -7
  89. package/apps/studio/sync/projection.ts +7 -2
  90. package/apps/studio/sync/remote-docs.ts +34 -0
  91. package/apps/studio/sync/writer-registry.ts +10 -0
  92. package/apps/studio/text-caret.ts +11 -2
  93. package/apps/studio/tree-state.ts +45 -0
  94. package/apps/studio/undo-stack.ts +2 -2
  95. package/apps/studio/use-annotation-resize.tsx +48 -23
  96. package/apps/studio/use-annotation-selection.tsx +9 -2
  97. package/apps/studio/use-collab.tsx +104 -1
  98. package/apps/studio/use-selection-set.tsx +4 -0
  99. package/apps/studio/whats-new.json +63 -0
  100. package/cli/lib/design-link.mjs +5 -1
  101. package/cli/lib/gitignore-block.mjs +1 -1
  102. package/cli/lib/gitignore-drift.mjs +2 -1
  103. package/package.json +9 -8
  104. package/plugins/design/templates/brief-board.tsx.template +1 -1
  105. package/apps/studio/annotation-edit-base.ts +0 -36
  106. package/apps/studio/commands/annotation-strokes-command.ts +0 -137
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @file annotations/types.ts — the annotations-v2 element contract (DDR-242)
3
+ * @scope apps/studio/annotations/types.ts
4
+ * @purpose Type-only module: the element record, the per-type definition
5
+ * interface every registry entry implements, and the geometry
6
+ * context a definition computes against. React-free, DOM-free.
7
+ */
8
+
9
+ import type { FieldSpecMap } from './fields.ts';
10
+
11
+ /**
12
+ * A canonical element record. Always carries `id`, `type` and `index`; `parent`
13
+ * names a container (a section) and makes `x`/`y` (and pen points / free arrow
14
+ * points) RELATIVE to that container's origin. Every other key is type-specific
15
+ * and default-valued keys are omitted.
16
+ */
17
+ export interface AnnotationElement {
18
+ id: string;
19
+ type: string;
20
+ parent?: string;
21
+ index: string;
22
+ [field: string]: unknown;
23
+ }
24
+
25
+ export interface Box {
26
+ x: number;
27
+ y: number;
28
+ w: number;
29
+ h: number;
30
+ }
31
+
32
+ /** Arrow end: bound to an element (auto-facing, or pinned to a magnet) or free. */
33
+ export type ArrowEnd = { el: string; nx?: number; ny?: number } | { x: number; y: number };
34
+
35
+ export interface ElementCaps {
36
+ /** Carries x/y/w/h (a card / shape / container). */
37
+ box: boolean;
38
+ rotatable: boolean;
39
+ resizable: boolean;
40
+ /** An arrow end can bind to it. */
41
+ bindable: boolean;
42
+ /** Other elements can have it as `parent` (sections). */
43
+ container: boolean;
44
+ /** Where its editable text lives: `text` field, `label` record, `label` string (section title), or none. */
45
+ textSlot: 'text' | 'label' | 'title' | null;
46
+ /** Resize keeps the aspect ratio (stickies are 1:1 at creation, not at resize). */
47
+ aspectLock?: boolean;
48
+ }
49
+
50
+ /**
51
+ * The world an element's geometry is computed in. Coordinates passed to and
52
+ * returned from an `ElementDef` are in the element's PARENT space; `resolve`
53
+ * gives another element's WORLD box + rotation (arrows need their hosts').
54
+ */
55
+ export interface GeomCtx {
56
+ /** World-space origin of the element's parent (0,0 at top level). */
57
+ origin: { x: number; y: number };
58
+ /** World-space unrotated box + rotation of any element, or null when missing / box-less. */
59
+ resolve(id: string): { box: Box; rot: number; bindable: boolean } | null;
60
+ }
61
+
62
+ export interface ElementDef {
63
+ type: string;
64
+ /** Type-specific fields, in canonical key order. */
65
+ fields: FieldSpecMap;
66
+ caps: ElementCaps;
67
+ /** Unrotated bounds in PARENT space, or null when degenerate. */
68
+ bounds(el: AnnotationElement, ctx: GeomCtx): Box | null;
69
+ /** Hit-test a point given in PARENT space. */
70
+ hitTest(el: AnnotationElement, px: number, py: number, tol: number, ctx: GeomCtx): boolean;
71
+ /** Field patch moving the element by (dx, dy) in parent space. */
72
+ translate(el: AnnotationElement, dx: number, dy: number): Record<string, unknown>;
73
+ /** Field patch fitting the element to `box` (parent space, normalized). */
74
+ resize(el: AnnotationElement, box: Box, ctx: GeomCtx): Record<string, unknown>;
75
+ /** False when the element is too small / empty to keep (a mis-tap). */
76
+ meaningful(el: AnnotationElement): boolean;
77
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * @file annotations/ui/board.ts — the canvas's element board (Task 26)
3
+ * @scope apps/studio/annotations/ui/board.ts
4
+ * @purpose DDR-242 AD8: the whiteboard UI works on v2 elements directly.
5
+ * One store per canvas holds the board as last applied (the
6
+ * server's state plus this tab's optimistic ops) and, during a
7
+ * gesture, a PREVIEW overlay (the dragged / resized records) that
8
+ * never reaches storage until the gesture commits as one op batch.
9
+ *
10
+ * React-free; the layer reads it through `useSyncExternalStore`
11
+ * (`version` changes on every change). `scene()` is the world-space
12
+ * view (parents, world boxes, arrow endpoints), cached per version.
13
+ */
14
+
15
+ import { type ApplyResult, applyOps, type Op } from '../ops.ts';
16
+ import { Scene } from '../scene.ts';
17
+ import type { AnnotationElement } from '../types.ts';
18
+
19
+ export class BoardStore {
20
+ private base = new Map<string, AnnotationElement>();
21
+ private overlay: Map<string, AnnotationElement | null> | null = null;
22
+ private merged: Map<string, AnnotationElement> | null = null;
23
+ private sceneCache: { version: number; scene: Scene } | null = null;
24
+ private listeners = new Set<() => void>();
25
+ version = 0;
26
+
27
+ subscribe = (fn: () => void): (() => void) => {
28
+ this.listeners.add(fn);
29
+ return () => {
30
+ this.listeners.delete(fn);
31
+ };
32
+ };
33
+
34
+ getVersion = (): number => this.version;
35
+
36
+ private changed(): void {
37
+ this.version++;
38
+ this.merged = null;
39
+ for (const l of [...this.listeners]) l();
40
+ }
41
+
42
+ /** The board as the UI shows it: base plus any gesture preview. */
43
+ get elements(): ReadonlyMap<string, AnnotationElement> {
44
+ if (!this.overlay) return this.base;
45
+ if (!this.merged) {
46
+ const m = new Map(this.base);
47
+ for (const [id, el] of this.overlay) {
48
+ if (el) m.set(id, el);
49
+ else m.delete(id);
50
+ }
51
+ this.merged = m;
52
+ }
53
+ return this.merged;
54
+ }
55
+
56
+ /** The board without the preview — what the next commit diffs against. */
57
+ get committed(): ReadonlyMap<string, AnnotationElement> {
58
+ return this.base;
59
+ }
60
+
61
+ get(id: string): AnnotationElement | undefined {
62
+ return this.elements.get(id);
63
+ }
64
+
65
+ scene(): Scene {
66
+ if (this.sceneCache?.version !== this.version) {
67
+ this.sceneCache = { version: this.version, scene: new Scene(this.elements.values()) };
68
+ }
69
+ return this.sceneCache.scene;
70
+ }
71
+
72
+ /** Replace the board (load, a peer's change). Unchanged records keep their identity. */
73
+ setAll(elements: Iterable<AnnotationElement>): void {
74
+ const next = new Map<string, AnnotationElement>();
75
+ for (const el of elements) {
76
+ const prev = this.base.get(el.id);
77
+ next.set(el.id, prev && sameRecord(prev, el) ? prev : el);
78
+ }
79
+ if (next.size === this.base.size && [...next].every(([id, el]) => this.base.get(id) === el))
80
+ return;
81
+ this.base = next;
82
+ this.changed();
83
+ }
84
+
85
+ /** Apply an op batch locally (optimistic). Returns what applied and its inverse. */
86
+ apply(ops: readonly Op[]): ApplyResult {
87
+ const r = applyOps(this.base, ops);
88
+ if (r.touched.size) {
89
+ this.base = r.state;
90
+ this.changed();
91
+ }
92
+ return r;
93
+ }
94
+
95
+ /** Show `records` in place of their stored versions (null hides one) — a gesture in flight. */
96
+ preview(records: ReadonlyMap<string, AnnotationElement | null>): void {
97
+ this.overlay = new Map(records);
98
+ this.changed();
99
+ }
100
+
101
+ clearPreview(): void {
102
+ if (!this.overlay) return;
103
+ this.overlay = null;
104
+ this.changed();
105
+ }
106
+
107
+ get previewing(): boolean {
108
+ return this.overlay !== null;
109
+ }
110
+ }
111
+
112
+ function sameRecord(a: AnnotationElement, b: AnnotationElement): boolean {
113
+ if (a === b) return true;
114
+ const ka = Object.keys(a);
115
+ const kb = Object.keys(b);
116
+ if (ka.length !== kb.length) return false;
117
+ for (const k of ka) {
118
+ const x = a[k];
119
+ const y = b[k];
120
+ if (x === y) continue;
121
+ if (typeof x !== 'object' || typeof y !== 'object' || JSON.stringify(x) !== JSON.stringify(y)) {
122
+ return false;
123
+ }
124
+ }
125
+ return true;
126
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * @file annotations/ui/containment.ts — what an operation on a selection acts on
3
+ * @scope apps/studio/annotations/ui/containment.ts
4
+ * @purpose DDR-242 AD8 (Task 20). Containment is explicit — an element's
5
+ * `parent` — so every operation on a section acts on its SUBTREE:
6
+ * move, nudge, align, distribute, delete, copy, duplicate. One
7
+ * expansion (groups, then section contents, to a fixpoint) instead
8
+ * of each call site deciding for itself — the source of the
9
+ * "section moved without its contents" bugs (characterization
10
+ * test, known bugs).
11
+ *
12
+ * Pure and React-free. It works on the element board (`parent`),
13
+ * so a nested section's contents come along with it.
14
+ */
15
+
16
+ import { Scene } from '../scene.ts';
17
+ import type { AnnotationElement } from '../types.ts';
18
+
19
+ export class Containment {
20
+ private readonly scene: Scene;
21
+ constructor(board: Iterable<AnnotationElement>) {
22
+ this.scene = new Scene(board);
23
+ }
24
+
25
+ /** Everything inside `id`, nested containers included (not `id` itself). */
26
+ contentsOf(id: string): string[] {
27
+ return this.scene.descendants(id).map((e) => e.id);
28
+ }
29
+
30
+ /** `ids` plus the full subtree of every container among them. */
31
+ withContents(ids: Iterable<string>): Set<string> {
32
+ const out = new Set<string>();
33
+ for (const id of ids) {
34
+ out.add(id);
35
+ for (const d of this.contentsOf(id)) out.add(d);
36
+ }
37
+ return out;
38
+ }
39
+
40
+ /** `ids` without those whose ancestor is also in `ids` (they ride along with it). */
41
+ roots(ids: Iterable<string>): string[] {
42
+ const set = new Set(ids);
43
+ return [...set].filter((id) => !this.scene.ancestors(id).some((a) => set.has(a)));
44
+ }
45
+
46
+ /** The container an element is drawn in, or null at top level. */
47
+ parentOf(id: string): string | null {
48
+ const el = this.scene.get(id);
49
+ return el ? this.scene.effectiveParent(el) : null;
50
+ }
51
+
52
+ /** Deepest container whose box holds the world point (the one a new section nests into). */
53
+ containerAt(wx: number, wy: number, exclude?: ReadonlySet<string>): string | null {
54
+ return this.scene.containerAt(wx, wy, exclude)?.id ?? null;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Selection → the ids an operation acts on: groups expanded, then every
60
+ * selected container's subtree, repeated until nothing new joins (a group
61
+ * member inside a section, a section inside a group).
62
+ */
63
+ export function expandForOp(
64
+ ids: readonly string[],
65
+ expandGroups: (ids: readonly string[]) => readonly string[],
66
+ containment: Containment
67
+ ): string[] {
68
+ let cur = new Set(ids);
69
+ for (let i = 0; i < 8; i++) {
70
+ const next = containment.withContents(expandGroups([...cur]));
71
+ if (next.size === cur.size) return [...next];
72
+ cur = next;
73
+ }
74
+ return [...cur];
75
+ }
76
+
77
+ export interface MarqueeItem {
78
+ id: string;
79
+ box: { x: number; y: number; w: number; h: number };
80
+ container: boolean;
81
+ }
82
+
83
+ /**
84
+ * What a marquee selects. Elements are taken by TOUCH; a container only when
85
+ * the marquee ENCLOSES it — so a marquee drawn over a section's contents
86
+ * selects the contents, never the section around them (FigJam).
87
+ */
88
+ export function marqueeHits(
89
+ items: readonly MarqueeItem[],
90
+ rect: { x1: number; y1: number; x2: number; y2: number }
91
+ ): string[] {
92
+ const xMin = Math.min(rect.x1, rect.x2);
93
+ const xMax = Math.max(rect.x1, rect.x2);
94
+ const yMin = Math.min(rect.y1, rect.y2);
95
+ const yMax = Math.max(rect.y1, rect.y2);
96
+ const hits: string[] = [];
97
+ for (const { id, box: b, container } of items) {
98
+ const hit = container
99
+ ? b.x >= xMin && b.x + b.w <= xMax && b.y >= yMin && b.y + b.h <= yMax
100
+ : b.x + b.w >= xMin && b.x <= xMax && b.y + b.h >= yMin && b.y <= yMax;
101
+ if (hit) hits.push(id);
102
+ }
103
+ return hits;
104
+ }
105
+
106
+ /**
107
+ * Where a new section goes in paint order: right ABOVE the section it is
108
+ * drawn inside (so it renders in front of it and adopts what sits on it), or
109
+ * at the very back at top level (a container under everything on it).
110
+ */
111
+ export function insertSection<T extends { id: string }>(
112
+ list: readonly T[],
113
+ section: T,
114
+ outerId: string | null
115
+ ): T[] {
116
+ const at = outerId ? list.findIndex((x) => x.id === outerId) : -1;
117
+ return at >= 0 ? [...list.slice(0, at + 1), section, ...list.slice(at + 1)] : [section, ...list];
118
+ }