@1agh/maude 1.4.4 → 1.4.5
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/apps/studio/api.ts +4 -0
- package/apps/studio/canvas-comment-mount.tsx +13 -1
- package/apps/studio/canvas-shell.tsx +62 -13
- package/apps/studio/canvas-source-memo.ts +39 -0
- package/apps/studio/canvas-text-patch.ts +117 -0
- package/apps/studio/client/app.jsx +105 -60
- package/apps/studio/client/file-tree.jsx +157 -0
- package/apps/studio/client/index.html +1 -1
- package/apps/studio/client/panels/DiffView.jsx +37 -8
- package/apps/studio/client/panels/GitPanel.jsx +2 -1
- package/apps/studio/client/panels/SourceConflictPanel.jsx +19 -17
- package/apps/studio/client/styles/3-shell-maude.css +21 -9
- package/apps/studio/client/styles/4-components.css +4 -2
- package/apps/studio/dist/client.bundle.js +1086 -1086
- package/apps/studio/dist/comment-mount.js +2 -2
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/hmr-broadcast.ts +59 -3
- package/apps/studio/http.ts +3 -0
- package/apps/studio/sync/accepted-cold-start.ts +35 -3
- package/apps/studio/sync/accepted-link.ts +165 -29
- package/apps/studio/sync/file-plane.ts +24 -1
- package/apps/studio/sync/index.ts +87 -17
- package/apps/studio/sync/journal-client.ts +9 -3
- package/apps/studio/sync/projection.ts +114 -11
- package/apps/studio/sync/remote-docs.ts +3 -2
- package/apps/studio/sync/source-recovery.ts +67 -0
- package/apps/studio/sync/status.ts +11 -3
- package/apps/studio/sync/transaction-client.ts +49 -4
- package/apps/studio/use-selection-set.tsx +9 -3
- package/apps/studio/use-undo-stack.tsx +22 -2
- package/apps/studio/whats-new.json +9 -0
- package/cli/lib/workspace-plan.mjs +10 -0
- package/package.json +8 -8
- package/plugins/design/templates/_shell.html +21 -0
|
@@ -152,12 +152,14 @@ export function SelectionSetProvider({
|
|
|
152
152
|
}) {
|
|
153
153
|
const [selected, setSelected] = useState<Selection[]>([]);
|
|
154
154
|
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
155
|
+
const pendingPostRef = useRef<(() => void) | null>(null);
|
|
155
156
|
|
|
156
157
|
const post = useCallback(
|
|
157
158
|
(next: Selection[]) => {
|
|
158
159
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
159
|
-
|
|
160
|
+
pendingPostRef.current = () => {
|
|
160
161
|
timerRef.current = null;
|
|
162
|
+
pendingPostRef.current = null;
|
|
161
163
|
const target = postTarget ?? (typeof window !== 'undefined' ? window.parent : null);
|
|
162
164
|
if (!target) return;
|
|
163
165
|
// Wire shape: single object for N=1 (back-compat), array for N>1, null for empty.
|
|
@@ -168,15 +170,19 @@ export function SelectionSetProvider({
|
|
|
168
170
|
} catch {
|
|
169
171
|
/* iframe likely cross-origin or detached */
|
|
170
172
|
}
|
|
171
|
-
}
|
|
173
|
+
};
|
|
174
|
+
timerRef.current = setTimeout(() => pendingPostRef.current?.(), POST_DEBOUNCE_MS);
|
|
172
175
|
},
|
|
173
176
|
[postTarget]
|
|
174
177
|
);
|
|
175
178
|
|
|
176
|
-
//
|
|
179
|
+
// Soft HMR replaces this provider too. Deliver the last local selection
|
|
180
|
+
// before the replacement's loaded handshake, so the shell can reselect it.
|
|
181
|
+
// Cancelling silently loses a click made inside the 50 ms debounce window.
|
|
177
182
|
useEffect(
|
|
178
183
|
() => () => {
|
|
179
184
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
185
|
+
pendingPostRef.current?.();
|
|
180
186
|
},
|
|
181
187
|
[]
|
|
182
188
|
);
|
|
@@ -99,6 +99,11 @@ const UndoSinksContext = createContext<UndoSinksValue | null>(null);
|
|
|
99
99
|
|
|
100
100
|
const NOOP_SINKS: UndoSinksValue = { setSink: () => {} };
|
|
101
101
|
|
|
102
|
+
// The iframe survives a soft HMR swap, but its Provider does not. Keep pending
|
|
103
|
+
// operations serialized across that handoff; remove idle queues so this map
|
|
104
|
+
// retains neither closed canvases nor completed command closures.
|
|
105
|
+
const canvasFlights = new Map<string, Promise<void>>();
|
|
106
|
+
|
|
102
107
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
103
108
|
// Provider
|
|
104
109
|
|
|
@@ -185,10 +190,25 @@ export function UndoStackProvider({
|
|
|
185
190
|
}, []);
|
|
186
191
|
|
|
187
192
|
const enqueue = useCallback((task: () => Promise<void>): Promise<void> => {
|
|
188
|
-
const
|
|
189
|
-
|
|
193
|
+
const file = fileRef.current;
|
|
194
|
+
const previous = (file && canvasFlights.get(file)) || inFlightRef.current;
|
|
195
|
+
const run = async () => {
|
|
196
|
+
// The previous mount may have persisted its ACK after this mount read
|
|
197
|
+
// the initial state. Re-read only at the serialized operation boundary.
|
|
198
|
+
if (file) stateRef.current = loadStackState(file);
|
|
199
|
+
await task();
|
|
200
|
+
};
|
|
201
|
+
const next = previous.then(run, run);
|
|
202
|
+
const settled = next.catch(() => {
|
|
190
203
|
/* swallow — per-op error already reported */
|
|
191
204
|
});
|
|
205
|
+
inFlightRef.current = settled;
|
|
206
|
+
if (file) {
|
|
207
|
+
canvasFlights.set(file, settled);
|
|
208
|
+
void settled.then(() => {
|
|
209
|
+
if (canvasFlights.get(file) === settled) canvasFlights.delete(file);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
192
212
|
return next;
|
|
193
213
|
}, []);
|
|
194
214
|
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./whats-new.schema.json",
|
|
3
3
|
"entries": [
|
|
4
|
+
{
|
|
5
|
+
"id": "team-projects-hold-up",
|
|
6
|
+
"version": "1.4.5",
|
|
7
|
+
"date": "2026-09-25",
|
|
8
|
+
"kind": "fix",
|
|
9
|
+
"title": "Team projects that hold up under failure",
|
|
10
|
+
"summary": "Edits made in the browser on a self-hosted workspace now reach the project, switching a project to saved revisions finishes on its own even if the connection drops for a moment, and removing a teammate ends their access at once. Cloud projects also keep work written just before a server crash.",
|
|
11
|
+
"surface": "design-ui"
|
|
12
|
+
},
|
|
4
13
|
{
|
|
5
14
|
"id": "steadier-team-projects",
|
|
6
15
|
"version": "1.4.2",
|
|
@@ -357,6 +357,15 @@ export function envEntries(cfg, { hubSecret, adminPassword, renderSecret }) {
|
|
|
357
357
|
value: '1',
|
|
358
358
|
comment: 'containment invariant enforced (DDR-193 §2)',
|
|
359
359
|
},
|
|
360
|
+
{
|
|
361
|
+
// The browser studio joins the project as a participant (loopback
|
|
362
|
+
// pairing, DDR-209). Without it a browser edit on a project that saves
|
|
363
|
+
// through accepted revisions never becomes an accepted action (F3 S09,
|
|
364
|
+
// 2026-09-25) — and the hub refuses that switch.
|
|
365
|
+
key: 'MAUDE_CELL_PAIRING',
|
|
366
|
+
value: '1',
|
|
367
|
+
comment: 'the browser studio is a project participant (required for accepted revisions)',
|
|
368
|
+
},
|
|
360
369
|
{
|
|
361
370
|
key: 'MAUDE_ADMIN_EMAIL',
|
|
362
371
|
value: cfg.adminEmail,
|
|
@@ -501,6 +510,7 @@ export function renderCompose(cfg) {
|
|
|
501
510
|
'HUB_WORKSPACE_MODE',
|
|
502
511
|
...(cfg.local ? ['HUB_INSECURE_HTTP'] : []),
|
|
503
512
|
'MAUDE_WORKSPACE_MODE',
|
|
513
|
+
'MAUDE_CELL_PAIRING',
|
|
504
514
|
'MAUDE_ADMIN_EMAIL',
|
|
505
515
|
// The password has to cross into the container too, or the hub knows WHO
|
|
506
516
|
// the first user is and has no way to create them — which is precisely the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1agh/maude",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.5",
|
|
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": {
|
|
@@ -58,13 +58,13 @@
|
|
|
58
58
|
"test:e2e:desktop:parity:cloud": "pnpm --filter @maude/desktop-e2e e2e:parity:cloud"
|
|
59
59
|
},
|
|
60
60
|
"optionalDependencies": {
|
|
61
|
-
"@1agh/maude-darwin-arm64": "1.4.
|
|
62
|
-
"@1agh/maude-darwin-x64": "1.4.
|
|
63
|
-
"@1agh/maude-linux-arm64": "1.4.
|
|
64
|
-
"@1agh/maude-linux-arm64-musl": "1.4.
|
|
65
|
-
"@1agh/maude-linux-x64": "1.4.
|
|
66
|
-
"@1agh/maude-linux-x64-musl": "1.4.
|
|
67
|
-
"@1agh/maude-win32-x64": "1.4.
|
|
61
|
+
"@1agh/maude-darwin-arm64": "1.4.5",
|
|
62
|
+
"@1agh/maude-darwin-x64": "1.4.5",
|
|
63
|
+
"@1agh/maude-linux-arm64": "1.4.5",
|
|
64
|
+
"@1agh/maude-linux-arm64-musl": "1.4.5",
|
|
65
|
+
"@1agh/maude-linux-x64": "1.4.5",
|
|
66
|
+
"@1agh/maude-linux-x64-musl": "1.4.5",
|
|
67
|
+
"@1agh/maude-win32-x64": "1.4.5"
|
|
68
68
|
},
|
|
69
69
|
"files": [
|
|
70
70
|
"cli",
|
|
@@ -266,6 +266,21 @@
|
|
|
266
266
|
}
|
|
267
267
|
});
|
|
268
268
|
|
|
269
|
+
// text-patches:begin
|
|
270
|
+
function applyTextPatches(patches) {
|
|
271
|
+
for (const patch of patches.slice(0, 500)) {
|
|
272
|
+
if (!patch || typeof patch.id !== 'string' || typeof patch.text !== 'string') continue;
|
|
273
|
+
if (!/^[0-9a-f]{8}$/.test(patch.id)) continue;
|
|
274
|
+
for (const el of document.querySelectorAll('[data-cd-id="' + patch.id + '"][data-cd-editable="text"]')) {
|
|
275
|
+
if (el.isContentEditable || el.contains(document.activeElement)) continue;
|
|
276
|
+
const only = el.childNodes.length === 1 ? el.firstChild : null;
|
|
277
|
+
if (!only || only.nodeType !== Node.TEXT_NODE) continue;
|
|
278
|
+
if (only.data !== patch.text) only.data = patch.text;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// text-patches:end
|
|
283
|
+
|
|
269
284
|
// Soft reload — re-import the canvas module (cache-busted) and swap it in
|
|
270
285
|
// via the runtime, holding the last good render on a build/import error
|
|
271
286
|
// (no white flash). Phase 30 (F4): used for EVERY canvas-body change while
|
|
@@ -473,6 +488,12 @@
|
|
|
473
488
|
} else if (msg.mode === 'module' || msg.mode === 'hard') {
|
|
474
489
|
// Only reload when the change touches *this* canvas (or `_lib`).
|
|
475
490
|
if (msg.mode === 'hard' || !msg.file || (canvasRel && msg.file === canvasRel)) {
|
|
491
|
+
// F4 — a text-only change arrives with the new strings. Show
|
|
492
|
+
// them now; the remount below still re-renders from source and
|
|
493
|
+
// confirms (or corrects) them. Only a leaf that is exactly one
|
|
494
|
+
// text node is touched, via textContent (never markup), and
|
|
495
|
+
// never the element someone is typing in on this device.
|
|
496
|
+
if (msg.mode === 'module' && Array.isArray(msg.patches)) applyTextPatches(msg.patches);
|
|
476
497
|
// Phase 12.3 — CSS-panel echo guard. The panel applies each
|
|
477
498
|
// edit optimistically to the live DOM (dgn:'apply-style'), so a
|
|
478
499
|
// `module` reload for THIS canvas's own edit is redundant churn:
|