Skip to content

Commit 626a866

Browse files
committed
refactor: cleanup and simplify pending workspace code
- Extract sanitizeBranchName() to deduplicate validation logic - Remove redundant Map.set() after in-place sort - Add isDisabled flag to consolidate isCreating || isDeleting checks - Improve aria-label for deleting state
1 parent e3023e4 commit 626a866

File tree

4 files changed

+38
-31
lines changed

4 files changed

+38
-31
lines changed

src/browser/components/WorkspaceListItem.tsx

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const WorkspaceListItemInner: React.FC<WorkspaceListItemProps> = ({
4646
// Destructure metadata for convenience
4747
const { id: workspaceId, name: workspaceName, namedWorkspacePath, status } = metadata;
4848
const isCreating = status === "creating";
49+
const isDisabled = isCreating || isDeleting;
4950
const gitStatus = useGitStatus(workspaceId);
5051

5152
// Get rename context
@@ -102,14 +103,14 @@ const WorkspaceListItemInner: React.FC<WorkspaceListItemProps> = ({
102103
<div
103104
className={cn(
104105
"py-1.5 pl-4 pr-2 border-l-[3px] border-transparent transition-all duration-150 text-[13px] relative flex gap-2",
105-
isCreating || isDeleting
106+
isDisabled
106107
? "cursor-default opacity-70"
107108
: "cursor-pointer hover:bg-hover [&:hover_button]:opacity-100",
108-
isSelected && !isCreating && "bg-hover border-l-blue-400",
109+
isSelected && !isDisabled && "bg-hover border-l-blue-400",
109110
isDeleting && "pointer-events-none"
110111
)}
111112
onClick={() => {
112-
if (isCreating) return; // Disable click while creating
113+
if (isDisabled) return;
113114
onSelectWorkspace({
114115
projectPath,
115116
projectName,
@@ -118,7 +119,7 @@ const WorkspaceListItemInner: React.FC<WorkspaceListItemProps> = ({
118119
});
119120
}}
120121
onKeyDown={(e) => {
121-
if (isCreating) return; // Disable keyboard while creating
122+
if (isDisabled) return;
122123
if (e.key === "Enter" || e.key === " ") {
123124
e.preventDefault();
124125
onSelectWorkspace({
@@ -130,12 +131,16 @@ const WorkspaceListItemInner: React.FC<WorkspaceListItemProps> = ({
130131
}
131132
}}
132133
role="button"
133-
tabIndex={isCreating ? -1 : 0}
134+
tabIndex={isDisabled ? -1 : 0}
134135
aria-current={isSelected ? "true" : undefined}
135136
aria-label={
136-
isCreating ? `Creating workspace ${displayName}` : `Select workspace ${displayName}`
137+
isCreating
138+
? `Creating workspace ${displayName}`
139+
: isDeleting
140+
? `Deleting workspace ${displayName}`
141+
: `Select workspace ${displayName}`
137142
}
138-
aria-disabled={isCreating}
143+
aria-disabled={isDisabled}
139144
data-workspace-path={namedWorkspacePath}
140145
data-workspace-id={workspaceId}
141146
>
@@ -158,14 +163,14 @@ const WorkspaceListItemInner: React.FC<WorkspaceListItemProps> = ({
158163
<span
159164
className={cn(
160165
"text-foreground -mx-1 min-w-0 flex-1 truncate rounded-sm px-1 text-left text-[14px] transition-colors duration-200",
161-
!isCreating && "cursor-pointer hover:bg-white/5"
166+
!isDisabled && "cursor-pointer hover:bg-white/5"
162167
)}
163168
onDoubleClick={(e) => {
164-
if (isCreating) return; // Disable rename while creating
169+
if (isDisabled) return;
165170
e.stopPropagation();
166171
startRenaming();
167172
}}
168-
title={isCreating ? "Creating workspace..." : "Double-click to rename"}
173+
title={isDisabled ? undefined : "Double-click to rename"}
169174
>
170175
{canInterrupt || isCreating ? (
171176
<Shimmer className="w-full truncate" colorClass="var(--color-foreground)">

src/browser/contexts/WorkspaceContext.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,9 @@ export function WorkspaceProvider(props: WorkspaceProviderProps) {
214214
setWorkspaceMetadata((prev) => {
215215
const updated = new Map(prev);
216216
const isNewWorkspace = !prev.has(event.workspaceId) && event.metadata !== null;
217+
const existingMeta = prev.get(event.workspaceId);
218+
const wasCreating = existingMeta?.status === "creating";
219+
const isNowReady = event.metadata !== null && event.metadata.status !== "creating";
217220

218221
if (event.metadata === null) {
219222
// Workspace deleted - remove from map
@@ -223,9 +226,10 @@ export function WorkspaceProvider(props: WorkspaceProviderProps) {
223226
updated.set(event.workspaceId, event.metadata);
224227
}
225228

226-
// If this is a new workspace (e.g., from fork), reload projects
227-
// to ensure the sidebar shows the updated workspace list
228-
if (isNewWorkspace) {
229+
// Reload projects when:
230+
// 1. New workspace appears (e.g., from fork)
231+
// 2. Workspace transitions from "creating" to ready (now saved to config)
232+
if (isNewWorkspace || (wasCreating && isNowReady)) {
229233
void refreshProjects();
230234
}
231235

src/browser/utils/ui/workspaceFiltering.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,13 @@ export function buildSortedWorkspacesByProject(
4848
}
4949
}
5050

51-
// Sort each project's workspaces by recency
52-
for (const [projectPath, metadataList] of result) {
51+
// Sort each project's workspaces by recency (sort mutates in place)
52+
for (const metadataList of result.values()) {
5353
metadataList.sort((a, b) => {
5454
const aTimestamp = workspaceRecency[a.id] ?? 0;
5555
const bTimestamp = workspaceRecency[b.id] ?? 0;
5656
return bTimestamp - aTimestamp;
5757
});
58-
result.set(projectPath, metadataList);
5958
}
6059

6160
return result;

src/node/services/workspaceTitleGenerator.ts

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,30 +46,29 @@ export async function generateWorkspaceName(
4646
}
4747

4848
/**
49-
* Validate and sanitize branch name to be git-safe
49+
* Sanitize a string to be git-safe: lowercase, hyphens only, no leading/trailing hyphens.
5050
*/
51-
function validateBranchName(name: string): string {
52-
// Ensure git-safe
53-
const cleaned = name.toLowerCase().replace(/[^a-z0-9-]/g, "-");
54-
// Remove leading/trailing hyphens and collapse multiple hyphens
55-
return cleaned
51+
function sanitizeBranchName(name: string, maxLength: number): string {
52+
return name
53+
.toLowerCase()
54+
.replace(/[^a-z0-9]+/g, "-")
5655
.replace(/^-+|-+$/g, "")
5756
.replace(/-+/g, "-")
58-
.substring(0, 50);
57+
.substring(0, maxLength);
58+
}
59+
60+
/**
61+
* Validate and sanitize branch name to be git-safe
62+
*/
63+
function validateBranchName(name: string): string {
64+
return sanitizeBranchName(name, 50);
5965
}
6066

6167
/**
6268
* Generate a placeholder name from the user's message for immediate display
6369
* while the AI generates the real title. This is git-safe and human-readable.
6470
*/
6571
export function generatePlaceholderName(message: string): string {
66-
// Take first ~40 chars, sanitize for git branch name
6772
const truncated = message.slice(0, 40).trim();
68-
const sanitized = truncated
69-
.toLowerCase()
70-
.replace(/[^a-z0-9]+/g, "-")
71-
.replace(/^-+|-+$/g, "")
72-
.replace(/-+/g, "-")
73-
.substring(0, 30);
74-
return sanitized || "new-workspace";
73+
return sanitizeBranchName(truncated, 30) || "new-workspace";
7574
}

0 commit comments

Comments
 (0)