History

plans

Julius Marminge committed 9 months ago

Commit 9fc5ad21 parent 79ac59f4 files · +391 −0Browse files

Unified · Split

4 files
  1. .plans/branch-environment-picker-in-chatview-input.md +74 −0
  2. .plans/git-flows-integration-tests.md +99 −0
  3. .plans/git-flows-test-plan.md +103 −0
  4. .plans/git-integration-branch-picker-worktrees.md +115 −0
.plans/branch-environment-picker-in-chatview-input.md+74−0
@@ -0,0 +1,74 @@
1+# Branch/Environment Picker in ChatView Input
2+
3+## Summary
4+
5+Add 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+
19+Add dependency + wrap app in `QueryClientProvider`.
20+
21+### 1. `apps/renderer/src/store.ts` — MODIFY
22+
23+Add a new action to the reducer:
24+
25+```ts
26+| { type: "SET_THREAD_BRANCH"; threadId: string; branch: string | null; worktreePath: string | null }
27+```
28+
29+Reducer 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
36+const 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+
62+1. `apps/renderer/package.json` — add `@tanstack/react-query`
63+2. `apps/renderer/src/main.tsx` (or App entry) — wrap in `QueryClientProvider`
64+3. `apps/renderer/src/store.ts` — add `SET_THREAD_BRANCH` action
65+4. `apps/renderer/src/components/ChatView.tsx` — branch/env picker UI with `useQuery`
66+
67+## Verification
68+
69+1. `turbo build` — compiles
70+2. Create a new thread → branch bar appears below input with "Local" + current branch
71+3. Change branch in dropdown → branch updates on thread
72+4. Toggle "New worktree" → send message → worktree created, session uses worktree cwd
73+5. After first message: env mode label locks to "Worktree" (not clickable), branch picker still works
74+6. Non-git project → no branch bar shown
.plans/git-flows-integration-tests.md+99−0
@@ -0,0 +1,99 @@
1+# Git Flows Integration Tests
2+
3+## Overview
4+
5+Real 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+
9+The 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+
20+Integration 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
25+import { mkdtemp, rm, writeFile } from "node:fs/promises";
26+import { tmpdir } from "node:os";
27+import path from "node:path";
28+import { afterEach, beforeEach, describe, expect, it } from "vitest";
29+import {
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
95+cd apps/desktop && bun run test
96+
97+# Or just the git test file
98+npx vitest run apps/desktop/src/git.test.ts
99+```
.plans/git-flows-test-plan.md+103−0
@@ -0,0 +1,103 @@
1+# Git Flows Test Plan
2+
3+## Overview
4+
5+Add tests for git branch/worktree flows. Two files:
6+
7+1. **Extend** `apps/renderer/src/store.test.ts` — reducer tests for `SET_THREAD_BRANCH`
8+2. **Create** `apps/renderer/src/git-flows.test.ts` — flow logic tests
9+
10+All 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+
14+Add `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+
23+Uses 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
96+cd apps/renderer && bun run test
97+
98+# Run just the new test file
99+npx vitest run apps/renderer/src/git-flows.test.ts
100+
101+# Run just the store tests
102+npx vitest run apps/renderer/src/store.test.ts
103+```
.plans/git-integration-branch-picker-worktrees.md+115−0
@@ -0,0 +1,115 @@
1+# Git Integration: Branch Picker + Worktrees
2+
3+## Summary
4+
5+Add 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+
20+New 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+
39+Add 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+
45+Reuses existing `runTerminalCommand()`.
46+
47+### 5. `apps/desktop/src/preload.ts` — MODIFY
48+
49+Add `git` namespace with 3 `ipcRenderer.invoke` calls.
50+
51+### 6. `apps/renderer/src/types.ts` — MODIFY
52+
53+Add to `Thread`:
54+
55+```
56+branch: string | null
57+worktreePath: 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+
82+Context 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+
91+1. `packages/contracts/src/git.ts` (new schemas)
92+2. `packages/contracts/src/ipc.ts` + `index.ts` (wire up channels)
93+3. `apps/desktop/src/main.ts` (git command handlers)
94+4. `apps/desktop/src/preload.ts` (bridge methods)
95+5. `apps/renderer/src/types.ts` (Thread type update)
96+6. `apps/renderer/src/persistenceSchema.ts` + `store.ts` (persistence migration)
97+7. `apps/renderer/src/components/Sidebar.tsx` (branch picker UI)
98+8. `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+
109+1. `turbo build` — confirm contracts/desktop/renderer all compile
110+2. Launch app, add a project pointing to a git repo
111+3. Click "+ New thread" → verify branch list loads
112+4. Select a branch, click Start → thread created with branch in title
113+5. Enable worktree checkbox, pick branch, Start → verify worktree directory created on disk
114+6. Send a message in worktree thread → verify agent runs in worktree cwd
115+7. Add a non-git project → verify graceful error, can still create thread

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t