Skip to content

feat(workspaces): add workspace base branch functionality#1562

Merged
Kitenite merged 16 commits into
superset-sh:mainfrom
Ipriyankrajai:feat/default-workspace-branch
Feb 20, 2026
Merged

feat(workspaces): add workspace base branch functionality#1562
Kitenite merged 16 commits into
superset-sh:mainfrom
Ipriyankrajai:feat/default-workspace-branch

Conversation

@Ipriyankrajai
Copy link
Copy Markdown
Contributor

@Ipriyankrajai Ipriyankrajai commented Feb 18, 2026

  • Introduced workspaceBaseBranch field in the project schema to allow setting a default base branch for new workspaces.
  • Updated project creation and workspace handling logic to utilize the new base branch setting.
  • Implemented utility function resolveWorkspaceBaseBranch to determine the effective base branch based on user preferences and existing branches.
  • Added tests for the new base branch resolution logic.
  • Enhanced UI components to support selection and display of the workspace base branch.
Screen.Recording.2026-02-19.at.3.58.12.PM.mov

Description

Fixes #1426

Related Issues

Type of Change

  • Bug fix
  • New feature
  • Documentation
  • Refactor
  • Other (please describe):

Testing

Screenshots (if applicable)

Additional Notes

Summary by CodeRabbit

  • New Features

    • Projects can store a Workspace Base Branch used when creating/opening workspaces and importing branches.
    • Base-branch resolution honors an explicit choice and sensible fallbacks.
  • UI

    • New "Workspace Base Branch" settings with options for repository default or a specific branch; shows missing/stale branch notices.
    • Workspace creation shows resolved base-branch info.
  • Data & Sync

    • Base branch persisted, included in project listings and workspace operations; git base config synced with local state.
  • Tests

    • Added unit tests for base-branch resolution and UI behavior.
  • Chores

    • Local DB migration added to store workspace base branch.

- Introduced `workspaceBaseBranch` field in the project schema to allow setting a default base branch for new workspaces.
- Updated project creation and workspace handling logic to utilize the new base branch setting.
- Implemented utility function `resolveWorkspaceBaseBranch` to determine the effective base branch based on user preferences and existing branches.
- Added tests for the new base branch resolution logic.
- Enhanced UI components to support selection and display of the workspace base branch.
- Added utility functions for managing base branch configurations, including `getBranchBaseConfig`, `setBranchBaseConfig`, and `unsetBranchBaseConfig`.
- Updated the branches router to utilize the new base branch configuration methods for retrieving and setting base branches.
- Refactored workspace creation procedures to leverage the new base branch utilities, ensuring consistent handling of base branch settings across workspaces.
- Enhanced the workspace initialization logic to check and apply base branch configurations effectively.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Add per-project workspaceBaseBranch (DB migration, schema and types), introduce branch base config helpers and resolvers, propagate/respect workspace base branch across workspace/worktree create/open flows and UI, update project update API, and add unit tests for resolver logic.

Changes

Cohort / File(s) Summary
Database Schema
packages/local-db/drizzle/0027_add_workspace_base_branch.sql, packages/local-db/drizzle/meta/.../0027_snapshot.json, packages/local-db/drizzle/meta/_journal.json, packages/local-db/src/schema/schema.ts
Add workspace_base_branch column to projects, update Drizzle snapshot/journal and generated types (InsertProject/SelectProject).
Projects Router
apps/desktop/src/lib/trpc/routers/projects/projects.ts
Extend update procedure input to accept workspaceBaseBranch (string
Workspace create & init flows
apps/desktop/src/lib/trpc/routers/workspaces/procedures/create.ts, apps/desktop/src/lib/trpc/routers/workspaces/utils/workspace-init.ts
Fetch known branches safely, compute resolved base branch via new resolver, propagate baseBranch into DB inserts for worktree/workspace, and replace direct git-config manipulation with new base-branch config helpers.
Base-branch resolver & tests (server)
apps/desktop/src/lib/trpc/routers/workspaces/utils/base-branch.ts, .../base-branch.test.ts
Add resolveWorkspaceBaseBranch utility to deterministically choose base branch from explicit, workspace preference, repo default, and known branches; include unit tests covering fallbacks and offline behavior.
Branch base config helpers
apps/desktop/src/lib/trpc/routers/workspaces/utils/base-branch-config.ts
Add getBranchBaseConfig, setBranchBaseConfig, unsetBranchBaseConfig to read/write branch.base and branch.base-explicit from Git config (safe I/O and normalized boolean parsing).
Changes & branches router
apps/desktop/src/lib/trpc/routers/changes/branches.ts
Switch reads/writes of branch base to use config helpers, compute persisted/preferring base branch for worktrees, persist worktree baseBranch in local DB, and return baseBranch in worktree responses.
Renderer base-branch utilities & tests
apps/desktop/src/renderer/lib/workspaceBaseBranch/workspaceBaseBranch.ts, .../workspaceBaseBranch.test.ts, .../index.ts
Add resolveEffectiveWorkspaceBaseBranch for UI normalization/precedence, unit tests for renderer behavior, and re-export for consumption in UI components.
UI: New workspace & project pages
apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/project/$projectId/page.tsx, apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/ProjectSettings.tsx
Use renderer resolver to compute effective base branch, send explicit baseBranch payload consistently, add Workspace Base Branch selector in project settings (with repo-default sentinel and missing-branch messaging).
Agent/tooling exposure
apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/list-projects.ts
Include workspaceBaseBranch in list_projects tool output mapping so external tooling sees the new field.

Sequence Diagram(s)

sequenceDiagram
    participant UI as Renderer UI
    participant TRPC as TRPC Router
    participant Resolver as BaseBranch Resolver
    participant GitCfg as Branch Config Helpers
    participant DB as Local DB

    UI->>TRPC: create/open workspace (projectId, explicitBaseBranch?)
    TRPC->>DB: SELECT project (includes workspace_base_branch) + branch info
    DB-->>TRPC: project, defaultBranch, knownBranches
    TRPC->>Resolver: resolveWorkspaceBaseBranch({ explicit, workspaceBaseBranch, defaultBranch, knownBranches })
    Resolver-->>TRPC: resolved baseBranch
    TRPC->>GitCfg: maybe setBranchBaseConfig(repoPath, branch, baseBranch, isExplicit)
    GitCfg-->>TRPC: OK
    TRPC->>DB: INSERT/UPDATE workspace/worktree with base_branch = resolved
    DB-->>TRPC: OK
    TRPC->>UI: return workspace/worktree (includes baseBranch)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I hopped through branches, sniffed the base,
tucked a field in projects, saved its place.
Resolvers listen, configs set with care,
workspaces sprout where choices pair —
happy hops for code and workspace grace.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The pull request description is vague and incomplete. While it lists high-level changes, it lacks critical details required by the template. Complete the template sections: provide a detailed description of changes, link related issues with GitHub keywords, mark the Type of Change, describe testing performed, and add any additional context needed for review.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding workspace base branch functionality to workspaces, which aligns with the primary objective of introducing the workspaceBaseBranch feature across the codebase.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

Copy link
Copy Markdown
Contributor

@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: 3

🧹 Nitpick comments (4)
apps/desktop/src/lib/trpc/routers/workspaces/utils/base-branch.ts (1)

1-6: Consider exporting ResolveWorkspaceBaseBranchParams.

The interface is currently internal, which is fine for current call sites. Exporting it improves ergonomics for any future callers that want to construct and pass the params object in a typed way.

♻️ Proposed change
-interface ResolveWorkspaceBaseBranchParams {
+export interface ResolveWorkspaceBaseBranchParams {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/lib/trpc/routers/workspaces/utils/base-branch.ts` around
lines 1 - 6, The interface ResolveWorkspaceBaseBranchParams is currently
internal; export it so callers can import and construct typed params — add the
export keyword to the interface declaration (export interface
ResolveWorkspaceBaseBranchParams { ... }) in base-branch.ts and update any
imports where external code will use it; rebuild/typecheck to ensure no further
changes are needed.
apps/desktop/src/lib/trpc/routers/workspaces/procedures/create.ts (1)

730-735: Move getKnownBranchesSafe past the early-exit checks in openExternalWorktree.

baseBranch is only consumed in the final "new worktree" insert path (Lines 840–900). The two common early-return paths — !exists (Line 742) and existingWorktree (Lines 756–838) — both return without ever using it, so the async branch-listing call is wasted I/O in those cases.

♻️ Proposed refactor
 .mutation(async ({ input }) => {
     const project = getProject(input.projectId);
     if (!project) {
         throw new Error(`Project ${input.projectId} not found`);
     }
-    const knownBranches = await getKnownBranchesSafe(project.mainRepoPath);
-    const baseBranch = resolveWorkspaceBaseBranch({
-        workspaceBaseBranch: project.workspaceBaseBranch,
-        defaultBranch: project.defaultBranch,
-        knownBranches,
-    });

     const exists = await worktreeExists(
         project.mainRepoPath,
         input.worktreePath,
     );
     if (!exists) {
         throw new Error("Worktree no longer exists on disk");
     }

     const existingWorktree = localDb.select()...get();

     if (existingWorktree) {
         // ... existing handling, early returns
     }

+    const knownBranches = await getKnownBranchesSafe(project.mainRepoPath);
+    const baseBranch = resolveWorkspaceBaseBranch({
+        workspaceBaseBranch: project.workspaceBaseBranch,
+        defaultBranch: project.defaultBranch,
+        knownBranches,
+    });
+
     const worktree = localDb.insert(worktrees).values({ ..., baseBranch, ... });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/lib/trpc/routers/workspaces/procedures/create.ts` around
lines 730 - 735, The call to getKnownBranchesSafe (and the subsequent
resolveWorkspaceBaseBranch into baseBranch) should be moved inside
openExternalWorktree so it runs only when creating the new worktree: delay
calling getKnownBranchesSafe(project.mainRepoPath) and
resolveWorkspaceBaseBranch(...) until after the early-exit checks for repository
existence (!exists) and for existingWorktree are performed and those checks
decide to proceed to the "new worktree" insert path; update code around
openExternalWorktree to remove the eager async call and instead compute
knownBranches/baseBranch just before the insert logic that uses baseBranch.
apps/desktop/src/lib/trpc/routers/workspaces/utils/base-branch.test.ts (1)

4-43: Add tests for the offline (no knownBranches) and ultimate "main" fallback cases.

The four existing tests cover the happy path and the stale-preference case well. Two important scenarios are missing:

  • Offline case (knownBranches omitted): when getKnownBranchesSafe returns undefined (branch listing fails), workspaceBaseBranch should still be returned as-is — this is the production safety guarantee but it's currently untested.
  • "main" fallback: when neither workspaceBaseBranch nor defaultBranch is set, the function should return "main". This verifies the hardcoded last-resort constant is actually reached.
✅ Suggested additional tests
+	test("uses workspace base branch when knownBranches is unavailable (offline)", () => {
+		const resolved = resolveWorkspaceBaseBranch({
+			workspaceBaseBranch: "feature/long-lived",
+			defaultBranch: "main",
+			// knownBranches intentionally omitted — simulates branch-listing failure
+		});
+
+		expect(resolved).toBe("feature/long-lived");
+	});
+
+	test('falls back to "main" when no defaultBranch or workspaceBaseBranch is provided', () => {
+		const resolved = resolveWorkspaceBaseBranch({});
+
+		expect(resolved).toBe("main");
+	});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/lib/trpc/routers/workspaces/utils/base-branch.test.ts`
around lines 4 - 43, Add two unit tests to
apps/desktop/src/lib/trpc/routers/workspaces/utils/base-branch.test.ts for
resolveWorkspaceBaseBranch: one that omits knownBranches (simulating
offline/getKnownBranchesSafe returning undefined) and verifies that the provided
workspaceBaseBranch is returned unchanged, and another that omits both
workspaceBaseBranch and defaultBranch and verifies the function returns the
hardcoded "main" fallback; put them alongside existing tests and use the same
resolveWorkspaceBaseBranch call pattern and expect(...) assertions.
apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/ProjectSettings.tsx (1)

65-68: getBranches is invalidated on every project update, not only workspace-base-branch changes.

onSettled fires for all updateProject mutations (name renames, color changes, hideImage toggles, etc.). getBranches runs several git operations — git.branch -a, for-each-ref, and potentially refreshDefaultBranch which may hit the network. Invalidating it unconditionally causes unnecessary git I/O on settings changes that have nothing to do with branches.

♻️ Suggested approach: conditional invalidation

Move the getBranches invalidation into handleWorkspaceBaseBranchChange instead of onSettled, or use a separate mutation for the base-branch update:

 const updateProject = electronTrpc.projects.update.useMutation({
   onSettled: () => {
     utils.projects.get.invalidate({ id: projectId });
-    utils.projects.getBranches.invalidate({ projectId });
     utils.workspaces.getAllGrouped.invalidate();
   },
 });
 const handleWorkspaceBaseBranchChange = (value: string) => {
   updateProject.mutate({
     id: projectId,
     patch: {
       workspaceBaseBranch: value === REPO_DEFAULT_BASE_BRANCH ? null : value,
     },
   });
+  utils.projects.getBranches.invalidate({ projectId });
 };

Also applies to: 88-92

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

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/project/`$projectId/components/ProjectSettings/ProjectSettings.tsx
around lines 65 - 68, The getBranches query is being invalidated on every
updateProject onSettled, causing unnecessary git work; instead, remove the
unconditional invalidation from the updateProject onSettled and perform
conditional invalidation only when the workspace base branch actually changes
(move the invalidate call into your handleWorkspaceBaseBranchChange handler) or
create a dedicated mutation for updating the base branch that calls
utils.projects.getBranches.invalidate on success; target symbols:
electronTrpc.projects.getBranches.useQuery, the updateProject mutation onSettled
block, and handleWorkspaceBaseBranchChange (or the new base-branch mutation) so
the expensive git queries run only when the base branch is modified.
🤖 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/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx`:
- Around line 132-136: effectiveBaseBranch currently uses
project?.workspaceBaseBranch without validating it against the available
branches, causing a stale "from X" label; update the calculation of
effectiveBaseBranch to check that project?.workspaceBaseBranch exists in
branchData?.branches (e.g., by matching branch name or id) before selecting it,
otherwise fall back to branchData?.defaultBranch or null so the displayed "from
X" matches what the server will resolve; modify the logic around
effectiveBaseBranch (and any helper where project?.workspaceBaseBranch is read)
to perform this inclusion check against branchData?.branches.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/project/`$projectId/page.tsx:
- Around line 105-109: The UI uses project?.workspaceBaseBranch directly when
computing effectiveBaseBranch (const effectiveBaseBranch = baseBranch ??
project?.workspaceBaseBranch ?? branchData?.defaultBranch ?? null), which can be
stale if that branch was deleted; update the logic to verify that
project.workspaceBaseBranch exists in branchData?.branches (or matches
branchData?.defaultBranch) before using it, otherwise fall back to
branchData?.defaultBranch (and then null). Apply the same guard to the
equivalent computation in NewWorkspaceModal (the workspace base branch
preference check around lines 132–136) so both places only show a "from X" label
when the branch actually exists in branchData.branches.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/project/`$projectId/components/ProjectSettings/ProjectSettings.tsx:
- Around line 205-209: The computed workspaceBaseBranchMissing currently treats
undefined branchData as "missing"; update the logic in the ProjectSettings
component so workspaceBaseBranchMissing only evaluates true when branchData is
available and the branch truly doesn't exist — e.g. guard on branchData (or
branchData.branches) before calling .some, or check a loading state (branchData
=== undefined or isLoading) and return false in that case; adjust the expression
that defines workspaceBaseBranchMissing (referencing workspaceBaseBranchMissing,
project.workspaceBaseBranch, and branchData?.branches) accordingly so the
`(missing)` label isn't shown while the query is in flight.

---

Nitpick comments:
In `@apps/desktop/src/lib/trpc/routers/workspaces/procedures/create.ts`:
- Around line 730-735: The call to getKnownBranchesSafe (and the subsequent
resolveWorkspaceBaseBranch into baseBranch) should be moved inside
openExternalWorktree so it runs only when creating the new worktree: delay
calling getKnownBranchesSafe(project.mainRepoPath) and
resolveWorkspaceBaseBranch(...) until after the early-exit checks for repository
existence (!exists) and for existingWorktree are performed and those checks
decide to proceed to the "new worktree" insert path; update code around
openExternalWorktree to remove the eager async call and instead compute
knownBranches/baseBranch just before the insert logic that uses baseBranch.

In `@apps/desktop/src/lib/trpc/routers/workspaces/utils/base-branch.test.ts`:
- Around line 4-43: Add two unit tests to
apps/desktop/src/lib/trpc/routers/workspaces/utils/base-branch.test.ts for
resolveWorkspaceBaseBranch: one that omits knownBranches (simulating
offline/getKnownBranchesSafe returning undefined) and verifies that the provided
workspaceBaseBranch is returned unchanged, and another that omits both
workspaceBaseBranch and defaultBranch and verifies the function returns the
hardcoded "main" fallback; put them alongside existing tests and use the same
resolveWorkspaceBaseBranch call pattern and expect(...) assertions.

In `@apps/desktop/src/lib/trpc/routers/workspaces/utils/base-branch.ts`:
- Around line 1-6: The interface ResolveWorkspaceBaseBranchParams is currently
internal; export it so callers can import and construct typed params — add the
export keyword to the interface declaration (export interface
ResolveWorkspaceBaseBranchParams { ... }) in base-branch.ts and update any
imports where external code will use it; rebuild/typecheck to ensure no further
changes are needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/project/`$projectId/components/ProjectSettings/ProjectSettings.tsx:
- Around line 65-68: The getBranches query is being invalidated on every
updateProject onSettled, causing unnecessary git work; instead, remove the
unconditional invalidation from the updateProject onSettled and perform
conditional invalidation only when the workspace base branch actually changes
(move the invalidate call into your handleWorkspaceBaseBranchChange handler) or
create a dedicated mutation for updating the base branch that calls
utils.projects.getBranches.invalidate on success; target symbols:
electronTrpc.projects.getBranches.useQuery, the updateProject mutation onSettled
block, and handleWorkspaceBaseBranchChange (or the new base-branch mutation) so
the expensive git queries run only when the base branch is modified.

Comment thread apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx Outdated
…on logic

- Added `resolveEffectiveWorkspaceBaseBranch` utility to determine the most appropriate base branch based on user-defined preferences and existing branches.
- Updated `NewWorkspaceModal` and `ProjectPage` components to utilize the new base branch resolution logic for improved workspace handling.
- Introduced tests for the new utility to ensure correct functionality across various scenarios.
- Refactored related components to enhance clarity and maintainability.
…eate procedures

- Moved base branch resolution logic to a more appropriate location within the workspace creation procedures for improved clarity and maintainability.
- Updated tests for `resolveWorkspaceBaseBranch` to cover additional scenarios, ensuring robust handling of base branch selection.
- Adjusted ProjectSettings component to correctly invalidate branch data after updates, enhancing data consistency.
Copy link
Copy Markdown
Contributor

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx (1)

374-384: ⚠️ Potential issue | 🟡 Minor

"from null" / "from " renders when effectiveBaseBranch is null during branch loading.

resolveEffectiveWorkspaceBaseBranch returns string | null. While branchData is still loading, all three inputs (explicitBaseBranch, workspaceBaseBranch presence in branches, defaultBranch) can be absent, causing null. React renders null as empty text, so the label shows "from " with nothing after it.

🐛 Proposed fix
-					<span className="text-muted-foreground/60">
-						from {effectiveBaseBranch}
-					</span>
+					{effectiveBaseBranch && (
+						<span className="text-muted-foreground/60">
+							from {effectiveBaseBranch}
+						</span>
+					)}
🧹 Nitpick comments (4)
apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/ProjectSettings.tsx (1)

165-173: getBranches invalidation is unnecessary here.

Changing workspaceBaseBranch does not alter the repository's branch list. updateProject.onSettled already invalidates projects.get, which carries the updated workspaceBaseBranch. The getBranches invalidation causes a spurious network round-trip.

♻️ Proposed fix
 	const handleWorkspaceBaseBranchChange = (value: string) => {
 		updateProject.mutate({
 			id: projectId,
 			patch: {
 				workspaceBaseBranch: value === REPO_DEFAULT_BASE_BRANCH ? null : value,
 			},
 		});
-		utils.projects.getBranches.invalidate({ projectId });
 	};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/project/`$projectId/components/ProjectSettings/ProjectSettings.tsx
around lines 165 - 173, The call to utils.projects.getBranches.invalidate inside
handleWorkspaceBaseBranchChange is unnecessary and causes an extra network
request; remove that invalidate call and keep the updateProject.mutate call
as-is so updateProject.onSettled can rely on its existing invalidation of
projects.get to propagate the updated workspaceBaseBranch. Specifically, edit
the handleWorkspaceBaseBranchChange function to drop
utils.projects.getBranches.invalidate({ projectId }) and only perform
updateProject.mutate({ id: projectId, patch: { workspaceBaseBranch: value ===
REPO_DEFAULT_BASE_BRANCH ? null : value } });.
apps/desktop/src/lib/trpc/routers/workspaces/procedures/create.ts (1)

978-983: All imported external worktrees receive the same resolved baseBranch.

For pre-existing DB worktrees (loop at lines 994–1026) the DB baseBranch is preserved (no update), which is correct. For external worktrees not yet in DB, the project-level baseBranch is the only available signal, so the approximation is the best achievable here. Worth a comment to document the intent.

📝 Suggested inline comment
 			const knownBranches = await getKnownBranchesSafe(project.mainRepoPath);
 			const baseBranch = resolveWorkspaceBaseBranch({
 				workspaceBaseBranch: project.workspaceBaseBranch,
 				defaultBranch: project.defaultBranch,
 				knownBranches,
 			});
+			// baseBranch is a project-level approximation; individual worktree origins are unknown for external imports.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/lib/trpc/routers/workspaces/procedures/create.ts` around
lines 978 - 983, The code resolves a single project-level baseBranch using
getKnownBranchesSafe and resolveWorkspaceBaseBranch which is then applied to all
imported external worktrees; add a concise inline comment near the block that
computes baseBranch (around the getKnownBranchesSafe /
resolveWorkspaceBaseBranch calls) explaining that for external worktrees not yet
present in the DB we must approximate with the project-level baseBranch and that
pre-existing DB worktrees keep their stored baseBranch (the loop handling DB
entries preserves baseBranch), so the shared resolved value is intentional and
not a bug.
apps/desktop/src/lib/trpc/routers/workspaces/utils/base-branch-config.ts (1)

13-16: Consider exporting BranchBaseConfig for consumer type annotations.

The interface is the return type of the exported getBranchBaseConfig but is not exported itself. Callers that want to explicitly type-annotate the result (e.g., in destructuring patterns like at workspace-init.ts line 68) must inline the shape or use Awaited<ReturnType<typeof getBranchBaseConfig>>.

♻️ Proposed fix
-interface BranchBaseConfig {
+export interface BranchBaseConfig {
 	baseBranch: string | null;
 	isExplicit: boolean;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/lib/trpc/routers/workspaces/utils/base-branch-config.ts`
around lines 13 - 16, Export the BranchBaseConfig interface so callers can use
it for explicit type annotations instead of inlining or using
Awaited<ReturnType<typeof getBranchBaseConfig>>; update the interface
declaration for BranchBaseConfig to be exported and keep getBranchBaseConfig's
signature intact so existing consumers can import BranchBaseConfig and annotate
results (e.g., const config: BranchBaseConfig = await getBranchBaseConfig(...)).
apps/desktop/src/renderer/lib/workspaceBaseBranch/workspaceBaseBranch.test.ts (1)

36-42: Consider adding edge-case tests for branches: [] and isolated defaultBranch.

Two realistic scenarios currently untested:

  1. Empty branches array — a consumer passes branches: [] (e.g., branches haven't been fetched yet). Since workspaceBaseBranch requires an existence match in branches, the outcome would differ from branches: undefined, but both are currently covered only by the latter.

  2. defaultBranch not in branches — the current test always has defaultBranch: "main" present in branches. The function description shows defaultBranch is returned via normalizeBranch regardless of whether it exists in branches, which is asymmetric to how workspaceBaseBranch is handled. A test would document this intentional asymmetry.

✅ Suggested additional tests
+	test("falls back to default branch when branches is empty", () => {
+		const resolved = resolveEffectiveWorkspaceBaseBranch({
+			workspaceBaseBranch: "feature/preferred",
+			defaultBranch: "main",
+			branches: [],
+		});
+
+		expect(resolved).toBe("main");
+	});
+
+	test("returns default branch even when it is absent from branches list", () => {
+		const resolved = resolveEffectiveWorkspaceBaseBranch({
+			workspaceBaseBranch: "feature/deleted",
+			defaultBranch: "main",
+			branches: [{ name: "feature/other" }],
+		});
+
+		expect(resolved).toBe("main");
+	});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/lib/workspaceBaseBranch/workspaceBaseBranch.test.ts`
around lines 36 - 42, Add two unit tests for
resolveEffectiveWorkspaceBaseBranch: one where the input has branches: [] and
workspaceBaseBranch set (e.g., "feature/deleted") asserting it returns null (to
cover the case where branches are present but empty), and another where
defaultBranch is set to a name not present in branches (e.g., defaultBranch:
"main", branches: [{name: "other"}]) asserting the function returns
normalizeBranch(defaultBranch) (to document the asymmetry between defaultBranch
and workspaceBaseBranch handling). Use the existing test structure and reference
resolveEffectiveWorkspaceBaseBranch and normalizeBranch when adding assertions.
🤖 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/desktop/src/lib/trpc/routers/workspaces/utils/workspace-init.ts`:
- Around line 249-254: resolveLocalRef currently calls setBranchBaseConfig to
update git config when effectiveBaseBranch falls back to result.fallbackBranch,
but it doesn't persist that fallback into the DB so worktrees.baseBranch remains
stale; after calling setBranchBaseConfig (the block updating git config for
repoPath/mainRepoPath, branch and baseBranch: result.fallbackBranch), also
update the worktree record in the DB to set worktrees.baseBranch =
result.fallbackBranch (i.e., persist the configuredBaseBranch into
persistedBaseBranch) so that the DB mirrors the git config and
getBranches/persistedBaseBranch stay consistent if the git config is later
cleared.

In `@apps/desktop/src/renderer/lib/workspaceBaseBranch/workspaceBaseBranch.ts`:
- Around line 28-33: The renderer currently falls back to defaultBranch when
branches is undefined because preferredExists uses branches?.some(...); change
the logic in the workspaceBaseBranch resolution so that if
normalizeBranch(workspaceBaseBranch) returns a preferred value and branches is
undefined (loading/unavailable) you return that preferred value
immediately—i.e., treat missing branches the same as the server's
resolveWorkspaceBaseBranch by checking for branches presence first and only
performing the branches.some(...) check when branches is defined; update the
code around normalizeBranch, preferredExists, and the return path so preferred
is returned when branches === undefined or when branches contains the preferred
branch.

---

Nitpick comments:
In `@apps/desktop/src/lib/trpc/routers/workspaces/procedures/create.ts`:
- Around line 978-983: The code resolves a single project-level baseBranch using
getKnownBranchesSafe and resolveWorkspaceBaseBranch which is then applied to all
imported external worktrees; add a concise inline comment near the block that
computes baseBranch (around the getKnownBranchesSafe /
resolveWorkspaceBaseBranch calls) explaining that for external worktrees not yet
present in the DB we must approximate with the project-level baseBranch and that
pre-existing DB worktrees keep their stored baseBranch (the loop handling DB
entries preserves baseBranch), so the shared resolved value is intentional and
not a bug.

In `@apps/desktop/src/lib/trpc/routers/workspaces/utils/base-branch-config.ts`:
- Around line 13-16: Export the BranchBaseConfig interface so callers can use it
for explicit type annotations instead of inlining or using
Awaited<ReturnType<typeof getBranchBaseConfig>>; update the interface
declaration for BranchBaseConfig to be exported and keep getBranchBaseConfig's
signature intact so existing consumers can import BranchBaseConfig and annotate
results (e.g., const config: BranchBaseConfig = await getBranchBaseConfig(...)).

In
`@apps/desktop/src/renderer/lib/workspaceBaseBranch/workspaceBaseBranch.test.ts`:
- Around line 36-42: Add two unit tests for resolveEffectiveWorkspaceBaseBranch:
one where the input has branches: [] and workspaceBaseBranch set (e.g.,
"feature/deleted") asserting it returns null (to cover the case where branches
are present but empty), and another where defaultBranch is set to a name not
present in branches (e.g., defaultBranch: "main", branches: [{name: "other"}])
asserting the function returns normalizeBranch(defaultBranch) (to document the
asymmetry between defaultBranch and workspaceBaseBranch handling). Use the
existing test structure and reference resolveEffectiveWorkspaceBaseBranch and
normalizeBranch when adding assertions.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/project/`$projectId/components/ProjectSettings/ProjectSettings.tsx:
- Around line 165-173: The call to utils.projects.getBranches.invalidate inside
handleWorkspaceBaseBranchChange is unnecessary and causes an extra network
request; remove that invalidate call and keep the updateProject.mutate call
as-is so updateProject.onSettled can rely on its existing invalidation of
projects.get to propagate the updated workspaceBaseBranch. Specifically, edit
the handleWorkspaceBaseBranchChange function to drop
utils.projects.getBranches.invalidate({ projectId }) and only perform
updateProject.mutate({ id: projectId, patch: { workspaceBaseBranch: value ===
REPO_DEFAULT_BASE_BRANCH ? null : value } });.

- Bump the version of the @superset/desktop package in bun.lock to ensure compatibility with the latest features and fixes.
- Refactor workspace base branch resolution logic in workspace-init.ts for improved clarity.
- Enhance NewWorkspaceModal to handle undefined effectiveBaseBranch gracefully.
- Add tests for edge cases in resolveEffectiveWorkspaceBaseBranch to ensure robust functionality.
- Update ProjectSettings to notify users when the specified workspace base branch is missing.
Copy link
Copy Markdown
Contributor

@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: 1

🧹 Nitpick comments (1)
apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/ProjectSettings.tsx (1)

65-69: enabled: !!projectId is redundant — projectId is always a non-empty string.

ProjectSettingsProps declares projectId: string as a required prop, so !!projectId is always true. The guard adds noise without protecting against a real case.

♻️ Proposed simplification
-	const { data: branchData, isLoading: isBranchDataLoading } =
-		electronTrpc.projects.getBranches.useQuery(
-			{ projectId },
-			{ enabled: !!projectId },
-		);
+	const { data: branchData, isLoading: isBranchDataLoading } =
+		electronTrpc.projects.getBranches.useQuery({ projectId });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/project/`$projectId/components/ProjectSettings/ProjectSettings.tsx
around lines 65 - 69, The query call uses a redundant boolean cast for the
enabled option: remove the unnecessary guard by changing
electronTrpc.projects.getBranches.useQuery({ projectId }, { enabled: !!projectId
}) to simply pass enabled: true (or omit the enabled option) because
ProjectSettingsProps defines projectId as a required string; update the call
site in ProjectSettings (the electronTrpc.projects.getBranches.useQuery
invocation) so it no longer uses !!projectId.
🤖 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/desktop/src/renderer/routes/_authenticated/settings/project/`$projectId/components/ProjectSettings/ProjectSettings.tsx:
- Around line 287-310: The Select can show a blank value when branches are
loading because no SelectItem matches workspaceBaseBranchValue; update the
Select's disabled prop to also disable while branch list is loading by changing
disabled={updateProject.isPending} to disabled={updateProject.isPending ||
isBranchDataLoading} (in the Select that uses workspaceBaseBranchValue and
handleWorkspaceBaseBranchChange) so the control is not interactive or rendered
with a missing selection until branchData.branches is available.

---

Duplicate comments:
In `@apps/desktop/src/renderer/lib/workspaceBaseBranch/workspaceBaseBranch.ts`:
- Around line 28-36: The offline resolver should mirror the server behavior by
returning the normalized preferred branch when the branch list is missing;
ensure the early-return guard in workspaceBaseBranch (use normalizeBranch to get
preferred, then if (preferred) { if (!branches) return preferred; if
(branches.some(branch => branch.name === preferred)) return preferred; })
remains intact so we short-circuit to preferred when branches is undefined and
only fall back to defaultBranch when preferred is not present in branches.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/project/`$projectId/components/ProjectSettings/ProjectSettings.tsx:
- Around line 205-211: The earlier flashing issue is resolved: the computation
for workspaceBaseBranchMissing now correctly guards with !isBranchDataLoading
and !!branchData before calling branchData.branches.some(), so no change is
required—confirm the variable workspaceBaseBranchMissing (and the related guards
isBranchDataLoading and branchData) remain as shown and remove any outdated
TODO/concern comments referencing the flashing label.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/settings/project/`$projectId/components/ProjectSettings/ProjectSettings.tsx:
- Around line 65-69: The query call uses a redundant boolean cast for the
enabled option: remove the unnecessary guard by changing
electronTrpc.projects.getBranches.useQuery({ projectId }, { enabled: !!projectId
}) to simply pass enabled: true (or omit the enabled option) because
ProjectSettingsProps defines projectId as a required string; update the call
site in ProjectSettings (the electronTrpc.projects.getBranches.useQuery
invocation) so it no longer uses !!projectId.

- Added logic to update the base branch of the worktree in the local database during workspace initialization.
- Ensures that the correct fallback branch is set for the worktree, improving workspace setup reliability.
- Updated the ProjectSettings component to disable the workspace base branch selection when branch data is loading, improving user experience and preventing potential errors during updates.
Copy link
Copy Markdown
Contributor

@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: 1

🧹 Nitpick comments (1)
apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/ProjectSettings.tsx (1)

27-27: Sentinel collision if a branch is named __repo_default__.

A branch named "__repo_default__" would appear as a regular <SelectItem> alongside the sentinel item — both sharing the same value. When selected, handleWorkspaceBaseBranchChange maps it to null, silently discarding the user's intended branch choice. Filtering the sentinel name out of the mapped list eliminates the collision at zero runtime cost.

♻️ Proposed fix
-			{(branchData?.branches ?? []).map((branch) => (
-				<SelectItem key={branch.name} value={branch.name}>
-					{branch.name}
-				</SelectItem>
-			))}
+			{(branchData?.branches ?? [])
+				.filter((branch) => branch.name !== REPO_DEFAULT_BASE_BRANCH)
+				.map((branch) => (
+					<SelectItem key={branch.name} value={branch.name}>
+						{branch.name}
+					</SelectItem>
+				))}

Also applies to: 296-309

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

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/project/`$projectId/components/ProjectSettings/ProjectSettings.tsx
at line 27, The sentinel constant REPO_DEFAULT_BASE_BRANCH("__repo_default__")
collides with a real branch name; update the selection handling in
handleWorkspaceBaseBranchChange (and the mapped selection logic around the
SelectItem rendering at lines ~296-309) to first filter out any values equal to
REPO_DEFAULT_BASE_BRANCH from the selected values array, then map the remaining
values to branches (or null only when the filtered array is empty) so a real
branch named "__repo_default__" is preserved and the sentinel is only used as UI
placeholder.
🤖 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/desktop/src/renderer/routes/_authenticated/settings/project/`$projectId/components/ProjectSettings/ProjectSettings.tsx:
- Around line 287-295: Select currently shows blank because SelectValue has no
placeholder while branchData?.branches is empty during isBranchDataLoading;
update the Select usage (the component rendering Select, SelectValue and the
SelectItem list) to pass a placeholder to SelectValue (e.g., "Loading branches…"
or a localized string) when isBranchDataLoading or when workspaceBaseBranchValue
is not found in branchData?.branches, so the control displays a loading hint
instead of empty; reference Select, SelectValue, workspaceBaseBranchValue,
isBranchDataLoading, and branchData?.branches to locate where to add the
conditional placeholder logic.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/settings/project/`$projectId/components/ProjectSettings/ProjectSettings.tsx:
- Line 27: The sentinel constant REPO_DEFAULT_BASE_BRANCH("__repo_default__")
collides with a real branch name; update the selection handling in
handleWorkspaceBaseBranchChange (and the mapped selection logic around the
SelectItem rendering at lines ~296-309) to first filter out any values equal to
REPO_DEFAULT_BASE_BRANCH from the selected values array, then map the remaining
values to branches (or null only when the filtered array is empty) so a real
branch named "__repo_default__" is preserved and the sentinel is only used as UI
placeholder.

- Updated the ProjectSettings component to display a loading placeholder in the branch selection when branch data is being loaded, enhancing user feedback during data retrieval.
Copy link
Copy Markdown
Contributor

@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.

🧹 Nitpick comments (1)
apps/desktop/src/lib/trpc/routers/projects/projects.ts (1)

802-802: Consider adding .min(1) to prevent empty-string persistence.

The z.string().nullable().optional() schema accepts empty strings. While consuming code (normalizeBranch()) treats empty/falsy values as undefined at resolution time, it's cleaner to reject them at the validation boundary for consistency with other branch-name fields.

Suggested improvement
-						workspaceBaseBranch: z.string().nullable().optional(),
+						workspaceBaseBranch: z.string().min(1).nullable().optional(),

The Drizzle .set() call and DB column (workspace_base_branch in @superset/local-db) are correctly wired.

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

In `@apps/desktop/src/lib/trpc/routers/projects/projects.ts` at line 802, The
schema for workspaceBaseBranch currently allows empty strings because it's
declared as workspaceBaseBranch: z.string().nullable().optional(); update this
validation to reject empty strings by adding .min(1) (e.g.,
z.string().min(1).nullable().optional()) so empty-string values are rejected at
the validation boundary; ensure this change is applied where the
workspaceBaseBranch schema is defined and that any callers relying on
normalizeBranch() still handle nullable/optional values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/desktop/src/lib/trpc/routers/projects/projects.ts`:
- Line 802: The schema for workspaceBaseBranch currently allows empty strings
because it's declared as workspaceBaseBranch: z.string().nullable().optional();
update this validation to reject empty strings by adding .min(1) (e.g.,
z.string().min(1).nullable().optional()) so empty-string values are rejected at
the validation boundary; ensure this change is applied where the
workspaceBaseBranch schema is defined and that any callers relying on
normalizeBranch() still handle nullable/optional values.

Ipriyankrajai and others added 7 commits February 19, 2026 15:58
- Updated the ProjectSettings component to show a loading message instead of a placeholder when branch data is being loaded, enhancing user feedback during data retrieval.
switchBranch was updating worktrees.branch without clearing
worktrees.baseBranch, causing the persisted fallback in getBranches
to return the previous branch's base branch after switching.
# Conflicts:
#	packages/local-db/drizzle/meta/0028_snapshot.json
#	packages/local-db/drizzle/meta/_journal.json
@Kitenite Kitenite merged commit ce8a978 into superset-sh:main Feb 20, 2026
5 of 7 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.

[feat] Allow setting a default base branch per repository in the UI

2 participants