@1agh/maude 1.2.0 → 1.3.1
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.
- package/README.md +3 -1
- package/apps/studio/acp/index.ts +43 -7
- package/apps/studio/annotations-layer.tsx +191 -116
- package/apps/studio/annotations-model.ts +39 -0
- package/apps/studio/annotations-sync.ts +50 -0
- package/apps/studio/api.ts +732 -38
- package/apps/studio/bin/annotate.mjs +3 -1
- package/apps/studio/bin/server-up.sh +17 -2
- package/apps/studio/canvas-build.ts +49 -8
- package/apps/studio/canvas-edit.ts +468 -7
- package/apps/studio/canvas-lib.tsx +82 -18
- package/apps/studio/canvas-list-watch.ts +33 -2
- package/apps/studio/canvas-notice-message.ts +16 -0
- package/apps/studio/canvas-notifications.tsx +19 -0
- package/apps/studio/canvas-shell.tsx +57 -6
- package/apps/studio/client/app.jsx +783 -245
- package/apps/studio/client/apply-edit-request.ts +55 -0
- package/apps/studio/client/canvas-url.js +39 -1
- package/apps/studio/client/export-center.jsx +111 -61
- package/apps/studio/client/github.js +38 -1
- package/apps/studio/client/index-loader.ts +76 -0
- package/apps/studio/client/panels/CloudBar.jsx +52 -6
- package/apps/studio/client/panels/GitPanel.jsx +103 -0
- package/apps/studio/client/panels/OnboardingWizard.jsx +36 -7
- package/apps/studio/client/panels/RepoBranchSwitcher.jsx +92 -27
- package/apps/studio/client/panels/SourceConflictPanel.jsx +166 -0
- package/apps/studio/client/panels/SyncPanel.jsx +113 -0
- package/apps/studio/client/panels/TeamProjects.jsx +376 -0
- package/apps/studio/client/panels/file-deep-link-dialog.jsx +54 -0
- package/apps/studio/client/photo-knobs.jsx +3 -0
- package/apps/studio/client/share-dialog.jsx +91 -0
- package/apps/studio/client/share-link.js +94 -0
- package/apps/studio/client/styles/3-shell-maude.css +36 -18
- package/apps/studio/client/styles/4-components.css +18 -1
- package/apps/studio/client/tree-row-menu.jsx +48 -3
- package/apps/studio/client/whats-new.jsx +26 -32
- package/apps/studio/cloud/endpoints.ts +132 -0
- package/apps/studio/collab/awareness-bridge.ts +65 -14
- package/apps/studio/collab/index.ts +4 -0
- package/apps/studio/collab/persistence.ts +5 -1
- package/apps/studio/collab/registry.ts +9 -3
- package/apps/studio/collab/room.ts +13 -2
- package/apps/studio/context.ts +59 -0
- package/apps/studio/dist/client.bundle.js +1594 -1562
- package/apps/studio/dist/comment-mount.js +2 -2
- package/apps/studio/dist/runtime/.min-sizes.json +1 -0
- package/apps/studio/dist/runtime/sonner.js +1 -0
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/git/accepted-guard.ts +36 -0
- package/apps/studio/hmr-broadcast.ts +34 -1
- package/apps/studio/http.ts +300 -18
- package/apps/studio/inspect.ts +19 -0
- package/apps/studio/managed-projects.ts +172 -0
- package/apps/studio/notifications.tsx +292 -0
- package/apps/studio/runtime-bundle.ts +2 -0
- package/apps/studio/server.ts +59 -3
- package/apps/studio/sync/accepted-cold-start.ts +225 -0
- package/apps/studio/sync/accepted-link.ts +320 -0
- package/apps/studio/sync/action-stage.ts +343 -0
- package/apps/studio/sync/agent.ts +12 -60
- package/apps/studio/sync/cell-file-events.ts +3 -0
- package/apps/studio/sync/codec.ts +73 -6
- package/apps/studio/sync/ctl-provider.ts +15 -2
- package/apps/studio/sync/document-discovery.ts +81 -0
- package/apps/studio/sync/file-membership.ts +10 -2
- package/apps/studio/sync/file-plane.ts +309 -26
- package/apps/studio/sync/index.ts +1174 -21
- package/apps/studio/sync/migrate-seed.ts +110 -19
- package/apps/studio/sync/poke.ts +4 -2
- package/apps/studio/sync/presentation.ts +315 -2
- package/apps/studio/sync/projection.ts +820 -23
- package/apps/studio/sync/repeated-module.ts +73 -0
- package/apps/studio/sync/revision-barrier.ts +129 -0
- package/apps/studio/sync/seed-repair.ts +46 -0
- package/apps/studio/sync/source-merge.ts +100 -0
- package/apps/studio/sync/source-ops.ts +289 -0
- package/apps/studio/sync/source-recovery.ts +70 -0
- package/apps/studio/sync/source-validation.ts +56 -0
- package/apps/studio/sync/status.ts +83 -1
- package/apps/studio/sync/transaction-client.ts +486 -0
- package/apps/studio/sync/writer-registry.ts +236 -0
- package/apps/studio/text-caret.ts +35 -0
- package/apps/studio/undo-hud.tsx +9 -87
- package/apps/studio/use-canvas-media-drop.tsx +4 -39
- package/apps/studio/use-tool-mode.tsx +44 -0
- package/apps/studio/whats-new.json +137 -0
- package/cli/lib/harness/codex-runtime.mjs +4 -1
- package/package.json +9 -8
- package/plugins/design/dependencies.json +3 -3
- package/plugins/design/templates/_shell.html +95 -7
- package/plugins/flow/dependencies.json +3 -3
|
@@ -1,6 +1,143 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./whats-new.schema.json",
|
|
3
3
|
"entries": [
|
|
4
|
+
{
|
|
5
|
+
"id": "live-canvases-long-sessions",
|
|
6
|
+
"version": "1.3.1",
|
|
7
|
+
"date": "2026-09-15",
|
|
8
|
+
"kind": "fix",
|
|
9
|
+
"title": "Canvases stay live in long sessions",
|
|
10
|
+
"summary": "A canvas open in your browser on a Maude Cloud project kept working for about 15 minutes and then quietly stopped showing teammates’ changes until you reloaded the page — it now stays live however long you work. And editing a design system’s tokens.css restyles every open canvas that uses it, straight away.",
|
|
11
|
+
"surface": "design-ui"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"id": "team-access-and-presence",
|
|
15
|
+
"version": "1.3.1",
|
|
16
|
+
"date": "2026-09-15",
|
|
17
|
+
"kind": "fix",
|
|
18
|
+
"title": "Lost access? Maude asks you to sign in again",
|
|
19
|
+
"summary": "If an owner removes your access or your sign-in expires, the Sync panel now tells you to sign in again — instead of \"check your connection\" — and keeps your changes on this computer until you do. Signing in again sends them. And when a teammate closes a canvas, their avatar and cursor now leave everyone else’s screen straight away.",
|
|
20
|
+
"surface": "design-ui"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "comment-tool-and-live-tree",
|
|
24
|
+
"version": "1.3.1",
|
|
25
|
+
"date": "2026-09-15",
|
|
26
|
+
"kind": "fix",
|
|
27
|
+
"title": "The Comment tool works from the toolbar; new images and folders show up for everyone at once",
|
|
28
|
+
"summary": "Picking Comment in the canvas toolbar now lets you click an element to comment — before, only the C key did. An image saved next to your canvases appears in the file tree straight away, on your machine and your teammates’. A folder you create or move outside Maude, or move in the tree, reaches the team in seconds instead of on the next launch.",
|
|
29
|
+
"surface": "design-ui"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "inspector-keeps-number-types",
|
|
33
|
+
"version": "1.3.1",
|
|
34
|
+
"date": "2026-09-15",
|
|
35
|
+
"kind": "fix",
|
|
36
|
+
"title": "Editing a number in the inspector keeps it a number",
|
|
37
|
+
"summary": "Changing a value like an artboard height or a video’s length used to turn it into text in the canvas source — some components then misread it. Numbers and on/off values now stay what they were, and leaving a value unchanged no longer rewrites the file.",
|
|
38
|
+
"surface": "design-ui"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"id": "artboard-rename-and-files",
|
|
42
|
+
"version": "1.3.0",
|
|
43
|
+
"date": "2026-09-15",
|
|
44
|
+
"kind": "feature",
|
|
45
|
+
"title": "Rename artboards on the canvas; manage notes and images in the tree",
|
|
46
|
+
"summary": "Double-click an artboard’s name to rename it — everyone on the project sees the new name. Notes, styles, images and videos beside your canvases now have their own ⋯ menu: Rename…, Move to… and Delete. A file a canvas still uses is not moved or deleted from under it; Maude names the canvas that uses it instead.",
|
|
47
|
+
"surface": "design-ui"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "cloud-connect-starts-syncing",
|
|
51
|
+
"version": "1.3.0",
|
|
52
|
+
"date": "2026-09-15",
|
|
53
|
+
"kind": "fix",
|
|
54
|
+
"title": "Connecting a project to Maude Cloud starts syncing right away again",
|
|
55
|
+
"summary": "Connect used to link the project and then ask you to restart Maude before anything synced. It syncs immediately now, a project that is not linked no longer greets you with a stale “Working offline” note, and a large video upload survives a dropped connection instead of failing.",
|
|
56
|
+
"surface": "design-ui"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": "file-tree-rename-duplicate",
|
|
60
|
+
"version": "1.3.0",
|
|
61
|
+
"date": "2026-09-15",
|
|
62
|
+
"kind": "feature",
|
|
63
|
+
"title": "Rename and duplicate canvases and folders from the file tree",
|
|
64
|
+
"summary": "The ⋯ menu on a canvas now offers Rename… and Duplicate, and folders can be renamed in place. Teammates see the new name immediately — a canvas they have open follows the rename. Menus near the bottom of a long tree now open upward instead of off-screen, and after ⌘D the copy is what stays selected.",
|
|
65
|
+
"surface": "design-ui"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"id": "open-team-project-no-folder",
|
|
69
|
+
"version": "1.3.0",
|
|
70
|
+
"date": "2026-09-15",
|
|
71
|
+
"kind": "feature",
|
|
72
|
+
"title": "Open your team’s project in one step",
|
|
73
|
+
"summary": "Invited to a project? Choose “Open a project you were invited to”, sign in to Maude Cloud or your team’s server with your email, pick the project and start designing — no folder to choose, no token, no Git. Maude keeps the project’s copy for you and lists it under “Open a team project…”.",
|
|
74
|
+
"surface": "design-ui"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": "reliable-project-saving",
|
|
78
|
+
"version": "1.3.0",
|
|
79
|
+
"date": "2026-09-15",
|
|
80
|
+
"kind": "feature",
|
|
81
|
+
"title": "Every change saved, shared and undoable",
|
|
82
|
+
"summary": "In projects that save through accepted revisions, the status says “Saving…” until your team’s project has your change, History lists each action with its author, Undo takes back only your own action, two people changing the same thing end with the later change instead of a conflict, an unfinished AI edit waits for your decision, and large videos upload and download resumably.",
|
|
83
|
+
"surface": "design-ui"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"id": "share-link-deeplink",
|
|
87
|
+
"version": "1.3.0",
|
|
88
|
+
"date": "2026-09-15",
|
|
89
|
+
"kind": "feature",
|
|
90
|
+
"title": "Share a link to any canvas",
|
|
91
|
+
"summary": "Copy a link that opens a canvas or file in the browser or Maude. After signing in, return to the shared file; links to another local project ask before switching.",
|
|
92
|
+
"learnMore": "https://maude.sh/docs/share-links",
|
|
93
|
+
"surface": "design-ui",
|
|
94
|
+
"tour": [
|
|
95
|
+
{
|
|
96
|
+
"target": "[data-testid=\"share-btn\"]",
|
|
97
|
+
"title": "Share this file",
|
|
98
|
+
"body": "Copy a web link, an app link, or a link for this Mac. You can also share another file from its row menu.",
|
|
99
|
+
"placement": "bottom",
|
|
100
|
+
"canvas": true
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"id": "native-codex-shared-workflows",
|
|
106
|
+
"version": "1.3.0",
|
|
107
|
+
"date": "2026-09-15",
|
|
108
|
+
"kind": "feature",
|
|
109
|
+
"title": "Use Maude workflows in Codex",
|
|
110
|
+
"summary": "Install Maude’s design and flow plugins natively in Codex and use the shared workflows alongside Claude Code. The new guide covers setup, dependencies and the browser requirements for working with Design Studio.",
|
|
111
|
+
"learnMore": "https://maude.sh/docs/codex",
|
|
112
|
+
"surface": "design-ui"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"id": "shared-toast-stack-export-errors",
|
|
116
|
+
"version": "1.3.0",
|
|
117
|
+
"date": "2026-09-15",
|
|
118
|
+
"kind": "improvement",
|
|
119
|
+
"title": "Notifications stack and clear themselves",
|
|
120
|
+
"summary": "Export updates, canvas messages and Undo now share a stack that expands when you hover or use the keyboard. Notifications dismiss automatically; long export errors stay short, with the complete message available on demand in export history.",
|
|
121
|
+
"surface": "design-ui"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"id": "source-sync-preserves-valid-canvases",
|
|
125
|
+
"version": "1.3.0",
|
|
126
|
+
"date": "2026-09-15",
|
|
127
|
+
"kind": "fix",
|
|
128
|
+
"title": "Safer canvas source syncing",
|
|
129
|
+
"summary": "Sync now holds incoming canvas code with syntax errors or duplicate declarations and warns you in the Sync panel. Concurrent edits preserve more unchanged code, and recovery copies help you recover local work when a conflicting update is held.",
|
|
130
|
+
"surface": "design-ui"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"id": "sticky-notes-take-a-second-line",
|
|
134
|
+
"version": "1.3.0",
|
|
135
|
+
"date": "2026-09-15",
|
|
136
|
+
"kind": "fix",
|
|
137
|
+
"title": "Sticky notes take a second line",
|
|
138
|
+
"summary": "Shift+Enter in a sticky note could look like it did nothing. A note never grew, and its text is clipped at the card's edge, so on a full note the new line was inserted and then hidden — notes now grow as you type and keep that height. Opening a note from the keyboard selects all of its text so you can retype it, and Shift+Enter used to replace the lot; it now adds a line instead, while typing still replaces. And on a project synced to a workspace, what you have typed into an open note is no longer overwritten mid-keystroke when a collaborator edits the same note.",
|
|
139
|
+
"surface": "design-ui"
|
|
140
|
+
},
|
|
4
141
|
{
|
|
5
142
|
"id": "pdf-export-print-safe-text",
|
|
6
143
|
"version": "1.2.0",
|
|
@@ -384,7 +384,7 @@ async function materializeRuntimeMarketplace({
|
|
|
384
384
|
return root;
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
-
async function materializePluginSource(item, target, ownedRoot) {
|
|
387
|
+
export async function materializePluginSource(item, target, ownedRoot) {
|
|
388
388
|
await removeOwnedPath(target, ownedRoot);
|
|
389
389
|
await assertTreeHasNoSymlinks(item.value.installPath);
|
|
390
390
|
await cp(item.value.installPath, target, { recursive: true });
|
|
@@ -395,6 +395,9 @@ async function materializePluginSource(item, target, ownedRoot) {
|
|
|
395
395
|
`plugin changed while materializing: ${item.name} (${item.sourceHash} != ${copiedHash})`
|
|
396
396
|
);
|
|
397
397
|
}
|
|
398
|
+
// A source-owned native package already supplies its entry points and paths.
|
|
399
|
+
// Flattening Claude commands over it would break relative reference links.
|
|
400
|
+
if (await pathExists(join(target, '.codex-plugin', 'plugin.json'))) return;
|
|
398
401
|
const commands = await snapshotCommandFiles(target);
|
|
399
402
|
const generatedRoot = join(target, 'skills');
|
|
400
403
|
await mkdir(generatedRoot, { recursive: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1agh/maude",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
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": {
|
|
@@ -52,16 +52,17 @@
|
|
|
52
52
|
"test:e2e:desktop:acp-cold-start": "pnpm --filter @maude/desktop-e2e e2e:acp-cold-start",
|
|
53
53
|
"test:e2e:desktop:parity": "pnpm --filter @maude/desktop-e2e e2e:parity",
|
|
54
54
|
"test:e2e:desktop:sidecar-respawn": "pnpm --filter @maude/desktop-e2e e2e:sidecar-respawn",
|
|
55
|
+
"test:e2e:desktop:team-project": "pnpm --filter @maude/desktop-e2e e2e:team-project",
|
|
55
56
|
"test:e2e:desktop:parity:cloud": "pnpm --filter @maude/desktop-e2e e2e:parity:cloud"
|
|
56
57
|
},
|
|
57
58
|
"optionalDependencies": {
|
|
58
|
-
"@1agh/maude-darwin-arm64": "1.
|
|
59
|
-
"@1agh/maude-darwin-x64": "1.
|
|
60
|
-
"@1agh/maude-linux-arm64": "1.
|
|
61
|
-
"@1agh/maude-linux-arm64-musl": "1.
|
|
62
|
-
"@1agh/maude-linux-x64": "1.
|
|
63
|
-
"@1agh/maude-linux-x64-musl": "1.
|
|
64
|
-
"@1agh/maude-win32-x64": "1.
|
|
59
|
+
"@1agh/maude-darwin-arm64": "1.3.1",
|
|
60
|
+
"@1agh/maude-darwin-x64": "1.3.1",
|
|
61
|
+
"@1agh/maude-linux-arm64": "1.3.1",
|
|
62
|
+
"@1agh/maude-linux-arm64-musl": "1.3.1",
|
|
63
|
+
"@1agh/maude-linux-x64": "1.3.1",
|
|
64
|
+
"@1agh/maude-linux-x64-musl": "1.3.1",
|
|
65
|
+
"@1agh/maude-win32-x64": "1.3.1"
|
|
65
66
|
},
|
|
66
67
|
"files": [
|
|
67
68
|
"cli",
|
|
@@ -152,11 +152,11 @@
|
|
|
152
152
|
"hardness": "soft",
|
|
153
153
|
"check": {
|
|
154
154
|
"command": "kg version",
|
|
155
|
-
"expectExit": 0
|
|
155
|
+
"expectExit": 0,
|
|
156
|
+
"minVersion": "1.5.1"
|
|
156
157
|
},
|
|
157
158
|
"install": {
|
|
158
|
-
"preferred": "
|
|
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"
|
|
159
|
+
"preferred": "Install kgai >= 1.5.1 using https://github.com/kgaidev/kgai installation instructions; reuse the existing trusted store. Classic file workflows need no kgai installation."
|
|
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.",
|
|
@@ -138,6 +138,7 @@
|
|
|
138
138
|
"react-dom/client": "./_canvas-runtime/react-dom_client.js",
|
|
139
139
|
"react/jsx-runtime": "./_canvas-runtime/react_jsx-runtime.js",
|
|
140
140
|
"react/jsx-dev-runtime": "./_canvas-runtime/react_jsx-dev-runtime.js",
|
|
141
|
+
"sonner": "./_canvas-runtime/sonner.js",
|
|
141
142
|
"pixi.js": "./_canvas-runtime/pixi-js.js",
|
|
142
143
|
"@imgly/background-removal": "./_canvas-runtime/@imgly_background-removal.js",
|
|
143
144
|
"motion": "./_canvas-runtime/motion.js",
|
|
@@ -204,7 +205,7 @@
|
|
|
204
205
|
// module, its tokens, its components CSS and its meta as separate
|
|
205
206
|
// requests. Getting the document through and nothing else is what
|
|
206
207
|
// "refused to connect" looks like from the outside.
|
|
207
|
-
|
|
208
|
+
let cap = params.get('t') || '';
|
|
208
209
|
// THE BASE THIS DOCUMENT WAS SERVED FROM, not the origin root.
|
|
209
210
|
//
|
|
210
211
|
// The fleet's canvas origin puts the project in the PATH
|
|
@@ -222,7 +223,7 @@
|
|
|
222
223
|
return cap ? abs + (abs.includes('?') ? '&' : '?') + 't=' + encodeURIComponent(cap) : abs;
|
|
223
224
|
};
|
|
224
225
|
const canvasQuery = shaParam ? ('?sha=' + encodeURIComponent(shaParam)) : '';
|
|
225
|
-
|
|
226
|
+
let canvasUrl = canvasRel ? withCap('/' + designRel + '/' + canvasRel + canvasQuery) : null;
|
|
226
227
|
const myFileKey = canvasRel ? (designRel + '/' + canvasRel) : null;
|
|
227
228
|
// True while an `ai-activity` entry is live for THIS canvas (an agent is
|
|
228
229
|
// running /design:edit|new on it). Mirrors ai-banner's gate: parent relays
|
|
@@ -249,6 +250,19 @@
|
|
|
249
250
|
// The panel just optimistically applied an edit → skip the redundant
|
|
250
251
|
// follow-up module reload (set here, parent-verified).
|
|
251
252
|
lastCssOptimisticAt = Date.now();
|
|
253
|
+
} else if (m.dgn === 'canvas-cap') {
|
|
254
|
+
// A re-minted capability (it expires; app.jsx refreshes it). Every
|
|
255
|
+
// URL built from here on carries it, and asking for this shell
|
|
256
|
+
// document with it re-plants the origin's cookie — the one module
|
|
257
|
+
// imports, assets and the live sockets fall back to. Without this an
|
|
258
|
+
// open canvas stopped loading and updating once its first capability
|
|
259
|
+
// expired. Only a well-formed token, and only in the cloud (a
|
|
260
|
+
// desktop canvas never had one).
|
|
261
|
+
const t = m.t;
|
|
262
|
+
if (!cap || typeof t !== 'string' || t.length > 4096 || !/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(t)) return;
|
|
263
|
+
cap = t;
|
|
264
|
+
if (canvasRel) canvasUrl = withCap('/' + designRel + '/' + canvasRel + canvasQuery);
|
|
265
|
+
fetch(withCap('/_canvas-shell.html'), { cache: 'no-store', credentials: 'same-origin' }).catch(() => {});
|
|
252
266
|
}
|
|
253
267
|
});
|
|
254
268
|
|
|
@@ -331,6 +345,7 @@
|
|
|
331
345
|
// - module: location.reload() — the canvas module re-fetches.
|
|
332
346
|
// - hard: location.reload() — _lib/* changed, everything re-bundles.
|
|
333
347
|
// The WS connection retries on close (dev-server restart resilience).
|
|
348
|
+
const pendingMediaHeals = new WeakMap();
|
|
334
349
|
function connectHmr() {
|
|
335
350
|
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
|
|
336
351
|
// `base` for the same reason every other URL here carries it: on the
|
|
@@ -392,12 +407,22 @@
|
|
|
392
407
|
if (msg.type !== 'canvas-hmr') return;
|
|
393
408
|
if (msg.mode === 'css') {
|
|
394
409
|
const v = msg.version || Date.now();
|
|
395
|
-
// Match by
|
|
396
|
-
|
|
410
|
+
// Match by PATH when we have one; otherwise refresh all. By path,
|
|
411
|
+
// not by file name: every design system has a `tokens.css`, and
|
|
412
|
+
// a change to one canvas's imported `system/<a>/tokens.css`
|
|
413
|
+
// "matched" the shell's link to `system/<b>/tokens.css` — which
|
|
414
|
+
// swapped the wrong sheet and skipped the reload below that the
|
|
415
|
+
// changed one needed (plan T31/L16).
|
|
416
|
+
const changed = msg.file ? '/' + msg.file : '';
|
|
397
417
|
let matched = false;
|
|
398
418
|
for (const link of document.querySelectorAll('link[rel="stylesheet"]')) {
|
|
399
419
|
const href = link.getAttribute('href') || '';
|
|
400
|
-
if (!
|
|
420
|
+
if (!href) continue;
|
|
421
|
+
let linkPath = '';
|
|
422
|
+
try {
|
|
423
|
+
linkPath = decodeURIComponent(new URL(href, location.href).pathname);
|
|
424
|
+
} catch {}
|
|
425
|
+
if (!changed || linkPath.endsWith(changed)) {
|
|
401
426
|
const base = href.split('?')[0];
|
|
402
427
|
link.setAttribute('href', base + '?v=' + v);
|
|
403
428
|
matched = true;
|
|
@@ -414,7 +439,21 @@
|
|
|
414
439
|
if (!matched && msg.file && canvasRel) {
|
|
415
440
|
const cssDir = msg.file.replace(/[^/]+$/, '');
|
|
416
441
|
const canvasDir = canvasRel.replace(/[^/]+$/, '');
|
|
417
|
-
if (cssDir === canvasDir)
|
|
442
|
+
if (cssDir === canvasDir) {
|
|
443
|
+
location.reload();
|
|
444
|
+
} else {
|
|
445
|
+
// …and a stylesheet imported from ANYWHERE else (a design
|
|
446
|
+
// system's tokens.css) is inlined the same way. The module's
|
|
447
|
+
// style tag names what it inlined; re-import so it re-injects.
|
|
448
|
+
// (Canvas script could add names here, but that can only
|
|
449
|
+
// FORCE a reload — the safe direction.)
|
|
450
|
+
var bundled = document.querySelector('style[data-canvas-css="bundled"]');
|
|
451
|
+
var names = bundled && bundled.dataset.canvasCssSources ? bundled.dataset.canvasCssSources.split('|') : [];
|
|
452
|
+
if (names.indexOf(msg.file) !== -1) {
|
|
453
|
+
if (window.__maudeCanvasRuntime && window.__maudeCanvasRuntime.remount) softReload(v);
|
|
454
|
+
else location.reload();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
418
457
|
}
|
|
419
458
|
} else if (msg.mode === 'module' || msg.mode === 'hard') {
|
|
420
459
|
// Only reload when the change touches *this* canvas (or `_lib`).
|
|
@@ -441,7 +480,11 @@
|
|
|
441
480
|
// direction; it can never suppress one.)
|
|
442
481
|
var hasComps = false;
|
|
443
482
|
try { hasComps = !!(window.__maude_comps__ && window.__maude_comps__().length); } catch (err) { hasComps = false; }
|
|
444
|
-
|
|
483
|
+
// …but NEVER a sync write (`remote`): it can carry a
|
|
484
|
+
// teammate's change merged under this person's own edit (a
|
|
485
|
+
// lost race re-applied onto theirs), and skipping it left
|
|
486
|
+
// their change off this canvas until a manual reload.
|
|
487
|
+
if (!hasComps && !msg.remote && Date.now() - lastCssOptimisticAt < 1500) return;
|
|
445
488
|
}
|
|
446
489
|
// Phase 30 (F4) — prefer an in-place hot-swap for ANY canvas-body
|
|
447
490
|
// (`module`) change whenever the canvas runtime is mounted: the
|
|
@@ -534,6 +577,51 @@
|
|
|
534
577
|
// working one is a cheap refetch of now-cached bytes.
|
|
535
578
|
el.setAttribute('href', href.split('?')[0] + '?v=' + v);
|
|
536
579
|
}
|
|
580
|
+
// A reference can reach a peer before its video/audio bytes.
|
|
581
|
+
// Retry failed players when those bytes arrive, without resetting
|
|
582
|
+
// healthy playback or dropping capability parameters from the URL.
|
|
583
|
+
const arrivedUrl = new URL(withCap('/' + designRel + '/' + msg.file), document.baseURI);
|
|
584
|
+
for (const media of document.querySelectorAll('video, audio')) {
|
|
585
|
+
if (media.readyState > 0) continue;
|
|
586
|
+
const candidates = media.hasAttribute('src')
|
|
587
|
+
? [media]
|
|
588
|
+
: Array.from(media.querySelectorAll('source[src]'));
|
|
589
|
+
const targets = candidates.flatMap((target) => {
|
|
590
|
+
const src = target.getAttribute('src');
|
|
591
|
+
if (!src) return [];
|
|
592
|
+
try {
|
|
593
|
+
const url = new URL(src, document.baseURI);
|
|
594
|
+
if (url.origin !== arrivedUrl.origin || url.pathname !== arrivedUrl.pathname) return [];
|
|
595
|
+
url.searchParams.set('v', String(v));
|
|
596
|
+
return [{ target, src, next: url.href }];
|
|
597
|
+
} catch { return []; }
|
|
598
|
+
});
|
|
599
|
+
if (!targets.length) continue;
|
|
600
|
+
pendingMediaHeals.get(media)?.();
|
|
601
|
+
const clear = () => {
|
|
602
|
+
media.removeEventListener('error', retry, true);
|
|
603
|
+
media.removeEventListener('loadedmetadata', clear);
|
|
604
|
+
media.removeEventListener('emptied', clear);
|
|
605
|
+
pendingMediaHeals.delete(media);
|
|
606
|
+
};
|
|
607
|
+
const retry = () => {
|
|
608
|
+
clear();
|
|
609
|
+
if (!media.isConnected || media.readyState > 0) return;
|
|
610
|
+
if (targets.some(({ target, src }) => target.getAttribute('src') !== src)) return;
|
|
611
|
+
for (const { target, next } of targets) target.setAttribute('src', next);
|
|
612
|
+
media.load();
|
|
613
|
+
};
|
|
614
|
+
if (media.error || media.networkState === 3) {
|
|
615
|
+
retry();
|
|
616
|
+
} else if (media.networkState === 2) {
|
|
617
|
+
// The initial response can still be an older in-flight 404.
|
|
618
|
+
// One arrival permits one retry; success/source change cancels it.
|
|
619
|
+
pendingMediaHeals.set(media, clear);
|
|
620
|
+
media.addEventListener('error', retry, true);
|
|
621
|
+
media.addEventListener('loadedmetadata', clear);
|
|
622
|
+
media.addEventListener('emptied', clear);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
537
625
|
}
|
|
538
626
|
} catch (e) {
|
|
539
627
|
console.warn('[canvas-shell] HMR message parse error', e);
|
|
@@ -101,11 +101,11 @@
|
|
|
101
101
|
"hardness": "soft",
|
|
102
102
|
"check": {
|
|
103
103
|
"command": "kg version",
|
|
104
|
-
"expectExit": 0
|
|
104
|
+
"expectExit": 0,
|
|
105
|
+
"minVersion": "1.5.1"
|
|
105
106
|
},
|
|
106
107
|
"install": {
|
|
107
|
-
"preferred": "
|
|
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"
|
|
108
|
+
"preferred": "Install kgai >= 1.5.1 using https://github.com/kgaidev/kgai installation instructions; reuse the existing trusted store. Classic file workflows need no kgai installation."
|
|
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.",
|