@1agh/maude 0.58.2 → 0.58.3

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 (105) hide show
  1. package/apps/studio/annotations-bindings.ts +83 -4
  2. package/apps/studio/api.ts +6 -1
  3. package/apps/studio/bin/_fetch-asset.mjs +169 -5
  4. package/apps/studio/bin/_import-asset.mjs +72 -0
  5. package/apps/studio/bin/_import-figma.mjs +1121 -0
  6. package/apps/studio/bin/_video-playwright.mjs +86 -3
  7. package/apps/studio/bin/import-figma.sh +38 -0
  8. package/apps/studio/bin/read-annotations.mjs +11 -1
  9. package/apps/studio/bun.lock +16 -22
  10. package/apps/studio/canvas-edit.ts +29 -5
  11. package/apps/studio/client/app.jsx +44 -1
  12. package/apps/studio/client/export-center.jsx +42 -4
  13. package/apps/studio/client/panels/CloudBar.jsx +92 -1
  14. package/apps/studio/client/panels/FigmaImportPanel.jsx +264 -0
  15. package/apps/studio/client/panels/GitPanel.jsx +26 -6
  16. package/apps/studio/client/panels/SettingsPanel.jsx +181 -0
  17. package/apps/studio/client/panels/SetupChecklist.jsx +26 -2
  18. package/apps/studio/client/panels/TimelinePanel.jsx +2 -2
  19. package/apps/studio/client/panels/timeline-parse.js +3 -3
  20. package/apps/studio/client/styles/3-shell-maude.css +7 -0
  21. package/apps/studio/client/styles/4-components.css +134 -0
  22. package/apps/studio/clip-ops.ts +93 -17
  23. package/apps/studio/cloud/endpoints.ts +78 -10
  24. package/apps/studio/cloud/renew.ts +183 -0
  25. package/apps/studio/context.ts +2 -1
  26. package/apps/studio/dist/client.bundle.js +1261 -1261
  27. package/apps/studio/dist/runtime/@remotion_media.js +56 -136
  28. package/apps/studio/dist/runtime/@remotion_player.js +18 -18
  29. package/apps/studio/dist/runtime/@remotion_transitions.js +9 -9
  30. package/apps/studio/dist/runtime/@remotion_transitions_clock-wipe.js +1 -1
  31. package/apps/studio/dist/runtime/remotion.js +12 -12
  32. package/apps/studio/dist/styles.css +1 -1
  33. package/apps/studio/exporters/_browser-bundles.ts +20 -6
  34. package/apps/studio/exporters/_runtime.ts +19 -0
  35. package/apps/studio/exporters/degraded.ts +92 -0
  36. package/apps/studio/exporters/index.ts +5 -0
  37. package/apps/studio/exporters/jobs.ts +19 -0
  38. package/apps/studio/exporters/unsupported-media.ts +170 -0
  39. package/apps/studio/exporters/video-encode-lib.ts +27 -1
  40. package/apps/studio/exporters/video-render-lib.ts +6 -0
  41. package/apps/studio/exporters/video.ts +62 -1
  42. package/apps/studio/figma/assets.test.ts +372 -0
  43. package/apps/studio/figma/assets.ts +398 -0
  44. package/apps/studio/figma/client.test.ts +395 -0
  45. package/apps/studio/figma/client.ts +513 -0
  46. package/apps/studio/figma/comments-to-strokes.test.ts +194 -0
  47. package/apps/studio/figma/comments-to-strokes.ts +173 -0
  48. package/apps/studio/figma/endpoints.ts +200 -0
  49. package/apps/studio/figma/sanitize.test.ts +256 -0
  50. package/apps/studio/figma/sanitize.ts +315 -0
  51. package/apps/studio/figma/style-map.ts +352 -0
  52. package/apps/studio/figma/to-artboard.test.ts +808 -0
  53. package/apps/studio/figma/to-artboard.ts +701 -0
  54. package/apps/studio/figma/to-render.test.ts +180 -0
  55. package/apps/studio/figma/to-render.ts +306 -0
  56. package/apps/studio/figma/to-strokes-roundtrip.test.ts +152 -0
  57. package/apps/studio/figma/to-strokes.test.ts +705 -0
  58. package/apps/studio/figma/to-strokes.ts +749 -0
  59. package/apps/studio/figma/to-tokens.test.ts +321 -0
  60. package/apps/studio/figma/to-tokens.ts +305 -0
  61. package/apps/studio/figma/types.ts +539 -0
  62. package/apps/studio/figma/url.test.ts +167 -0
  63. package/apps/studio/figma/url.ts +160 -0
  64. package/apps/studio/http.ts +129 -0
  65. package/apps/studio/sync/asset-push.ts +124 -0
  66. package/apps/studio/sync/connection-state.ts +11 -0
  67. package/apps/studio/sync/hub-link.ts +63 -7
  68. package/apps/studio/sync/hubs-config.ts +31 -3
  69. package/apps/studio/sync/index.ts +276 -26
  70. package/apps/studio/sync/migrate-flat-fallback.ts +121 -0
  71. package/apps/studio/sync/presentation.ts +45 -1
  72. package/apps/studio/sync/supervisor.ts +5 -1
  73. package/apps/studio/sync/workspace-signin.ts +7 -3
  74. package/apps/studio/test/annotations-bindings.test.ts +150 -12
  75. package/apps/studio/test/canvas-create-api.test.ts +4 -1
  76. package/apps/studio/test/canvas-origin-gate.test.ts +13 -0
  77. package/apps/studio/test/capture-determinism-shape.test.ts +135 -0
  78. package/apps/studio/test/clip-addressing.test.ts +6 -1
  79. package/apps/studio/test/clip-ops.test.ts +5 -1
  80. package/apps/studio/test/cloud-endpoints.test.ts +96 -0
  81. package/apps/studio/test/cloud-renew.test.ts +205 -0
  82. package/apps/studio/test/cloud-shell-surfaces.test.ts +11 -2
  83. package/apps/studio/test/exporters/degraded-propagation.test.ts +123 -0
  84. package/apps/studio/test/exporters/unsupported-media.test.ts +123 -0
  85. package/apps/studio/test/fetch-asset-gate.test.ts +189 -0
  86. package/apps/studio/test/figma-provenance.test.ts +108 -0
  87. package/apps/studio/test/figma-routes.test.ts +294 -0
  88. package/apps/studio/test/git-cloud-posture.test.ts +50 -0
  89. package/apps/studio/test/hub-link.test.ts +11 -0
  90. package/apps/studio/test/import-figma.test.ts +479 -0
  91. package/apps/studio/test/sync-asset-push.test.ts +124 -0
  92. package/apps/studio/test/sync-connection-state.test.ts +13 -0
  93. package/apps/studio/test/sync-hubs-config.test.ts +5 -0
  94. package/apps/studio/test/sync-migrate-flat-fallback.test.ts +98 -0
  95. package/apps/studio/test/sync-path-pull.test.ts +63 -0
  96. package/apps/studio/test/sync-presentation.test.ts +77 -0
  97. package/apps/studio/test/sync-runtime.test.ts +316 -1
  98. package/apps/studio/test/video-comp.test.ts +23 -1
  99. package/apps/studio/test/workspace-containment.test.ts +1 -0
  100. package/apps/studio/video-comp.tsx +70 -6
  101. package/apps/studio/whats-new.json +27 -0
  102. package/apps/studio/workspace-mode.ts +4 -0
  103. package/cli/commands/design.mjs +8 -0
  104. package/package.json +8 -8
  105. package/plugins/flow/.claude-plugin/config.schema.json +3 -3
@@ -81,7 +81,20 @@ interface MaudeSeekWindow {
81
81
  __maudeVideoComps?: Map<string, CompEntry>;
82
82
  /** Seek every registered comp to `frame` (paused). Ordinary artboards fall
83
83
  * back to a time-based WAAPI seek. Resolves once the frame has painted. */
84
- __maude_seek__?: (frame: number, opts?: { fps?: number }) => Promise<void>;
84
+ __maude_seek__?: (
85
+ frame: number,
86
+ opts?: {
87
+ fps?: number;
88
+ /** Capture mode: throw when a seek never lands, instead of only counting it. */
89
+ strict?: boolean;
90
+ }
91
+ ) => Promise<void>;
92
+ /**
93
+ * Count of seeks that never landed. The capture shim reads this and refuses
94
+ * to ship the export rather than encoding a stale frame — a silent wrong
95
+ * frame is worse than a loud failure.
96
+ */
97
+ __maude_seek_failures__?: number;
85
98
  /** Serializable comp list for the capture shim / Timeline panel. */
86
99
  __maude_comps__?: () => CompSnapshot[];
87
100
  /**
@@ -140,6 +153,36 @@ function afterPaint(): Promise<void> {
140
153
  });
141
154
  }
142
155
 
156
+ /** How many paint-spaced attempts a cold Player gets before a seek is a failure. */
157
+ const SEEK_ATTEMPTS = 3;
158
+
159
+ /**
160
+ * Seek one comp, giving a not-yet-mounted Player a bounded number of chances.
161
+ *
162
+ * Returns whether the seek actually landed. The retry replaces the old silent
163
+ * swallow: a Player that is genuinely mid-mount settles within a paint or two,
164
+ * and one that never settles is a real fault the caller must not paper over.
165
+ */
166
+ async function seekEntryWithRetry(
167
+ ref: { current: { pause?: () => void; seekTo: (f: number) => void } | null },
168
+ frame: number
169
+ ): Promise<boolean> {
170
+ for (let attempt = 0; attempt < SEEK_ATTEMPTS; attempt += 1) {
171
+ try {
172
+ const player = ref.current;
173
+ if (player) {
174
+ player.pause?.();
175
+ player.seekTo(frame);
176
+ return true;
177
+ }
178
+ } catch {
179
+ /* fall through to the retry — the next paint may settle it */
180
+ }
181
+ if (attempt < SEEK_ATTEMPTS - 1) await afterPaint();
182
+ }
183
+ return false;
184
+ }
185
+
143
186
  /**
144
187
  * Install the singleton seek bridge on `window`. Idempotent — the first
145
188
  * VideoComp to mount wins; later mounts reuse the same functions. The bridge
@@ -156,11 +199,32 @@ export function installMaudeSeekBridge(): void {
156
199
  if (entries.length > 0) {
157
200
  for (const e of entries) {
158
201
  const f = clamp(Math.round(frame), 0, Math.max(0, e.durationInFrames - 1));
159
- try {
160
- e.ref.current?.pause();
161
- e.ref.current?.seekTo(f);
162
- } catch {
163
- /* Player not yet ready — a later seek settles it */
202
+ // This used to be a bare try/catch with an empty body and the comment
203
+ // "Player not yet ready — a later seek settles it". That made a seek
204
+ // that COMPLETELY FAILED resolve as success: `afterPaint()` proves a
205
+ // paint happened, not that the right content was in it. Serially it was
206
+ // benign — frame N+1 repaired frame N's miss, and only frame 0 was ever
207
+ // exposed — which is exactly why it survived. It is not benign the
208
+ // moment anything captures ranges in parallel: every range boundary is a
209
+ // first seek on a cold page, so "a later seek settles it" would be
210
+ // invoked once per worker instead of once per export, and a stale frame
211
+ // is a valid-looking file with wrong pixels, not a crash.
212
+ //
213
+ // So the hope becomes an explicit bounded retry, and exhausting it is a
214
+ // real failure the capture shim can see and refuse to ship.
215
+ if (!(await seekEntryWithRetry(e.ref, f))) {
216
+ // ALWAYS recorded, so the capture shim can refuse the export even on
217
+ // the non-strict path. Only THROWN under `strict`, which the shim
218
+ // passes: a live UI scrub of a still-mounting comp should stay
219
+ // resilient, but a capture must never proceed on a seek that did not
220
+ // happen. The old code did neither — it just resolved.
221
+ w.__maude_seek_failures__ = (w.__maude_seek_failures__ ?? 0) + 1;
222
+ if (opts?.strict) {
223
+ throw new Error(
224
+ `__maude_seek__: comp did not accept frame ${f} after ${SEEK_ATTEMPTS} attempts ` +
225
+ '(Player never became ready) — refusing to report a seek that did not happen'
226
+ );
227
+ }
164
228
  }
165
229
  }
166
230
  await afterPaint();
@@ -1,6 +1,33 @@
1
1
  {
2
2
  "$schema": "./whats-new.schema.json",
3
3
  "entries": [
4
+ {
5
+ "id": "cloud-sync-matches-your-desktop",
6
+ "version": "0.58.3",
7
+ "date": "2026-08-11",
8
+ "kind": "fix",
9
+ "title": "The cloud copy matches your desktop",
10
+ "summary": "A project you connect to the cloud now looks exactly like it does on your machine: no duplicated files, every canvas opens instead of breaking, and your images show up instead of grey boxes. The cloud picker says \"Connected\" for the project this folder is linked to, with a Disconnect right there — and once you're connected, the cloud saves every change automatically, so there's one place your work is saved, not two.",
11
+ "surface": "design-ui"
12
+ },
13
+ {
14
+ "id": "cloud-link-self-renews",
15
+ "version": "0.58.3",
16
+ "date": "2026-08-11",
17
+ "kind": "fix",
18
+ "title": "Cloud sync keeps itself signed in",
19
+ "summary": "A workspace you connect to the cloud stays synced — the session refreshes itself in the background instead of quietly expiring after a few hours and stranding your canvases on \"connecting…\". If a link ever can't sync for a few minutes it now says so and points you to reconnect, instead of spinning forever.",
20
+ "surface": "design-ui"
21
+ },
22
+ {
23
+ "id": "figma-import",
24
+ "version": "0.58.3",
25
+ "date": "2026-08-11",
26
+ "kind": "feature",
27
+ "title": "Bring your Figma work in",
28
+ "summary": "Paste a Figma link and get the real thing, not a picture of it: design frames become editable canvases, a FigJam board becomes your whiteboard with its sticky notes and arrows still attached, and your colour and text styles become design tokens. Add a Figma access token once in Settings to get started.",
29
+ "surface": "design-ui"
30
+ },
4
31
  {
5
32
  "id": "sync-carries-the-path",
6
33
  "version": "0.58.2",
@@ -66,6 +66,10 @@ export const FORBIDDEN_ROUTE_PREFIXES: ReadonlyArray<{ prefix: string; why: stri
66
66
  prefix: '/_api/generate',
67
67
  why: 'media generation runs tenant-authored prompts against a provider key held here',
68
68
  },
69
+ {
70
+ prefix: '/_api/figma',
71
+ why: 'the Figma lane STORES and SPENDS a user personal access token, and imports write the design root — a cell must hold no provider credential (DDR-216 D2/D3, same class as /_api/generate)',
72
+ },
69
73
  {
70
74
  prefix: '/_api/shell-shot',
71
75
  why: 'the shell screenshot spawns a headless browser against the studio — the same evaluation `/_api/export` is forbidden for',
@@ -69,6 +69,14 @@ const BIN_VERBS = new Set([
69
69
  // contract, and patches (theme-block-scoped) or scaffolds the target
70
70
  // design system. CLI-only — no HTTP route, no in-app panel.
71
71
  'import-tokens',
72
+ // DDR-216 (feature-figma-import, Phase 2 / T6). `import-figma` is the REST
73
+ // door: pulls a real Figma/FigJam document over the API and translates it
74
+ // with DETERMINISTIC code — no vision model, no agent anywhere in the
75
+ // ingestion path. That is the structural difference from
76
+ // `/design:import --reconstruct` (DDR-174), whose whole orchestrator/agent
77
+ // split exists because an LLM reads its input; do not copy that architecture
78
+ // into this one, it would close nothing here.
79
+ 'import-figma',
72
80
  // DDR-173 (Phase 3 / T12). `import-brand` extracts typed palette/font
73
81
  // cues + a hardened logo asset from an already-DDR-167-sanitized SVG for
74
82
  // `/design:setup-ds --from-brand` — never re-reads or re-sanitizes the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1agh/maude",
3
- "version": "0.58.2",
3
+ "version": "0.58.3",
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": {
@@ -54,13 +54,13 @@
54
54
  "test:e2e:desktop:parity:cloud": "pnpm --filter @maude/desktop-e2e e2e:parity:cloud"
55
55
  },
56
56
  "optionalDependencies": {
57
- "@1agh/maude-darwin-arm64": "0.58.2",
58
- "@1agh/maude-darwin-x64": "0.58.2",
59
- "@1agh/maude-linux-arm64": "0.58.2",
60
- "@1agh/maude-linux-arm64-musl": "0.58.2",
61
- "@1agh/maude-linux-x64": "0.58.2",
62
- "@1agh/maude-linux-x64-musl": "0.58.2",
63
- "@1agh/maude-win32-x64": "0.58.2"
57
+ "@1agh/maude-darwin-arm64": "0.58.3",
58
+ "@1agh/maude-darwin-x64": "0.58.3",
59
+ "@1agh/maude-linux-arm64": "0.58.3",
60
+ "@1agh/maude-linux-arm64-musl": "0.58.3",
61
+ "@1agh/maude-linux-x64": "0.58.3",
62
+ "@1agh/maude-linux-x64-musl": "0.58.3",
63
+ "@1agh/maude-win32-x64": "0.58.3"
64
64
  },
65
65
  "files": [
66
66
  "cli",
@@ -314,7 +314,7 @@
314
314
  "type": "string",
315
315
  "enum": ["auto", "on", "off"],
316
316
  "default": "auto",
317
- "description": "Opt-out default `auto` (absent block == `auto`). `auto` = active only when `kg` is on PATH AND (`store` is set OR a local `.kgai/store` exists) — conservative, mirrors `orchestration.mode:auto`; a first-run repo stays on files until the user opts in via onboarding. `on` = force kgai active (errors surface rather than silently falling back). `off` = disable entirely; today's classic `.ai/decisions/` + STATE.md path, byte-for-byte unchanged."
317
+ "description": "Opt-out default `auto` (absent block == `auto`). `auto` = active only when `kg` is on PATH AND a store resolves (`kg config` store_root via an approved committed `.kgairc`, this `store` value, or a legacy local `.kgai/store`) — conservative, mirrors `orchestration.mode:auto`; a first-run repo stays on files until the user opts in via onboarding. An unapproved `.kgairc` (`kg config` → pending_approval) counts as NOT resolved until a human runs `kg trust`. `on` = force kgai active (errors surface rather than silently falling back). `off` = disable entirely; today's classic `.ai/decisions/` + STATE.md path, byte-for-byte unchanged."
318
318
  },
319
319
  "engine": {
320
320
  "type": "string",
@@ -325,12 +325,12 @@
325
325
  "engineVersion": {
326
326
  "type": "string",
327
327
  "default": "v1.0.0",
328
- "description": "PINNED kgai release the CLI + desktop bundle target. Advance deliberately (harness-verified via `maude kg check-upstream`), never float — auto-tracking `latest` would let an upstream change silently alter behavior mid-rollout and is a supply-chain surface (DDR-054/056 reasoning: the bundled `kg` is third-party)."
328
+ "description": "MINIMUM kgai release the integration targets (a floor, not a pin — the engine installs via the official installer, which self-updates at SessionStart; the manual pinned-download flow is retired). Must be at least `v1.5.1` — the `.kgairc` three-layer config + `kg config`/`kg prompt`/`kg trust` surface. Verify with `kg version`; drift check via `maude kg check-upstream`."
329
329
  },
330
330
  "store": {
331
331
  "type": "string",
332
332
  "default": "",
333
- "description": "Remote store the graph syncs to. `s3://company-kg/store` (shared company remote, cross-repo) | `kgai://org/project` (cloud-broker keyspace) | git remote | `\"\"` = local-only `.kgai/store`. Empty string keeps everything on this machine."
333
+ "description": "Fallback/override store for the `maude kg` verbs. Since kgai v1.5.1 store resolution is primarily the ENGINE's job: a repo with an approved committed `.kgairc` resolves its store via `kg config` (typically a shared parent-folder store like `../.kgai-shared`), and that wins — this value is consulted only when `kg config` yields no store (legacy per-repo setups). The sync remote is set per-STORE with `kg remote` (session layer), never here and never via `kg remote --global`."
334
334
  },
335
335
  "scope": {
336
336
  "type": "object",