fix(desktop): fix workspace deletion bug#2889
Conversation
📝 WalkthroughWalkthroughThis PR introduces a draft workspace provisioning workflow, replacing direct workspace/worktree creation with a Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant WorkspaceInitMgr as WorkspaceInitManager
participant Procedures
participant Database
participant FileSystem as File System
rect rgba(100, 200, 100, 0.5)
Note over Client,FileSystem: Draft Workspace Creation Flow (New)
Client->>Procedures: create(projectId, branch)
Procedures->>WorkspaceInitMgr: startJob(workspaceId, projectId, draftJob)
WorkspaceInitMgr->>WorkspaceInitMgr: Store draft in InitJob
Procedures->>Procedures: buildDraftWorkspaceRow(draftJob)
Procedures->>Procedures: initializeWorkspaceWorktree(params with draftJob)
Procedures->>FileSystem: Create worktree on disk
Procedures->>Procedures: persistDraftWorkspaceIfNeeded()
Procedures->>Database: Insert workspace row (on persistence)
Procedures->>Database: Insert worktree row (on persistence)
Procedures-->>Client: Return created workspace
end
rect rgba(100, 100, 200, 0.5)
Note over Client,FileSystem: Draft Workspace Deletion Flow (New)
Client->>Procedures: delete(workspaceId)
Procedures->>WorkspaceInitMgr: getDraftJob(workspaceId)
alt Draft job exists
Procedures->>WorkspaceInitMgr: Cancel job (up to 30s)
Procedures->>FileSystem: Remove worktree disk path
Procedures->>WorkspaceInitMgr: Clear draft job
else Persisted workspace exists
Procedures->>Database: Delete workspace record
Procedures->>FileSystem: Remove worktree disk path
end
Procedures-->>Client: Return success
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 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 |
🚀 Preview Deployment🔗 Preview Links
Preview updates automatically with new commits |
There was a problem hiding this comment.
Actionable comments posted: 4
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/lib/trpc/routers/workspaces/procedures/external-worktree-import.test.ts (1)
154-161:⚠️ Potential issue | 🟡 MinorDuplicate assertion detected.
Lines 155-157 and 158-160 both assert the same condition (
important-data.txtexistence). This appears to be a copy-paste artifact.🔧 Suggested fix
// Verify data exists before expect(existsSync(join(externalWorktreePath, "important-data.txt"))).toBe( true, ); - expect(existsSync(join(externalWorktreePath, "important-data.txt"))).toBe( - true, - ); expect(existsSync(join(externalWorktreePath, "test.txt"))).toBe(true);🤖 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/external-worktree-import.test.ts` around lines 154 - 161, The test contains a duplicated assertion checking existsSync(join(externalWorktreePath, "important-data.txt")) — remove the redundant expect so the setup only asserts that "important-data.txt" exists once (leave the distinct expect for "test.txt" intact); locate the duplicate expect calls that reference externalWorktreePath and delete one to avoid the copy‑paste artifact.
🤖 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/procedures/delete.ts`:
- Around line 200-213: The branch clears the draft job and returns success even
if workspaceInitManager.waitForInit(input.id, 30000) timed out and the
initializer is still running, which allows persistDraftWorkspaceIfNeeded() to
recreate the draft; change the logic in the block that calls
workspaceInitManager.cancel(input.id) and await
workspaceInitManager.waitForInit(input.id, 30000) so you check the result (or
re-check workspaceInitManager.isInitializing(input.id)) and only call
workspaceInitManager.clearJob(input.id),
hideProjectIfNoWorkspaces(draftJob.projectId), and track("workspace_deleted",
...) and return { success: true } when the initializer has actually stopped; if
the wait timed out keep the job intact (or surface an error) so the background
initializer cannot later recreate the draft workspace.
- Around line 309-316: The delete flow currently always calls
removeWorktreeFromDisk for every workspace/worktree; instead, check the
worktree's origin flag (createdBySuperset) before performing destructive disk
removal: in the branch around the removeWorktreeFromDisk call (and the other
branch that mirrors this logic) use worktree.createdBySuperset (or the
equivalent flag set by createWorkspaceFromExternalWorktree) and only call
removeWorktreeFromDisk when createdBySuperset === true; if false, skip disk
deletion and proceed to remove only the Superset record and
clearWorkspaceDeletingStatus(input.id), returning a successful result for the
DB-only delete path. Ensure both affected code paths that call
removeWorktreeFromDisk are updated to respect createdBySuperset.
In `@apps/desktop/src/lib/trpc/routers/workspaces/procedures/init.ts`:
- Around line 196-250: The code currently calls
workspaceInitManager.clearJob/startJob but never cancels a running
initializeWorkspaceWorktree, allowing concurrent inits for the same workspace;
update the flow to explicitly cancel or await the previous initializer before
starting a new one by using the workspaceInitManager cancellation API (e.g., a
cancelJob/abortJob or returned AbortController) and/or making
initializeWorkspaceWorktree accept an AbortSignal and check it; specifically,
before calling workspaceInitManager.clearJob/startJob and before calling
initializeWorkspaceWorktree (both the regular and draft paths where
persistDraftWorkspaceIfNeeded races), invoke
workspaceInitManager.cancelJob(input.workspaceId) or await the previous job’s
completion, pass the cancellation signal into initializeWorkspaceWorktree, and
ensure any in-flight persistDraftWorkspaceIfNeeded is guarded by that signal to
prevent duplicate inserts or resurrected state.
In `@apps/desktop/src/lib/trpc/routers/workspaces/utils/workspace-init.ts`:
- Around line 128-188: The DB insert sequence in persistDraftWorkspaceIfNeeded
must be made atomic and the draft should be marked persisted immediately after
the DB writes succeed: wrap the localDb.insert(...) calls that create worktrees
and workspaces (the insert into worktrees with draftJob.worktreeId and the
insert into workspaces with workspaceId and tabOrder) in a single transaction,
commit the transaction, then set workspacePersisted = true right after the
transaction succeeds (before calling setBranchBaseConfig, activateProject,
setLastActiveWorkspace, or track). This ensures the persisted boundary is the
successful DB commit and prevents later exceptions (in setBranchBaseConfig,
activateProject, track, etc.) from leaving rows in SQLite while
workspacePersisted remains false.
---
Outside diff comments:
In
`@apps/desktop/src/lib/trpc/routers/workspaces/procedures/external-worktree-import.test.ts`:
- Around line 154-161: The test contains a duplicated assertion checking
existsSync(join(externalWorktreePath, "important-data.txt")) — remove the
redundant expect so the setup only asserts that "important-data.txt" exists once
(leave the distinct expect for "test.txt" intact); locate the duplicate expect
calls that reference externalWorktreePath and delete one to avoid the copy‑paste
artifact.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 18a70215-bc92-4b73-9701-e5c00edc2902
📒 Files selected for processing (13)
apps/desktop/src/lib/trpc/routers/workspaces/procedures/create.tsapps/desktop/src/lib/trpc/routers/workspaces/procedures/delete.tsapps/desktop/src/lib/trpc/routers/workspaces/procedures/external-worktree-import.test.tsapps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.tsapps/desktop/src/lib/trpc/routers/workspaces/procedures/init.tsapps/desktop/src/lib/trpc/routers/workspaces/procedures/query.tsapps/desktop/src/lib/trpc/routers/workspaces/utils/draft-workspace.tsapps/desktop/src/lib/trpc/routers/workspaces/utils/git.tsapps/desktop/src/lib/trpc/routers/workspaces/utils/workspace-creation.tsapps/desktop/src/lib/trpc/routers/workspaces/utils/workspace-init.tsapps/desktop/src/main/lib/workspace-init-manager.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
| if (!workspace && draftJob) { | ||
| if (workspaceInitManager.isInitializing(input.id)) { | ||
| console.log( | ||
| `[workspace/delete] Cancelling draft init for ${input.id}, waiting for completion...`, | ||
| ); | ||
| workspaceInitManager.cancel(input.id); | ||
| await workspaceInitManager.waitForInit(input.id, 30000); | ||
| } | ||
|
|
||
| workspaceInitManager.clearJob(input.id); | ||
| hideProjectIfNoWorkspaces(draftJob.projectId); | ||
| track("workspace_deleted", { workspace_id: input.id, draft: true }); | ||
|
|
||
| return { success: true }; |
There was a problem hiding this comment.
Don't clear the draft job unless the initializer actually stopped.
waitForInit(input.id, 30000) resolves on timeout, so this branch can fall through to clearJob() and return { success: true } while the original init is still running. If that task reaches persistDraftWorkspaceIfNeeded() later, the draft workspace/worktree rows get recreated after the user already deleted them.
🤖 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/delete.ts` around
lines 200 - 213, The branch clears the draft job and returns success even if
workspaceInitManager.waitForInit(input.id, 30000) timed out and the initializer
is still running, which allows persistDraftWorkspaceIfNeeded() to recreate the
draft; change the logic in the block that calls
workspaceInitManager.cancel(input.id) and await
workspaceInitManager.waitForInit(input.id, 30000) so you check the result (or
re-check workspaceInitManager.isInitializing(input.id)) and only call
workspaceInitManager.clearJob(input.id),
hideProjectIfNoWorkspaces(draftJob.projectId), and track("workspace_deleted",
...) and return { success: true } when the initializer has actually stopped; if
the wait timed out keep the job intact (or surface an error) so the background
initializer cannot later recreate the draft workspace.
| workspaceInitManager.clearJob(input.workspaceId); | ||
| workspaceInitManager.startJob(input.workspaceId, workspace.projectId); | ||
|
|
||
| initializeWorkspaceWorktree({ | ||
| workspaceId: input.workspaceId, | ||
| projectId: workspace.projectId, | ||
| worktreeId: worktree.id, | ||
| worktreePath, | ||
| branch, | ||
| mainRepoPath: project.mainRepoPath, | ||
| }); | ||
| } else { | ||
| const { draftJob, project } = target; | ||
| const { branch, worktreePath } = await resolveRetryTarget({ | ||
| currentBranch: draftJob.branch, | ||
| currentPath: draftJob.worktreePath, | ||
| project, | ||
| deduplicateBranchName: input.deduplicateBranchName, | ||
| applyUpdate: (next) => { | ||
| workspaceInitManager.updateDraftJob(input.workspaceId, { | ||
| branch: next.branch, | ||
| worktreePath: next.worktreePath, | ||
| workspaceName: draftJob.isUnnamed | ||
| ? next.branch | ||
| : draftJob.workspaceName, | ||
| }); | ||
| }, | ||
| }); | ||
| const nextDraftJob = { | ||
| ...(workspaceInitManager.getDraftJob(input.workspaceId) ?? | ||
| draftJob), | ||
| branch, | ||
| worktreePath, | ||
| workspaceName: draftJob.isUnnamed ? branch : draftJob.workspaceName, | ||
| }; | ||
|
|
||
| workspaceInitManager.clearJob(input.workspaceId); | ||
| workspaceInitManager.startJob( | ||
| input.workspaceId, | ||
| nextDraftJob.projectId, | ||
| nextDraftJob, | ||
| ); | ||
|
|
||
| initializeWorkspaceWorktree({ | ||
| workspaceId: input.workspaceId, | ||
| projectId: nextDraftJob.projectId, | ||
| worktreeId: nextDraftJob.worktreeId, | ||
| worktreePath, | ||
| branch, | ||
| mainRepoPath: project.mainRepoPath, | ||
| startPointBranch: nextDraftJob.startPointBranch, | ||
| namingPrompt: nextDraftJob.namingPrompt, | ||
| useExistingBranch: nextDraftJob.useExistingBranch, | ||
| draftJob: nextDraftJob, | ||
| }); |
There was a problem hiding this comment.
Cancel the old initializer before reusing this workspace ID.
clearJob()/startJob() only swap the in-memory record; they do not stop an already-running initializeWorkspaceWorktree() call. A quick retry can therefore run a second init against the same workspace while the first attempt is still unwinding. In the draft path, that races persistDraftWorkspaceIfNeeded() and can produce duplicate inserts or resurrect stale state.
🤖 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/init.ts` around lines
196 - 250, The code currently calls workspaceInitManager.clearJob/startJob but
never cancels a running initializeWorkspaceWorktree, allowing concurrent inits
for the same workspace; update the flow to explicitly cancel or await the
previous initializer before starting a new one by using the workspaceInitManager
cancellation API (e.g., a cancelJob/abortJob or returned AbortController) and/or
making initializeWorkspaceWorktree accept an AbortSignal and check it;
specifically, before calling workspaceInitManager.clearJob/startJob and before
calling initializeWorkspaceWorktree (both the regular and draft paths where
persistDraftWorkspaceIfNeeded races), invoke
workspaceInitManager.cancelJob(input.workspaceId) or await the previous job’s
completion, pass the cancellation signal into initializeWorkspaceWorktree, and
ensure any in-flight persistDraftWorkspaceIfNeeded is guarded by that signal to
prevent duplicate inserts or resurrected state.
| const persistDraftWorkspaceIfNeeded = async (): Promise<void> => { | ||
| if (!draftJob) return; | ||
|
|
||
| const existingWorkspace = localDb | ||
| .select() | ||
| .from(workspaces) | ||
| .where(eq(workspaces.id, workspaceId)) | ||
| .get(); | ||
|
|
||
| if (!existingWorkspace) { | ||
| localDb | ||
| .insert(worktrees) | ||
| .values({ | ||
| id: draftJob.worktreeId, | ||
| projectId, | ||
| path: worktreePath, | ||
| branch, | ||
| baseBranch: effectiveCompareBaseBranch, | ||
| gitStatus: null, | ||
| createdBySuperset: true, | ||
| }) | ||
| .run(); | ||
|
|
||
| const maxTabOrder = getMaxProjectChildTabOrder(projectId); | ||
| localDb | ||
| .insert(workspaces) | ||
| .values({ | ||
| id: workspaceId, | ||
| projectId, | ||
| worktreeId: draftJob.worktreeId, | ||
| type: "worktree", | ||
| branch, | ||
| name: draftJob.workspaceName, | ||
| isUnnamed: draftJob.isUnnamed, | ||
| tabOrder: maxTabOrder + 1, | ||
| }) | ||
| .run(); | ||
|
|
||
| setLastActiveWorkspace(workspaceId); | ||
| if (project) { | ||
| activateProject(project); | ||
| } | ||
|
|
||
| track("workspace_created", { | ||
| workspace_id: workspaceId, | ||
| project_id: projectId, | ||
| branch, | ||
| base_branch: effectiveCompareBaseBranch, | ||
| use_existing_branch: useExistingBranch ?? false, | ||
| }); | ||
| } | ||
|
|
||
| await setBranchBaseConfig({ | ||
| repoPath: mainRepoPath, | ||
| branch, | ||
| compareBaseBranch: effectiveCompareBaseBranch, | ||
| isExplicit: draftJob.compareBaseBranchIsExplicit, | ||
| }); | ||
|
|
||
| workspacePersisted = true; | ||
| }; |
There was a problem hiding this comment.
Treat the draft as persisted as soon as the DB writes succeed.
Any exception after the inserts run—setBranchBaseConfig(), activateProject(), analytics, etc.—still leaves the new workspaces/worktrees rows in SQLite while workspacePersisted stays false. The outer catch then removes the worktree from disk, recreating the orphaned-state this PR is meant to avoid. Please move the persisted boundary up to the insert step and make that insert sequence atomic.
🤖 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/workspace-init.ts` around
lines 128 - 188, The DB insert sequence in persistDraftWorkspaceIfNeeded must be
made atomic and the draft should be marked persisted immediately after the DB
writes succeed: wrap the localDb.insert(...) calls that create worktrees and
workspaces (the insert into worktrees with draftJob.worktreeId and the insert
into workspaces with workspaceId and tabOrder) in a single transaction, commit
the transaction, then set workspacePersisted = true right after the transaction
succeeds (before calling setBranchBaseConfig, activateProject,
setLastActiveWorkspace, or track). This ensures the persisted boundary is the
successful DB commit and prevents later exceptions (in setBranchBaseConfig,
activateProject, track, etc.) from leaving rows in SQLite while
workspacePersisted remains false.
There was a problem hiding this comment.
4 issues found across 13 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/desktop/src/lib/trpc/routers/workspaces/procedures/delete.ts">
<violation number="1" location="apps/desktop/src/lib/trpc/routers/workspaces/procedures/delete.ts:206">
P1: After cancelling a draft initialization, verify the initializer has actually stopped before clearing the job and returning success; otherwise a still-running init can re-persist the workspace after deletion.</violation>
<violation number="2" location="apps/desktop/src/lib/trpc/routers/workspaces/procedures/delete.ts:309">
P0: Deletion no longer checks `createdBySuperset`, so imported external worktrees can now be removed from disk instead of only being unlinked from the DB.</violation>
</file>
<file name="apps/desktop/src/lib/trpc/routers/workspaces/procedures/init.ts">
<violation number="1" location="apps/desktop/src/lib/trpc/routers/workspaces/procedures/init.ts:233">
P1: Cancel (or await completion of) any in-flight initializer before starting a retry for the same workspace ID; clearing in-memory job state alone can allow two init flows to run concurrently.</violation>
</file>
<file name="apps/desktop/src/lib/trpc/routers/workspaces/utils/workspace-init.ts">
<violation number="1" location="apps/desktop/src/lib/trpc/routers/workspaces/utils/workspace-init.ts:180">
P1: Set the persisted boundary immediately after successful workspace/worktree inserts (or make the insert sequence atomic) before later side effects. Otherwise a later exception can trigger cleanup that removes the worktree while the new DB rows remain.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| console.log( | ||
| `[workspace/delete] Skipping disk deletion for external worktree at ${worktree.path}`, | ||
| ); | ||
| const removeResult = await removeWorktreeFromDisk({ |
There was a problem hiding this comment.
P0: Deletion no longer checks createdBySuperset, so imported external worktrees can now be removed from disk instead of only being unlinked from the DB.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/lib/trpc/routers/workspaces/procedures/delete.ts, line 309:
<comment>Deletion no longer checks `createdBySuperset`, so imported external worktrees can now be removed from disk instead of only being unlinked from the DB.</comment>
<file context>
@@ -267,43 +306,13 @@ export const createDeleteProcedures = () => {
- console.log(
- `[workspace/delete] Skipping disk deletion for external worktree at ${worktree.path}`,
- );
+ const removeResult = await removeWorktreeFromDisk({
+ mainRepoPath: project.mainRepoPath,
+ worktreePath: worktree.path,
</file context>
| `[workspace/delete] Cancelling draft init for ${input.id}, waiting for completion...`, | ||
| ); | ||
| workspaceInitManager.cancel(input.id); | ||
| await workspaceInitManager.waitForInit(input.id, 30000); |
There was a problem hiding this comment.
P1: After cancelling a draft initialization, verify the initializer has actually stopped before clearing the job and returning success; otherwise a still-running init can re-persist the workspace after deletion.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/lib/trpc/routers/workspaces/procedures/delete.ts, line 206:
<comment>After cancelling a draft initialization, verify the initializer has actually stopped before clearing the job and returning success; otherwise a still-running init can re-persist the workspace after deletion.</comment>
<file context>
@@ -173,6 +193,25 @@ export const createDeleteProcedures = () => {
+ `[workspace/delete] Cancelling draft init for ${input.id}, waiting for completion...`,
+ );
+ workspaceInitManager.cancel(input.id);
+ await workspaceInitManager.waitForInit(input.id, 30000);
+ }
+
</file context>
| }; | ||
|
|
||
| workspaceInitManager.clearJob(input.workspaceId); | ||
| workspaceInitManager.startJob( |
There was a problem hiding this comment.
P1: Cancel (or await completion of) any in-flight initializer before starting a retry for the same workspace ID; clearing in-memory job state alone can allow two init flows to run concurrently.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/lib/trpc/routers/workspaces/procedures/init.ts, line 233:
<comment>Cancel (or await completion of) any in-flight initializer before starting a retry for the same workspace ID; clearing in-memory job state alone can allow two init flows to run concurrently.</comment>
<file context>
@@ -150,27 +174,81 @@ export const createInitProcedures = () => {
+ };
+
+ workspaceInitManager.clearJob(input.workspaceId);
+ workspaceInitManager.startJob(
+ input.workspaceId,
+ nextDraftJob.projectId,
</file context>
| }); | ||
| } | ||
|
|
||
| await setBranchBaseConfig({ |
There was a problem hiding this comment.
P1: Set the persisted boundary immediately after successful workspace/worktree inserts (or make the insert sequence atomic) before later side effects. Otherwise a later exception can trigger cleanup that removes the worktree while the new DB rows remain.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/lib/trpc/routers/workspaces/utils/workspace-init.ts, line 180:
<comment>Set the persisted boundary immediately after successful workspace/worktree inserts (or make the insert sequence atomic) before later side effects. Otherwise a later exception can trigger cleanup that removes the worktree while the new DB rows remain.</comment>
<file context>
@@ -101,13 +113,79 @@ export async function initializeWorkspaceWorktree({
+ });
+ }
+
+ await setBranchBaseConfig({
+ repoPath: mainRepoPath,
+ branch,
</file context>
Summary
listExternalWorktreessafety checks and renaming that helper tolistGitWorktreesdeletionResearch.mdWhy
The old flow persisted a real workspace before
git worktree addhad succeeded. When provisioning failed early, Superset could leave behind a misleading failed workspace row attached to a path it never actually created. This PR changes that boundary: a workspace only becomes real after the git worktree has been acquired.Testing
bunx tsc -p apps/desktop/tsconfig.json --noEmit --ignoreDeprecations 6.0bun test apps/desktop/src/lib/trpc/routers/workspaces/utils/git.test.ts apps/desktop/src/lib/trpc/routers/workspaces/procedures/external-worktree-import.test.tsbunx biome check apps/desktop/src/main/lib/workspace-init-manager.ts apps/desktop/src/lib/trpc/routers/workspaces/utils/draft-workspace.ts apps/desktop/src/lib/trpc/routers/workspaces/utils/workspace-init.ts apps/desktop/src/lib/trpc/routers/workspaces/procedures/create.ts apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts apps/desktop/src/lib/trpc/routers/workspaces/procedures/init.ts apps/desktop/src/lib/trpc/routers/workspaces/procedures/delete.ts apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsxSummary by CodeRabbit
Release Notes
New Features
Improvements