feat: Add new "Chat" feature for non-project threads#4515
feat: Add new "Chat" feature for non-project threads#4515jakeleventhal wants to merge 4 commits into
Conversation
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>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
… 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>
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>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 5 potential issues.
❌ 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; |
There was a problem hiding this comment.
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.
Reviewed by Cursor Bugbot for commit db78a83. Configure here.
| Effect.catch((cause) => | ||
| Effect.logWarning("startup chats project ensure failed", { cause }), | ||
| ), | ||
| ), |
There was a problem hiding this comment.
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.
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; |
There was a problem hiding this comment.
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.
Reviewed by Cursor Bugbot for commit db78a83. Configure here.
| }, [ | ||
| activeKnownTerminalIds, | ||
| activeProject, | ||
| activeProjectIsChats, |
There was a problem hiding this comment.
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)
Reviewed by Cursor Bugbot for commit db78a83. Configure here.
| useRightPanelStore | ||
| .getState() | ||
| .reconcileWorkspaceSurfaces(activeThreadRef, !activeProjectIsChats); | ||
| }, [activeEnvironmentBootstrapComplete, activeProject, activeProjectIsChats, activeThreadRef]); |
There was a problem hiding this comment.
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.
Reviewed by Cursor Bugbot for commit db78a83. Configure here.
ApprovabilityVerdict: 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. |
There was a problem hiding this comment.
💡 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, |
There was a problem hiding this comment.
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 👍 / 👎.
| const chatProject = useMemo( | ||
| () => pickerProjects.find((project) => isChatsProject(project)) ?? null, | ||
| [pickerProjects], |
There was a problem hiding this comment.
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 👍 / 👎.
| representative, | ||
| members, | ||
| }) | ||
| : representative.title, | ||
| : projectDisplayTitle(representative), |
There was a problem hiding this comment.
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 👍 / 👎.
| 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; |
There was a problem hiding this comment.
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 👍 / 👎.
| kind: projectKindForWorkspaceRoot(row.workspaceRoot), | ||
| repositoryIdentity: repositoryIdentities.get(row.projectId) ?? null, |
There was a problem hiding this comment.
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 👍 / 👎.


Demo
Screen.Recording.2026-07-25.at.9.23.03.AM.mov
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
chatsDir(<baseDir>/chats) inderiveServerPathsand create it inensureServerDirectories, alongsideworktrees/,caches/etc.chats.ensurestartup phase creates the project once, through the normalproject.createcommand path (idempotent, non-fatal on failure).kind: "standard" | "chats"discriminator onOrchestrationProject/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.ServerConfigis read viaEffect.serviceOptionso test layers without it keep working.Client (web + mobile)
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-dirchanges, and duplicates state that the workspace root already determines. This mirrors howrepositoryIdentityis already resolved at read time.UI Changes
Verification
Checked against this branch's base (
upstream/main), in a clean worktree with its ownpnpm install:apps/web,apps/server,apps/mobiletypecheck clean.apps/server/src/project,serverRuntimeStartup.test.ts,ProjectionSnapshotQuery.test.ts(50) andCommandPalette.logic.test.ts,rightPanelStore.test.ts(34).kindflowing throughgetShellSnapshotandgetActiveProjectByWorkspaceRoot, and the palette's Chat labelling/path-hiding.chats/and the project, and/api/orchestration/shellreturns"kind": "chats"for it.Checklist
Note
Add Chat pseudo-project for non-project threads across web and mobile
chats) created at server startup in<baseDir>/chats; the server ensures this directory and project exist on boot.resolveProjectKind,isChatsProject,projectDisplayTitle, andCHATS_PROJECT_LABELutilities to classify and display the Chat project consistently.ChatView,ChatHeader, andDraftHeroHeadline, Chat threads hide files, diff, terminal, git actions, and project scripts; the headline changes toMacroscope 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>/chatsfor conversations not tied to a repository, withkind: "standard" | "chats"derived at read time from the workspace root (not persisted).Server: Adds
chatsDirto derived paths and directory bootstrap;ensureChatsProjectruns at startup (forked, non-fatal) and idempotently dispatchesproject.createwhen no project exists at that root. Projection snapshots attachkindviaresolveProjectKind.Clients (web + mobile): When
isChatsProjectis 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 andprojectDisplayTitle("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.