feat: adopt shared wasm wire schemas; resurrect collab e2e bundle (ysync 0008 A)

- standalone wasm/collab/types.ts: the CollabItem/CollabDelta data shapes now
  come from MIT @pcbjam/shared (collab-wire zod schemas) via re-export; the
  RUNTIME adapter interface (CollabBridge) stays here. emptyDelta/isEmptyDelta
  alias the shared helpers — reconciler untouched.
- tests/collab/browser-entry.ts: fix the import path stale since the repo
  restructure (web/apps/frontend → web/standalone) — the collab bundle had been
  silently unbuildable; its opts type now tracks startCollab's real signature.
- tests/kicad/*-collab.spec.ts: update KicadCollab.start calls from the
  pre-ysync-0004 { channel, settleMs } API to { provider: { kind:
  "broadcastchannel", settleMs }, room } — the drift the broken bundle hid.

Verified: pl_editor 2/2, eeschema 2 passed, pcbnew 7 passed (skips pre-existing);
shared 47 unit tests; both typechecks clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Gergő Törcsvári 2026-06-11 10:48:02 +02:00
commit b69ba09ebd
No known key found for this signature in database
GPG key ID: 8E75F2CDE64E5322
7 changed files with 28 additions and 26 deletions

View file

@ -16,11 +16,12 @@ import {
function start( function start(
mod: CollabModule, mod: CollabModule,
win: CollabWindow, win: CollabWindow,
opts: { channel: string; settleMs?: number }, // Same opts as startCollab itself (ysync 0004: { provider, room }).
opts: Parameters<typeof startCollab>[2],
): ReturnType<typeof startCollab> { ): ReturnType<typeof startCollab> {
return startCollab(mod, win, { return startCollab(mod, win, {
provider: { kind: "broadcastchannel", settleMs: opts.settleMs }, provider: { kind: "broadcastchannel", settleMs: opts.settleMs },
room: opts.channel, room: opts.room,
}); });
} }

View file

@ -240,7 +240,7 @@ test.describe("eeschema collab bridge — two tabs (BroadcastChannel)", () => {
KicadCollab: { start: (m: unknown, win: unknown, o: unknown) => Promise<unknown> }; KicadCollab: { start: (m: unknown, win: unknown, o: unknown) => Promise<unknown> };
Module: unknown; Module: unknown;
}; };
await w.KicadCollab.start(w.Module, window, { channel: ch, settleMs: 500 }); await w.KicadCollab.start(w.Module, window, { provider: { kind: "broadcastchannel", settleMs: 500 }, room: ch });
}, channel); }, channel);
await startCollab(tabA); await startCollab(tabA);
await startCollab(tabB); await startCollab(tabB);

View file

@ -414,7 +414,7 @@ test.describe("pcbnew collab bridge — two tabs (BroadcastChannel)", () => {
KicadCollab: { start: (m: unknown, win: unknown, o: unknown) => Promise<unknown> }; KicadCollab: { start: (m: unknown, win: unknown, o: unknown) => Promise<unknown> };
Module: unknown; Module: unknown;
}; };
await w.KicadCollab.start(w.Module, window, { channel: ch, settleMs: 500 }); await w.KicadCollab.start(w.Module, window, { provider: { kind: "broadcastchannel", settleMs: 500 }, room: ch });
}, channel); }, channel);
await startCollab(tabA); await startCollab(tabA);
await startCollab(tabB); await startCollab(tabB);

View file

@ -195,7 +195,7 @@ test.describe("pl_editor collab bridge — two tabs (BroadcastChannel)", () => {
Module: unknown; Module: unknown;
__collab?: unknown; __collab?: unknown;
}; };
w.__collab = await w.KicadCollab.start(w.Module, window, { channel: ch, settleMs: 500 }); w.__collab = await w.KicadCollab.start(w.Module, window, { provider: { kind: "broadcastchannel", settleMs: 500 }, room: ch });
}, channel); }, channel);
await startCollab(tabA); await startCollab(tabA);
await startCollab(tabB); await startCollab(tabB);

@ -1 +1 @@
Subproject commit 011962c0992c5c30eba5487033f0c3609712dd28 Subproject commit 26d2d7951ba6a9574ff5149fb1a29a2a69d9176c

3
web/pnpm-lock.yaml generated
View file

@ -45,6 +45,9 @@ importers:
'@ts-rest/core': '@ts-rest/core':
specifier: ^3.52.1 specifier: ^3.52.1
version: 3.52.1(@types/node@22.19.19)(zod@3.25.76) version: 3.52.1(@types/node@22.19.19)(zod@3.25.76)
yjs:
specifier: ^13.6.18
version: 13.6.31
zod: zod:
specifier: ^3.24.1 specifier: ^3.24.1
version: 3.25.76 version: 3.25.76

View file

@ -1,18 +1,21 @@
// Wire contract shared with the C++ bridge (wasm/bindings/pl_editor_embind.cpp). // Wire contract shared with the C++ bridge (wasm/bindings/*_embind.cpp).
// Schema-agnostic by design: an item is just { id, type, …arbitrary fields }. The //
// reconciler hardcodes no field names — it diffs values keyed by field name. // The data shapes (CollabItem / CollabDelta) and their zod schemas now live in
// MIT `@pcbjam/shared` (collab-wire.ts, ysync 0008) so any consumer — this GPL
// editor, tests, a backend — can validate the wasm from-to JSON against one
// source of truth. This module re-exports them under the established local names
// and keeps the RUNTIME adapter interface (CollabBridge), which is about how this
// app talks to a live wasm Module, not about the data.
export type CollabItem = { import {
id: string; emptyCollabDelta,
type: string; isEmptyCollabDelta,
[field: string]: unknown; type CollabDelta,
}; type CollabItem,
} from "@pcbjam/shared";
export type CollabDelta = { export { collabDeltaSchema, collabItemSchema, parseCollabDelta } from "@pcbjam/shared";
added: CollabItem[]; export type { CollabDelta, CollabItem };
changed: CollabItem[];
removed: string[]; // uuids
};
/** /**
* The two C++ bridge entry points + the emit hook, abstracted so the reconciler is * The two C++ bridge entry points + the emit hook, abstracted so the reconciler is
@ -27,10 +30,5 @@ export interface CollabBridge {
onDelta(cb: (deltaJson: string) => void): void; onDelta(cb: (deltaJson: string) => void): void;
} }
export function emptyDelta(): CollabDelta { export const emptyDelta = emptyCollabDelta;
return { added: [], changed: [], removed: [] }; export const isEmptyDelta = isEmptyCollabDelta;
}
export function isEmptyDelta(d: CollabDelta): boolean {
return d.added.length === 0 && d.changed.length === 0 && d.removed.length === 0;
}