@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.
Files changed (34) hide show
  1. package/apps/studio/api.ts +4 -0
  2. package/apps/studio/canvas-comment-mount.tsx +13 -1
  3. package/apps/studio/canvas-shell.tsx +62 -13
  4. package/apps/studio/canvas-source-memo.ts +39 -0
  5. package/apps/studio/canvas-text-patch.ts +117 -0
  6. package/apps/studio/client/app.jsx +105 -60
  7. package/apps/studio/client/file-tree.jsx +157 -0
  8. package/apps/studio/client/index.html +1 -1
  9. package/apps/studio/client/panels/DiffView.jsx +37 -8
  10. package/apps/studio/client/panels/GitPanel.jsx +2 -1
  11. package/apps/studio/client/panels/SourceConflictPanel.jsx +19 -17
  12. package/apps/studio/client/styles/3-shell-maude.css +21 -9
  13. package/apps/studio/client/styles/4-components.css +4 -2
  14. package/apps/studio/dist/client.bundle.js +1086 -1086
  15. package/apps/studio/dist/comment-mount.js +2 -2
  16. package/apps/studio/dist/styles.css +1 -1
  17. package/apps/studio/hmr-broadcast.ts +59 -3
  18. package/apps/studio/http.ts +3 -0
  19. package/apps/studio/sync/accepted-cold-start.ts +35 -3
  20. package/apps/studio/sync/accepted-link.ts +165 -29
  21. package/apps/studio/sync/file-plane.ts +24 -1
  22. package/apps/studio/sync/index.ts +87 -17
  23. package/apps/studio/sync/journal-client.ts +9 -3
  24. package/apps/studio/sync/projection.ts +114 -11
  25. package/apps/studio/sync/remote-docs.ts +3 -2
  26. package/apps/studio/sync/source-recovery.ts +67 -0
  27. package/apps/studio/sync/status.ts +11 -3
  28. package/apps/studio/sync/transaction-client.ts +49 -4
  29. package/apps/studio/use-selection-set.tsx +9 -3
  30. package/apps/studio/use-undo-stack.tsx +22 -2
  31. package/apps/studio/whats-new.json +9 -0
  32. package/cli/lib/workspace-plan.mjs +10 -0
  33. package/package.json +8 -8
  34. 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
- timerRef.current = setTimeout(() => {
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
- }, POST_DEBOUNCE_MS);
173
+ };
174
+ timerRef.current = setTimeout(() => pendingPostRef.current?.(), POST_DEBOUNCE_MS);
172
175
  },
173
176
  [postTarget]
174
177
  );
175
178
 
176
- // Cleanup the debounce timer on unmount.
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 next = inFlightRef.current.then(task, task);
189
- inFlightRef.current = next.catch(() => {
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.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.4",
62
- "@1agh/maude-darwin-x64": "1.4.4",
63
- "@1agh/maude-linux-arm64": "1.4.4",
64
- "@1agh/maude-linux-arm64-musl": "1.4.4",
65
- "@1agh/maude-linux-x64": "1.4.4",
66
- "@1agh/maude-linux-x64-musl": "1.4.4",
67
- "@1agh/maude-win32-x64": "1.4.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: