@1agh/maude 0.60.7 → 1.0.2

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 (145) hide show
  1. package/apps/studio/acp/index.ts +1 -0
  2. package/apps/studio/ai-banner.tsx +1 -0
  3. package/apps/studio/annotations-context-toolbar.tsx +3 -1
  4. package/apps/studio/annotations-layer.tsx +33 -16
  5. package/apps/studio/api.ts +108 -18
  6. package/apps/studio/artboard-guides-overlay.tsx +5 -1
  7. package/apps/studio/assets-s3.ts +6 -1
  8. package/apps/studio/bin/_import-figma.mjs +8 -3
  9. package/apps/studio/build.ts +1 -1
  10. package/apps/studio/canvas-artifacts.ts +21 -0
  11. package/apps/studio/canvas-build.ts +14 -9
  12. package/apps/studio/canvas-comment-mount.tsx +27 -30
  13. package/apps/studio/canvas-icons.tsx +1 -1
  14. package/apps/studio/canvas-lib.tsx +94 -5
  15. package/apps/studio/canvas-list-watch.ts +14 -1
  16. package/apps/studio/canvas-shell.tsx +8 -0
  17. package/apps/studio/client/app.jsx +224 -45
  18. package/apps/studio/client/panels/GitPanel.jsx +121 -13
  19. package/apps/studio/client/panels/SettingsPanel.jsx +1 -1
  20. package/apps/studio/client/panels/SyncConsentDialog.jsx +182 -0
  21. package/apps/studio/client/panels/SyncPanel.jsx +595 -1
  22. package/apps/studio/client/styles/3-shell-maude.css +38 -0
  23. package/apps/studio/clip-ops.ts +8 -1
  24. package/apps/studio/cloud/endpoints.ts +265 -3
  25. package/apps/studio/collab/origins.ts +3 -1
  26. package/apps/studio/comments-overlay.tsx +5 -0
  27. package/apps/studio/config.schema.json +24 -0
  28. package/apps/studio/context-menu.tsx +40 -27
  29. package/apps/studio/context.ts +57 -8
  30. package/apps/studio/cursors-overlay.tsx +25 -13
  31. package/apps/studio/dist/client.bundle.js +686 -686
  32. package/apps/studio/dist/comment-mount.js +2 -2
  33. package/apps/studio/dist/styles.css +1 -1
  34. package/apps/studio/exporters/jobs.ts +77 -15
  35. package/apps/studio/exporters/remote.ts +190 -0
  36. package/apps/studio/exporters/video-encode-lib.ts +10 -4
  37. package/apps/studio/figma/to-strokes.ts +11 -9
  38. package/apps/studio/gifenc.d.ts +51 -0
  39. package/apps/studio/git/log-format.ts +88 -0
  40. package/apps/studio/git/safe-rel.ts +96 -0
  41. package/apps/studio/git/service.ts +46 -27
  42. package/apps/studio/hmr-broadcast.ts +10 -0
  43. package/apps/studio/http.ts +384 -6
  44. package/apps/studio/participants-chrome.tsx +1 -0
  45. package/apps/studio/photo-store.ts +7 -0
  46. package/apps/studio/react-augment.d.ts +17 -0
  47. package/apps/studio/runtime-bundle.ts +6 -1
  48. package/apps/studio/server.ts +43 -8
  49. package/apps/studio/sync/agent.ts +70 -82
  50. package/apps/studio/sync/asset-push.ts +28 -71
  51. package/apps/studio/sync/autocommit.ts +106 -5
  52. package/apps/studio/sync/cell-file-events.ts +117 -0
  53. package/apps/studio/sync/cell-pairing.ts +20 -5
  54. package/apps/studio/sync/cell-write-nudge.ts +244 -0
  55. package/apps/studio/sync/codec.ts +155 -3
  56. package/apps/studio/sync/cold-start-apply.ts +211 -0
  57. package/apps/studio/sync/ctl-heal.ts +253 -0
  58. package/apps/studio/sync/ctl-provider.ts +217 -0
  59. package/apps/studio/sync/decide-file.ts +335 -0
  60. package/apps/studio/sync/file-ledger.ts +581 -0
  61. package/apps/studio/sync/file-membership.ts +32 -0
  62. package/apps/studio/sync/file-plane.ts +1400 -0
  63. package/apps/studio/sync/file-pull.ts +41 -4
  64. package/apps/studio/sync/hub-link.ts +16 -1
  65. package/apps/studio/sync/hub-listing.ts +46 -0
  66. package/apps/studio/sync/hubs-config.ts +16 -0
  67. package/apps/studio/sync/index.ts +915 -308
  68. package/apps/studio/sync/journal-client.ts +200 -0
  69. package/apps/studio/sync/migrate-seed.ts +99 -67
  70. package/apps/studio/sync/poke.ts +50 -0
  71. package/apps/studio/sync/projection.ts +13 -0
  72. package/apps/studio/sync/pull-budget.ts +86 -0
  73. package/apps/studio/sync/settings.ts +110 -0
  74. package/apps/studio/sync/status.ts +68 -0
  75. package/apps/studio/sync/trash.ts +243 -0
  76. package/apps/studio/sync/untrusted.ts +30 -10
  77. package/apps/studio/test/_helpers.ts +8 -0
  78. package/apps/studio/test/canvas-build.test.ts +63 -0
  79. package/apps/studio/test/canvas-list-watch.test.ts +17 -0
  80. package/apps/studio/test/canvas-move-api.test.ts +31 -0
  81. package/apps/studio/test/canvas-origin-gate.test.ts +12 -0
  82. package/apps/studio/test/canvas-shell-build-error.test.ts +49 -0
  83. package/apps/studio/test/cloud-history-hardening.test.ts +165 -0
  84. package/apps/studio/test/cloud-history-posture.test.ts +230 -0
  85. package/apps/studio/test/cloud-session-role.test.ts +30 -0
  86. package/apps/studio/test/cloud-shell-surfaces.test.ts +39 -0
  87. package/apps/studio/test/cold-start-apply.test.ts +303 -0
  88. package/apps/studio/test/collab-stress.test.ts +9 -1
  89. package/apps/studio/test/export-lane.test.ts +245 -0
  90. package/apps/studio/test/fixtures/video-comp-fixture.tsx +1 -1
  91. package/apps/studio/test/git-log-format.test.ts +95 -0
  92. package/apps/studio/test/git-safe-rel.test.ts +132 -0
  93. package/apps/studio/test/hmr-broadcast.test.ts +26 -0
  94. package/apps/studio/test/peer-selection-follows-camera.test.tsx +131 -0
  95. package/apps/studio/test/shared-doc-cell-pairing.test.ts +5 -2
  96. package/apps/studio/test/sync-agent.test.ts +78 -0
  97. package/apps/studio/test/sync-asset-push.test.ts +71 -108
  98. package/apps/studio/test/sync-autocommit.test.ts +80 -0
  99. package/apps/studio/test/sync-cell-write-nudge.test.ts +346 -0
  100. package/apps/studio/test/sync-ctl-channel.test.ts +508 -0
  101. package/apps/studio/test/sync-decide-file.test.ts +420 -0
  102. package/apps/studio/test/sync-file-ledger.test.ts +334 -0
  103. package/apps/studio/test/sync-file-membership.test.ts +17 -1
  104. package/apps/studio/test/sync-file-plane.test.ts +976 -0
  105. package/apps/studio/test/sync-hub-listing.test.ts +46 -0
  106. package/apps/studio/test/sync-meta-codec.test.ts +76 -0
  107. package/apps/studio/test/sync-move-retirement.test.ts +231 -0
  108. package/apps/studio/test/sync-panel-surface.test.ts +20 -0
  109. package/apps/studio/test/sync-path-pull.test.ts +67 -1
  110. package/apps/studio/test/sync-pull-budget.test.ts +169 -0
  111. package/apps/studio/test/sync-seed-defers-to-hub.test.ts +83 -0
  112. package/apps/studio/test/sync-settings-routes.test.ts +195 -0
  113. package/apps/studio/test/sync-settings.test.ts +151 -0
  114. package/apps/studio/test/sync-status.test.ts +69 -0
  115. package/apps/studio/test/sync-trash.test.ts +132 -0
  116. package/apps/studio/test/workspace-containment.test.ts +45 -9
  117. package/apps/studio/tsconfig.json +9 -10
  118. package/apps/studio/use-annotation-resize.tsx +14 -3
  119. package/apps/studio/use-collab.tsx +3 -1
  120. package/apps/studio/whats-new.json +90 -0
  121. package/apps/studio/workspace-mode.ts +110 -62
  122. package/apps/studio/ws.ts +22 -1
  123. package/cli/bin/claude-design-server.mjs +19 -0
  124. package/cli/commands/design.mjs +25 -6
  125. package/cli/commands/hub-workspace.mjs +243 -22
  126. package/cli/commands/hub-workspace.test.mjs +171 -0
  127. package/cli/commands/hub.mjs +71 -1
  128. package/cli/lib/design-link.mjs +186 -1
  129. package/cli/lib/design-ownership.mjs +330 -0
  130. package/cli/lib/design-ownership.test.mjs +329 -0
  131. package/cli/lib/hubs-config.mjs +21 -0
  132. package/cli/lib/hubs-config.test.mjs +47 -1
  133. package/cli/lib/workspace-plan.mjs +298 -5
  134. package/cli/lib/workspace-plan.test.mjs +215 -1
  135. package/package.json +10 -10
  136. package/plugins/design/templates/_shell.html +43 -2
  137. package/plugins/design/templates/design-system-inspiration/SUB-AGENT-PROMPTS.md +1 -1
  138. package/plugins/design/templates/design-system-inspiration/core/preview/_motion-readme.md.tpl +1 -1
  139. package/apps/studio/server.mjs +0 -1312
  140. package/apps/studio/sync/asset-pull.ts +0 -210
  141. package/apps/studio/sync/asset-push-worker.ts +0 -84
  142. package/apps/studio/sync/asset-sweep.ts +0 -262
  143. package/apps/studio/test/sync-asset-pull.test.ts +0 -161
  144. package/apps/studio/test/sync-asset-push-worker.test.ts +0 -183
  145. package/apps/studio/test/sync-asset-sweep.test.ts +0 -243
@@ -77,6 +77,7 @@
77
77
  * deprecation log at dev-server boot.
78
78
  */
79
79
 
80
+ import type { Easing } from 'motion/react';
80
81
  import {
81
82
  AnimatePresence as _MotionAnimatePresence,
82
83
  motion as _motionImpl,
@@ -226,7 +227,7 @@ const ZOOM_MIN = 0.02;
226
227
  const ZOOM_MAX = 4.0;
227
228
  const ZOOM_STEP_IN = 1.2;
228
229
  const ZOOM_STEP_OUT = 1 / 1.2;
229
- const WHEEL_ZOOM_K = 0.0015; // larger = more sensitive wheel
230
+ const WHEEL_ZOOM_K = 0.0035; // larger = more sensitive wheel — tuned to match Figma/FigJam's zoom-per-notch feel
230
231
  const SETTLE_MS = 500;
231
232
  const PUBLISH_MS = 50;
232
233
 
@@ -257,7 +258,12 @@ type DcPerfCounters = {
257
258
  instrumented?: boolean;
258
259
  };
259
260
 
260
- export function countRender(key: keyof DcPerfCounters): void {
261
+ /** The numeric counters only — `instrumented` is a flag, not something to add 1 to. */
262
+ type DcPerfCounterKey = {
263
+ [K in keyof DcPerfCounters]-?: DcPerfCounters[K] extends number | undefined ? K : never;
264
+ }[keyof DcPerfCounters];
265
+
266
+ export function countRender(key: DcPerfCounterKey): void {
261
267
  if (typeof window === 'undefined') return;
262
268
  const perf = (window as unknown as { __dcPerf?: DcPerfCounters }).__dcPerf;
263
269
  if (!perf) return;
@@ -2618,7 +2624,14 @@ function buildCanvasRectsManifest(): CanvasRectsManifest {
2618
2624
  if (rect.width <= 0 || rect.height <= 0) continue;
2619
2625
  const id = el.getAttribute('data-dc-screen');
2620
2626
  if (!id) continue;
2621
- artboards.push({ id, ...toWorld(rect) });
2627
+ // `kind` is part of ArtboardRect and the manifest's consumers (canvas-rects
2628
+ // → the whiteboard toolkit, DDR-151) read it. It was being dropped here —
2629
+ // invisible until A2 put this file under a checker. Same element carries
2630
+ // `data-dc-kind`, always stamped by DCArtboard; 'digital' is its own default.
2631
+ const kindAttr = el.getAttribute('data-dc-kind');
2632
+ const kind: ArtboardKind =
2633
+ kindAttr === 'print' || kindAttr === 'web' || kindAttr === 'video' ? kindAttr : 'digital';
2634
+ artboards.push({ id, kind, ...toWorld(rect) });
2622
2635
  }
2623
2636
 
2624
2637
  const elements: CanvasRectsElement[] = [];
@@ -3021,6 +3034,39 @@ export function PhotoPreviewBridge() {
3021
3034
  };
3022
3035
  }, []);
3023
3036
 
3037
+ // A peer's photo edit arrived: the sidecar changed on disk, the shell heard
3038
+ // it on the HMR socket and re-dispatched it here. Re-fetch and re-apply —
3039
+ // deliberately OUTSIDE the hydration scan's once-per-asset `attempted` set,
3040
+ // which exists to stop mutation-driven fetch amplification and which is
3041
+ // exactly why a synced edit used to appear only after Cmd+R (the scan had
3042
+ // already spent this asset's one attempt on mount).
3043
+ useEffect(() => {
3044
+ const onRefreshed = (e: Event) => {
3045
+ const sha8 = (e as CustomEvent<{ sha8?: unknown }>).detail?.sha8;
3046
+ if (typeof sha8 !== 'string' || !/^[0-9a-f]{8}$/i.test(sha8)) return;
3047
+ // Resolve the full `assets/<sha8>.<ext>` ref from the live DOM — the
3048
+ // event carries only the hash (the sidecar name has no extension).
3049
+ let asset: string | null = null;
3050
+ for (const n of document.querySelectorAll('img, image')) {
3051
+ const ref = extractAssetRef(n);
3052
+ if (ref?.includes(`assets/${sha8}.`)) {
3053
+ asset = ref;
3054
+ break;
3055
+ }
3056
+ }
3057
+ if (!asset) return; // no photo on this canvas uses that asset
3058
+ fetch(`/_api/photo-edit?asset=${encodeURIComponent(asset)}`)
3059
+ .then((r) => (r.ok ? r.json() : null))
3060
+ .then((edit) => {
3061
+ // A deleted/reset edit applies as null → restores the original src.
3062
+ apply(asset as string, edit && !isDefaultEdit(edit) ? (edit as PhotoEdit) : null);
3063
+ })
3064
+ .catch(() => {});
3065
+ };
3066
+ document.addEventListener('maude:photo-edit-refreshed', onRefreshed);
3067
+ return () => document.removeEventListener('maude:photo-edit-refreshed', onRefreshed);
3068
+ }, [apply]);
3069
+
3024
3070
  useEffect(() => {
3025
3071
  const onMsg = (e: MessageEvent) => {
3026
3072
  const m = e.data as { dgn?: string; asset?: unknown; edit?: unknown; busy?: unknown } | null;
@@ -4019,6 +4065,49 @@ export function easingFromToken(
4019
4065
  return undefined;
4020
4066
  }
4021
4067
 
4068
+ /**
4069
+ * The same token, in the shape MOTION accepts.
4070
+ *
4071
+ * `easingFromToken` returns CSS (`cubic-bezier(a, b, c, d)`), which is right for
4072
+ * a stylesheet and wrong for motion: its `Easing` is a named curve or a
4073
+ * four-number BezierDefinition, never a CSS string. Both MotionDemo call sites
4074
+ * were handing it the CSS form, so motion silently fell back to its own default
4075
+ * — a DS motion specimen was not demonstrating the DS's easing at all. Invisible
4076
+ * until A2 put this file under a checker.
4077
+ *
4078
+ * Named curves pass through; anything unparseable returns undefined, which is
4079
+ * the same "let motion decide" the bug produced, only deliberately.
4080
+ */
4081
+ function motionEaseFromToken(token: string, easings: Record<string, string>): Easing | undefined {
4082
+ const css = easingFromToken(token, easings);
4083
+ if (!css) return undefined;
4084
+ const named = css.trim();
4085
+ if (
4086
+ named === 'linear' ||
4087
+ named === 'easeIn' ||
4088
+ named === 'easeOut' ||
4089
+ named === 'easeInOut' ||
4090
+ named === 'circIn' ||
4091
+ named === 'circOut' ||
4092
+ named === 'circInOut' ||
4093
+ named === 'backIn' ||
4094
+ named === 'backOut' ||
4095
+ named === 'backInOut' ||
4096
+ named === 'anticipate'
4097
+ ) {
4098
+ return named;
4099
+ }
4100
+ // CSS keywords motion spells differently.
4101
+ if (named === 'ease-in') return 'easeIn';
4102
+ if (named === 'ease-out') return 'easeOut';
4103
+ if (named === 'ease-in-out') return 'easeInOut';
4104
+ const m = /^cubic-bezier\(([^)]*)\)$/.exec(named);
4105
+ if (!m || !m[1]) return undefined;
4106
+ const nums = m[1].split(',').map((n) => Number(n.trim()));
4107
+ if (nums.length !== 4 || nums.some((n) => !Number.isFinite(n))) return undefined;
4108
+ return [nums[0], nums[1], nums[2], nums[3]] as [number, number, number, number];
4109
+ }
4110
+
4022
4111
  interface MotionDemoProps {
4023
4112
  role: MotionRole;
4024
4113
  loop?: MotionLoop;
@@ -4048,7 +4137,7 @@ export function MotionDemo({
4048
4137
  const reduced = _useReducedMotion();
4049
4138
  const durationMs = tokens.durations[cfg.durationToken] ?? cfg.fallbackMs;
4050
4139
  const isSpring = cfg.easingToken === 'spring';
4051
- const ease = isSpring ? undefined : easingFromToken(cfg.easingToken, tokens.easings);
4140
+ const ease = isSpring ? undefined : motionEaseFromToken(cfg.easingToken, tokens.easings);
4052
4141
  const repeat = reduced || loop === 'once' ? 0 : Number.POSITIVE_INFINITY;
4053
4142
  const repeatType: 'reverse' | 'loop' = loop === 'always' ? 'reverse' : 'loop';
4054
4143
  const animate = reduced ? undefined : cfg.keyframes;
@@ -4131,7 +4220,7 @@ export function TokenPlayback({
4131
4220
  const tokens = useMotionTokens();
4132
4221
  const reduced = _useReducedMotion();
4133
4222
  const durationMs = tokens.durations[duration] ?? 220;
4134
- const ease = easing === 'spring' ? undefined : easingFromToken(easing, tokens.easings);
4223
+ const ease = easing === 'spring' ? undefined : motionEaseFromToken(easing, tokens.easings);
4135
4224
  const [tick, setTick] = useState(0);
4136
4225
  const fire = useCallback(() => {
4137
4226
  if (!reduced) setTick((n) => n + 1);
@@ -47,10 +47,23 @@ export function isCanvasCandidate(rel: string, groupPaths: string[]): boolean {
47
47
  const p = rel.replace(/\\/g, '/').replace(/^\/+/, '');
48
48
  if (!p) return false;
49
49
  const low = p.toLowerCase();
50
- if (!low.endsWith('.tsx') && !low.endsWith('.html')) return false;
50
+ // A DIRECTORY IS A CANDIDATE TOO. Renaming or moving a folder OUTSIDE the app
51
+ // (Finder, `mv`, `git checkout` of a branch that renamed it) changes the path
52
+ // of every canvas inside it, but the only event that reaches here names the
53
+ // DIRECTORY — `ui/dbox2` — which has no `.tsx` on the end. Gating on the
54
+ // extension therefore ignored it, so the tree kept listing the old folder and,
55
+ // worse, the sync runtime kept a descriptor pointing at a path that no longer
56
+ // existed: the canvases in that folder silently stopped syncing until some
57
+ // unrelated write to a `.tsx` happened to nudge the same recompute.
58
+ // `snapshot()` is authoritative, so a directory that turns out to hold no
59
+ // canvases costs one recompute and emits nothing.
60
+ const last = p.slice(p.lastIndexOf('/') + 1);
61
+ const looksLikeDir = !last.includes('.');
62
+ if (!looksLikeDir && !low.endsWith('.tsx') && !low.endsWith('.html')) return false;
51
63
  const segs = p.split('/');
52
64
  for (const s of segs) {
53
65
  if (!s) return false;
66
+ if (s === '..') return false; // the extension test used to be the guard here
54
67
  if (s.startsWith('_')) return false;
55
68
  if (SKIP_DIRS.has(s)) return false;
56
69
  }
@@ -3005,6 +3005,14 @@ function CanvasRouter({
3005
3005
  // Record onto the in-canvas undo stack so Cmd+Z reverts the rewrite. The
3006
3006
  // edit already posted above (record, don't re-run do()). before/after are
3007
3007
  // the trimmed bodies the edit-text endpoint persists.
3008
+ //
3009
+ // ONLY the record is gated on `file`. An earlier version of this guard
3010
+ // sat above the `postMessage` and returned out of the whole function —
3011
+ // which silently dropped THE EDIT ITSELF, not just an unusable undo
3012
+ // entry. `deriveFile` returning undefined must not cost the user their
3013
+ // typing; it costs them only a Cmd+Z step they could not have applied
3014
+ // anyway (a record with no canvas is one undo can never target).
3015
+ if (!file) return;
3008
3016
  undoStackRef.current.record(
3009
3017
  buildEditSourceRecord({
3010
3018
  op: 'text',