| ... | ... | @@ -1,6 +1,6 @@ |
| 1 | 1 | import type { GitBranch, ThreadId } from "@t3tools/contracts"; |
| 2 | 2 | import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; |
| 3 | | import { useState } from "react"; |
| 3 | import { useOptimistic, useState } from "react"; |
| 4 | 4 | |
| 5 | 5 | import { newCommandId } from "../lib/utils"; |
| 6 | 6 | import { |
| ... | ... | @@ -72,10 +72,14 @@ export default function BranchToolbar({ |
| 72 | 72 | |
| 73 | 73 | const branches = dedupeRemoteBranchesWithLocalMatches(branchesQuery.data?.branches ?? []); |
| 74 | 74 | const currentGitBranch = branches.find((branch) => branch.current)?.name ?? null; |
| 75 | | const resolvedActiveBranch = |
| 75 | const canonicalActiveBranch = |
| 76 | 76 | effectiveEnvMode === "worktree" && !activeWorktreePath |
| 77 | 77 | ? activeThreadBranch |
| 78 | 78 | : (currentGitBranch ?? activeThreadBranch); |
| 79 | const [resolvedActiveBranch, setOptimisticBranch] = useOptimistic( |
| 80 | canonicalActiveBranch, |
| 81 | (_currentBranch: string | null, optimisticBranch: string | null) => optimisticBranch, |
| 82 | ); |
| 79 | 83 | const branchNames = branches.map((branch) => branch.name); |
| 80 | 84 | const branchByName = new Map(branches.map((branch) => [branch.name, branch])); |
| 81 | 85 | const trimmedBranchQuery = branchQuery.trim(); |
| ... | ... | @@ -168,6 +172,7 @@ export default function BranchToolbar({ |
| 168 | 172 | const selectedBranchName = branch.isRemote |
| 169 | 173 | ? deriveLocalBranchNameFromRemoteRef(branch.name) |
| 170 | 174 | : branch.name; |
| 175 | setOptimisticBranch(selectedBranchName); |
| 171 | 176 | |
| 172 | 177 | checkoutMutation.mutate(branch.name, { |
| 173 | 178 | onSuccess: async () => { |
| ... | ... | @@ -179,11 +184,13 @@ export default function BranchToolbar({ |
| 179 | 184 | nextBranchName = status.branch; |
| 180 | 185 | } |
| 181 | 186 | } |
| 187 | setOptimisticBranch(nextBranchName); |
| 182 | 188 | setThreadBranch(nextBranchName, activeWorktreePath); |
| 183 | 189 | setIsBranchMenuOpen(false); |
| 184 | 190 | onComposerFocusRequest?.(); |
| 185 | 191 | }, |
| 186 | 192 | onError: (error) => { |
| 193 | setOptimisticBranch(canonicalActiveBranch); |
| 187 | 194 | setThreadError(error instanceof Error ? error.message : "Failed to checkout branch."); |
| 188 | 195 | setIsBranchMenuOpen(true); |
| 189 | 196 | }, |
| ... | ... | @@ -194,8 +201,10 @@ export default function BranchToolbar({ |
| 194 | 201 | const name = rawName.trim(); |
| 195 | 202 | const api = readNativeApi(); |
| 196 | 203 | if (!api || !activeThreadId || !branchCwd || !name || createBranchMutation.isPending) return; |
| 204 | setOptimisticBranch(name); |
| 197 | 205 | createBranchMutation.mutate(name, { |
| 198 | 206 | onSuccess: () => { |
| 207 | setOptimisticBranch(name); |
| 199 | 208 | setThreadError(null); |
| 200 | 209 | setThreadBranch(name, activeWorktreePath); |
| 201 | 210 | setBranchQuery(""); |
| ... | ... | @@ -203,6 +212,7 @@ export default function BranchToolbar({ |
| 203 | 212 | onComposerFocusRequest?.(); |
| 204 | 213 | }, |
| 205 | 214 | onError: (error) => { |
| 215 | setOptimisticBranch(canonicalActiveBranch); |
| 206 | 216 | setThreadError(error instanceof Error ? error.message : "Failed to create branch."); |
| 207 | 217 | }, |
| 208 | 218 | }); |
| ... | ... | @@ -223,9 +233,7 @@ export default function BranchToolbar({ |
| 223 | 233 | variant="ghost" |
| 224 | 234 | className="text-muted-foreground/70 hover:text-foreground/80" |
| 225 | 235 | size="xs" |
| 226 | | onClick={() => |
| 227 | | onEnvModeChange(effectiveEnvMode === "local" ? "worktree" : "local") |
| 228 | | } |
| 236 | onClick={() => onEnvModeChange(effectiveEnvMode === "local" ? "worktree" : "local")} |
| 229 | 237 | > |
| 230 | 238 | {effectiveEnvMode === "worktree" ? "New worktree" : "Local"} |
| 231 | 239 | </Button> |
| ... | ... | @@ -313,7 +321,9 @@ export default function BranchToolbar({ |
| 313 | 321 | > |
| 314 | 322 | <div className="flex w-full items-center justify-between gap-2"> |
| 315 | 323 | <span className="truncate">{itemValue}</span> |
| 316 | | {badge && <span className="shrink-0 text-[10px] text-muted-foreground/45">{badge}</span>} |
| 324 | {badge && ( |
| 325 | <span className="shrink-0 text-[10px] text-muted-foreground/45">{badge}</span> |
| 326 | )} |
| 317 | 327 | </div> |
| 318 | 328 | </ComboboxItem> |
| 319 | 329 | ); |