@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
@@ -1,6 +1,69 @@
1
1
  {
2
2
  "$schema": "./whats-new.schema.json",
3
3
  "entries": [
4
+ {
5
+ "id": "whiteboard-text-sections-live-previews",
6
+ "version": "1.5.0",
7
+ "date": "2026-09-30",
8
+ "kind": "improvement",
9
+ "title": "Smoother whiteboard editing, live together",
10
+ "summary": "Typing in stickies and shapes now wraps exactly as it will look, works with any input method (accents, Japanese), and is never lost — it reaches teammates within a second, and if someone deletes the note you are typing in, Enter puts it back with your text. Sections move, copy and delete together with everything in them, teammates see your drag or resize live before you let go, and undo only takes back your own changes.",
11
+ "surface": "design-ui"
12
+ },
13
+ {
14
+ "id": "whiteboard-edits-sync-per-element",
15
+ "version": "1.5.0",
16
+ "date": "2026-09-30",
17
+ "kind": "improvement",
18
+ "title": "Whiteboard edits no longer overwrite each other",
19
+ "summary": "Each sticky, shape and arrow now saves on its own, so two people editing the same board keep both changes, and even text typed into the same sticky at once is merged. Existing boards convert automatically on start, and editing text is steadier: the cursor stays on a new line after Shift+Enter, double-clicking a word selects it, and Shift+click removes an item from a selection.",
20
+ "surface": "design-ui"
21
+ },
22
+ {
23
+ "id": "file-tree-remembers-folders",
24
+ "version": "1.5.0",
25
+ "date": "2026-09-30",
26
+ "kind": "improvement",
27
+ "title": "The file tree remembers your folders",
28
+ "summary": "The Files panel now starts collapsed and keeps the folders you open, even after you switch panels, reload or restart the app. Opening a canvas unfolds the way to it.",
29
+ "surface": "design-ui"
30
+ },
31
+ {
32
+ "id": "comments-stay-where-you-put-them",
33
+ "version": "1.4.6",
34
+ "date": "2026-09-30",
35
+ "kind": "fix",
36
+ "title": "Comments stay where you put them",
37
+ "summary": "A comment on a sticky, a drawing or empty canvas no longer disappears a few seconds after you save it. A comment on a sticky moves with it, and if the thing it points at is deleted the comment stays, marked as detached, until you remove it.",
38
+ "surface": "design-ui"
39
+ },
40
+ {
41
+ "id": "comments-reach-every-peer",
42
+ "version": "1.4.6",
43
+ "date": "2026-09-30",
44
+ "kind": "fix",
45
+ "title": "Web comments reach the desktop app",
46
+ "summary": "Comments added on the web while your desktop app was closed now show up when you reopen it, and a comment deleted elsewhere no longer comes back.",
47
+ "surface": "design-ui"
48
+ },
49
+ {
50
+ "id": "large-boards-pan-smoothly",
51
+ "version": "1.4.6",
52
+ "date": "2026-09-30",
53
+ "kind": "improvement",
54
+ "title": "Large boards pan smoothly again",
55
+ "summary": "Panning a board with many artboards stays smooth while something is selected or a teammate is on the canvas — on the desktop app it could drop to a frame a second.",
56
+ "surface": "design-ui"
57
+ },
58
+ {
59
+ "id": "sync-no-phantom-waiting-files",
60
+ "version": "1.4.6",
61
+ "date": "2026-09-30",
62
+ "kind": "fix",
63
+ "title": "Sync stops counting files that are done",
64
+ "summary": "The Sync panel no longer shows files as waiting or stuck when there is nothing left to deliver, and Resync now brings down a file the project changed while you were uploading it.",
65
+ "surface": "design-ui"
66
+ },
4
67
  {
5
68
  "id": "team-projects-hold-up",
6
69
  "version": "1.4.5",
@@ -809,7 +809,11 @@ function collectAdoptManifest(cwd) {
809
809
  const files = [];
810
810
  try {
811
811
  for (const f of readdirSync(base)) {
812
- if (f.endsWith('.html') || f.endsWith('.annotations.svg')) {
812
+ if (
813
+ f.endsWith('.html') ||
814
+ f.endsWith('.annotations.json') ||
815
+ f.endsWith('.annotations.svg')
816
+ ) {
813
817
  try {
814
818
  files.push({ rel: `.design/${f}`, bytes: statSync(resolve(base, f)).size });
815
819
  } catch {
@@ -25,7 +25,7 @@ export function buildBlock(designRel = '.design', { s3Assets = false } = {}) {
25
25
  // The canonical IGNORED set is the DDR-115 runtime-state taxonomy — kept in
26
26
  // lockstep with `apps/studio/git/service.ts` (isMaudeRuntimeState backstop)
27
27
  // and the repo's own `.gitignore`. VERSIONED content (canvases, `.meta.json`,
28
- // `*.annotations.svg`, `system/**`, `config.json`) is deliberately absent.
28
+ // `*.annotations.json` (v1 `.svg`), `system/**`, `config.json`) is deliberately absent.
29
29
  const lines = [
30
30
  BEGIN_MARKER,
31
31
  '# Maude design plugin runtime — gitignored even in linked mode (DDR-056/DDR-115).',
@@ -37,7 +37,8 @@
37
37
  * `gitignore-block.mjs` and the taxonomy in `apps/studio/git/service.ts`.
38
38
  */
39
39
  const VERSIONED_PATTERNS = [
40
- { pattern: '*.annotations.svg', what: 'canvas draw layers (annotations)' },
40
+ { pattern: '*.annotations.json', what: 'canvas draw layers (annotations)' },
41
+ { pattern: '*.annotations.svg', what: 'canvas draw layers (annotations, v1 form)' },
41
42
  { pattern: '*.meta.json', what: 'canvas sidecars (layout, artboards, tags)' },
42
43
  { pattern: '*.tsx', what: 'canvas bodies' },
43
44
  { pattern: 'config.json', what: 'the project design config' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1agh/maude",
3
- "version": "1.4.5",
3
+ "version": "1.5.0",
4
4
  "description": "Marketplace of Claude Code plugins by Michal Dovrtěl: `design` (canvas-first design iteration) + `flow` (generic agentic workflow loop with .ai second brain). Ships the `maude` CLI (with `mdcc` legacy alias) to scaffold workspace, run the design dev server, and manage configs.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -58,13 +58,13 @@
58
58
  "test:e2e:desktop:parity:cloud": "pnpm --filter @maude/desktop-e2e e2e:parity:cloud"
59
59
  },
60
60
  "optionalDependencies": {
61
- "@1agh/maude-darwin-arm64": "1.4.5",
62
- "@1agh/maude-darwin-x64": "1.4.5",
63
- "@1agh/maude-linux-arm64": "1.4.5",
64
- "@1agh/maude-linux-arm64-musl": "1.4.5",
65
- "@1agh/maude-linux-x64": "1.4.5",
66
- "@1agh/maude-linux-x64-musl": "1.4.5",
67
- "@1agh/maude-win32-x64": "1.4.5"
61
+ "@1agh/maude-darwin-arm64": "1.5.0",
62
+ "@1agh/maude-darwin-x64": "1.5.0",
63
+ "@1agh/maude-linux-arm64": "1.5.0",
64
+ "@1agh/maude-linux-arm64-musl": "1.5.0",
65
+ "@1agh/maude-linux-x64": "1.5.0",
66
+ "@1agh/maude-linux-x64-musl": "1.5.0",
67
+ "@1agh/maude-win32-x64": "1.5.0"
68
68
  },
69
69
  "files": [
70
70
  "cli",
@@ -131,6 +131,7 @@
131
131
  "@decimalturn/toml-patch": "3.0.5",
132
132
  "ajv": "^8.20.0",
133
133
  "ajv-formats": "^3.0.1",
134
+ "diff": "8.0.4",
134
135
  "happy-dom": "20.10.6",
135
136
  "svgo": "^4.0.1",
136
137
  "yaml": "2.9.0"
@@ -13,7 +13,7 @@
13
13
  * a VERBATIM brief and inserts the matching generated artboards right here, below
14
14
  * this frame — the board becomes a living brief you sketch and Claude fills in.
15
15
  *
16
- * The annotation layer lives in a sibling `<slug>.annotations.svg` (NEVER in this
16
+ * The annotation layer lives in a sibling `<slug>.annotations.json` (NEVER in this
17
17
  * file), so your notes stay floating on top across every regeneration. No design
18
18
  * system tokens or classes are wired in — the board is a neutral surface; the
19
19
  * generated artboards bring the DS with them on ingest.
@@ -1,36 +0,0 @@
1
- // Which SVG an annotation edit names as its base (DDR-241 accepted revisions).
2
- //
3
- // The project merges an annotation proposal element by element against the
4
- // text it was derived from. The canvas used to send the CANONICAL
5
- // re-serialization of the strokes it held — fine while the stored text is
6
- // canonical too, and silently fatal when it is not: an element written by an
7
- // older serializer, a hand edit or any other writer then looks changed on BOTH
8
- // sides, the merge refuses it as a conflict, and the person's edit is reverted
9
- // without a word (plan T31, L09 arrowheads on a non-canonical arrow).
10
- //
11
- // An edit made on exactly the state this canvas last RECEIVED names the text
12
- // it received — the project then sees "nothing changed underneath", accepts
13
- // the edit whole, and the stored text becomes canonical as a side effect. Any
14
- // other state (edits stacked before the echo returns) is this canvas's own
15
- // canonical output, which is what the project holds after accepting it.
16
-
17
- import { type Stroke, strokesToSvg } from './annotations-model.ts';
18
-
19
- export interface ReceivedAnnotations {
20
- /** The SVG text exactly as the project delivered it. */
21
- raw: string;
22
- /** The same state as this client serializes it. */
23
- canon: string;
24
- }
25
-
26
- export function receivedAnnotations(raw: string, strokes: readonly Stroke[]): ReceivedAnnotations {
27
- return { raw, canon: strokesToSvg(strokes) };
28
- }
29
-
30
- export function annotationEditBase(
31
- before: readonly Stroke[],
32
- received: ReceivedAnnotations | null
33
- ): string {
34
- const canon = strokesToSvg(before);
35
- return received && received.canon === canon ? received.raw : canon;
36
- }
@@ -1,137 +0,0 @@
1
- /**
2
- * @file commands/annotation-strokes-command.ts — undo entry for annotation
3
- * stroke add / erase / batch-translate.
4
- * @scope apps/studio/commands/annotation-strokes-command.ts
5
- * @purpose Records a `Stroke[]` pair (before/after) and routes both
6
- * directions through a single `putFn`. The server endpoint is
7
- * `PUT /_api/annotations` which replaces the entire SVG; we
8
- * match that shape rather than diffing.
9
- *
10
- * Per DDR-050 rev 2 the runtime command is rebuilt per iframe
11
- * mount from a serializable `CommandRecord` so the stack
12
- * survives canvas switches. The `putFn` here also updates the
13
- * iframe's local React strokes state (see annotations-layer
14
- * `putStrokes`) so undo/redo visibly refresh, not just PUT.
15
- *
16
- * Why per-stroke commands (not coalesced into a 300 ms window):
17
- * matches Figma/FigJam — Cmd+Z reverts the most recent tap or
18
- * pen stroke individually. Coalescing into windows led to a
19
- * matoucí "why did Cmd+Z erase two of my last lines?" UX.
20
- */
21
-
22
- import type { Stroke } from '../annotations-layer.tsx';
23
- import type { CommandRecord, EditCommand } from '../undo-stack.ts';
24
- import { registerCommand } from '../undo-stack.ts';
25
-
26
- /**
27
- * Push-once callable that submits a full `Stroke[]` to the server (or its
28
- * test stub) AND updates the iframe's local strokes state. The
29
- * annotations-layer `putStrokes` is the production binding.
30
- *
31
- * `before` is the baseline THIS command's `next` was computed from — passed
32
- * through so the consumer can distinguish "an id `prev` still has that
33
- * `before` already lacked too" (a genuinely concurrent addition, fold it in)
34
- * from "an id `before` had that `next` deliberately dropped" (this command's
35
- * own delete — must stay dropped even if React's rendered state hasn't
36
- * caught up yet). See `reconcileCommit` in annotations-layer.tsx.
37
- */
38
- export type StrokesPutFn = (
39
- next: readonly Stroke[],
40
- before: readonly Stroke[]
41
- ) => void | Promise<void>;
42
-
43
- export interface AnnotationStrokesPayload {
44
- before: readonly Stroke[];
45
- after: readonly Stroke[];
46
- }
47
-
48
- export const ANNOTATION_STROKES_KIND = 'annotation-strokes';
49
-
50
- export interface AnnotationStrokesCommandInit {
51
- before: readonly Stroke[];
52
- after: readonly Stroke[];
53
- putFn: StrokesPutFn;
54
- /** Optional label override (eraser → "erase 1 stroke"). */
55
- label?: string;
56
- /** Telemetry kind. */
57
- kind?: string;
58
- }
59
-
60
- export function createAnnotationStrokesCommand(init: AnnotationStrokesCommandInit): EditCommand {
61
- const { putFn } = init;
62
- const before = init.before.map(cloneStroke);
63
- const after = init.after.map(cloneStroke);
64
- const label = init.label ?? defaultLabel(before, after);
65
- const kind = init.kind ?? ANNOTATION_STROKES_KIND;
66
-
67
- return {
68
- kind,
69
- label,
70
- async do() {
71
- await putFn(after.map(cloneStroke), before.map(cloneStroke));
72
- },
73
- async undo() {
74
- await putFn(before.map(cloneStroke), after.map(cloneStroke));
75
- },
76
- };
77
- }
78
-
79
- /**
80
- * Build a persistable record from the same inputs as `createAnnotationStrokesCommand`.
81
- * Used by the consumer alongside the EditCommand so the runtime side-effect
82
- * and the stored shape share one snapshot.
83
- */
84
- export function buildAnnotationStrokesRecord(opts: {
85
- before: readonly Stroke[];
86
- after: readonly Stroke[];
87
- label?: string;
88
- }): CommandRecord<AnnotationStrokesPayload> {
89
- const before = opts.before.map(cloneStroke);
90
- const after = opts.after.map(cloneStroke);
91
- return {
92
- kind: ANNOTATION_STROKES_KIND,
93
- label: opts.label ?? defaultLabel(before, after),
94
- payload: { before, after },
95
- };
96
- }
97
-
98
- // ─────────────────────────────────────────────────────────────────────────────
99
- // Registry
100
-
101
- registerCommand<AnnotationStrokesPayload>(ANNOTATION_STROKES_KIND, (record, sinks) => {
102
- const putFn = sinks.strokesPutFn as StrokesPutFn | undefined;
103
- if (!putFn) return null;
104
- return createAnnotationStrokesCommand({
105
- before: record.payload.before,
106
- after: record.payload.after,
107
- putFn,
108
- label: record.label,
109
- });
110
- });
111
-
112
- // ─────────────────────────────────────────────────────────────────────────────
113
- // Internals
114
-
115
- /**
116
- * Structured clone — works for every Stroke variant because they're plain
117
- * JSON-shaped (no Date / Map / DOM nodes). The pen `points: WorldPoint[]`
118
- * array is nested-cloned too so callers can't poison the snapshot by
119
- * mutating the source.
120
- */
121
- function cloneStroke<T extends Stroke>(s: T): T {
122
- // structuredClone is available on every runtime we ship to (Bun, modern
123
- // browsers, Node 17+). Fall back to JSON for the rare older harness.
124
- if (typeof structuredClone === 'function') return structuredClone(s);
125
- return JSON.parse(JSON.stringify(s)) as T;
126
- }
127
-
128
- function defaultLabel(before: readonly Stroke[], after: readonly Stroke[]): string {
129
- const added = after.length - before.length;
130
- if (added > 0) return `add ${added} stroke${added === 1 ? '' : 's'}`;
131
- if (added < 0) {
132
- const erased = -added;
133
- return `erase ${erased} stroke${erased === 1 ? '' : 's'}`;
134
- }
135
- // Same count — could be translate / edit / fill-change.
136
- return `edit ${after.length} stroke${after.length === 1 ? '' : 's'}`;
137
- }