Skip to content

Bootstrap default thread on server welcome and simplify dev runner behavior#101

Merged
juliusmarminge merged 6 commits intomainfrom
codething/ad120d1f
Feb 27, 2026
Merged

Bootstrap default thread on server welcome and simplify dev runner behavior#101
juliusmarminge merged 6 commits intomainfrom
codething/ad120d1f

Conversation

@juliusmarminge
Copy link
Copy Markdown
Member

@juliusmarminge juliusmarminge commented Feb 27, 2026

Summary

  • Auto-bootstrap now creates (or reuses) the cwd project and ensures a default "New thread" exists, then includes bootstrapProjectId/bootstrapThreadId in server.welcome.
  • Web client now reacts to welcome bootstrap IDs by syncing snapshot, expanding the project, and routing / to the bootstrapped thread.
  • Updated WebSocket contracts/tests to validate optional bootstrap IDs in welcome payloads.
  • Added store support for explicitly setting project expanded state and adjusted sidebar/diff rendering behavior.
  • Simplified scripts/dev-runner.mjs by removing CLI flag-forwarding/state-dir override plumbing and deleting related tests/docs.
  • Desktop app state dir is now fixed to ~/.t3/userdata (no T3CODE_STATE_DIR override).

Testing

  • apps/server/src/wsServer.test.ts: verifies welcome payload includes bootstrap IDs and snapshot includes bootstrapped project/thread.
  • apps/web/src/wsNativeApi.test.ts: verifies welcome payload handling preserves optional bootstrap IDs.
  • apps/server/integration/orchestrationEngine.integration.test.ts: removed shutdown-failure temp-dir cleanup case due to harness disposal simplification.
  • apps/web/src/lib/diffRendering.test.ts: removed cache-scope-specific key test to match cache key usage changes.
  • Lint: Not run.
  • Full test suite: Not run.

Note

Medium Risk
Changes startup orchestration by auto-creating/reusing projects/threads and wiring those IDs into client routing, which can affect persisted state and navigation flows. Scope is contained to bootstrap/welcome plumbing with updated contract validation and tests.

Overview
Auto-bootstrap now ensures both a cwd project and a default "New thread" exist, reusing non-deleted records when present, and includes bootstrapProjectId/bootstrapThreadId in the server.welcome push.

The web client consumes these optional IDs to sync the snapshot, expand the bootstrapped project, and (when currently on /) redirect to the bootstrapped thread; the store adds SET_PROJECT_EXPANDED to support explicit expansion. WebSocket contracts/tests are updated to validate/preserve the optional bootstrap IDs, and server tests now assert bootstrap ID stability across restarts with a real sqlite persistence layer.

Written by Cursor Bugbot for commit 8effcf3. This will update automatically on new commits. Configure here.

Note

Bootstrap a default project and thread on server start and include bootstrapProjectId and bootstrapThreadId in serverWelcome

wsServer.createServer now creates or reuses a project and a thread for the current cwd when autoBootstrapProjectFromCwd is enabled and adds bootstrapProjectId and bootstrapThreadId to the serverWelcome payload; the web EventRouter consumes these fields to expand the project and navigate to the thread, and tests and schemas are updated accordingly.

📍Where to Start

Start in the server bootstrap flow inside wsServer.createServer in wsServer.ts, then review the UI handling in apps/web/src/routes/__root.tsx.

Macroscope summarized 8effcf3.

Summary by CodeRabbit

  • New Features

    • Auto-bootstrap: app may create a project and thread on first connection and navigate to the bootstrapped thread (navigation guarded to avoid duplicates and only when at root).
    • Project expansion state: track and set whether projects are expanded or collapsed.
  • Updates

    • Server welcome payload now optionally includes bootstrap project and thread identifiers.
  • Tests

    • Tests validate bootstrap ID propagation and use relaxed welcome payload matching.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae4e353 and 8effcf3.

📒 Files selected for processing (6)
  • apps/server/src/wsServer.test.ts
  • apps/server/src/wsServer.ts
  • apps/web/src/routes/__root.tsx
  • apps/web/src/store.ts
  • apps/web/src/wsNativeApi.test.ts
  • packages/contracts/src/ws.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/server/src/wsServer.ts

Walkthrough

Adds server auto-bootstrap that finds or creates a project and thread on connect, includes their IDs in serverWelcome, and updates the web client to expand the project and navigate to the bootstrapped thread; tests and contract schema updated accordingly.

Changes

Cohort / File(s) Summary
Server bootstrap & welcome payload
apps/server/src/wsServer.ts
Implements cwd-based find-or-create project and thread during connect, records welcomeBootstrapProjectId/welcomeBootstrapThreadId, and includes optional bootstrapProjectId/bootstrapThreadId in serverWelcome pushes.
Server tests
apps/server/src/wsServer.test.ts
Test scaffolding updated to use makeSqlitePersistenceLive alongside in-memory persistence; snapshots and assertions extended to expect bootstrap IDs in welcome.data; new test added for reuse of bootstrap IDs.
Web client routing & handler
apps/web/src/routes/__root.tsx
onServerWelcome made async: awaits snapshot sync, dispatches SET_PROJECT_EXPANDED for bootstrap project, and navigates to the bootstrap thread when at root; adds refs to avoid duplicate handling and tracks pathname.
Client state update
apps/web/src/store.ts
Adds SET_PROJECT_EXPANDED action variant and reducer case to set a project's expanded flag by projectId.
Web client tests
apps/web/src/wsNativeApi.test.ts
Adjusts assertions to use expect.objectContaining for serverWelcome payloads and adds a test verifying preservation of bootstrapProjectId/bootstrapThreadId.
Contract schema
packages/contracts/src/ws.ts
Extends WsWelcomePayload schema to include optional bootstrapProjectId and bootstrapThreadId (imports ProjectId, ThreadId).

Sequence Diagram(s)

sequenceDiagram
    participant Client as Web Client
    participant Server as WS Server
    participant DB as Project/Thread Store

    Client->>Server: WebSocket Connect
    Server->>DB: Find non-deleted project for cwd
    alt project not found
        Server->>DB: Create project (returns projectId)
    end
    Server->>DB: Find non-deleted thread for project
    alt thread not found
        Server->>DB: Create thread (returns threadId)
    end
    Server->>Client: Push serverWelcome (includes optional bootstrapProjectId/bootstrapThreadId)
    Client->>Client: Sync snapshot/state
    alt client pathname is root and IDs present and not handled
        Client->>Client: Dispatch SET_PROJECT_EXPANDED for projectId
        Client->>Router: Navigate to /$threadId (replace: true)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main changes: introducing bootstrap default thread creation on server welcome and simplifying dev runner behavior, which aligns with the primary objectives of the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codething/ad120d1f

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Bootstrap IDs missing when project/thread already exist
    • Added an else branch to assign welcomeBootstrapProjectId and welcomeBootstrapThreadId from the existing project and thread when they already exist.
  • ✅ Fixed: Pathname dependency causes effect re-subscription on every navigation
    • Replaced the pathname dependency with a pathnameRef that tracks the current pathname without being in the useEffect dependency array, preventing teardown/re-subscription on navigation.

Create PR

Or push these changes by commenting:

@cursor push b003595d60
Preview (b003595d60)
diff --git a/apps/server/src/wsServer.ts b/apps/server/src/wsServer.ts
--- a/apps/server/src/wsServer.ts
+++ b/apps/server/src/wsServer.ts
@@ -387,6 +387,9 @@
       });
       welcomeBootstrapProjectId = bootstrapProjectId;
       welcomeBootstrapThreadId = threadId;
+    } else {
+      welcomeBootstrapProjectId = bootstrapProjectId;
+      welcomeBootstrapThreadId = existingThread.id;
     }
   }
 

diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx
--- a/apps/web/src/routes/__root.tsx
+++ b/apps/web/src/routes/__root.tsx
@@ -125,6 +125,8 @@
   const queryClient = useQueryClient();
   const navigate = useNavigate();
   const pathname = useRouterState({ select: (state) => state.location.pathname });
+  const pathnameRef = useRef(pathname);
+  pathnameRef.current = pathname;
   const lastConfigIssuesSignatureRef = useRef<string | null>(null);
   const handledBootstrapThreadIdRef = useRef<string | null>(null);
 
@@ -187,7 +189,7 @@
           projectId: payload.bootstrapProjectId,
           expanded: true,
         });
-        if (pathname !== "/") {
+        if (pathnameRef.current !== "/") {
           return;
         }
         if (handledBootstrapThreadIdRef.current === payload.bootstrapThreadId) {
@@ -249,7 +251,7 @@
       unsubWelcome();
       unsubServerConfigUpdated();
     };
-  }, [dispatch, navigate, pathname, queryClient]);
+  }, [dispatch, navigate, queryClient]);
 
   return null;
 }

Comment thread apps/server/src/wsServer.ts Outdated
Comment thread apps/web/src/routes/__root.tsx Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
apps/server/src/wsServer.test.ts (1)

440-507: Add a bootstrap reuse test case to lock behavior.

This test covers the creation path well. Please add a case with an already-existing project/thread and assert welcome still includes bootstrapProjectId/bootstrapThreadId (and no extra default thread is created).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/wsServer.test.ts` around lines 440 - 507, Add a new test that
starts a server with autoBootstrapProjectFromCwd true but with an existing
project/thread already present, using the same helpers (createTestServer,
connectWs, waitForMessage, sendRequest) and channels (WS_CHANNELS,
ORCHESTRATION_WS_METHODS); on connect assert the welcome message still contains
bootstrapProjectId and bootstrapThreadId, then call getSnapshot and assert
snapshot.projects contains the existing project (by
id/workspaceRoot/title/defaultModel) and snapshot.threads contains the existing
thread (by id/projectId/title/model) and no additional default thread was
created (e.g., thread count for that project did not increase or no second
thread with title "New thread" exists).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/server/src/wsServer.ts`:
- Around line 371-390: The bootstrap reuse path fails to set
welcomeBootstrapProjectId/welcomeBootstrapThreadId when an existing thread is
found and also doesn't ensure the thread has the default title "New thread";
update the lookup and assignment: when checking snapshot.threads (the
snapshot.threads.find call) restrict the match to projectId ===
bootstrapProjectId, deletedAt === null AND title === "New thread" (or, if any
thread is acceptable, still assign IDs), and if an existing thread is found
assign welcomeBootstrapProjectId = bootstrapProjectId and
welcomeBootstrapThreadId = existingThread.id (or equivalent identifier used in
the thread object) so that the welcome payload is populated; otherwise keep the
current creation path that uses ThreadId.makeUnsafe/crypto.randomUUID and
orchestrationEngine.dispatch({ type: "thread.create", ... }) to create and set
the bootstrap IDs.

In `@apps/web/src/routes/__root.tsx`:
- Around line 178-203: In the onServerWelcome callback registered to
unsubWelcome, add an immediate disposed check after the await syncSnapshot()
(i.e., insert "if (disposed) return;") so the async continuation doesn't call
dispatch() or navigate() on an unmounted/disposed effect; also move the
assignment to handledBootstrapThreadIdRef.current so it happens after successful
navigation (set it after the await navigate(...) completes) to ensure the flag
only flips when navigation actually finished.

---

Nitpick comments:
In `@apps/server/src/wsServer.test.ts`:
- Around line 440-507: Add a new test that starts a server with
autoBootstrapProjectFromCwd true but with an existing project/thread already
present, using the same helpers (createTestServer, connectWs, waitForMessage,
sendRequest) and channels (WS_CHANNELS, ORCHESTRATION_WS_METHODS); on connect
assert the welcome message still contains bootstrapProjectId and
bootstrapThreadId, then call getSnapshot and assert snapshot.projects contains
the existing project (by id/workspaceRoot/title/defaultModel) and
snapshot.threads contains the existing thread (by id/projectId/title/model) and
no additional default thread was created (e.g., thread count for that project
did not increase or no second thread with title "New thread" exists).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10c1cae and 567b095.

📒 Files selected for processing (6)
  • apps/server/src/wsServer.test.ts
  • apps/server/src/wsServer.ts
  • apps/web/src/routes/__root.tsx
  • apps/web/src/store.ts
  • apps/web/src/wsNativeApi.test.ts
  • packages/contracts/src/ws.ts

Comment thread apps/server/src/wsServer.ts Outdated
Comment thread apps/web/src/routes/__root.tsx
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: SET_PROJECT_EXPANDED always lost due to sync race
    • Changed syncSnapshot() to return the in-flight syncPromise when syncing is true, so the welcome handler actually waits for the sync to complete and dispatches SET_PROJECT_EXPANDED against a populated store.

Create PR

Or push these changes by commenting:

@cursor push 1f0e7a6eae
Preview (1f0e7a6eae)
diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx
--- a/apps/web/src/routes/__root.tsx
+++ b/apps/web/src/routes/__root.tsx
@@ -149,20 +149,25 @@
       }
     };
 
-    const syncSnapshot = async () => {
+    let syncPromise: Promise<void> = Promise.resolve();
+
+    const syncSnapshot = (): Promise<void> => {
       if (syncing) {
         pending = true;
-        return;
+        return syncPromise;
       }
       syncing = true;
-      try {
-        pending = false;
-        await flushSnapshotSync();
-      } catch {
-        // Keep prior state and wait for next domain event to trigger a resync.
-      } finally {
-        syncing = false;
-      }
+      syncPromise = (async () => {
+        try {
+          pending = false;
+          await flushSnapshotSync();
+        } catch {
+          // Keep prior state and wait for next domain event to trigger a resync.
+        } finally {
+          syncing = false;
+        }
+      })();
+      return syncPromise;
     };
 
     void syncSnapshot().catch(() => undefined);

Comment thread apps/web/src/routes/__root.tsx
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
apps/web/src/routes/__root.tsx (1)

180-204: ⚠️ Potential issue | 🟠 Major

Add a post-await disposal guard in the welcome handler.

After await syncSnapshot() (Line 182), the callback may continue after cleanup and still run dispatch/navigate. Add an immediate disposed check before further side effects.

Suggested fix
     const unsubWelcome = onServerWelcome((payload) => {
       void (async () => {
         await syncSnapshot();
+        if (disposed) return;

         if (!payload.bootstrapProjectId || !payload.bootstrapThreadId) {
           return;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/routes/__root.tsx` around lines 180 - 204, The welcome handler
can continue after cleanup; add a post-await disposed guard: introduce a local
flag (e.g., let disposed = false) captured by the async handler created for
onServerWelcome, check if (disposed) return immediately after await
syncSnapshot() to avoid dispatch/navigate after teardown, and ensure the
unsubscribe returned by onServerWelcome is wrapped or augmented so calling
unsubWelcome() sets disposed = true; reference the existing unsubWelcome,
onServerWelcome, syncSnapshot, handledBootstrapThreadIdRef, pathnameRef,
dispatch and navigate identifiers when making this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@apps/web/src/routes/__root.tsx`:
- Around line 180-204: The welcome handler can continue after cleanup; add a
post-await disposed guard: introduce a local flag (e.g., let disposed = false)
captured by the async handler created for onServerWelcome, check if (disposed)
return immediately after await syncSnapshot() to avoid dispatch/navigate after
teardown, and ensure the unsubscribe returned by onServerWelcome is wrapped or
augmented so calling unsubWelcome() sets disposed = true; reference the existing
unsubWelcome, onServerWelcome, syncSnapshot, handledBootstrapThreadIdRef,
pathnameRef, dispatch and navigate identifiers when making this change.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 567b095 and 3b8c5cd.

📒 Files selected for processing (1)
  • apps/web/src/routes/__root.tsx

@juliusmarminge juliusmarminge force-pushed the codething/ad120d1f branch 4 times, most recently from 70ac3b5 to 4ac2e0d Compare February 27, 2026 04:44
- Create or reuse cwd project, create initial thread, and include bootstrap IDs in `server.welcome`
- Update web event routing to expand the project and auto-navigate from `/` to the bootstrapped thread
- Extend WebSocket welcome contract and add server/web tests for bootstrap payload handling
- Track latest `pathname` in a ref for bootstrap event handling
- Prevent effect resubscriptions by removing `pathname` from deps
- Keep auto-navigation gated to root route using current path
- 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
- Fetch orchestration snapshot when bootstrap project/thread IDs are absent
- Resolve project by current workspace and choose latest non-deleted thread
- Continue auto-navigation and expansion using resolved fallback IDs
- Remove snapshot fallback lookup for missing bootstrap project/thread IDs
- Only expand project and navigate when server payload includes both IDs
- inject `NodeServices.layer` into sqlite persistence setup in `wsServer.test.ts`
- fixes test runtime dependencies for existing-state bootstrap path
@juliusmarminge juliusmarminge merged commit a1f208f into main Feb 27, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant