@1agh/maude 0.58.1 → 0.58.2

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.
@@ -1171,6 +1171,124 @@ describe('DDR-102 — auth-failure intelligence + boot summary', () => {
1171
1171
  }
1172
1172
  });
1173
1173
 
1174
+ test('a successful re-probe clears the refusal, and the reconcile is a separate word', async () => {
1175
+ // A REGRESSION GUARD, not a falsifier — this already held. It is here
1176
+ // because the clear moved: it used to sit at the BOTTOM of the
1177
+ // post-handshake path, below a reconcile that can throw into a rejection
1178
+ // `settleWait` swallows, so a document that reconnected fine but failed to
1179
+ // settle on disk would have kept telling the user to go fix a credential
1180
+ // that was never the problem. The clear now runs on the handshake, and the
1181
+ // reconcile reports separately (`pending` vs `connected`) and loudly.
1182
+ const url = 'https://hub.example.com';
1183
+ writeHubsConfig(url, 'mau_test');
1184
+ const ctx = makeCtx({ url, linkedAt: 1 });
1185
+ writeFileSync(join(ctx.paths.designRoot, 'ui', 'a.html'), '<a/>');
1186
+
1187
+ const timers = fakeTimerQueue();
1188
+ const origWarn = console.warn;
1189
+ const origLog = console.log;
1190
+ console.warn = () => {};
1191
+ console.log = () => {};
1192
+
1193
+ let round = 0;
1194
+ const factory = (args: { documentName: string; document?: Y.Doc }) => {
1195
+ const document = args.document ?? new Y.Doc();
1196
+ const first = round++ === 0;
1197
+ return {
1198
+ document,
1199
+ onAuthFailed(cb: (info: { reason: string }) => void) {
1200
+ if (first) queueMicrotask(() => cb({ reason: 'token not authorized' }));
1201
+ return () => {};
1202
+ },
1203
+ onceSynced: () => (first ? new Promise<void>(() => {}) : Promise.resolve()),
1204
+ destroy() {},
1205
+ };
1206
+ };
1207
+
1208
+ try {
1209
+ const runtime = createSyncRuntime(ctx, {
1210
+ providerFactory: factory,
1211
+ auth: {
1212
+ warnDebounceMs: 2_000,
1213
+ reprobeMs: 300_000,
1214
+ settleTimeoutMs: 15_000,
1215
+ setTimer: timers.setTimer,
1216
+ clearTimer: timers.clearTimer,
1217
+ },
1218
+ });
1219
+ await runtime?.start();
1220
+ await new Promise((res) => setTimeout(res, 10));
1221
+ expect(runtime?.status().docs?.rejected).toBe(1);
1222
+
1223
+ timers.fire(300_000);
1224
+ await new Promise((res) => setTimeout(res, 40));
1225
+
1226
+ expect(runtime?.status().docs?.rejected).toBe(0);
1227
+ expect(runtime?.status().rejectedSlugs ?? []).toEqual([]);
1228
+
1229
+ await runtime?.stop();
1230
+ } finally {
1231
+ console.warn = origWarn;
1232
+ console.log = origLog;
1233
+ }
1234
+ });
1235
+
1236
+ test('a new process never serves the previous one’s verdict, not even mid-boot', async () => {
1237
+ // `_sync.json` is a file and `/_sync-status` returns whatever is in it. The
1238
+ // first honest snapshot of a run is not written until every provider has
1239
+ // been built — after a scan AND a listing fetch that waits up to 6 seconds.
1240
+ // For that whole window the CLI and the browser banner were reading the
1241
+ // LAST run's counters as current. So the window is what this asserts: the
1242
+ // fetch is held open, and the file must ALREADY be clean.
1243
+ const url = 'https://hub.example.com';
1244
+ writeHubsConfig(url, 'mau_test');
1245
+ const ctx = makeCtx({ url, linkedAt: 1 });
1246
+ const statusFile = join(ctx.paths.designRoot, '_sync.json');
1247
+ writeFileSync(
1248
+ statusFile,
1249
+ JSON.stringify({
1250
+ url,
1251
+ canvases: 76,
1252
+ state: 'online',
1253
+ docs: { synced: 0, pending: 3, rejected: 73 },
1254
+ rejectedSlugs: ['ui-a', 'ui-b'],
1255
+ })
1256
+ );
1257
+ writeFileSync(join(ctx.paths.designRoot, 'ui', 'a.html'), '<a/>');
1258
+
1259
+ const realFetch = globalThis.fetch;
1260
+ let releaseListing: () => void = () => {};
1261
+ const listingReached = new Promise<void>((res) => {
1262
+ globalThis.fetch = (async () => {
1263
+ res();
1264
+ await new Promise<void>((r) => {
1265
+ releaseListing = r;
1266
+ });
1267
+ return new Response(JSON.stringify({ documents: [] }), { status: 200 });
1268
+ }) as typeof fetch;
1269
+ });
1270
+
1271
+ try {
1272
+ const { factory } = inMemoryProviderFactory();
1273
+ const runtime = createSyncRuntime(ctx, { providerFactory: factory });
1274
+ const booting = runtime?.start();
1275
+ await listingReached;
1276
+
1277
+ // Mid-boot, with the listing still in flight — exactly where the stale
1278
+ // file used to be authoritative.
1279
+ const midBoot = JSON.parse(readFileSync(statusFile, 'utf8'));
1280
+ expect(midBoot.docs.rejected).toBe(0);
1281
+ expect(midBoot.rejectedSlugs ?? []).toEqual([]);
1282
+ expect(midBoot.state).toBe('connecting');
1283
+
1284
+ releaseListing();
1285
+ await booting;
1286
+ await runtime?.stop();
1287
+ } finally {
1288
+ globalThis.fetch = realFetch;
1289
+ }
1290
+ });
1291
+
1174
1292
  test('boot summary prints AFTER settle with honest counts (not the premature N/N)', async () => {
1175
1293
  const url = 'https://hub.example.com';
1176
1294
  writeHubsConfig(url, 'mau_test');
@@ -1,6 +1,15 @@
1
1
  {
2
2
  "$schema": "./whats-new.schema.json",
3
3
  "entries": [
4
+ {
5
+ "id": "sync-carries-the-path",
6
+ "version": "0.58.2",
7
+ "date": "2026-08-08",
8
+ "kind": "fix",
9
+ "title": "Canvases arrive in the folder you made",
10
+ "summary": "A canvas you create in a nested folder now shows up on your other machine — and in the cloud — in that same folder, instead of quietly landing somewhere the file tree never listed. Connecting a project to an empty folder brings the whole thing down with its structure intact.",
11
+ "surface": "design-ui"
12
+ },
4
13
  {
5
14
  "id": "release-version-visible",
6
15
  "version": "0.58.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1agh/maude",
3
- "version": "0.58.1",
3
+ "version": "0.58.2",
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.1",
58
- "@1agh/maude-darwin-x64": "0.58.1",
59
- "@1agh/maude-linux-arm64": "0.58.1",
60
- "@1agh/maude-linux-arm64-musl": "0.58.1",
61
- "@1agh/maude-linux-x64": "0.58.1",
62
- "@1agh/maude-linux-x64-musl": "0.58.1",
63
- "@1agh/maude-win32-x64": "0.58.1"
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"
64
64
  },
65
65
  "files": [
66
66
  "cli",