Skip to content

Commit 6db7e18

Browse files
authored
🤖 Clarify workspace ID vs name vs path architecture (#420)
Fixes terminology inconsistencies that could mislead contributors about workspace identity vs directory naming. ## Changes **Parameter renaming:** - `config.getWorkspacePath(workspaceId)` → `getWorkspacePath(directoryName)` - `CreateWorktreeOptions.workspaceId` → `directoryName` **Documentation:** - Clarified "Workspace Path Architecture" in config.ts to separate workspace ID (stable identifier) from directory name (branch name) - Removed outdated symlink references (directories use workspace names directly now) **Test fixtures:** - Updated App.stories.tsx to use hex-like IDs (e.g., `1a2b3c4d5e`) matching production format All changes are non-functional: comments, docs, parameter names, and test fixtures only. _Generated with `cmux`_
1 parent f64d2ea commit 6db7e18

File tree

9 files changed

+43
-39
lines changed

9 files changed

+43
-39
lines changed

‎src/App.stories.tsx‎

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -182,15 +182,16 @@ export const SingleProject: Story = {
182182

183183
export const MultipleProjects: Story = {
184184
render: () => {
185+
// Note: Workspace IDs are fixtures using hex-like format (production uses random 10-hex chars)
185186
const projects = new Map<string, ProjectConfig>([
186187
[
187188
"/home/user/projects/frontend",
188189
{
189190
workspaces: [
190-
{ path: "/home/user/.cmux/src/frontend/main", id: "frontend-main", name: "main" },
191+
{ path: "/home/user/.cmux/src/frontend/main", id: "1a2b3c4d5e", name: "main" },
191192
{
192193
path: "/home/user/.cmux/src/frontend/redesign",
193-
id: "frontend-redesign",
194+
id: "2b3c4d5e6f",
194195
name: "redesign",
195196
},
196197
],
@@ -200,11 +201,11 @@ export const MultipleProjects: Story = {
200201
"/home/user/projects/backend",
201202
{
202203
workspaces: [
203-
{ path: "/home/user/.cmux/src/backend/main", id: "backend-main", name: "main" },
204-
{ path: "/home/user/.cmux/src/backend/api-v2", id: "backend-api-v2", name: "api-v2" },
204+
{ path: "/home/user/.cmux/src/backend/main", id: "3c4d5e6f7a", name: "main" },
205+
{ path: "/home/user/.cmux/src/backend/api-v2", id: "4d5e6f7a8b", name: "api-v2" },
205206
{
206207
path: "/home/user/.cmux/src/backend/db-migration",
207-
id: "backend-db-migration",
208+
id: "5e6f7a8b9c",
208209
name: "db-migration",
209210
},
210211
],
@@ -214,50 +215,50 @@ export const MultipleProjects: Story = {
214215
"/home/user/projects/mobile",
215216
{
216217
workspaces: [
217-
{ path: "/home/user/.cmux/src/mobile/main", id: "mobile-main", name: "main" },
218+
{ path: "/home/user/.cmux/src/mobile/main", id: "6f7a8b9c0d", name: "main" },
218219
],
219220
},
220221
],
221222
]);
222223

223224
const workspaces: FrontendWorkspaceMetadata[] = [
224225
{
225-
id: "frontend-main",
226+
id: "1a2b3c4d5e",
226227
name: "main",
227228
projectPath: "/home/user/projects/frontend",
228229
projectName: "frontend",
229230
namedWorkspacePath: "/home/user/.cmux/src/frontend/main",
230231
},
231232
{
232-
id: "frontend-redesign",
233+
id: "2b3c4d5e6f",
233234
name: "redesign",
234235
projectPath: "/home/user/projects/frontend",
235236
projectName: "frontend",
236237
namedWorkspacePath: "/home/user/.cmux/src/frontend/redesign",
237238
},
238239
{
239-
id: "backend-main",
240+
id: "3c4d5e6f7a",
240241
name: "main",
241242
projectPath: "/home/user/projects/backend",
242243
projectName: "backend",
243244
namedWorkspacePath: "/home/user/.cmux/src/backend/main",
244245
},
245246
{
246-
id: "backend-api-v2",
247+
id: "4d5e6f7a8b",
247248
name: "api-v2",
248249
projectPath: "/home/user/projects/backend",
249250
projectName: "backend",
250251
namedWorkspacePath: "/home/user/.cmux/src/backend/api-v2",
251252
},
252253
{
253-
id: "backend-db-migration",
254+
id: "5e6f7a8b9c",
254255
name: "db-migration",
255256
projectPath: "/home/user/projects/backend",
256257
projectName: "backend",
257258
namedWorkspacePath: "/home/user/.cmux/src/backend/db-migration",
258259
},
259260
{
260-
id: "mobile-main",
261+
id: "6f7a8b9c0d",
261262
name: "main",
262263
projectPath: "/home/user/projects/mobile",
263264
projectName: "mobile",

‎src/App.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ function AppInner() {
219219

220220
const openWorkspaceInTerminal = useCallback(
221221
(workspaceId: string) => {
222-
// Look up workspace metadata to get the named path (user-friendly symlink)
222+
// Look up workspace metadata to get the worktree path (directory uses workspace name)
223223
const metadata = workspaceMetadata.get(workspaceId);
224224
if (metadata) {
225225
void window.api.workspace.openTerminal(metadata.namedWorkspacePath);

‎src/components/WorkspaceListItem.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { cn } from "@/lib/utils";
1313
export interface WorkspaceSelection {
1414
projectPath: string;
1515
projectName: string;
16-
namedWorkspacePath: string; // User-friendly path (symlink for new workspaces)
16+
namedWorkspacePath: string; // Worktree path (directory uses workspace name)
1717
workspaceId: string;
1818
}
1919
export interface WorkspaceListItemProps {

‎src/config.ts‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -126,12 +126,12 @@ export class Config {
126126
}
127127

128128
/**
129-
* Get the workspace worktree path for a given workspace ID.
130-
* New workspaces use stable IDs, legacy workspaces use the old format.
129+
* Get the workspace worktree path for a given directory name.
130+
* The directory name is the workspace name (branch name).
131131
*/
132-
getWorkspacePath(projectPath: string, workspaceId: string): string {
132+
getWorkspacePath(projectPath: string, directoryName: string): string {
133133
const projectName = this.getProjectName(projectPath);
134-
return path.join(this.srcDir, projectName, workspaceId);
134+
return path.join(this.srcDir, projectName, directoryName);
135135
}
136136

137137
/**
@@ -212,10 +212,13 @@ export class Config {
212212
/**
213213
* Workspace Path Architecture:
214214
*
215-
* Workspace paths are computed on-demand from projectPath + workspaceId using
216-
* config.getWorkspacePath(). This ensures single source of truth for path format.
215+
* Workspace paths are computed on-demand from projectPath + workspace name using
216+
* config.getWorkspacePath(projectPath, directoryName). This ensures a single source of truth.
217217
*
218-
* Backend: Uses getWorkspacePath(metadata.projectPath, metadata.name) for directory paths (worktree directories use name)
218+
* - Worktree directory name: uses workspace.name (the branch name)
219+
* - Workspace ID: stable random identifier for identity and sessions (not used for directories)
220+
*
221+
* Backend: Uses getWorkspacePath(metadata.projectPath, metadata.name) for worktree directory paths
219222
* Frontend: Gets enriched metadata with paths via IPC (FrontendWorkspaceMetadata)
220223
*
221224
* WorkspaceMetadata.workspacePath is deprecated and will be removed. Use computed

‎src/git.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export interface WorktreeResult {
1212
export interface CreateWorktreeOptions {
1313
trunkBranch: string;
1414
/** Directory name to use for the worktree (if not provided, uses branchName) */
15-
workspaceId?: string;
15+
directoryName?: string;
1616
}
1717

1818
export async function listLocalBranches(projectPath: string): Promise<string[]> {
@@ -76,9 +76,9 @@ export async function createWorktree(
7676
options: CreateWorktreeOptions
7777
): Promise<WorktreeResult> {
7878
try {
79-
// Use workspaceId for directory name if provided, otherwise fall back to branchName (legacy)
80-
const directoryName = options.workspaceId ?? branchName;
81-
const workspacePath = config.getWorkspacePath(projectPath, directoryName);
79+
// Use directoryName if provided, otherwise fall back to branchName (legacy)
80+
const dirName = options.directoryName ?? branchName;
81+
const workspacePath = config.getWorkspacePath(projectPath, dirName);
8282
const { trunkBranch } = options;
8383
const normalizedTrunkBranch = typeof trunkBranch === "string" ? trunkBranch.trim() : "";
8484

‎src/services/ipcMain.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ export class IpcMain {
275275
// Create the git worktree with the workspace name as directory name
276276
const result = await createWorktree(this.config, projectPath, branchName, {
277277
trunkBranch: normalizedTrunkBranch,
278-
workspaceId: branchName, // Use name for directory (workspaceId param is misnamed, it's directoryName)
278+
directoryName: branchName,
279279
});
280280

281281
if (result.success && result.path) {
@@ -504,7 +504,7 @@ export class IpcMain {
504504
// Create new git worktree branching from source workspace's branch
505505
const result = await createWorktree(this.config, foundProjectPath, newName, {
506506
trunkBranch: sourceBranch,
507-
workspaceId: newName, // Use name for directory (workspaceId param is misnamed, it's directoryName)
507+
directoryName: newName,
508508
});
509509

510510
if (!result.success || !result.path) {

‎src/types/workspace.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ export const WorkspaceMetadataSchema = z.object({
3030
* - name is the branch/workspace name (e.g., "feature-branch")
3131
*
3232
* Path handling:
33-
* - Worktree paths are computed on-demand via config.getWorkspacePath(projectPath, id)
33+
* - Worktree paths are computed on-demand via config.getWorkspacePath(projectPath, name)
34+
* - Directory name uses workspace.name (the branch name)
3435
* - This avoids storing redundant derived data
35-
* - Frontend can show symlink paths, backend uses real paths
3636
*/
3737
export interface WorkspaceMetadata {
3838
/** Stable unique identifier (10 hex chars for new workspaces, legacy format for old) */

‎src/utils/ui/workspaceFiltering.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ describe("partitionWorkspacesByAge", () => {
1111
name: `workspace-${id}`,
1212
projectName: "test-project",
1313
projectPath: "/test/project",
14-
namedWorkspacePath: `/test/project/.worktrees/${id}`,
14+
namedWorkspacePath: `/test/project/workspace-${id}`, // Path is arbitrary for this test
1515
});
1616

1717
it("should partition workspaces into recent and old based on 24-hour threshold", () => {

‎tests/ipcMain/removeWorkspace.test.ts‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,14 @@ describeIntegration("IpcMain remove workspace integration tests", () => {
4040
.catch(() => false);
4141
expect(worktreeExistsBefore).toBe(true);
4242

43-
// Get the symlink path before removing
43+
// Get the worktree directory path before removing
4444
const projectName = tempGitRepo.split("/").pop() || "unknown";
45-
const symlinkPath = `${env.config.srcDir}/${projectName}/${metadata.name}`;
46-
const symlinkExistsBefore = await fs
47-
.lstat(symlinkPath)
45+
const worktreeDirPath = `${env.config.srcDir}/${projectName}/${metadata.name}`;
46+
const worktreeDirExistsBefore = await fs
47+
.lstat(worktreeDirPath)
4848
.then(() => true)
4949
.catch(() => false);
50-
expect(symlinkExistsBefore).toBe(true);
50+
expect(worktreeDirExistsBefore).toBe(true);
5151

5252
// Remove the workspace
5353
const removeResult = await env.mockIpcRenderer.invoke(
@@ -60,12 +60,12 @@ describeIntegration("IpcMain remove workspace integration tests", () => {
6060
const worktreeRemoved = await waitForFileNotExists(workspacePath, 5000);
6161
expect(worktreeRemoved).toBe(true);
6262

63-
// Verify symlink is removed
64-
const symlinkExistsAfter = await fs
65-
.lstat(symlinkPath)
63+
// Verify worktree directory is removed
64+
const worktreeDirExistsAfter = await fs
65+
.lstat(worktreeDirPath)
6666
.then(() => true)
6767
.catch(() => false);
68-
expect(symlinkExistsAfter).toBe(false);
68+
expect(worktreeDirExistsAfter).toBe(false);
6969

7070
// Verify workspace is no longer in config
7171
const config = env.config.loadConfigOrDefault();

0 commit comments

Comments
 (0)