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
38 TerminalWriteInput,38 TerminalWriteInput,
39} from "@t3tools/contracts";39} from "@t3tools/contracts";
40import { TerminalManager, type TerminalManagerShape } from "./terminal/Services/Manager";40import { TerminalManager, type TerminalManagerShape } from "./terminal/Services/Manager";
41import { SqlitePersistenceMemory } from "./persistence/Layers/Sqlite";41import { makeSqlitePersistenceLive, SqlitePersistenceMemory } from "./persistence/Layers/Sqlite";
42import { SqlClient } from "effect/unstable/sql";42import { SqlClient } from "effect/unstable/sql";
43import { ProviderService, type ProviderServiceShape } from "./provider/Services/ProviderService";43import { ProviderService, type ProviderServiceShape } from "./provider/Services/ProviderService";
44import { Open, type OpenShape } from "./open";44import { Open, type OpenShape } from "./open";
@@ -506,6 +506,61 @@ describe("WebSocket Server", () => { · 461 unmodified lines
506 );506 );
507 });507 });
508 508
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
509 it("logs outbound websocket push events in dev mode", async () => {564 it("logs outbound websocket push events in dev mode", async () => {
510 const logSpy = vi.spyOn(console, "log").mockImplementation(() => {565 const logSpy = vi.spyOn(console, "log").mockImplementation(() => {
511 // Keep test output clean while verifying websocket logs.566 // 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
388 });388 });
389 welcomeBootstrapProjectId = bootstrapProjectId;389 welcomeBootstrapProjectId = bootstrapProjectId;
390 welcomeBootstrapThreadId = threadId;390 welcomeBootstrapThreadId = threadId;
391 } else {
392 welcomeBootstrapProjectId = bootstrapProjectId;
393 welcomeBootstrapThreadId = existingThread.id;
391 }394 }
392 }).pipe(395 }).pipe(
393 Effect.mapError(396 Effect.mapError(
apps/web/src/routes/__root.tsx+4−1
@@ -194,6 +194,9 @@ function EventRouter() { · 193 unmodified lines
194 const unsubWelcome = onServerWelcome((payload) => {194 const unsubWelcome = onServerWelcome((payload) => {
195 void (async () => {195 void (async () => {
196 await syncSnapshot();196 await syncSnapshot();
197 if (disposed) {
198 return;
199 }
197 200
198 if (!payload.bootstrapProjectId || !payload.bootstrapThreadId) {201 if (!payload.bootstrapProjectId || !payload.bootstrapThreadId) {
199 return;202 return;
@@ -209,12 +212,12 @@ function EventRouter() { · 9 unmodified lines
209 if (handledBootstrapThreadIdRef.current === payload.bootstrapThreadId) {212 if (handledBootstrapThreadIdRef.current === payload.bootstrapThreadId) {
210 return;213 return;
211 }214 }
212 handledBootstrapThreadIdRef.current = payload.bootstrapThreadId;
213 await navigate({215 await navigate({
214 to: "/$threadId",216 to: "/$threadId",
215 params: { threadId: payload.bootstrapThreadId },217 params: { threadId: payload.bootstrapThreadId },
216 replace: true,218 replace: true,
217 });219 });
220 handledBootstrapThreadIdRef.current = payload.bootstrapThreadId;
218 })().catch(() => undefined);221 })().catch(() => undefined);
219 });222 });
220 const unsubServerConfigUpdated = onServerConfigUpdated((payload) => {223 const unsubServerConfigUpdated = onServerConfigUpdated((payload) => {