@1agh/maude 0.53.2 → 0.55.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 (55) hide show
  1. package/apps/studio/annotations-layer.tsx +11 -1
  2. package/apps/studio/api.ts +13 -7
  3. package/apps/studio/bin/screenshot.sh +43 -3
  4. package/apps/studio/build.ts +37 -3
  5. package/apps/studio/canvas-build-sandbox.ts +101 -9
  6. package/apps/studio/canvas-build-worker.ts +7 -1
  7. package/apps/studio/canvas-build.ts +9 -1
  8. package/apps/studio/client/app.jsx +226 -25
  9. package/apps/studio/client/export-center.jsx +10 -2
  10. package/apps/studio/client/github.js +7 -0
  11. package/apps/studio/client/panels/CloudBar.jsx +461 -40
  12. package/apps/studio/client/panels/SettingsPanel.jsx +6 -1
  13. package/apps/studio/client/styles/3-shell-maude.css +30 -0
  14. package/apps/studio/client/styles/4-components.css +40 -0
  15. package/apps/studio/cloud/endpoints.ts +48 -6
  16. package/apps/studio/cloud-build.ts +208 -0
  17. package/apps/studio/collab/room.ts +43 -7
  18. package/apps/studio/context.ts +12 -5
  19. package/apps/studio/dist/client.bundle.js +983 -983
  20. package/apps/studio/dist/comment-mount.js +2 -2
  21. package/apps/studio/dist/styles.css +1 -1
  22. package/apps/studio/git/repo-lock.ts +305 -0
  23. package/apps/studio/git/service.ts +171 -21
  24. package/apps/studio/http.ts +159 -13
  25. package/apps/studio/inspect.ts +66 -6
  26. package/apps/studio/server.ts +114 -17
  27. package/apps/studio/session-scope.ts +101 -0
  28. package/apps/studio/sync/autocommit.ts +84 -46
  29. package/apps/studio/sync/index.ts +20 -0
  30. package/apps/studio/test/canvas-origin-gate.test.ts +61 -0
  31. package/apps/studio/test/canvas-shell-base.test.ts +107 -0
  32. package/apps/studio/test/cloud-build.test.ts +95 -0
  33. package/apps/studio/test/cloud-endpoints.test.ts +231 -4
  34. package/apps/studio/test/cloud-shell-surfaces.test.ts +31 -2
  35. package/apps/studio/test/collab-readonly-gate.test.ts +194 -0
  36. package/apps/studio/test/git-system-engine.test.ts +135 -0
  37. package/apps/studio/test/read-only-gate.test.ts +8 -0
  38. package/apps/studio/test/repo-concurrency.test.ts +146 -0
  39. package/apps/studio/test/repo-lock.test.ts +350 -0
  40. package/apps/studio/test/server-lifecycle.test.ts +11 -1
  41. package/apps/studio/test/session-runtime-state.test.ts +226 -0
  42. package/apps/studio/test/session-scope.test.ts +100 -0
  43. package/apps/studio/test/shell-importmap.test.ts +8 -1
  44. package/apps/studio/test/sync-autocommit.test.ts +76 -0
  45. package/apps/studio/test/sync-hardening.test.ts +75 -0
  46. package/apps/studio/use-collab.tsx +21 -1
  47. package/apps/studio/whats-new.json +54 -0
  48. package/apps/studio/ws.ts +44 -8
  49. package/cli/commands/kg.mjs +49 -19
  50. package/cli/lib/gitignore-block.mjs +3 -0
  51. package/package.json +11 -9
  52. package/plugins/design/dependencies.json +1 -1
  53. package/plugins/design/templates/_shell.html +66 -32
  54. package/plugins/flow/.claude-plugin/config.schema.json +1 -1
  55. package/plugins/flow/dependencies.json +1 -1
package/apps/studio/ws.ts CHANGED
@@ -9,7 +9,7 @@ import type { Api } from './api.ts';
9
9
  import type { Collab, RoomConn } from './collab/index.ts';
10
10
  import type { Context } from './context.ts';
11
11
  import { createHmrBroadcaster } from './hmr-broadcast.ts';
12
- import type { Inspect } from './inspect.ts';
12
+ import type { InspectRegistry } from './inspect.ts';
13
13
 
14
14
  /**
15
15
  * Per-connection state. `kind` discriminates between the legacy JSON
@@ -29,6 +29,11 @@ export type WsData =
29
29
  * per session, and a handshake is the one moment the session is
30
30
  * unambiguous. */
31
31
  readOnly: boolean;
32
+ /** WHOSE socket this is — Cloud Phase 27 D3. Same reasoning as `readOnly`
33
+ * one line up, for the other thing a cell has more than one of. `''` on
34
+ * a desktop, which is what makes every broadcast below reach everybody
35
+ * exactly as it always did. */
36
+ session: string;
32
37
  }
33
38
  | {
34
39
  id: string;
@@ -39,6 +44,14 @@ export type WsData =
39
44
  * canvas iframe origin, whose sync frames go through the DDR-122
40
45
  * origin gate in collab/room.ts. Set at upgrade time in server.ts. */
41
46
  realm: 'main' | 'canvas';
47
+ /** Cloud collab lane (RCA issue-cloud-live-collaboration-dead) — the
48
+ * role this socket was opened under, stamped at upgrade time from the
49
+ * proxy's injected header (fail-closed in a cell, always false on a
50
+ * loopback desktop). A read-only collab socket receives everything and
51
+ * publishes awareness (presence is the point), but its sync writes are
52
+ * gated to the comment lane in collab/room.ts — the WS mirror of the
53
+ * role matrix's `viewer.comment === true`. */
54
+ readOnly?: boolean;
42
55
  }
43
56
  | {
44
57
  // T2 (9.1-A) — HMR-only socket for the segregated canvas origin. Receives
@@ -139,7 +152,10 @@ export interface Ws {
139
152
  export function createWs(
140
153
  ctx: Context,
141
154
  api: Api,
142
- inspect: Inspect,
155
+ /** Cloud Phase 27 D3 — one inspector per member, not one per process. Every
156
+ * read/write below resolves through the socket's own session key, so a
157
+ * desktop (key `''`) keeps hitting the single instance it always had. */
158
+ inspects: InspectRegistry,
143
159
  collab: Collab,
144
160
  activity: Activity,
145
161
  acp: Acp
@@ -156,10 +172,16 @@ export function createWs(
156
172
 
157
173
  // Privileged inspector feed — comments, selection, ai-activity, git-lifecycle,
158
174
  // sync:status, fs:*. ONLY the same-origin inspector clients (the shell) get it.
159
- function broadcast(payload: unknown) {
175
+ function broadcast(payload: unknown, session?: string) {
160
176
  const msg = typeof payload === 'string' ? payload : JSON.stringify(payload);
161
177
  for (const ws of clients) {
162
178
  if (ws.data.kind !== 'inspector') continue;
179
+ // D3 — an event ABOUT one member goes only to that member. `session` is
180
+ // set by the emitter (inspect.ts stamps its own key); everything else —
181
+ // fs changes, comments, git lifecycle, sync status — is about the
182
+ // PROJECT and reaches everyone, which is why the audience is opt-in
183
+ // rather than the default.
184
+ if (session && ws.data.session !== session) continue;
163
185
  send(ws, msg);
164
186
  }
165
187
  }
@@ -177,8 +199,10 @@ export function createWs(
177
199
 
178
200
  // Wire bus -> WS broadcasts. inspect.ts emits 'selected' / 'active' after every
179
201
  // state write; fs-watch.ts emits 'fs:*' on every save.
180
- ctx.bus.on('selected', (sel) => broadcast({ type: 'selected', selected: sel }));
181
- ctx.bus.on('active', (file) => broadcast({ type: 'active', file }));
202
+ ctx.bus.on('selected', (sel, meta) =>
203
+ broadcast({ type: 'selected', selected: sel }, meta?.session)
204
+ );
205
+ ctx.bus.on('active', (file, meta) => broadcast({ type: 'active', file }, meta?.session));
182
206
  ctx.bus.on('fs:html', (file) => broadcast({ type: 'fs:html', file }));
183
207
  ctx.bus.on('fs:css', (file) => broadcast({ type: 'fs:css', file }));
184
208
  ctx.bus.on('fs:json', (file) => broadcast({ type: 'fs:json', file }));
@@ -259,11 +283,13 @@ export function createWs(
259
283
  function bindCollab(
260
284
  ws: ServerWebSocket<WsData>,
261
285
  slug: string,
262
- realm: 'main' | 'canvas'
286
+ realm: 'main' | 'canvas',
287
+ readOnly: boolean
263
288
  ): RoomConn {
264
289
  const conn: RoomConn = {
265
290
  id: ws.data.id,
266
291
  realm,
292
+ readOnly,
267
293
  send(payload: Uint8Array) {
268
294
  try {
269
295
  // Bun's ws.send accepts Uint8Array directly as binary.
@@ -281,7 +307,12 @@ export function createWs(
281
307
  async open(ws) {
282
308
  if (ws.data.kind === 'collab') {
283
309
  const room = collab.registry.get(ws.data.slug);
284
- const conn = bindCollab(ws, ws.data.slug, ws.data.realm ?? 'main');
310
+ const conn = bindCollab(
311
+ ws,
312
+ ws.data.slug,
313
+ ws.data.realm ?? 'main',
314
+ ws.data.readOnly === true
315
+ );
285
316
  await room.connect(conn);
286
317
  return;
287
318
  }
@@ -299,7 +330,11 @@ export function createWs(
299
330
  // opening mid-edit seeds its overlay state (Phase 13). Inspector-origin
300
331
  // sockets only — `canvas-hmr` sockets get no snapshot by design and rely
301
332
  // on live `activity` broadcasts.
302
- send(ws, { type: 'snapshot', state: inspect.state, activity: activity.state });
333
+ send(ws, {
334
+ type: 'snapshot',
335
+ state: inspects.for(ws.data.kind === 'inspector' ? ws.data.session : '').state,
336
+ activity: activity.state,
337
+ });
303
338
  },
304
339
  async close(ws) {
305
340
  if (ws.data.kind === 'acp') {
@@ -377,6 +412,7 @@ export function createWs(
377
412
  });
378
413
  return;
379
414
  }
415
+ const inspect = inspects.for(ws.data.kind === 'inspector' ? ws.data.session : '');
380
416
  if (msg.type === 'active' && typeof msg.file === 'string') inspect.setActive(msg.file);
381
417
  else if (msg.type === 'tabs' && Array.isArray(msg.tabs)) inspect.setOpenTabs(msg.tabs);
382
418
  else if (msg.type === 'select' && msg.selection) inspect.setSelected(msg.selection);
@@ -18,7 +18,7 @@ import { basename, join, resolve } from 'node:path';
18
18
  import { parseArgs } from '../lib/argv.mjs';
19
19
 
20
20
  const CONFIG_PATH = '.ai/workflows.config.json';
21
- const DEFAULT_ENGINE_VERSION = 'v0.1.9';
21
+ const DEFAULT_ENGINE_VERSION = 'v1.0.0';
22
22
  const KGAI_REPO = 'kgaidev/kgai';
23
23
 
24
24
  const VERBS = new Set([
@@ -195,21 +195,32 @@ function verbDoctor(state) {
195
195
  async function verbCheckUpstream(state) {
196
196
  const pinned = state.engineVersion;
197
197
  process.stdout.write(`maude kg check-upstream\n\n pinned (engineVersion): ${pinned}\n`);
198
- let latest = null;
199
- let assets = [];
200
- try {
201
- const res = await fetch(`https://api.github.com/repos/${KGAI_REPO}/releases/latest`, {
202
- headers: { 'user-agent': 'maude-kg', accept: 'application/vnd.github+json' },
203
- signal: AbortSignal.timeout(15000),
204
- });
205
- if (res.ok) {
198
+ const ghHeaders = { 'user-agent': 'maude-kg', accept: 'application/vnd.github+json' };
199
+ /** Asset names for one release ref, or null when the fetch itself failed. */
200
+ const assetsFor = async (path) => {
201
+ try {
202
+ const res = await fetch(`https://api.github.com/repos/${KGAI_REPO}/releases/${path}`, {
203
+ headers: ghHeaders,
204
+ signal: AbortSignal.timeout(15000),
205
+ });
206
+ if (!res.ok) return { ok: false, tag: null, assets: [] };
206
207
  const rel = await res.json();
207
- latest = rel.tag_name;
208
- assets = (rel.assets ?? []).map((a) => a.name);
208
+ return { ok: true, tag: rel.tag_name, assets: (rel.assets ?? []).map((a) => a.name) };
209
+ } catch {
210
+ return null; // offline — best-effort
209
211
  }
210
- } catch {
211
- /* offline — best-effort */
212
- }
212
+ };
213
+
214
+ const latestRel = await assetsFor('latest');
215
+ const latest = latestRel?.tag ?? null;
216
+ // The PINNED release is the one that matters: sync-kg.mjs downloads the
217
+ // desktop sidecars from `releases/download/<pinned>`. Reporting `latest`'s
218
+ // assets here said "✓" while the pinned release had been stripped of every
219
+ // asset upstream, so this check stayed green through a desktop build that was
220
+ // failing on exactly what it claims to verify (v0.53.2, kgai v0.1.9). A tag is
221
+ // a name, not a content hash — upstream can rewrite what it points at.
222
+ const pinnedRel = await assetsFor(`tags/${encodeURIComponent(pinned)}`);
223
+ const assets = pinnedRel?.assets ?? [];
213
224
  if (!latest) {
214
225
  process.stdout.write(' latest: unknown (offline) — using pinned.\n');
215
226
  return 0;
@@ -218,12 +229,31 @@ async function verbCheckUpstream(state) {
218
229
  process.stdout.write(
219
230
  ` status: ${latest === pinned ? '✓ up to date' : `⚠ upstream moved ${pinned} → ${latest}`}\n`
220
231
  );
221
- // Capability diff — flags/commands the plan's assumptions hinge on.
232
+ // Prebuilt assets ON THE PIN — what the desktop build actually downloads.
222
233
  const hasDarwin = assets.some((a) => /kg-darwin/.test(a));
223
234
  const hasKuzu = assets.some((a) => /libkuzu/.test(a));
224
- process.stdout.write('\n prebuilt assets:\n');
225
- process.stdout.write(` macOS kg binary: ${hasDarwin ? '✓' : '✗'}\n`);
226
- process.stdout.write(` libkuzu dylib/so: ${hasKuzu ? '✓' : '✗'}\n`);
235
+ const hasLinux = assets.some((a) => /kg-linux/.test(a));
236
+ process.stdout.write(`\n prebuilt assets on the PIN (${pinned}):\n`);
237
+ if (pinnedRel === null) {
238
+ process.stdout.write(' (offline — not checked)\n');
239
+ } else if (!pinnedRel.ok) {
240
+ process.stdout.write(` ✗ release ${pinned} not found upstream\n`);
241
+ } else {
242
+ process.stdout.write(` macOS kg binary: ${hasDarwin ? '✓' : '✗'}\n`);
243
+ process.stdout.write(` linux kg binary: ${hasLinux ? '✓' : '✗'}\n`);
244
+ process.stdout.write(` libkuzu dylib/so: ${hasKuzu ? '✓' : '✗'}\n`);
245
+ }
246
+ // A pin whose assets are gone breaks `sync-kg.mjs`, which fails the desktop
247
+ // build for macOS and Linux. Exit non-zero so this can gate a release.
248
+ const pinBroken = pinnedRel !== null && (!pinnedRel.ok || !hasDarwin || !hasKuzu || !hasLinux);
249
+ if (pinBroken) {
250
+ process.stdout.write(
251
+ `\n ✗ the pinned release cannot build the desktop app — sync-kg.mjs downloads from\n` +
252
+ ` releases/download/${pinned}, and those assets are missing. Upstream deleted or\n` +
253
+ ` rewrote the release. Bump the pin to one that still carries them (verify\n` +
254
+ ` compatibility first: CLI surface diff + a canonical-export comparison).\n`
255
+ );
256
+ }
227
257
  if (latest !== pinned) {
228
258
  process.stdout.write(
229
259
  '\n → Re-scan the capability surface (native --scope filter? kg import? Stop-hook/guessActor changes?),\n' +
@@ -231,7 +261,7 @@ async function verbCheckUpstream(state) {
231
261
  ' config.knowledgeGraph.engineVersion deliberately (never float — supply-chain surface, DDR-054/056).\n'
232
262
  );
233
263
  }
234
- return 0;
264
+ return pinBroken ? 1 : 0;
235
265
  }
236
266
 
237
267
  // ── verb: session-sync (SessionStart hook — non-blocking pull) ──────────────
@@ -34,6 +34,9 @@ export function buildBlock(designRel = '.design', { s3Assets = false } = {}) {
34
34
  `${root}/_server.log`,
35
35
  `${root}/_server.lock`,
36
36
  `${root}/_active.json`,
37
+ // Per-member siblings in a cloud cell (Phase 27 D3) — same runtime state,
38
+ // one file per person, and no more committable than the singleton.
39
+ `${root}/_active.*.json`,
37
40
  `${root}/_sync.json`, // linked-mode offline/sync status (Task 8)
38
41
  `${root}/_preflight.json`,
39
42
  `${root}/_locator.json`, // regenerable slug→path index
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1agh/maude",
3
- "version": "0.53.2",
3
+ "version": "0.55.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": {
@@ -49,16 +49,18 @@
49
49
  "test:e2e:desktop:lifecycle": "pnpm --filter @maude/desktop-e2e e2e:lifecycle",
50
50
  "test:e2e:desktop:switchrepos": "pnpm --filter @maude/desktop-e2e e2e:switchrepos",
51
51
  "test:e2e:desktop:onboarding": "pnpm --filter @maude/desktop-e2e e2e:onboarding",
52
- "test:e2e:desktop:acp-cold-start": "pnpm --filter @maude/desktop-e2e e2e:acp-cold-start"
52
+ "test:e2e:desktop:acp-cold-start": "pnpm --filter @maude/desktop-e2e e2e:acp-cold-start",
53
+ "test:e2e:desktop:parity": "pnpm --filter @maude/desktop-e2e e2e:parity",
54
+ "test:e2e:desktop:parity:cloud": "pnpm --filter @maude/desktop-e2e e2e:parity:cloud"
53
55
  },
54
56
  "optionalDependencies": {
55
- "@1agh/maude-darwin-arm64": "0.53.2",
56
- "@1agh/maude-darwin-x64": "0.53.2",
57
- "@1agh/maude-linux-arm64": "0.53.2",
58
- "@1agh/maude-linux-arm64-musl": "0.53.2",
59
- "@1agh/maude-linux-x64": "0.53.2",
60
- "@1agh/maude-linux-x64-musl": "0.53.2",
61
- "@1agh/maude-win32-x64": "0.53.2"
57
+ "@1agh/maude-darwin-arm64": "0.55.0",
58
+ "@1agh/maude-darwin-x64": "0.55.0",
59
+ "@1agh/maude-linux-arm64": "0.55.0",
60
+ "@1agh/maude-linux-arm64-musl": "0.55.0",
61
+ "@1agh/maude-linux-x64": "0.55.0",
62
+ "@1agh/maude-linux-x64-musl": "0.55.0",
63
+ "@1agh/maude-win32-x64": "0.55.0"
62
64
  },
63
65
  "files": [
64
66
  "cli",
@@ -156,7 +156,7 @@
156
156
  },
157
157
  "install": {
158
158
  "preferred": "See docs/kgai-onboarding.md — download the pinned kgai release binary (kgai is capability-gated + opt-out; nothing to install to use classic .ai/ mode)",
159
- "darwin": "curl -fsSL https://github.com/kgaidev/kgai/releases/download/v0.1.9/kg-darwin-arm64 -o /usr/local/bin/kg && chmod +x /usr/local/bin/kg"
159
+ "darwin": "curl -fsSL https://github.com/kgaidev/kgai/releases/download/v1.0.0/kg-darwin-arm64 -o /usr/local/bin/kg && chmod +x /usr/local/bin/kg"
160
160
  },
161
161
  "autoInstall": false,
162
162
  "fallbackBehavior": "Design DS/canvas/brand decisions are not recorded to the kgai graph; the classic .ai/ path (via /flow:record-ddr) and on-disk sidecars are unchanged.",
@@ -133,30 +133,30 @@
133
133
  <script type="importmap">
134
134
  {
135
135
  "imports": {
136
- "react": "/_canvas-runtime/react.js",
137
- "react-dom": "/_canvas-runtime/react-dom.js",
138
- "react-dom/client": "/_canvas-runtime/react-dom_client.js",
139
- "react/jsx-runtime": "/_canvas-runtime/react_jsx-runtime.js",
140
- "react/jsx-dev-runtime": "/_canvas-runtime/react_jsx-dev-runtime.js",
141
- "pixi.js": "/_canvas-runtime/pixi-js.js",
142
- "@imgly/background-removal": "/_canvas-runtime/@imgly_background-removal.js",
143
- "motion": "/_canvas-runtime/motion.js",
144
- "motion/react": "/_canvas-runtime/motion_react.js",
145
- "yjs": "/_canvas-runtime/yjs.js",
146
- "y-protocols/sync": "/_canvas-runtime/y-protocols_sync.js",
147
- "y-protocols/awareness": "/_canvas-runtime/y-protocols_awareness.js",
148
- "lib0/decoding": "/_canvas-runtime/lib0_decoding.js",
149
- "lib0/encoding": "/_canvas-runtime/lib0_encoding.js",
150
- "remotion": "/_canvas-runtime/remotion.js",
151
- "@remotion/media": "/_canvas-runtime/@remotion_media.js",
152
- "@remotion/player": "/_canvas-runtime/@remotion_player.js",
153
- "@remotion/transitions": "/_canvas-runtime/@remotion_transitions.js",
154
- "@remotion/transitions/fade": "/_canvas-runtime/@remotion_transitions_fade.js",
155
- "@remotion/transitions/slide": "/_canvas-runtime/@remotion_transitions_slide.js",
156
- "@remotion/transitions/wipe": "/_canvas-runtime/@remotion_transitions_wipe.js",
157
- "@remotion/transitions/flip": "/_canvas-runtime/@remotion_transitions_flip.js",
158
- "@remotion/transitions/clock-wipe": "/_canvas-runtime/@remotion_transitions_clock-wipe.js",
159
- "@remotion/transitions/none": "/_canvas-runtime/@remotion_transitions_none.js"
136
+ "react": "./_canvas-runtime/react.js",
137
+ "react-dom": "./_canvas-runtime/react-dom.js",
138
+ "react-dom/client": "./_canvas-runtime/react-dom_client.js",
139
+ "react/jsx-runtime": "./_canvas-runtime/react_jsx-runtime.js",
140
+ "react/jsx-dev-runtime": "./_canvas-runtime/react_jsx-dev-runtime.js",
141
+ "pixi.js": "./_canvas-runtime/pixi-js.js",
142
+ "@imgly/background-removal": "./_canvas-runtime/@imgly_background-removal.js",
143
+ "motion": "./_canvas-runtime/motion.js",
144
+ "motion/react": "./_canvas-runtime/motion_react.js",
145
+ "yjs": "./_canvas-runtime/yjs.js",
146
+ "y-protocols/sync": "./_canvas-runtime/y-protocols_sync.js",
147
+ "y-protocols/awareness": "./_canvas-runtime/y-protocols_awareness.js",
148
+ "lib0/decoding": "./_canvas-runtime/lib0_decoding.js",
149
+ "lib0/encoding": "./_canvas-runtime/lib0_encoding.js",
150
+ "remotion": "./_canvas-runtime/remotion.js",
151
+ "@remotion/media": "./_canvas-runtime/@remotion_media.js",
152
+ "@remotion/player": "./_canvas-runtime/@remotion_player.js",
153
+ "@remotion/transitions": "./_canvas-runtime/@remotion_transitions.js",
154
+ "@remotion/transitions/fade": "./_canvas-runtime/@remotion_transitions_fade.js",
155
+ "@remotion/transitions/slide": "./_canvas-runtime/@remotion_transitions_slide.js",
156
+ "@remotion/transitions/wipe": "./_canvas-runtime/@remotion_transitions_wipe.js",
157
+ "@remotion/transitions/flip": "./_canvas-runtime/@remotion_transitions_flip.js",
158
+ "@remotion/transitions/clock-wipe": "./_canvas-runtime/@remotion_transitions_clock-wipe.js",
159
+ "@remotion/transitions/none": "./_canvas-runtime/@remotion_transitions_none.js"
160
160
  }
161
161
  }
162
162
  </script>
@@ -190,8 +190,38 @@
190
190
  // `git show <sha>:<path>`). Baked into the module URL so both the initial
191
191
  // import and the HMR soft-reload carry it.
192
192
  const shaParam = params.get('sha');
193
+ // Cloud Phase 27 (DDR-209) — THE CAPABILITY TRAVELS WITH EVERY REQUEST.
194
+ //
195
+ // On a desktop the canvas origin is loopback and needs none, so `cap` is
196
+ // empty and every URL below is byte-identical to what it has always been.
197
+ //
198
+ // In the cloud the canvas origin is a cookieless hostname on the public
199
+ // internet: a cookie able to reach it would be readable by the canvas
200
+ // content itself, which is exactly what the DDR-054 split exists to
201
+ // prevent. So the capability rides in the URL — and it has to ride in
202
+ // EVERY url, not just this document's, because the shell then fetches its
203
+ // module, its tokens, its components CSS and its meta as separate
204
+ // requests. Getting the document through and nothing else is what
205
+ // "refused to connect" looks like from the outside.
206
+ const cap = params.get('t') || '';
207
+ // THE BASE THIS DOCUMENT WAS SERVED FROM, not the origin root.
208
+ //
209
+ // The fleet's canvas origin puts the project in the PATH
210
+ // (`canvas.<zone>/<project>/…`), so an absolute `/…` URL built here
211
+ // resolves against the origin and loses the project — the browser then
212
+ // asks for `canvas.<zone>/.design/…`, the data plane reads `.design` as a
213
+ // project name, and every dynamic import fails. Derived from where this
214
+ // document actually is rather than passed in, so it is right by
215
+ // construction in all three shells: on a desktop the shell is served at
216
+ // `/_canvas-shell.html` and the base is empty, leaving every URL below
217
+ // byte-identical to what it has always been.
218
+ const base = location.pathname.replace(/\/_canvas-shell(\.html)?$/, '');
219
+ const withCap = (u) => {
220
+ const abs = u.startsWith('/') ? base + u : u;
221
+ return cap ? abs + (abs.includes('?') ? '&' : '?') + 't=' + encodeURIComponent(cap) : abs;
222
+ };
193
223
  const canvasQuery = shaParam ? ('?sha=' + encodeURIComponent(shaParam)) : '';
194
- const canvasUrl = canvasRel ? ('/' + designRel + '/' + canvasRel + canvasQuery) : null;
224
+ const canvasUrl = canvasRel ? withCap('/' + designRel + '/' + canvasRel + canvasQuery) : null;
195
225
  const myFileKey = canvasRel ? (designRel + '/' + canvasRel) : null;
196
226
  // True while an `ai-activity` entry is live for THIS canvas (an agent is
197
227
  // running /design:edit|new on it). Mirrors ai-banner's gate: parent relays
@@ -274,7 +304,11 @@
274
304
  // The WS connection retries on close (dev-server restart resilience).
275
305
  function connectHmr() {
276
306
  const proto = location.protocol === 'https:' ? 'wss' : 'ws';
277
- const ws = new WebSocket(proto + '://' + location.host + '/_ws');
307
+ // `base` for the same reason every other URL here carries it: on the
308
+ // fleet's canvas origin the project is in the path. (A cell's HMR socket
309
+ // is a separate matter — it has no cookie to authenticate with, so it
310
+ // simply does not connect there; a canvas still renders.)
311
+ const ws = new WebSocket(proto + '://' + location.host + base + '/_ws');
278
312
  ws.addEventListener('message', (ev) => {
279
313
  try {
280
314
  const msg = JSON.parse(ev.data);
@@ -417,7 +451,7 @@
417
451
  // listens for `maude:meta-refreshed` and re-applies layout +
418
452
  // viewport in-place (no full reload — preserves tool / undo state).
419
453
  const metaFileParam = encodeURIComponent(designRel + '/' + canvasRel);
420
- fetch('/_api/canvas-meta?file=' + metaFileParam, {
454
+ fetch(withCap('/_api/canvas-meta?file=' + metaFileParam), {
421
455
  headers: { 'Cache-Control': 'no-store' },
422
456
  })
423
457
  .then((r) => (r.ok ? r.json() : null))
@@ -444,13 +478,13 @@
444
478
  if (!canvasRel) {
445
479
  showError('Missing ?canvas= query parameter. Add ?canvas=ui/<slug>.tsx.');
446
480
  } else {
447
- if (tokensRel) document.getElementById('canvas-tokens').href = '/' + designRel + '/' + tokensRel;
448
- if (componentsRel) document.getElementById('canvas-components').href = '/' + designRel + '/' + componentsRel;
481
+ if (tokensRel) document.getElementById('canvas-tokens').href = withCap('/' + designRel + '/' + tokensRel);
482
+ if (componentsRel) document.getElementById('canvas-components').href = withCap('/' + designRel + '/' + componentsRel);
449
483
  if (layoutRel) {
450
484
  // Inject a third <link> for the DS layout chrome (specimens load this).
451
485
  const layoutLink = document.createElement('link');
452
486
  layoutLink.rel = 'stylesheet';
453
- layoutLink.href = '/' + designRel + '/' + layoutRel;
487
+ layoutLink.href = withCap('/' + designRel + '/' + layoutRel);
454
488
  layoutLink.dataset.canvasCss = 'layout';
455
489
  document.head.appendChild(layoutLink);
456
490
  }
@@ -466,7 +500,7 @@
466
500
  // server-side api.fileSlug (which trims paths.designRel).
467
501
  window.__canvas_design_rel__ = designRel;
468
502
  window.__canvas_rel__ = canvasRel;
469
- const metaFetch = fetch('/_api/canvas-meta?file=' + metaFileParam, {
503
+ const metaFetch = fetch(withCap('/_api/canvas-meta?file=' + metaFileParam), {
470
504
  headers: { 'Cache-Control': 'no-store' },
471
505
  })
472
506
  .then((r) => (r.ok ? r.json() : null))
@@ -484,7 +518,7 @@
484
518
  // the importmap so the canvas module and the comment layer share one
485
519
  // React singleton (inlining a second React would break hooks).
486
520
  const [{ mountCanvas }, mod] = await Promise.all([
487
- import('/_client/comment-mount.js'),
521
+ import(withCap('/_client/comment-mount.js')),
488
522
  import(canvasUrl),
489
523
  metaFetch,
490
524
  ]);
@@ -324,7 +324,7 @@
324
324
  },
325
325
  "engineVersion": {
326
326
  "type": "string",
327
- "default": "v0.1.9",
327
+ "default": "v1.0.0",
328
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)."
329
329
  },
330
330
  "store": {
@@ -105,7 +105,7 @@
105
105
  },
106
106
  "install": {
107
107
  "preferred": "See docs/kgai-onboarding.md — download the pinned kgai release binary (kgai is capability-gated + opt-out; nothing to install to use classic .ai/ mode)",
108
- "darwin": "curl -fsSL https://github.com/kgaidev/kgai/releases/download/v0.1.9/kg-darwin-arm64 -o /usr/local/bin/kg && chmod +x /usr/local/bin/kg"
108
+ "darwin": "curl -fsSL https://github.com/kgaidev/kgai/releases/download/v1.0.0/kg-darwin-arm64 -o /usr/local/bin/kg && chmod +x /usr/local/bin/kg"
109
109
  },
110
110
  "autoInstall": false,
111
111
  "fallbackBehavior": "flow/design fall back to the classic .ai/decisions/ + STATE.md file path (no regression). The kgai knowledge-graph backend (kg context/ingest/sync, cross-repo company memory) is inactive.",