Add per-project settings #2567
Conversation
- add project details/settings RPCs and persistence - use project remote overrides for source control detection - add project details page and sidebar navigation
|
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 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)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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 |
ApprovabilityVerdict: Needs human review 2 blocking correctness issues found. This PR introduces a substantial new feature (per-project settings) with ~3000 lines of new logic including a new settings page, per-project provider filtering, model defaults, action environment variables, and git fetch intervals. Additionally, open review comments identify two medium-severity bugs (missing projectId parameter, incorrect null vs undefined check) that warrant human attention before merging. You can customize Macroscope's approvability policy. Learn more. |
- Replace sidebar rename action with project settings access - Show detected remotes and project path in settings - Extract remote override parsing into shared server helper
- Persist project action environment and surface it in setup/runtime - Seed new threads from project default model selection - Refine source control detection precedence and project settings UI
- sync script keybindings against existing local bindings - preserve sticky draft state when seeding new threads - reject duplicate action environment keys during normalization
- Replace the manual remote block with a detected remote summary - Add provider icons and friendlier labels for Git remotes - Keep custom remote override controls available when enabled
- Seed new chat drafts from per-project defaults - Cache project settings edits locally while commits are in flight - Prevent duplicate action-environment keys
- update project settings from the latest persisted snapshot - skip keybinding validation when no server is available - cover the new atomic update path with tests
- block action environment keys that start with `T3CODE_` - keep runtime `T3CODE_*` values owned by T3Code - add coverage for reserved env validation
- Remove the Electron-only guard around project script keybinding sync - Pass the local server when available and let the helper handle non-Electron cases
- Deploy the web app from release workflow - Replace broad Effect imports with subpath imports - Add lint rules and diagnostics for cleaner schema usage
- Remove detected-remote summary and provider icon usage - Show effective remote URL inline with external-link opening - Reuse shared external URL helper for remote actions
- Persist project-level Git fetch refresh settings - Thread project context through VCS status polling and UI - Update project settings schema and tests for the new field
There was a problem hiding this comment.
🟡 Medium
t3code/apps/web/src/components/DiffPanel.tsx
Lines 221 to 224 in 2640720
The call to useGitStatus at lines 221-224 is missing the projectId parameter, which the hook now requires after the changes in gitStatusState.ts. The hook's useEffect passes projectId to watchGitStatus and includes it in the dependency array, but this call site only provides environmentId and cwd. Since activeProjectId is already available at line 211, pass projectId: activeProjectId to ensure the effect dependencies are complete and the watcher is properly scoped.
- const gitStatusQuery = useGitStatus({
- environmentId: activeThread?.environmentId ?? null,
- cwd: activeCwd ?? null,
- });🤖 Copy this AI Prompt to have your agent fix this:
In file apps/web/src/components/DiffPanel.tsx around lines 221-224:
The call to `useGitStatus` at lines 221-224 is missing the `projectId` parameter, which the hook now requires after the changes in `gitStatusState.ts`. The hook's `useEffect` passes `projectId` to `watchGitStatus` and includes it in the dependency array, but this call site only provides `environmentId` and `cwd`. Since `activeProjectId` is already available at line 211, pass `projectId: activeProjectId` to ensure the effect dependencies are complete and the watcher is properly scoped.
Evidence trail:
apps/web/src/components/DiffPanel.tsx lines 211, 221-224 (REVIEWED_COMMIT): `activeProjectId` available but not passed to `useGitStatus`.
apps/web/src/lib/gitStatusState.ts lines 37-42 (REVIEWED_COMMIT): `GitStatusTarget` interface with optional `projectId`.
apps/web/src/lib/gitStatusState.ts lines 74-81 (REVIEWED_COMMIT): `getGitStatusTargetKey` includes `projectId` in key.
apps/web/src/lib/gitStatusState.ts lines 172-185 (REVIEWED_COMMIT): `useGitStatus` hook passes `target.projectId` to `watchGitStatus` and includes in deps.
apps/web/src/components/ChatView.tsx line 1632-1636 (REVIEWED_COMMIT): other call site passes `projectId`.
apps/web/src/components/Sidebar.tsx lines 371-375 (REVIEWED_COMMIT): another call site passes `projectId`.
apps/web/src/components/ThreadStatusIndicators.tsx lines 153-157 (REVIEWED_COMMIT): another call site passes `projectId`.
| if (isElectron && input.keybinding !== undefined && !keybindingServer) { | ||
| throw new Error("Local API unavailable."); | ||
| } |
There was a problem hiding this comment.
🟡 Medium components/ChatView.tsx:1992
When deleteProjectScript calls persistProjectScripts with keybinding: null, the guard isElectron && input.keybinding !== undefined evaluates to true (since null !== undefined), causing the code to throw "Local API unavailable." if readLocalApi()?.server is falsy. The old code skipped this branch entirely for null keybindings because decodeProjectScriptKeybindingRule returned a null rule. This causes script deletion in Electron to surface a misleading error toast even though the server-side deletion already succeeded. Consider checking input.keybinding != null instead of !== undefined to preserve the original null-skipping behavior.
- const keybindingServer =
- isElectron && input.keybinding !== undefined ? readLocalApi()?.server : null;
- if (isElectron && input.keybinding !== undefined && !keybindingServer) {
+ const keybindingServer =
+ isElectron && input.keybinding != null ? readLocalApi()?.server : null;
+ if (isElectron && input.keybinding != null && !keybindingServer) {🤖 Copy this AI Prompt to have your agent fix this:
In file apps/web/src/components/ChatView.tsx around lines 1992-1994:
When `deleteProjectScript` calls `persistProjectScripts` with `keybinding: null`, the guard `isElectron && input.keybinding !== undefined` evaluates to `true` (since `null !== undefined`), causing the code to throw "Local API unavailable." if `readLocalApi()?.server` is falsy. The old code skipped this branch entirely for `null` keybindings because `decodeProjectScriptKeybindingRule` returned a null rule. This causes script deletion in Electron to surface a misleading error toast even though the server-side deletion already succeeded. Consider checking `input.keybinding != null` instead of `!== undefined` to preserve the original null-skipping behavior.
Evidence trail:
apps/web/src/components/ChatView.tsx lines 1990-1994 (new guard with `!== undefined`), line 2086 (`keybinding: null` in deleteProjectScript), lines 1983-1988 (dispatchCommand runs before the guard). Diff at MERGE_BASE..REVIEWED_COMMIT showing old code used `isElectron && keybindingRule` which was null for null keybinding. apps/web/src/lib/projectScriptKeybindings.ts lines 94-95 (`syncProjectScriptKeybinding` handles null server with early return). apps/web/src/routes/projects.$projectId.tsx lines 448-453 (parallel implementation without the throwing guard).
- Persist disabled provider instance IDs in project settings - Gate chat and project selection on project-specific provider access - Update project settings UI and validation
# Conflicts: # apps/server/src/provider/Layers/ProviderRegistry.test.ts # apps/server/src/serverSettings.ts # apps/server/src/ws.ts # packages/shared/src/serverSettings.ts
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 0f2ff8c. Configure here.
| automaticGitFetchInterval: null, | ||
| actionEnvironment: {}, | ||
| disabledProviderInstanceIds: [], | ||
| }; |
There was a problem hiding this comment.
Duplicated emptyProjectSettings constant across two production files
Low Severity
The emptyProjectSettings constant is defined identically in both serverSettings.ts and ws.ts. Since ws.ts already imports from serverSettings.ts (via ServerSettingsService), exporting the constant from serverSettings.ts and reusing it would eliminate the duplication and reduce the risk of them diverging when ProjectSettings fields change in the future.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 0f2ff8c. Configure here.


What Changed
Added a per-project settings page that can be opened from the project row in the sidebar.
/projects/$projectIdWhy
Git remote detection can fail for self-hosted providers where the remote URL does not clearly identify the host, such as a self-hosted GitLab instance without
gitlab.comin the URL.This gives users a project-level fallback instead of requiring the app to guess correctly. The project name change is also scoped to T3 Code metadata only; it does not rename or move the directory on disk.
UI Changes
Checklist
Note
Medium Risk
Adds new persisted per-project settings and threads them through source control detection, git status streaming, and provider selection/dispatch validation, which could affect repo/provider resolution and message sending behavior. Changes span both server RPCs and core UI flows, but are mostly additive with tests.
Overview
Adds persisted per-project settings and exposes them via new WS RPCs (
projectsGetDetails,projectsUpdateSettings) plus a new/projects/$projectIdroute; the sidebar now navigates to this page from the project row while a separate caret toggles thread expansion.Wires project settings into behavior: remote overrides influence
SourceControlProviderRegistry.resolveHandle(trackingcontextSource) andGitManagerprovider resolution, projectactionEnvironmentis injected into setup scripts and terminal launches (withT3CODE_*protected), git status subscriptions/refresh are keyed byprojectId, and provider selection/sending is constrained by per-project disabled provider instances (validated server-side onthread.turn.startand enforced in the composer UI).Reviewed by Cursor Bugbot for commit 0f2ff8c. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add per-project settings including provider policy, action environment, and remote override
ProjectSettingscontract with fields forremoteOverride,actionEnvironment,disabledProviderInstanceIds, andautomaticGitFetchInterval; server settings now persist a per-project settings mapprojectsGetDetailsandprojectsUpdateSettingsWebSocket RPC handlers in ws.ts that return git-detected remotes, effective remote, and project settingsproject.defaultModelSelectioninstead of a sticky fallback when creating or reusing empty draftsactionEnvironmentvariables; runtimeT3CODE_*vars can no longer be overridden by user envSourceControlProviderRegistrynow prefers a settings-based remote override (taggedcontextSource='override') over auto-detected remotesprojectsUpdateSettingsis rejected server-side, but existing sessions are not forcibly disconnected until their next turnMacroscope summarized 0f2ff8c.