authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-09-03 17:08:54-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-03 17:08:54-07:00
log9c9ae3dc0e94a957d9c4a61bb211caf914828054
treedf8cc90df72f78bb64fffe9e2e4df1489c992e4b
parentc0ebc882b88dbcffd5a712e403b3c3c517ba4ea6
signature Signed by PGP key B5690EEEBB952194

fix(server): keep events during thread subscription startup (#9521)


2 files changed, 11 insertions(+), 5 deletions(-)

apps/server/src/server.test.ts+8-4
......@@ -7304,7 +7304,6 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
73047304 projectionSnapshotQuery: {
73057305 getThreadDetailSnapshot: () =>
73067306 Effect.gen(function* () {
7307 yield* Effect.sleep("25 millis");
73087307 yield* PubSub.publish(liveEvents, messageEvent);
73097308 return Option.some({ snapshotSequence: 1, thread });
73107309 }),
......@@ -7317,14 +7316,19 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
73177316 withWsRpcClient(wsUrl, (client) =>
73187317 client[ORCHESTRATION_WS_METHODS.subscribeThread]({
73197318 threadId: defaultThreadId,
7320 }).pipe(Stream.take(2), Stream.runCollect),
7319 requestCompletionMarker: true,
7320 }).pipe(
7321 Stream.takeUntil((item) => item.kind === "synchronized"),
7322 Stream.runCollect,
7323 ),
73217324 ),
7322 ).pipe(Effect.timeout("2 seconds"));
7325 );
73237326
73247327 assert.equal(items[0]?.kind, "snapshot");
73257328 assert.equal(items[1]?.kind, "event");
73267329 assert.equal(items[1]?.kind === "event" ? items[1].event.sequence : null, 2);
7327 }).pipe(Effect.provide(NodeHttpServer.layerTest), TestClock.withLive),
7330 assert.equal(items[2]?.kind, "synchronized");
7331 }).pipe(Effect.provide(NodeHttpServer.layerTest)),
73287332 );
73297333
73307334 it.effect("coalesces buffered live tool updates to the latest state", () =>
apps/server/src/ws.ts+3-1
......@@ -1549,7 +1549,9 @@ const makeWsRpcLayer = (
15491549 // Attach live delivery before reading either replay or snapshot state.
15501550 // Otherwise an event published while the snapshot is loading is lost.
15511551 const liveBuffer = yield* makeThreadLiveEventCoalescer();
1552 yield* Effect.forkScoped(liveStream.pipe(Stream.runForEach(liveBuffer.offer)));
1552 yield* Effect.forkScoped(liveStream.pipe(Stream.runForEach(liveBuffer.offer)), {
1553 startImmediately: true,
1554 });
15531555 const bufferedLiveStream = liveBuffer.stream;
15541556
15551557 // When the client already loaded the snapshot over HTTP it passes