@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
@@ -2,9 +2,10 @@
2
2
  // project file the canvas lanes do not own (feature-sync-file-plane, binding
3
3
  // decision maude/sync-two-plane-manifest-architecture).
4
4
  //
5
- // WHAT THIS REPLACES, AND WHY THE HEADER OF `asset-pull.ts` STILL MATTERS.
6
- // The asset pull derives its wants LOCALLY (it fetches only names its own
7
- // files reference), so a hostile hub can never place a file nobody asked for.
5
+ // WHAT THIS REPLACES, AND WHY THE DELETED `asset-pull.ts`'s INVARIANT STILL
6
+ // MATTERS. The reference-derived asset pull (deleted in Sync v2 Increment 5)
7
+ // derived its wants LOCALLY (it fetched only names its own files reference),
8
+ // so a hostile hub could never place a file nobody asked for.
8
9
  // That invariant is exactly what made the 103-file gap unfixable: a fresh
9
10
  // link REFERENCES nothing, so it can want nothing, so the design system that
10
11
  // makes the canvases render never arrives (RCA
@@ -52,6 +53,7 @@ import {
52
53
  type FileClass,
53
54
  isFilePlaneClass,
54
55
  } from './file-membership.ts';
56
+ import { createPullBudget } from './pull-budget.ts';
55
57
  import { quarantineFile } from './tombstone-apply.ts';
56
58
 
57
59
  /** How long to wait for the manifest. Same figure as the doc listing. */
@@ -93,6 +95,9 @@ export interface FilePullResult {
93
95
  /** Refused by re-classification or per-class admission. */
94
96
  dropped: { rel: string; reason: string }[];
95
97
  failed: { rel: string; reason: string }[];
98
+ /** F6 — the pass stopped on the aggregate byte budget rather than the count
99
+ * cap. The remainder is the next pass's work. */
100
+ budgetExhausted?: true;
96
101
  }
97
102
 
98
103
  /**
@@ -153,6 +158,8 @@ export interface PullFilesOptions {
153
158
  fetchImpl?: typeof fetch;
154
159
  log?: Pick<Console, 'log' | 'warn'>;
155
160
  now?: () => number;
161
+ /** F6 — override the aggregate per-pass byte ceiling (tests). */
162
+ maxPassBytes?: number;
156
163
  }
157
164
 
158
165
  /**
@@ -255,8 +262,26 @@ export async function pullFiles(opts: PullFilesOptions): Promise<FilePullResult>
255
262
  );
256
263
  }
257
264
 
265
+ // F6 (DDR-226 §9) — the per-file cap and the count cap multiply, and the hub
266
+ // picks both factors. One aggregate ceiling per pass closes that. This lane
267
+ // has the manifest, so the charge lands BEFORE the request: an oversize batch
268
+ // costs no transfer at all.
269
+ const budget = createPullBudget({
270
+ label: 'sync/files',
271
+ log,
272
+ ...(opts.maxPassBytes !== undefined ? { maxBytes: opts.maxPassBytes } : {}),
273
+ });
274
+
258
275
  for (const { entry, abs, conflict } of batch) {
259
276
  const rel = entry.path;
277
+ if (budget.exhausted()) break;
278
+ // The manifest size is hub-supplied and therefore a hint — it reserves the
279
+ // budget; the ACTUAL bytes are re-charged below, so an understated size
280
+ // cannot buy a bigger transfer.
281
+ if (!budget.take(entry.size)) {
282
+ out.budgetExhausted = true;
283
+ break;
284
+ }
260
285
  try {
261
286
  const res = await fetchImpl(
262
287
  `${base}/_project-file/${rel.split('/').map(encodeURIComponent).join('/')}`,
@@ -274,6 +299,16 @@ export async function pullFiles(opts: PullFilesOptions): Promise<FilePullResult>
274
299
  out.failed.push({ rel, reason: `implausible size (${body.byteLength} B)` });
275
300
  continue;
276
301
  }
302
+ // Charge the overrun: the manifest's size was a hint, the wire is the
303
+ // fact. A body larger than declared must not slip past the ceiling.
304
+ if (body.byteLength > entry.size && !budget.take(body.byteLength - entry.size)) {
305
+ out.failed.push({
306
+ rel,
307
+ reason: `pass byte budget reached (declared ${entry.size} B, sent ${body.byteLength} B)`,
308
+ });
309
+ out.budgetExhausted = true;
310
+ break;
311
+ }
277
312
  // The manifest named a hash; the bytes must BE that hash. A mismatch is
278
313
  // usually a write racing the poll — a free retry, never a landing.
279
314
  const gotSha = createHash('sha256').update(body).digest('hex');
@@ -311,7 +346,9 @@ export async function pullFiles(opts: PullFilesOptions): Promise<FilePullResult>
311
346
 
312
347
  if (out.pulled.length > 0 || out.conflicts.length > 0) {
313
348
  log.log(
314
- `[sync/files] pulled ${out.pulled.length} project file(s) down (${out.skipped} already here, ${out.conflicts.length} conflict(s), ${out.failed.length} failed).`
349
+ `[sync/files] pulled ${out.pulled.length} project file(s) down (${out.skipped} already here, ${out.conflicts.length} conflict(s), ${out.failed.length} failed${
350
+ out.budgetExhausted ? ', pass byte budget reached — resuming next pass' : ''
351
+ }).`
315
352
  );
316
353
  }
317
354
  return out;
@@ -35,7 +35,17 @@ export interface HubLinkResult {
35
35
  const HUB_PROBE_TIMEOUT_MS = 4000;
36
36
 
37
37
  interface HubsFile {
38
- hubs: Record<string, { token: string; linkedAt: number; role?: string; expiresAt?: number }>;
38
+ hubs: Record<
39
+ string,
40
+ {
41
+ token: string;
42
+ linkedAt: number;
43
+ role?: string;
44
+ expiresAt?: number;
45
+ /** Local consent — see `HubRecord.codeModulesAllowed` in hubs-config.ts. */
46
+ codeModulesAllowed?: boolean;
47
+ }
48
+ >;
39
49
  trusted?: string[];
40
50
  }
41
51
 
@@ -135,11 +145,16 @@ export function saveHubCredential(
135
145
  /* malformed → start fresh rather than throw */
136
146
  }
137
147
  }
148
+ // `codeModulesAllowed` is LOCAL consent and survives every re-save. A
149
+ // sign-in response can change what the hub claims your role is; it must not
150
+ // be able to change what you agreed this hub may deliver.
151
+ const priorConsent = cfg.hubs[normUrl]?.codeModulesAllowed;
138
152
  cfg.hubs[normUrl] = {
139
153
  token,
140
154
  linkedAt: Date.now(),
141
155
  ...(role ? { role } : {}),
142
156
  ...(typeof expiresAt === 'number' && Number.isFinite(expiresAt) ? { expiresAt } : {}),
157
+ ...(typeof priorConsent === 'boolean' ? { codeModulesAllowed: priorConsent } : {}),
143
158
  };
144
159
  if (!Array.isArray(cfg.trusted)) cfg.trusted = [];
145
160
  if (!cfg.trusted.includes(normUrl)) cfg.trusted.push(normUrl);
@@ -0,0 +1,46 @@
1
+ // What the hub says it holds — indexed the way the rest of the runtime asks.
2
+ //
3
+ // THE BUG THIS MODULE EXISTS TO PREVENT. The listing was indexed by
4
+ // `slugFromDocName`, which strips the `ws/<workspace>/<branch>/` namespace by
5
+ // design, while the very next consumer of the same listing (`diffRemoteDocs`)
6
+ // compares full document names. One input, two key spaces — so a `hero` on
7
+ // `main` answered for a DIFFERENT peer's `hero` on `feat/x`, and that peer's
8
+ // cold-start seed deferred forever while logging that the hub's state was on its
9
+ // way. Nothing was coming, and the canvas simply never synced. A peer token is
10
+ // commonly `scope: '*'`, so one listing spans every namespace on the hub: no
11
+ // hostile hub is needed, one teammate naming a canvas the same thing on another
12
+ // branch is enough.
13
+ //
14
+ // Keeping the index and its question in one place, with one key space, is the
15
+ // whole point of the module.
16
+
17
+ /** One row of `GET /api/documents` — names and byte counts, never content. */
18
+ export interface HubDocRow {
19
+ name: string;
20
+ bytes?: number;
21
+ }
22
+
23
+ /**
24
+ * Index one listing by FULL document name.
25
+ *
26
+ * Rows without a usable name are dropped rather than coerced: this map answers
27
+ * a question about identity, and a blank key would answer it for everyone.
28
+ */
29
+ export function indexHubDocs(docs: readonly HubDocRow[]): Map<string, number> {
30
+ const out = new Map<string, number>();
31
+ for (const d of docs) {
32
+ if (typeof d?.name === 'string' && d.name) out.set(d.name, Number(d.bytes ?? 0));
33
+ }
34
+ return out;
35
+ }
36
+
37
+ /**
38
+ * Does the hub hold STATE for this exact document?
39
+ *
40
+ * A row with zero bytes is a document that exists but carries nothing — not
41
+ * state, and deferring to it would leave a canvas that never syncs in either
42
+ * direction. An absent row is the same answer as a listing we never got.
43
+ */
44
+ export function hubHolds(index: ReadonlyMap<string, number>, docName: string): boolean {
45
+ return (index.get(docName) ?? 0) > 0;
46
+ }
@@ -37,6 +37,22 @@ export interface HubRecord {
37
37
  * renewal", which is exactly the old behaviour.
38
38
  */
39
39
  expiresAt?: number;
40
+ /**
41
+ * A LOCAL, user-given consent: may this hub deliver executable modules?
42
+ *
43
+ * `role` above is the hub's own claim about you, re-read from every sign-in
44
+ * response — a cache of what the hub said, not local knowledge. Deriving the
45
+ * `code-module` receive gate from it meant the gate was set by the party it
46
+ * defends against: a hostile hub answers `user.role: "owner"` once and every
47
+ * `.ts`/`.mjs` it offers lands in `.design/`, where the AGENT and the
48
+ * `maude design *` helpers read it — a different blast radius from a `.tsx`
49
+ * rendering in the sandboxed canvas origin.
50
+ *
51
+ * So it is recorded here at link time by an explicit answer and NEVER
52
+ * rewritten by a login response. Absent means no — the pessimistic branch,
53
+ * the same direction every other default in this file leans.
54
+ */
55
+ codeModulesAllowed?: boolean;
40
56
  }
41
57
 
42
58
  export interface HubsConfig {