History

Return existing bootstrap thread IDs in welcome and guard root navigatio

- Server welcome now includes bootstrap project/thread IDs when cwd project/thread already exist - Added regression test for reconnecting with persisted bootstrap state - Root route ignores post-dispose welcome handling and marks thread as handled after navigate

Julius Marminge committed 8 months ago

Commit cd685c91 parent a8d0e9b3 files · +63 −2Browse files

Unified · Split

3 files
  1. apps/server/src/wsServer.test.ts +56 −1
  2. apps/server/src/wsServer.ts +3 −0
  3. apps/web/src/routes/__root.tsx +4 −1
apps/server/src/wsServer.test.ts+56−1
@@ -38,7 +38,7 @@ import type { · 37 unmodified lines
3838 TerminalWriteInput,
3939 } from "@t3tools/contracts";
4040 import { TerminalManager, type TerminalManagerShape } from "./terminal/Services/Manager";
41−import { SqlitePersistenceMemory } from "./persistence/Layers/Sqlite";
41+import { makeSqlitePersistenceLive, SqlitePersistenceMemory } from "./persistence/Layers/Sqlite";
4242 import { SqlClient } from "effect/unstable/sql";
4343 import { ProviderService, type ProviderServiceShape } from "./provider/Services/ProviderService";
4444 import { Open, type OpenShape } from "./open";
@@ -506,6 +506,61 @@ describe("WebSocket Server", () => { · 461 unmodified lines
506506 );
507507 });
508508
509+ it("includes bootstrap ids in welcome when cwd project and thread already exist", async () => {
510+ const stateDir = makeTempDir("t3code-state-bootstrap-existing-");
511+ const persistenceLayer = makeSqlitePersistenceLive(
512+ path.join(stateDir, "state.sqlite"),
513+ ) as unknown as Layer.Layer<SqlClient.SqlClient, never>;
514+ const cwd = "/test/bootstrap-existing";
515+
516+ server = await createTestServer({
517+ cwd,
518+ stateDir,
519+ persistenceLayer,
520+ autoBootstrapProjectFromCwd: true,
521+ });
522+ let addr = server.address();
523+ let port = typeof addr === "object" && addr !== null ? addr.port : 0;
524+ expect(port).toBeGreaterThan(0);
525+
526+ const firstWs = await connectWs(port);
527+ connections.push(firstWs);
528+ const firstWelcome = (await waitForMessage(firstWs)) as WsPush;
529+ const firstBootstrapProjectId = (firstWelcome.data as { bootstrapProjectId?: string })
530+ .bootstrapProjectId;
531+ const firstBootstrapThreadId = (firstWelcome.data as { bootstrapThreadId?: string })
532+ .bootstrapThreadId;
533+ expect(firstBootstrapProjectId).toBeDefined();
534+ expect(firstBootstrapThreadId).toBeDefined();
535+
536+ firstWs.close();
537+ await closeTestServer();
538+ server = null;
539+
540+ server = await createTestServer({
541+ cwd,
542+ stateDir,
543+ persistenceLayer,
544+ autoBootstrapProjectFromCwd: true,
545+ });
546+ addr = server.address();
547+ port = typeof addr === "object" && addr !== null ? addr.port : 0;
548+ expect(port).toBeGreaterThan(0);
549+
550+ const secondWs = await connectWs(port);
551+ connections.push(secondWs);
552+ const secondWelcome = (await waitForMessage(secondWs)) as WsPush;
553+ expect(secondWelcome.channel).toBe(WS_CHANNELS.serverWelcome);
554+ expect(secondWelcome.data).toEqual(
555+ expect.objectContaining({
556+ cwd,
557+ projectName: "bootstrap-existing",
558+ bootstrapProjectId: firstBootstrapProjectId,
559+ bootstrapThreadId: firstBootstrapThreadId,
560+ }),
561+ );
562+ });
563+
509564 it("logs outbound websocket push events in dev mode", async () => {
510565 const logSpy = vi.spyOn(console, "log").mockImplementation(() => {
511566 // Keep test output clean while verifying websocket logs.
apps/server/src/wsServer.ts+3−0
@@ -388,6 +388,9 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return< · 387 unmodified lines
388388 });
389389 welcomeBootstrapProjectId = bootstrapProjectId;
390390 welcomeBootstrapThreadId = threadId;
391+ } else {
392+ welcomeBootstrapProjectId = bootstrapProjectId;
393+ welcomeBootstrapThreadId = existingThread.id;
391394 }
392395 }).pipe(
393396 Effect.mapError(
apps/web/src/routes/__root.tsx+4−1
@@ -194,6 +194,9 @@ function EventRouter() { · 193 unmodified lines
194194 const unsubWelcome = onServerWelcome((payload) => {
195195 void (async () => {
196196 await syncSnapshot();
197+ if (disposed) {
198+ return;
199+ }
197200
198201 if (!payload.bootstrapProjectId || !payload.bootstrapThreadId) {
199202 return;
@@ -209,12 +212,12 @@ function EventRouter() { · 9 unmodified lines
209212 if (handledBootstrapThreadIdRef.current === payload.bootstrapThreadId) {
210213 return;
211214 }
212− handledBootstrapThreadIdRef.current = payload.bootstrapThreadId;
213215 await navigate({
214216 to: "/$threadId",
215217 params: { threadId: payload.bootstrapThreadId },
216218 replace: true,
217219 });
220+ handledBootstrapThreadIdRef.current = payload.bootstrapThreadId;
218221 })().catch(() => undefined);
219222 });
220223 const unsubServerConfigUpdated = onServerConfigUpdated((payload) => {