authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-01 21:35:42-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-01 22:36:35-08:00
log69a4fa82155f05637f73b3d2f61a5cdf9366b4b9
treefcd87a8439e1006d85ad0f18a9ff7eef2a190e97
parent9e04bad0a504dafbf19182fb0ec361c55018c780

Show branch toolbar for local draft threads

Co-authored-by: codex <codex@users.noreply.github.com>

1 files changed, 48 insertions(+), 19 deletions(-)

apps/web/src/components/BranchToolbar.tsx+48-19
......@@ -9,6 +9,7 @@ import {
99 gitCreateBranchAndCheckoutMutationOptions,
1010} from "../lib/gitReactQuery";
1111import { readNativeApi } from "../nativeApi";
12import { useComposerDraftStore } from "../composerDraftStore";
1213import { useStore } from "../store";
1314import {
1415 dedupeRemoteBranchesWithLocalMatches,
......@@ -43,17 +44,21 @@ export default function BranchToolbar({
4344 onComposerFocusRequest,
4445}: BranchToolbarProps) {
4546 const { state, dispatch } = useStore();
47 const draftThread = useComposerDraftStore((store) => store.getDraftThread(threadId));
48 const setDraftThreadContext = useComposerDraftStore((store) => store.setDraftThreadContext);
4649 const queryClient = useQueryClient();
4750
4851 const [isBranchMenuOpen, setIsBranchMenuOpen] = useState(false);
4952 const [branchQuery, setBranchQuery] = useState("");
5053
51 const activeThread = state.threads.find((thread) => thread.id === threadId);
52 const activeProject = state.projects.find((project) => project.id === activeThread?.projectId);
53 const activeThreadId = activeThread?.id;
54 const activeThreadBranch = activeThread?.branch ?? null;
55 const activeWorktreePath = activeThread?.worktreePath ?? null;
54 const serverThread = state.threads.find((thread) => thread.id === threadId);
55 const activeProjectId = serverThread?.projectId ?? draftThread?.projectId ?? null;
56 const activeProject = state.projects.find((project) => project.id === activeProjectId);
57 const activeThreadId = serverThread?.id ?? (draftThread ? threadId : undefined);
58 const activeThreadBranch = serverThread?.branch ?? draftThread?.branch ?? null;
59 const activeWorktreePath = serverThread?.worktreePath ?? draftThread?.worktreePath ?? null;
5660 const branchCwd = activeWorktreePath ?? activeProject?.cwd ?? null;
61 const hasServerThread = serverThread !== undefined;
5762
5863 // ── Queries ───────────────────────────────────────────────────────────
5964
......@@ -95,7 +100,7 @@ export default function BranchToolbar({
95100 if (!activeThreadId || !syncedBranch) return;
96101 const api = readNativeApi();
97102
98 if (api) {
103 if (api && hasServerThread) {
99104 void api.orchestration.dispatchCommand({
100105 type: "thread.meta.update",
101106 commandId: newCommandId(),
......@@ -104,13 +109,30 @@ export default function BranchToolbar({
104109 worktreePath: null,
105110 });
106111 }
107 dispatch({
108 type: "SET_THREAD_BRANCH",
109 threadId: activeThreadId,
112 if (hasServerThread) {
113 dispatch({
114 type: "SET_THREAD_BRANCH",
115 threadId: activeThreadId,
116 branch: syncedBranch,
117 worktreePath: null,
118 });
119 return;
120 }
121 setDraftThreadContext(threadId, {
110122 branch: syncedBranch,
111123 worktreePath: null,
112124 });
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 ]);
114136
115137 useEffect(() => {
116138 if (isBranchMenuOpen) return;
......@@ -129,7 +151,7 @@ export default function BranchToolbar({
129151 const api = readNativeApi();
130152 // If the effective cwd is about to change, stop the running session so the
131153 // next message creates a new one with the correct cwd.
132 const sessionId = activeThread?.session?.sessionId;
154 const sessionId = serverThread?.session?.sessionId;
133155 if (sessionId && worktreePath !== activeWorktreePath && api) {
134156 void api.orchestration
135157 .dispatchCommand({
......@@ -140,7 +162,7 @@ export default function BranchToolbar({
140162 })
141163 .catch(() => undefined);
142164 }
143 if (api) {
165 if (api && hasServerThread) {
144166 void api.orchestration.dispatchCommand({
145167 type: "thread.meta.update",
146168 commandId: newCommandId(),
......@@ -149,7 +171,14 @@ export default function BranchToolbar({
149171 worktreePath,
150172 });
151173 }
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 });
153182 };
154183
155184 const selectBranch = (branch: GitBranch) => {
......@@ -221,7 +250,7 @@ export default function BranchToolbar({
221250 });
222251 };
223252
224 if (!activeThread || !activeProject) return null;
253 if (!activeThreadId || !activeProject) return null;
225254
226255 return (
227256 <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({
248277 autoHighlight
249278 onOpenChange={(open) => setIsBranchMenuOpen(open)}
250279 open={isBranchMenuOpen}
251 value={activeThread.branch}
280 value={activeThreadBranch}
252281 >
253282 <ComboboxTrigger
254283 render={<Button variant="ghost" size="xs" />}
......@@ -256,10 +285,10 @@ export default function BranchToolbar({
256285 disabled={branchesQuery.isLoading}
257286 >
258287 <span className="max-w-[240px] truncate">
259 {activeThread.branch
288 {activeThreadBranch
260289 ? envMode === "worktree" && !activeWorktreePath
261 ? `From ${activeThread.branch}`
262 : activeThread.branch
290 ? `From ${activeThreadBranch}`
291 : activeThreadBranch
263292 : "Select branch"}
264293 </span>
265294 <ChevronDownIcon />
......@@ -313,7 +342,7 @@ export default function BranchToolbar({
313342 key={itemValue}
314343 value={itemValue}
315344 className={
316 itemValue === activeThread.branch ? "bg-accent text-foreground" : undefined
345 itemValue === activeThreadBranch ? "bg-accent text-foreground" : undefined
317346 }
318347 onClick={() => selectBranch(branch)}
319348 >