| ... | @@ -9,6 +9,7 @@ import { | ... | @@ -9,6 +9,7 @@ import { |
| 9 | gitCreateBranchAndCheckoutMutationOptions, | 9 | gitCreateBranchAndCheckoutMutationOptions, |
| 10 | } from "../lib/gitReactQuery"; | 10 | } from "../lib/gitReactQuery"; |
| 11 | import { readNativeApi } from "../nativeApi"; | 11 | import { readNativeApi } from "../nativeApi"; |
| | 12 | import { useComposerDraftStore } from "../composerDraftStore"; |
| 12 | import { useStore } from "../store"; | 13 | import { useStore } from "../store"; |
| 13 | import { | 14 | import { |
| 14 | dedupeRemoteBranchesWithLocalMatches, | 15 | dedupeRemoteBranchesWithLocalMatches, |
| ... | @@ -43,17 +44,21 @@ export default function BranchToolbar({ | ... | @@ -43,17 +44,21 @@ export default function BranchToolbar({ |
| 43 | onComposerFocusRequest, | 44 | onComposerFocusRequest, |
| 44 | }: BranchToolbarProps) { | 45 | }: BranchToolbarProps) { |
| 45 | const { state, dispatch } = useStore(); | 46 | const { state, dispatch } = useStore(); |
| | 47 | const draftThread = useComposerDraftStore((store) => store.getDraftThread(threadId)); |
| | 48 | const setDraftThreadContext = useComposerDraftStore((store) => store.setDraftThreadContext); |
| 46 | const queryClient = useQueryClient(); | 49 | const queryClient = useQueryClient(); |
| 47 | | 50 | |
| 48 | const [isBranchMenuOpen, setIsBranchMenuOpen] = useState(false); | 51 | const [isBranchMenuOpen, setIsBranchMenuOpen] = useState(false); |
| 49 | const [branchQuery, setBranchQuery] = useState(""); | 52 | const [branchQuery, setBranchQuery] = useState(""); |
| 50 | | 53 | |
| 51 | const activeThread = state.threads.find((thread) => thread.id === threadId); | 54 | const serverThread = state.threads.find((thread) => thread.id === threadId); |
| 52 | const activeProject = state.projects.find((project) => project.id === activeThread?.projectId); | 55 | const activeProjectId = serverThread?.projectId ?? draftThread?.projectId ?? null; |
| 53 | const activeThreadId = activeThread?.id; | 56 | const activeProject = state.projects.find((project) => project.id === activeProjectId); |
| 54 | const activeThreadBranch = activeThread?.branch ?? null; | 57 | const activeThreadId = serverThread?.id ?? (draftThread ? threadId : undefined); |
| 55 | const activeWorktreePath = activeThread?.worktreePath ?? null; | 58 | const activeThreadBranch = serverThread?.branch ?? draftThread?.branch ?? null; |
| | 59 | const activeWorktreePath = serverThread?.worktreePath ?? draftThread?.worktreePath ?? null; |
| 56 | const branchCwd = activeWorktreePath ?? activeProject?.cwd ?? null; | 60 | const branchCwd = activeWorktreePath ?? activeProject?.cwd ?? null; |
| | 61 | const hasServerThread = serverThread !== undefined; |
| 57 | | 62 | |
| 58 | // ── Queries ─────────────────────────────────────────────────────────── | 63 | // ── Queries ─────────────────────────────────────────────────────────── |
| 59 | | 64 | |
| ... | @@ -95,7 +100,7 @@ export default function BranchToolbar({ | ... | @@ -95,7 +100,7 @@ export default function BranchToolbar({ |
| 95 | if (!activeThreadId || !syncedBranch) return; | 100 | if (!activeThreadId || !syncedBranch) return; |
| 96 | const api = readNativeApi(); | 101 | const api = readNativeApi(); |
| 97 | | 102 | |
| 98 | if (api) { | 103 | if (api && hasServerThread) { |
| 99 | void api.orchestration.dispatchCommand({ | 104 | void api.orchestration.dispatchCommand({ |
| 100 | type: "thread.meta.update", | 105 | type: "thread.meta.update", |
| 101 | commandId: newCommandId(), | 106 | commandId: newCommandId(), |
| ... | @@ -104,13 +109,30 @@ export default function BranchToolbar({ | ... | @@ -104,13 +109,30 @@ export default function BranchToolbar({ |
| 104 | worktreePath: null, | 109 | worktreePath: null, |
| 105 | }); | 110 | }); |
| 106 | } | 111 | } |
| 107 | dispatch({ | 112 | if (hasServerThread) { |
| 108 | type: "SET_THREAD_BRANCH", | 113 | dispatch({ |
| 109 | threadId: activeThreadId, | 114 | type: "SET_THREAD_BRANCH", |
| | 115 | threadId: activeThreadId, |
| | 116 | branch: syncedBranch, |
| | 117 | worktreePath: null, |
| | 118 | }); |
| | 119 | return; |
| | 120 | } |
| | 121 | setDraftThreadContext(threadId, { |
| 110 | branch: syncedBranch, | 122 | branch: syncedBranch, |
| 111 | worktreePath: null, | 123 | worktreePath: null, |
| 112 | }); | 124 | }); |
| 113 | }, [activeThreadId, activeWorktreePath, activeThreadBranch, queryBranches, envMode, dispatch]); | 125 | }, [ |
| | 126 | activeThreadId, |
| | 127 | activeWorktreePath, |
| | 128 | activeThreadBranch, |
| | 129 | queryBranches, |
| | 130 | envMode, |
| | 131 | dispatch, |
| | 132 | hasServerThread, |
| | 133 | setDraftThreadContext, |
| | 134 | threadId, |
| | 135 | ]); |
| 114 | | 136 | |
| 115 | useEffect(() => { | 137 | useEffect(() => { |
| 116 | if (isBranchMenuOpen) return; | 138 | if (isBranchMenuOpen) return; |
| ... | @@ -129,7 +151,7 @@ export default function BranchToolbar({ | ... | @@ -129,7 +151,7 @@ export default function BranchToolbar({ |
| 129 | const api = readNativeApi(); | 151 | const api = readNativeApi(); |
| 130 | // If the effective cwd is about to change, stop the running session so the | 152 | // If the effective cwd is about to change, stop the running session so the |
| 131 | // next message creates a new one with the correct cwd. | 153 | // next message creates a new one with the correct cwd. |
| 132 | const sessionId = activeThread?.session?.sessionId; | 154 | const sessionId = serverThread?.session?.sessionId; |
| 133 | if (sessionId && worktreePath !== activeWorktreePath && api) { | 155 | if (sessionId && worktreePath !== activeWorktreePath && api) { |
| 134 | void api.orchestration | 156 | void api.orchestration |
| 135 | .dispatchCommand({ | 157 | .dispatchCommand({ |
| ... | @@ -140,7 +162,7 @@ export default function BranchToolbar({ | ... | @@ -140,7 +162,7 @@ export default function BranchToolbar({ |
| 140 | }) | 162 | }) |
| 141 | .catch(() => undefined); | 163 | .catch(() => undefined); |
| 142 | } | 164 | } |
| 143 | if (api) { | 165 | if (api && hasServerThread) { |
| 144 | void api.orchestration.dispatchCommand({ | 166 | void api.orchestration.dispatchCommand({ |
| 145 | type: "thread.meta.update", | 167 | type: "thread.meta.update", |
| 146 | commandId: newCommandId(), | 168 | commandId: newCommandId(), |
| ... | @@ -149,7 +171,14 @@ export default function BranchToolbar({ | ... | @@ -149,7 +171,14 @@ export default function BranchToolbar({ |
| 149 | worktreePath, | 171 | worktreePath, |
| 150 | }); | 172 | }); |
| 151 | } | 173 | } |
| 152 | dispatch({ type: "SET_THREAD_BRANCH", threadId: activeThreadId, branch, worktreePath }); | 174 | if (hasServerThread) { |
| | 175 | dispatch({ type: "SET_THREAD_BRANCH", threadId: activeThreadId, branch, worktreePath }); |
| | 176 | return; |
| | 177 | } |
| | 178 | setDraftThreadContext(threadId, { |
| | 179 | branch, |
| | 180 | worktreePath, |
| | 181 | }); |
| 153 | }; | 182 | }; |
| 154 | | 183 | |
| 155 | const selectBranch = (branch: GitBranch) => { | 184 | const selectBranch = (branch: GitBranch) => { |
| ... | @@ -221,7 +250,7 @@ export default function BranchToolbar({ | ... | @@ -221,7 +250,7 @@ export default function BranchToolbar({ |
| 221 | }); | 250 | }); |
| 222 | }; | 251 | }; |
| 223 | | 252 | |
| 224 | if (!activeThread || !activeProject) return null; | 253 | if (!activeThreadId || !activeProject) return null; |
| 225 | | 254 | |
| 226 | return ( | 255 | return ( |
| 227 | <div className="mx-auto flex w-full max-w-3xl items-center justify-between px-5 pb-3 pt-1"> | 256 | <div className="mx-auto flex w-full max-w-3xl items-center justify-between px-5 pb-3 pt-1"> |
| ... | @@ -248,7 +277,7 @@ export default function BranchToolbar({ | ... | @@ -248,7 +277,7 @@ export default function BranchToolbar({ |
| 248 | autoHighlight | 277 | autoHighlight |
| 249 | onOpenChange={(open) => setIsBranchMenuOpen(open)} | 278 | onOpenChange={(open) => setIsBranchMenuOpen(open)} |
| 250 | open={isBranchMenuOpen} | 279 | open={isBranchMenuOpen} |
| 251 | value={activeThread.branch} | 280 | value={activeThreadBranch} |
| 252 | > | 281 | > |
| 253 | <ComboboxTrigger | 282 | <ComboboxTrigger |
| 254 | render={<Button variant="ghost" size="xs" />} | 283 | render={<Button variant="ghost" size="xs" />} |
| ... | @@ -256,10 +285,10 @@ export default function BranchToolbar({ | ... | @@ -256,10 +285,10 @@ export default function BranchToolbar({ |
| 256 | disabled={branchesQuery.isLoading} | 285 | disabled={branchesQuery.isLoading} |
| 257 | > | 286 | > |
| 258 | <span className="max-w-[240px] truncate"> | 287 | <span className="max-w-[240px] truncate"> |
| 259 | {activeThread.branch | 288 | {activeThreadBranch |
| 260 | ? envMode === "worktree" && !activeWorktreePath | 289 | ? envMode === "worktree" && !activeWorktreePath |
| 261 | ? `From ${activeThread.branch}` | 290 | ? `From ${activeThreadBranch}` |
| 262 | : activeThread.branch | 291 | : activeThreadBranch |
| 263 | : "Select branch"} | 292 | : "Select branch"} |
| 264 | </span> | 293 | </span> |
| 265 | <ChevronDownIcon /> | 294 | <ChevronDownIcon /> |
| ... | @@ -313,7 +342,7 @@ export default function BranchToolbar({ | ... | @@ -313,7 +342,7 @@ export default function BranchToolbar({ |
| 313 | key={itemValue} | 342 | key={itemValue} |
| 314 | value={itemValue} | 343 | value={itemValue} |
| 315 | className={ | 344 | className={ |
| 316 | itemValue === activeThread.branch ? "bg-accent text-foreground" : undefined | 345 | itemValue === activeThreadBranch ? "bg-accent text-foreground" : undefined |
| 317 | } | 346 | } |
| 318 | onClick={() => selectBranch(branch)} | 347 | onClick={() => selectBranch(branch)} |
| 319 | > | 348 | > |