authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-10 00:30:21-08:00
committergravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-11 09:00:18-08:00
log9fc5ad20830a148ae36abf2381efef8283dc3699
treebe5c3b8832eb4f3fed177fbb93b386d00420cbe8
parent79ac59f04e8f02b113dfdf5de9e9a5305c748f33

plans


4 files changed, 391 insertions(+), 0 deletions(-)

.plans/branch-environment-picker-in-chatview-input.md created+74
......@@ -0,0 +1,74 @@
1# Branch/Environment Picker in ChatView Input
2
3## Summary
4
5Add a secondary toolbar below the ChatView input area (similar to Codex UI) that lets users select the target branch and environment mode (Local vs New worktree) before sending their first message.
6
7## UX
8
9- A toolbar appears **below** the input form (always visible when it's a git repo)
10- Two controls:
11 1. **Environment mode** (left side): toggles between "Local" and "New worktree" — **locked after first message** (no longer clickable, just shows current mode as label)
12 2. **Branch picker** (right side): dropdown showing local branches — **always changeable**, even after messages are sent
13- If not a git repo, the toolbar is hidden entirely (thread uses project cwd as-is)
14
15## Changes
16
17### 0. Install `@tanstack/react-query` in `apps/renderer`
18
19Add dependency + wrap app in `QueryClientProvider`.
20
21### 1. `apps/renderer/src/store.ts` — MODIFY
22
23Add a new action to the reducer:
24
25```ts
26| { type: "SET_THREAD_BRANCH"; threadId: string; branch: string | null; worktreePath: string | null }
27```
28
29Reducer case updates `branch` and `worktreePath` on the thread.
30
31### 2. `apps/renderer/src/components/ChatView.tsx` — MODIFY
32
33**Fetch branches** via `useQuery`:
34
35```ts
36const branchQuery = useQuery({
37 queryKey: ["git-branches", activeProject?.cwd],
38 queryFn: () => api.git.listBranches({ cwd: activeProject!.cwd }),
39 enabled: !!activeProject,
40});
41```
42
43**Local state:**
44
45- `envMode: "local" | "worktree"` — environment mode (local component state)
46
47**UI:** Below the `<form>`, render a toolbar bar (hidden if `!branchQuery.data?.isRepo`):
48
49- Left side: env mode button ("Local" / "New worktree") — disabled after first message (locked in)
50- Right side: branch dropdown from `branchQuery.data.branches`
51- Both styled like existing model picker (small text, chevron, dropdown menus)
52
53**Behavior:**
54
55- Branch picker is always active — changing branch dispatches `SET_THREAD_BRANCH` immediately
56- Env mode is only clickable when `activeThread.messages.length === 0`. After first message, it becomes a static label showing the locked-in mode
57- On first send (`onSend`): if `envMode === "worktree"` and a branch is selected, call `api.git.createWorktree` before starting the session, then dispatch `SET_THREAD_BRANCH` with the worktreePath
58- `ensureSession` already uses `activeThread.worktreePath ?? activeProject.cwd`
59
60### Files to modify
61
621. `apps/renderer/package.json` — add `@tanstack/react-query`
632. `apps/renderer/src/main.tsx` (or App entry) — wrap in `QueryClientProvider`
643. `apps/renderer/src/store.ts` — add `SET_THREAD_BRANCH` action
654. `apps/renderer/src/components/ChatView.tsx` — branch/env picker UI with `useQuery`
66
67## Verification
68
691. `turbo build` — compiles
702. Create a new thread → branch bar appears below input with "Local" + current branch
713. Change branch in dropdown → branch updates on thread
724. Toggle "New worktree" → send message → worktree created, session uses worktree cwd
735. After first message: env mode label locks to "Worktree" (not clickable), branch picker still works
746. Non-git project → no branch bar shown
.plans/git-flows-integration-tests.md created+99
......@@ -0,0 +1,99 @@
1# Git Flows Integration Tests
2
3## Overview
4
5Real integration tests that run actual git commands against temporary repos. No mocking.
6
7## Step 1: Extract git functions into `apps/desktop/src/git.ts`
8
9The git functions (`listGitBranches`, `createGitWorktree`, `removeGitWorktree`, `createGitBranch`, `checkoutGitBranch`, `initGitRepo`) and their helper `runTerminalCommand` are currently private in `main.ts`. Extract them into a new `apps/desktop/src/git.ts` module with named exports.
10
11`main.ts` will import and re-use them — no behavior change, just moving code.
12
13**Files modified:**
14
15- `apps/desktop/src/git.ts` — new file with all git functions exported
16- `apps/desktop/src/main.ts` — import from `./git` instead of defining inline
17
18## Step 2: Create `apps/desktop/src/git.test.ts`
19
20Integration tests using real temp git repos. Each test group creates a fresh temp directory with `git init`, makes commits, creates branches as needed, and cleans up after.
21
22### Setup/teardown pattern
23
24```ts
25import { mkdtemp, rm, writeFile } from "node:fs/promises";
26import { tmpdir } from "node:os";
27import path from "node:path";
28import { afterEach, beforeEach, describe, expect, it } from "vitest";
29import {
30 listGitBranches,
31 createGitBranch,
32 checkoutGitBranch,
33 createGitWorktree,
34 removeGitWorktree,
35 initGitRepo,
36} from "./git";
37
38// Helper: run a raw git command in a dir (for test setup, not under test)
39// Helper: create an initial commit (git needs at least one commit for branches)
40```
41
42### Test groups
43
44**1. initGitRepo**
45
46- Creates a valid git repo in a temp dir
47- listGitBranches reports `isRepo: true` after init
48
49**2. listGitBranches**
50
51- Returns `isRepo: false` for non-git directory
52- Returns the current branch with `current: true`
53- Sorts current branch first
54- Lists multiple branches after creating them
55- `isDefault` is false when no remote (no origin/HEAD)
56
57**3. checkoutGitBranch**
58
59- Checks out an existing branch (current flag moves)
60- Throws when branch doesn't exist
61- Throws when checkout would overwrite uncommitted changes (dirty working tree)
62
63**4. createGitBranch**
64
65- Creates a new branch (appears in listGitBranches)
66- Throws when branch already exists
67
68**5. createGitWorktree + removeGitWorktree**
69
70- Creates a worktree directory at the expected path
71- Worktree has the correct branch checked out
72- Throws when branch is already checked out in another worktree
73- removeGitWorktree cleans up the worktree
74
75**6. Full flow: local branch checkout**
76
77- init → commit → create branch → checkout → verify current
78
79**7. Full flow: worktree creation from selected branch**
80
81- init → commit → create branch → create worktree → verify worktree dir exists and has correct branch
82
83**8. Full flow: thread switching simulation**
84
85- init → commit → create branch-a, branch-b → checkout a → checkout b → checkout a → verify current matches
86
87**9. Full flow: checkout conflict**
88
89- init → commit → create branch → modify file (unstaged) → checkout other branch → expect error
90
91## Verification
92
93```bash
94# Run the git integration tests
95cd apps/desktop && bun run test
96
97# Or just the git test file
98npx vitest run apps/desktop/src/git.test.ts
99```
.plans/git-flows-test-plan.md created+103
......@@ -0,0 +1,103 @@
1# Git Flows Test Plan
2
3## Overview
4
5Add tests for git branch/worktree flows. Two files:
6
71. **Extend** `apps/renderer/src/store.test.ts` — reducer tests for `SET_THREAD_BRANCH`
82. **Create** `apps/renderer/src/git-flows.test.ts` — flow logic tests
9
10All tests are pure Vitest unit tests (no React rendering). They test the reducer directly and simulate handler logic via sequential reducer dispatches + mocked API calls.
11
12## File 1: `apps/renderer/src/store.test.ts` (extend)
13
14Add `describe("SET_THREAD_BRANCH reducer")` with 6 tests:
15
16- Sets branch + worktreePath atomically
17- Clears both to null
18- Updates branch while preserving worktreePath
19- Does not affect other threads (multi-thread state)
20- No-op for nonexistent thread id
21- Does not mutate messages, error, or session fields
22
23Uses existing `makeThread`, `makeState` factories.
24
25## File 2: `apps/renderer/src/git-flows.test.ts` (new)
26
27### Factories
28
29- `makeThread()`, `makeState()`, `makeSession()` — same pattern as store.test.ts
30- `makeBranch()` — creates `GitBranch` objects
31- `makeMessage()` — creates `ChatMessage` objects
32- `makeGitApi()` — returns `{ checkout, createWorktree, createBranch, listBranches }` with `vi.fn()` mocks
33
34### Test groups (~30 tests total)
35
36**1. Local branch checkout flow** (2 tests)
37
38- Successful checkout → SET_THREAD_BRANCH updates branch
39- Checkout failure → SET_ERROR, branch unchanged
40
41**2. Thread branch conflict on send** (3 tests)
42
43- Two threads maintain independent branch state after SET_ACTIVE_THREAD
44- Branch state preserved through multiple thread switches + updates
45- Checkout failure on thread switch sets error only on target thread
46
47**3. Worktree creation on send** (5 tests)
48
49- First message in worktree mode → createWorktree → SET_THREAD_BRANCH with worktreePath
50- No worktree when messages already exist
51- No worktree in local envMode
52- No worktree when worktreePath already set
53- createWorktree failure → SET_ERROR, send aborted, no messages pushed
54
55**4. Env mode locking** (4 tests)
56
57- envLocked=false when no messages
58- envLocked=true with messages
59- Transitions false→true after PUSH_USER_MESSAGE
60- Remains true after SET_ERROR and UPDATE_SESSION
61
62**5. Auto-fill current branch** (3 tests)
63
64- Dispatches SET_THREAD_BRANCH when thread has no branch and current branch exists
65- Does not overwrite existing branch
66- No-op when no branch is marked current
67
68**6. Default branch detection** (2 tests)
69
70- isDefault flag on branch objects
71- current and isDefault can be on different branches
72
73**7. Branch creation + checkout** (3 tests)
74
75- Successful create + checkout updates branch
76- createBranch failure → error, branch unchanged
77- checkout failure after successful create → error, branch unchanged
78
79**8. Session CWD resolution** (3 tests)
80
81- Uses worktreePath when available
82- cwdOverride takes precedence over worktreePath
83- Falls back to project cwd when no worktree
84
85**9. Error handling patterns** (4 tests)
86
87- SET_ERROR sets error on correct thread
88- SET_ERROR with null clears error
89- Error on one thread doesn't affect others
90- Error cleared before successful branch operations
91
92## Verification
93
94```bash
95# Run all renderer tests
96cd apps/renderer && bun run test
97
98# Run just the new test file
99npx vitest run apps/renderer/src/git-flows.test.ts
100
101# Run just the store tests
102npx vitest run apps/renderer/src/store.test.ts
103```
.plans/git-integration-branch-picker-worktrees.md created+115
......@@ -0,0 +1,115 @@
1# Git Integration: Branch Picker + Worktrees
2
3## Summary
4
5Add git integration to let users start new threads from a specific branch, optionally creating a git worktree for isolated agent work.
6
7## UX Flow
8
9- **Left click** "+ New thread" → immediately creates a thread (current behavior, unchanged)
10- **Right click** "+ New thread" → opens a context menu with git options:
11 - List of local branches → clicking one creates a thread on that branch (uses project cwd)
12 - Each branch has a "worktree" sub-option → creates a worktree, then creates thread with worktree as cwd
13- When thread has a worktree, the agent session uses the worktree path as its cwd
14- If git fails (not a repo), context menu shows "Not a git repository" disabled item
15
16## Changes
17
18### 1. `packages/contracts/src/git.ts` — CREATE
19
20New Zod schemas and types:
21
22- `gitListBranchesInputSchema` — `{ cwd: string }`
23- `gitCreateWorktreeInputSchema` — `{ cwd: string, branch: string, path?: string }`
24- `gitRemoveWorktreeInputSchema` — `{ cwd: string, path: string }`
25- `gitBranchSchema` — `{ name: string, current: boolean }`
26- Result types for each
27
28### 2. `packages/contracts/src/ipc.ts` — MODIFY
29
30- Add 3 IPC channels: `git:list-branches`, `git:create-worktree`, `git:remove-worktree`
31- Add `git` namespace to `NativeApi` with `listBranches`, `createWorktree`, `removeWorktree`
32
33### 3. `packages/contracts/src/index.ts` — MODIFY
34
35- Add `export * from "./git"`
36
37### 4. `apps/desktop/src/main.ts` — MODIFY
38
39Add 3 IPC handlers + helper functions:
40
41- `listGitBranches()` — runs `git branch --no-color`, parses output into `{ name, current }[]`
42- `createGitWorktree()` — runs `git worktree add <path> <branch>`, defaults path to `../{repo}-worktrees/{branch}`
43- `removeGitWorktree()` — runs `git worktree remove <path>`
44
45Reuses existing `runTerminalCommand()`.
46
47### 5. `apps/desktop/src/preload.ts` — MODIFY
48
49Add `git` namespace with 3 `ipcRenderer.invoke` calls.
50
51### 6. `apps/renderer/src/types.ts` — MODIFY
52
53Add to `Thread`:
54
55```
56branch: string | null
57worktreePath: string | null
58```
59
60### 7. `apps/renderer/src/persistenceSchema.ts` — MODIFY
61
62- Add optional `branch`/`worktreePath` to persisted thread schema (`.nullable().optional()` for backwards compat)
63- Add V3 schema, update union
64- Update `hydrateThread` to default new fields to `null`
65- Update `toPersistedState` to serialize new fields
66
67### 8. `apps/renderer/src/store.ts` — MODIFY
68
69- Update persisted state key to v3, keep v2 as legacy fallback
70
71### 9. `apps/renderer/src/components/Sidebar.tsx` — MODIFY (main UI work)
72
73- Keep existing left-click `handleNewThread` unchanged (immediate thread creation)
74- Add `onContextMenu` handler to "+ New thread" buttons (both global and per-project)
75- On right-click: fetch branches via `api.git.listBranches`, show a custom context menu
76- Context menu items: branch names, each with a nested option to create with worktree
77- Clicking a branch → creates thread with `branch` set, title = branch name
78- Clicking "with worktree" → calls `api.git.createWorktree` first, then creates thread with `worktreePath`
79- Show branch badge on thread list items
80- If not a git repo, show "Not a git repository" as disabled menu item
81
82Context menu component: a positioned `<div>` with `position: fixed` anchored to the click position, dismissed on click-outside or Escape. Follows the existing dropdown pattern from ChatView's model picker.
83
84### 10. `apps/renderer/src/components/ChatView.tsx` — MODIFY
85
86- Line 157: use `activeThread.worktreePath ?? activeProject.cwd` as session cwd
87- Show branch/worktree badge in header bar
88
89## Implementation Order
90
911. `packages/contracts/src/git.ts` (new schemas)
922. `packages/contracts/src/ipc.ts` + `index.ts` (wire up channels)
933. `apps/desktop/src/main.ts` (git command handlers)
944. `apps/desktop/src/preload.ts` (bridge methods)
955. `apps/renderer/src/types.ts` (Thread type update)
966. `apps/renderer/src/persistenceSchema.ts` + `store.ts` (persistence migration)
977. `apps/renderer/src/components/Sidebar.tsx` (branch picker UI)
988. `apps/renderer/src/components/ChatView.tsx` (worktree cwd + badge)
99
100## Edge Cases
101
102- **Not a git repo**: `git branch` fails → context menu shows "Not a git repository" disabled item
103- **Branch has slashes**: `feature/foo` → worktree dir becomes `feature-foo`
104- **Worktree exists**: git error surfaces to user via inline error message in context menu
105- **No persistence breakage**: `.nullable().optional()` fields parse fine with old data
106
107## Verification
108
1091. `turbo build` — confirm contracts/desktop/renderer all compile
1102. Launch app, add a project pointing to a git repo
1113. Click "+ New thread" → verify branch list loads
1124. Select a branch, click Start → thread created with branch in title
1135. Enable worktree checkbox, pick branch, Start → verify worktree directory created on disk
1146. Send a message in worktree thread → verify agent runs in worktree cwd
1157. Add a non-git project → verify graceful error, can still create thread