Skip to content

feat: Add new "Chat" feature for non-project threads#4515

Open
jakeleventhal wants to merge 4 commits into
pingdotgg:mainfrom
jakeleventhal:feat/chat-pseudo-project
Open

feat: Add new "Chat" feature for non-project threads#4515
jakeleventhal wants to merge 4 commits into
pingdotgg:mainfrom
jakeleventhal:feat/chat-pseudo-project

Conversation

@jakeleventhal

@jakeleventhal jakeleventhal commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Demo

Screen.Recording.2026-07-25.at.9.23.03.AM.mov
chat-project-1-thread chat-project-2-command-palette chat-project-3-new-thread-picker chat-project-4-control-t3code

What Changed

Adds a reserved Chat pseudo-project for one-off conversations that aren't tied to any repository, and hides the project-scoped UI that has no meaning without a codebase.

Server

  • Derive chatsDir (<baseDir>/chats) in deriveServerPaths and create it in ensureServerDirectories, alongside worktrees/, caches/ etc.
  • New chats.ensure startup phase creates the project once, through the normal project.create command path (idempotent, non-fatal on failure).
  • New kind: "standard" | "chats" discriminator on OrchestrationProject / OrchestrationProjectShell. It is derived at read time from the workspace root rather than persisted, so it needs no migration, stays correct if the base dir moves, and applies to pre-existing rows. ServerConfig is read via Effect.serviceOption so test layers without it keep working.

Client (web + mobile)

  • Chat threads hide: diff, files, terminal, the branch/worktree/env-mode strip, git actions, project scripts and open-in-editor. Persisted diff/terminal/file tabs are reconciled away.
  • Chat shows a chat-bubble icon and no filesystem path, and is kept out of project lists entirely — a dedicated "Start a new chat" action replaces it in the ⌘K root (between "New thread in…" and "Add project") and above the Projects group in the project picker. "New thread in X" always names a real codebase.
  • Generic composer greeting ("What's on your mind?") instead of "What should we build in …?".
  • Sidebar thread cards collapse their meta row when a thread has no branch, PR or diff, so the provider icon sits inline instead of leaving an empty gutter (60px vs 78px).

Why

Not every conversation is about code. Today the only way to have one is to pick some unrelated repo, which pollutes that project's thread list and surrounds a plain chat with diff/branch/worktree affordances that can't do anything useful.

The kind is derived rather than stored deliberately: the alternative (a persisted column) needs a migration, goes stale if --base-dir changes, and duplicates state that the workspace root already determines. This mirrors how repositoryIdentity is already resolved at read time.

UI Changes

  1. Chat thread — no diff/files/terminal/branch strip/git actions/scripts, bubble icon, no folder path, compact sidebar card with the provider logo inline.
  2. ⌘K root — "New thread in t3code" (never "in Chat"), with "Start a new chat" between "New thread in…" and "Add project".
  3. "New thread in…" picker — standalone "Start a new chat" above the Projects group; Projects lists only real codebases.
  4. Control: t3code project — scripts, Open-in, git actions, terminal and the branch strip all unchanged.

Verification

Checked against this branch's base (upstream/main), in a clean worktree with its own pnpm install:

  • apps/web, apps/server, apps/mobile typecheck clean.
  • 84 focused tests pass: apps/server/src/project, serverRuntimeStartup.test.ts, ProjectionSnapshotQuery.test.ts (50) and CommandPalette.logic.test.ts, rightPanelStore.test.ts (34).
  • New tests cover kind resolution, the idempotent startup ensure (creates once / no-ops when present), kind flowing through getShellSnapshot and getActiveProjectByWorkspaceRoot, and the palette's Chat labelling/path-hiding.
  • Manually exercised in an isolated dev environment: booting with a fresh home dir creates chats/ and the project, and /api/orchestration/shell returns "kind": "chats" for it.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Add Chat pseudo-project for non-project threads across web and mobile

  • Introduces a reserved "Chat" project (kind: chats) created at server startup in <baseDir>/chats; the server ensures this directory and project exist on boot.
  • Adds resolveProjectKind, isChatsProject, projectDisplayTitle, and CHATS_PROJECT_LABEL utilities to classify and display the Chat project consistently.
  • In ChatView, ChatHeader, and DraftHeroHeadline, Chat threads hide files, diff, terminal, git actions, and project scripts; the headline changes to

Macroscope summarized db78a83.


Note

Medium Risk
Touches server startup orchestration and broad UI gating across web and mobile, but changes are additive with read-time kind derivation and idempotent project creation rather than migrations or auth/data rewrites.

Overview
Introduces a reserved Chat pseudo-project at <baseDir>/chats for conversations not tied to a repository, with kind: "standard" | "chats" derived at read time from the workspace root (not persisted).

Server: Adds chatsDir to derived paths and directory bootstrap; ensureChatsProject runs at startup (forked, non-fatal) and idempotently dispatches project.create when no project exists at that root. Projection snapshots attach kind via resolveProjectKind.

Clients (web + mobile): When isChatsProject is true, hides git/files/terminal/diff, project scripts, open-in, and branch-oriented UI; reconciles away persisted diff/terminal/file panel tabs. Chat uses bubble icon and projectDisplayTitle ("Chat"); it is excluded from normal project lists in favor of a dedicated Start a new chat action in the command palette. Draft hero copy switches to "What's on your mind?" for Chat threads; sidebar cards collapse empty meta rows when there is no branch/PR/diff.

Reviewed by Cursor Bugbot for commit db78a83. Bugbot is set up for automated code reviews on this repo. Configure here.

Adds a reserved "Chat" pseudo-project rooted at `<baseDir>/chats` for
one-off conversations that are not tied to a repository, and hides the
project-scoped surfaces that make no sense without a codebase.

Server:
- Derive `chatsDir` from the base dir and create it alongside the other
  runtime directories.
- Ensure the Chat project exists at startup via the normal
  `project.create` command path (idempotent, non-fatal on failure).
- Resolve a `kind` discriminator at read time from the workspace root so
  it needs no migration and survives base-dir changes.

Client:
- Hide diff, files, terminal, branch/worktree strip, git actions,
  scripts and open-in-editor for Chat threads (web and mobile).
- Show a chat-bubble icon and no filesystem path for Chat; keep it out of
  project lists, with a dedicated "Start a new chat" action instead.
- Collapse the sidebar card's meta row when a thread has no branch, PR or
  diff so the provider icon sits inline instead of leaving a gap.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4856586c-9974-47fc-bad9-418736064dd4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Jul 25, 2026
@jakeleventhal jakeleventhal changed the title feat: add Chat pseudo-project for codebase-free conversations feat: Add new "Chat" feature for non-project threads Jul 25, 2026
Comment thread apps/mobile/src/features/threads/ThreadRouteScreen.tsx
Comment thread apps/mobile/src/features/threads/ThreadRouteScreen.tsx
… Chat

Addresses review feedback on the Chat pseudo-project:

- androidHeaderActions built its list independently of showActionControls,
  guarding on selectedThreadCwd / workspaceRoot. Both are non-null for Chat,
  so Android still showed Files, Terminal, Git and Inspector. Return early
  after the navigation action when the project is Chat.
- The auxiliary-pane close effect only fired when selectedThreadCwd was null.
  Chat has a real workspace root, so switching from a Files/Git inspector into
  a Chat thread left the pane open with no registered content. Close it when
  the project is Chat as well.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Jul 25, 2026
jakeleventhal and others added 2 commits July 25, 2026 09:37
buildHomeProjectScopes special-cased single-member groups to use the
project title, which dropped the repository label that
deriveProjectGroupLabel prefers — a lone project with a repositoryIdentity
rendered its directory name instead of the repo name.

Key the override on the Chat pseudo-project instead, so every real project
keeps the derived group label regardless of group size.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jakeleventhal
jakeleventhal marked this pull request as ready for review July 25, 2026 13:42

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

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 using high effort and found 5 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit db78a83. Configure here.

(currentProjectId ? (projectTitleById.get(currentProjectId) ?? null) : null);
codebasePickerEntries.find((entry) => entry.isPreferred)?.group.displayName ??
codebasePickerEntries[0]?.group.displayName ??
null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

New thread targets wrong project

Medium Severity

The root “New thread in …” action labels a real codebase from codebasePickerEntries, but its run still calls startNewThreadFromContext, which resolves the project from the active thread. While a Chat thread is open, the UI can say “New thread in t3code” yet create another thread in the Chat pseudo-project.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit db78a83. Configure here.

Effect.catch((cause) =>
Effect.logWarning("startup chats project ensure failed", { cause }),
),
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Startup ensure misses defect failures

Low Severity

The new chats.ensure startup phase wraps ensureChatsProject in Effect.catch, which only handles typed failures. Defects or interruptions during project creation can skip the warning path and fail the forked fiber without the intended non-fatal degradation.

Fix in Cursor Fix in Web

Triggered by learned rule: Effect.exit not Effect.result for recording dispatch outcomes

Reviewed by Cursor Bugbot for commit db78a83. Configure here.

terminalUiLaunchContext?.threadId === activeThreadId ? terminalUiLaunchContext : null;
// Default true while loading to avoid toolbar flicker.
const isGitRepo = gitStatusQuery.data?.isRepo ?? true;
const isGitRepo = (gitStatusQuery.data?.isRepo ?? true) && !activeProjectIsChats;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Diff shortcut bypasses Chat guards

Medium Severity

onToggleDiff only checks isServerThread, while addDiffSurface and diffAvailable use isGitRepo (which excludes Chat). The diff.toggle keybinding still opens a diff tab on Chat threads after reconcileWorkspaceSurfaces clears one.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit db78a83. Configure here.

}, [
activeKnownTerminalIds,
activeProject,
activeProjectIsChats,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Terminal shortcuts ignore Chat project

Medium Severity

createNewTerminal and splitTerminal do not check activeProjectIsChats, but toggleTerminalVisibility and addTerminalSurface do. The terminal.new and split keybindings still spawn shells in the Chat workspace via openTerminal.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit db78a83. Configure here.

useRightPanelStore
.getState()
.reconcileWorkspaceSurfaces(activeThreadRef, !activeProjectIsChats);
}, [activeEnvironmentBootstrapComplete, activeProject, activeProjectIsChats, activeThreadRef]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Chat threads keep open terminal drawer

Medium Severity

The reconcile effect clears file, diff, and right-panel terminal tabs for Chat but never closes the bottom PersistentThreadTerminalDrawer when terminalUiState.terminalOpen is persisted. Chat threads can still show the drawer UI.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit db78a83. Configure here.

@macroscopeapp

macroscopeapp Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a new 'Chat' feature with significant cross-cutting changes across server, web, and mobile. Multiple unresolved review comments identify bugs where terminal/diff shortcuts and command palette actions bypass the Chat guards. New features of this scope warrant human review.

You can customize Macroscope's approvability policy. Learn more.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: db78a83b35

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return () => window.removeEventListener("keydown", handler, true);
}, [
activeProject,
activeProjectIsChats,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Block project-only shortcuts in Chat threads

Adding activeProjectIsChats to the keyboard effect's dependencies does not use it to suppress commands: with a Chat thread active, terminal.new and terminal.split still call the terminal creation handlers, while diff.toggle still opens the diff surface. These shortcuts bypass the newly hidden buttons and can launch a shell in <baseDir>/chats or reopen a meaningless diff panel; guard the project-only command branches when activeProjectIsChats is true.

Useful? React with 👍 / 👎.

Comment on lines +593 to +595
const chatProject = useMemo(
() => pickerProjects.find((project) => isChatsProject(project)) ?? null,
[pickerProjects],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Select the Chat project for the active environment

When multiple environments are connected, each server supplies its own Chat pseudo-project, but this chooses the first Chat entry in the activity-sorted project list. From a remote project, “Start a new chat” can therefore silently create the thread in the local or another remote environment, and the destination can change as project activity changes; resolve the Chat project using the contextual/current environment instead of an unqualified find.

Useful? React with 👍 / 👎.

Comment on lines 206 to +209
representative,
members,
})
: representative.title,
: projectDisplayTitle(representative),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep the reserved Chat project out of project management

The Chat pseudo-project is still returned as an ordinary sidebar project group, so consumers such as the SidebarV2 project filter expose it with the normal rename/remove action. Removing it deletes the project and makes “Start a new chat” disappear until the server restarts and runs the startup ensure again, contrary to the reserved-project behavior; exclude Chat from ordinary project-list/management consumers while retaining it for the dedicated chat action.

Useful? React with 👍 / 👎.

Comment on lines 1191 to +1194
const activeProjectTitle =
projectPickerEntries.find((entry) => entry.isPreferred)?.group.displayName ??
(currentProjectId ? (projectTitleById.get(currentProjectId) ?? null) : null);
codebasePickerEntries.find((entry) => entry.isPreferred)?.group.displayName ??
codebasePickerEntries[0]?.group.displayName ??
null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Route the codebase-labelled action to that codebase

When the active thread belongs to Chat, this computes a real codebase name for the label, but the action still calls startNewThreadFromContext, which prioritizes the active thread's project and therefore creates another Chat draft. The root palette can consequently say “New thread in t3code” while behaving identically to “Start a new chat”; pass the selected codebasePickerEntries target to handleNewThread so the action matches its label.

Useful? React with 👍 / 👎.

Comment on lines +1197 to 1198
kind: projectKindForWorkspaceRoot(row.workspaceRoot),
repositoryIdentity: repositoryIdentities.get(row.projectId) ?? null,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Prevent Chat from inheriting a parent repository identity

When an explicit base directory is located inside a Git checkout, resolving identity for <baseDir>/chats assigns the parent repository's canonical identity to the Chat project. Under the default repository grouping mode, Chat is then merged with the real codebase; because the picker exposes one target per logical group, chatProject may disappear entirely or the real project may be filtered as Chat. Emit a null repository identity for kind === "chats" or otherwise force the pseudo-project to use a distinct logical key.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant