From 4ff0f76b7bbd3a76e151d8eac1669bd7e4b59843 Mon Sep 17 00:00:00 2001 From: nshcr <104677079+nshcr@users.noreply.github.com> Date: Sat, 21 Feb 2026 18:36:56 +0800 Subject: [PATCH] feat(ui): use backend normalize method to validate user-entered branch names refactor: rename slugified to normalized and fix race condition in validation Applied changes from PR review #12488: - Renamed onslugifiedvalue callback to onnormalizedvalue - Renamed slugifiedRefName variables to normalizedRefName across all components - Fixed race condition in validation state using validationCounter pattern Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../components/AddDependentBranchModal.svelte | 12 ++- .../src/components/BranchNameTextbox.svelte | 91 +++++++++++++++++-- .../src/components/BranchRenameModal.svelte | 12 ++- .../components/ChangedFilesContextMenu.svelte | 13 ++- .../src/components/CreateBranchModal.svelte | 14 +-- 5 files changed, 111 insertions(+), 31 deletions(-) diff --git a/apps/desktop/src/components/AddDependentBranchModal.svelte b/apps/desktop/src/components/AddDependentBranchModal.svelte index f3e2a66aff3..8f48d0e81a0 100644 --- a/apps/desktop/src/components/AddDependentBranchModal.svelte +++ b/apps/desktop/src/components/AddDependentBranchModal.svelte @@ -18,17 +18,18 @@ let modal = $state(); let branchName = $state(); - let slugifiedRefName: string | undefined = $state(); + let normalizedRefName: string | undefined = $state(); + let isBranchNameValid = $state(false); async function handleAddDependentBranch(close: () => void) { - if (!slugifiedRefName) return; + if (!normalizedRefName) return; await createNewBranch({ projectId, stackId, request: { targetPatch: undefined, - name: slugifiedRefName, + name: normalizedRefName, }, }); @@ -52,7 +53,8 @@ placeholder="Branch name" bind:value={branchName} autofocus - onslugifiedvalue={(value) => (slugifiedRefName = value)} + onnormalizedvalue={(value) => (normalizedRefName = value)} + onvalidationchange={(isValid) => (isBranchNameValid = isValid)} /> {#snippet controls(close)} @@ -61,7 +63,7 @@ testId={TestId.BranchHeaderAddDependanttBranchModal_ActionButton} style="pop" type="submit" - disabled={!slugifiedRefName} + disabled={!isBranchNameValid} loading={branchCreation.current.isLoading}>Add branch {/snippet} diff --git a/apps/desktop/src/components/BranchNameTextbox.svelte b/apps/desktop/src/components/BranchNameTextbox.svelte index 239c52e7d0a..f51bf87f500 100644 --- a/apps/desktop/src/components/BranchNameTextbox.svelte +++ b/apps/desktop/src/components/BranchNameTextbox.svelte @@ -1,32 +1,91 @@ - + + {#snippet customIconRight()} + {#if isValidating} + + {/if} + {/snippet} + diff --git a/apps/desktop/src/components/BranchRenameModal.svelte b/apps/desktop/src/components/BranchRenameModal.svelte index fd1fc820908..b2f40625954 100644 --- a/apps/desktop/src/components/BranchRenameModal.svelte +++ b/apps/desktop/src/components/BranchRenameModal.svelte @@ -20,7 +20,8 @@ const [renameBranch, renameQuery] = stackService.updateBranchName; let newName: string | undefined = $state(); - let slugifiedRefName: string | undefined = $state(); + let normalizedRefName: string | undefined = $state(); + let isBranchNameValid = $state(false); let modal: Modal | undefined = $state(); let branchNameInput = $state>(); @@ -40,8 +41,8 @@ type={isPushed ? "warning" : "info"} bind:this={modal} onSubmit={async (close) => { - if (slugifiedRefName) { - renameBranch({ projectId, stackId, laneId, branchName, newName: slugifiedRefName }); + if (normalizedRefName) { + renameBranch({ projectId, stackId, laneId, branchName, newName: normalizedRefName }); } close(); }} @@ -52,7 +53,8 @@ id={ElementId.NewBranchNameInput} bind:value={newName} autofocus - onslugifiedvalue={(value) => (slugifiedRefName = value)} + onnormalizedvalue={(value) => (normalizedRefName = value)} + onvalidationchange={(isValid) => (isBranchNameValid = isValid)} /> {#if isPushed} @@ -68,7 +70,7 @@ testId={TestId.BranchHeaderRenameModal_ActionButton} style="pop" type="submit" - disabled={!slugifiedRefName} + disabled={!isBranchNameValid} loading={renameQuery.current.isLoading}>Rename {/snippet} diff --git a/apps/desktop/src/components/ChangedFilesContextMenu.svelte b/apps/desktop/src/components/ChangedFilesContextMenu.svelte index a96c3b2635e..ca6387dd704 100644 --- a/apps/desktop/src/components/ChangedFilesContextMenu.svelte +++ b/apps/desktop/src/components/ChangedFilesContextMenu.svelte @@ -168,7 +168,8 @@ } let stashBranchName = $state(); - let slugifiedRefName: string | undefined = $state(); + let normalizedRefName: string | undefined = $state(); + let isStashBranchNameValid = $state(false); let stashBranchNameInput = $state>(); let absorbPlan = $state([]); @@ -639,7 +640,8 @@ type="info" title="Stash changes into a new branch" bind:this={stashConfirmationModal} - onSubmit={(_, item) => isChangedFilesItem(item) && confirmStashIntoBranch(item, slugifiedRefName)} + onSubmit={(_, item) => + isChangedFilesItem(item) && confirmStashIntoBranch(item, normalizedRefName)} > {#snippet children(item)}
@@ -649,7 +651,8 @@ placeholder="Enter your branch name..." bind:value={stashBranchName} autofocus - onslugifiedvalue={(value) => (slugifiedRefName = value)} + onnormalizedvalue={(value) => (normalizedRefName = value)} + onvalidationchange={(isValid) => (isStashBranchNameValid = isValid)} />

@@ -675,9 +678,9 @@ await confirmStashIntoBranch(item, slugifiedRefName)} + action={async () => await confirmStashIntoBranch(item, normalizedRefName)} > Stash into branch diff --git a/apps/desktop/src/components/CreateBranchModal.svelte b/apps/desktop/src/components/CreateBranchModal.svelte index 6e5c1770c73..83f0eb99777 100644 --- a/apps/desktop/src/components/CreateBranchModal.svelte +++ b/apps/desktop/src/components/CreateBranchModal.svelte @@ -42,7 +42,8 @@ // Persisted preference for branch placement const addToLeftmost = persisted(false, "branch-placement-leftmost"); - let slugifiedRefName: string | undefined = $state(); + let normalizedRefName: string | undefined = $state(); + let isBranchNameValid = $state(false); // Get all stacks in the workspace const allStacksQuery = $derived(stackService.stacks(projectId)); @@ -91,7 +92,7 @@ await createNewStack({ projectId, branch: { - name: slugifiedRefName, + name: normalizedRefName, // If addToLeftmost is true, place at position 0 (leftmost) // Otherwise, leave undefined to append to the right order: $addToLeftmost ? 0 : undefined, @@ -99,14 +100,14 @@ }); createRefModal?.close(); } else { - if (!selectedStackId || !slugifiedRefName) { + if (!selectedStackId || !normalizedRefName) { // TODO: Add input validation. return; } await createNewBranch({ projectId, stackId: selectedStackId, - request: { targetPatch: undefined, name: slugifiedRefName }, + request: { targetPatch: undefined, name: normalizedRefName }, }); createRefModal?.close(); } @@ -145,7 +146,8 @@ id={ElementId.NewBranchNameInput} value={createRefName} autofocus - onslugifiedvalue={(value) => (slugifiedRefName = value)} + onnormalizedvalue={(value) => (normalizedRefName = value)} + onvalidationchange={(isValid) => (isBranchNameValid = isValid)} />

@@ -270,7 +272,7 @@ style="pop" type="submit" onclick={addNew} - disabled={!createRefName || (createRefType === "dependent" && !selectedStackId)} + disabled={!isBranchNameValid || (createRefType === "dependent" && !selectedStackId)} loading={isAddingNew} testId={TestId.ConfirmSubmit} >