From 9fc5ad20830a148ae36abf2381efef8283dc3699 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 10 Feb 2026 00:30:21 -0800 Subject: [PATCH] plans --- ...ch-environment-picker-in-chatview-input.md | 74 +++++++++++ .plans/git-flows-integration-tests.md | 99 +++++++++++++++ .plans/git-flows-test-plan.md | 103 ++++++++++++++++ ...git-integration-branch-picker-worktrees.md | 115 ++++++++++++++++++ 4 files changed, 391 insertions(+) create mode 100644 .plans/branch-environment-picker-in-chatview-input.md create mode 100644 .plans/git-flows-integration-tests.md create mode 100644 .plans/git-flows-test-plan.md create mode 100644 .plans/git-integration-branch-picker-worktrees.md diff --git a/.plans/branch-environment-picker-in-chatview-input.md b/.plans/branch-environment-picker-in-chatview-input.md new file mode 100644 index 0000000000000000000000000000000000000000..2c1994d2c8d6f05995134a7173ae6465e9486178 --- /dev/null +++ b/.plans/branch-environment-picker-in-chatview-input.md @@ -0,0 +1,74 @@ +# Branch/Environment Picker in ChatView Input + +## Summary + +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. + +## UX + +- A toolbar appears **below** the input form (always visible when it's a git repo) +- Two controls: + 1. **Environment mode** (left side): toggles between "Local" and "New worktree" — **locked after first message** (no longer clickable, just shows current mode as label) + 2. **Branch picker** (right side): dropdown showing local branches — **always changeable**, even after messages are sent +- If not a git repo, the toolbar is hidden entirely (thread uses project cwd as-is) + +## Changes + +### 0. Install `@tanstack/react-query` in `apps/renderer` + +Add dependency + wrap app in `QueryClientProvider`. + +### 1. `apps/renderer/src/store.ts` — MODIFY + +Add a new action to the reducer: + +```ts +| { type: "SET_THREAD_BRANCH"; threadId: string; branch: string | null; worktreePath: string | null } +``` + +Reducer case updates `branch` and `worktreePath` on the thread. + +### 2. `apps/renderer/src/components/ChatView.tsx` — MODIFY + +**Fetch branches** via `useQuery`: + +```ts +const branchQuery = useQuery({ + queryKey: ["git-branches", activeProject?.cwd], + queryFn: () => api.git.listBranches({ cwd: activeProject!.cwd }), + enabled: !!activeProject, +}); +``` + +**Local state:** + +- `envMode: "local" | "worktree"` — environment mode (local component state) + +**UI:** Below the `
`, render a toolbar bar (hidden if `!branchQuery.data?.isRepo`): + +- Left side: env mode button ("Local" / "New worktree") — disabled after first message (locked in) +- Right side: branch dropdown from `branchQuery.data.branches` +- Both styled like existing model picker (small text, chevron, dropdown menus) + +**Behavior:** + +- Branch picker is always active — changing branch dispatches `SET_THREAD_BRANCH` immediately +- Env mode is only clickable when `activeThread.messages.length === 0`. After first message, it becomes a static label showing the locked-in mode +- 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 +- `ensureSession` already uses `activeThread.worktreePath ?? activeProject.cwd` + +### Files to modify + +1. `apps/renderer/package.json` — add `@tanstack/react-query` +2. `apps/renderer/src/main.tsx` (or App entry) — wrap in `QueryClientProvider` +3. `apps/renderer/src/store.ts` — add `SET_THREAD_BRANCH` action +4. `apps/renderer/src/components/ChatView.tsx` — branch/env picker UI with `useQuery` + +## Verification + +1. `turbo build` — compiles +2. Create a new thread → branch bar appears below input with "Local" + current branch +3. Change branch in dropdown → branch updates on thread +4. Toggle "New worktree" → send message → worktree created, session uses worktree cwd +5. After first message: env mode label locks to "Worktree" (not clickable), branch picker still works +6. Non-git project → no branch bar shown diff --git a/.plans/git-flows-integration-tests.md b/.plans/git-flows-integration-tests.md new file mode 100644 index 0000000000000000000000000000000000000000..70e233a008607fd7ec542c7285ea4a56496aad8c --- /dev/null +++ b/.plans/git-flows-integration-tests.md @@ -0,0 +1,99 @@ +# Git Flows Integration Tests + +## Overview + +Real integration tests that run actual git commands against temporary repos. No mocking. + +## Step 1: Extract git functions into `apps/desktop/src/git.ts` + +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. + +`main.ts` will import and re-use them — no behavior change, just moving code. + +**Files modified:** + +- `apps/desktop/src/git.ts` — new file with all git functions exported +- `apps/desktop/src/main.ts` — import from `./git` instead of defining inline + +## Step 2: Create `apps/desktop/src/git.test.ts` + +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. + +### Setup/teardown pattern + +```ts +import { mkdtemp, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { + listGitBranches, + createGitBranch, + checkoutGitBranch, + createGitWorktree, + removeGitWorktree, + initGitRepo, +} from "./git"; + +// Helper: run a raw git command in a dir (for test setup, not under test) +// Helper: create an initial commit (git needs at least one commit for branches) +``` + +### Test groups + +**1. initGitRepo** + +- Creates a valid git repo in a temp dir +- listGitBranches reports `isRepo: true` after init + +**2. listGitBranches** + +- Returns `isRepo: false` for non-git directory +- Returns the current branch with `current: true` +- Sorts current branch first +- Lists multiple branches after creating them +- `isDefault` is false when no remote (no origin/HEAD) + +**3. checkoutGitBranch** + +- Checks out an existing branch (current flag moves) +- Throws when branch doesn't exist +- Throws when checkout would overwrite uncommitted changes (dirty working tree) + +**4. createGitBranch** + +- Creates a new branch (appears in listGitBranches) +- Throws when branch already exists + +**5. createGitWorktree + removeGitWorktree** + +- Creates a worktree directory at the expected path +- Worktree has the correct branch checked out +- Throws when branch is already checked out in another worktree +- removeGitWorktree cleans up the worktree + +**6. Full flow: local branch checkout** + +- init → commit → create branch → checkout → verify current + +**7. Full flow: worktree creation from selected branch** + +- init → commit → create branch → create worktree → verify worktree dir exists and has correct branch + +**8. Full flow: thread switching simulation** + +- init → commit → create branch-a, branch-b → checkout a → checkout b → checkout a → verify current matches + +**9. Full flow: checkout conflict** + +- init → commit → create branch → modify file (unstaged) → checkout other branch → expect error + +## Verification + +```bash +# Run the git integration tests +cd apps/desktop && bun run test + +# Or just the git test file +npx vitest run apps/desktop/src/git.test.ts +``` diff --git a/.plans/git-flows-test-plan.md b/.plans/git-flows-test-plan.md new file mode 100644 index 0000000000000000000000000000000000000000..45b86b622b5f044a2b6bf373ebb5f3f00fa4e0de --- /dev/null +++ b/.plans/git-flows-test-plan.md @@ -0,0 +1,103 @@ +# Git Flows Test Plan + +## Overview + +Add tests for git branch/worktree flows. Two files: + +1. **Extend** `apps/renderer/src/store.test.ts` — reducer tests for `SET_THREAD_BRANCH` +2. **Create** `apps/renderer/src/git-flows.test.ts` — flow logic tests + +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. + +## File 1: `apps/renderer/src/store.test.ts` (extend) + +Add `describe("SET_THREAD_BRANCH reducer")` with 6 tests: + +- Sets branch + worktreePath atomically +- Clears both to null +- Updates branch while preserving worktreePath +- Does not affect other threads (multi-thread state) +- No-op for nonexistent thread id +- Does not mutate messages, error, or session fields + +Uses existing `makeThread`, `makeState` factories. + +## File 2: `apps/renderer/src/git-flows.test.ts` (new) + +### Factories + +- `makeThread()`, `makeState()`, `makeSession()` — same pattern as store.test.ts +- `makeBranch()` — creates `GitBranch` objects +- `makeMessage()` — creates `ChatMessage` objects +- `makeGitApi()` — returns `{ checkout, createWorktree, createBranch, listBranches }` with `vi.fn()` mocks + +### Test groups (~30 tests total) + +**1. Local branch checkout flow** (2 tests) + +- Successful checkout → SET_THREAD_BRANCH updates branch +- Checkout failure → SET_ERROR, branch unchanged + +**2. Thread branch conflict on send** (3 tests) + +- Two threads maintain independent branch state after SET_ACTIVE_THREAD +- Branch state preserved through multiple thread switches + updates +- Checkout failure on thread switch sets error only on target thread + +**3. Worktree creation on send** (5 tests) + +- First message in worktree mode → createWorktree → SET_THREAD_BRANCH with worktreePath +- No worktree when messages already exist +- No worktree in local envMode +- No worktree when worktreePath already set +- createWorktree failure → SET_ERROR, send aborted, no messages pushed + +**4. Env mode locking** (4 tests) + +- envLocked=false when no messages +- envLocked=true with messages +- Transitions false→true after PUSH_USER_MESSAGE +- Remains true after SET_ERROR and UPDATE_SESSION + +**5. Auto-fill current branch** (3 tests) + +- Dispatches SET_THREAD_BRANCH when thread has no branch and current branch exists +- Does not overwrite existing branch +- No-op when no branch is marked current + +**6. Default branch detection** (2 tests) + +- isDefault flag on branch objects +- current and isDefault can be on different branches + +**7. Branch creation + checkout** (3 tests) + +- Successful create + checkout updates branch +- createBranch failure → error, branch unchanged +- checkout failure after successful create → error, branch unchanged + +**8. Session CWD resolution** (3 tests) + +- Uses worktreePath when available +- cwdOverride takes precedence over worktreePath +- Falls back to project cwd when no worktree + +**9. Error handling patterns** (4 tests) + +- SET_ERROR sets error on correct thread +- SET_ERROR with null clears error +- Error on one thread doesn't affect others +- Error cleared before successful branch operations + +## Verification + +```bash +# Run all renderer tests +cd apps/renderer && bun run test + +# Run just the new test file +npx vitest run apps/renderer/src/git-flows.test.ts + +# Run just the store tests +npx vitest run apps/renderer/src/store.test.ts +``` diff --git a/.plans/git-integration-branch-picker-worktrees.md b/.plans/git-integration-branch-picker-worktrees.md new file mode 100644 index 0000000000000000000000000000000000000000..b5b5e82e3284091dd9988e6c3cb0c2e691bfc7c5 --- /dev/null +++ b/.plans/git-integration-branch-picker-worktrees.md @@ -0,0 +1,115 @@ +# Git Integration: Branch Picker + Worktrees + +## Summary + +Add git integration to let users start new threads from a specific branch, optionally creating a git worktree for isolated agent work. + +## UX Flow + +- **Left click** "+ New thread" → immediately creates a thread (current behavior, unchanged) +- **Right click** "+ New thread" → opens a context menu with git options: + - List of local branches → clicking one creates a thread on that branch (uses project cwd) + - Each branch has a "worktree" sub-option → creates a worktree, then creates thread with worktree as cwd +- When thread has a worktree, the agent session uses the worktree path as its cwd +- If git fails (not a repo), context menu shows "Not a git repository" disabled item + +## Changes + +### 1. `packages/contracts/src/git.ts` — CREATE + +New Zod schemas and types: + +- `gitListBranchesInputSchema` — `{ cwd: string }` +- `gitCreateWorktreeInputSchema` — `{ cwd: string, branch: string, path?: string }` +- `gitRemoveWorktreeInputSchema` — `{ cwd: string, path: string }` +- `gitBranchSchema` — `{ name: string, current: boolean }` +- Result types for each + +### 2. `packages/contracts/src/ipc.ts` — MODIFY + +- Add 3 IPC channels: `git:list-branches`, `git:create-worktree`, `git:remove-worktree` +- Add `git` namespace to `NativeApi` with `listBranches`, `createWorktree`, `removeWorktree` + +### 3. `packages/contracts/src/index.ts` — MODIFY + +- Add `export * from "./git"` + +### 4. `apps/desktop/src/main.ts` — MODIFY + +Add 3 IPC handlers + helper functions: + +- `listGitBranches()` — runs `git branch --no-color`, parses output into `{ name, current }[]` +- `createGitWorktree()` — runs `git worktree add `, defaults path to `../{repo}-worktrees/{branch}` +- `removeGitWorktree()` — runs `git worktree remove ` + +Reuses existing `runTerminalCommand()`. + +### 5. `apps/desktop/src/preload.ts` — MODIFY + +Add `git` namespace with 3 `ipcRenderer.invoke` calls. + +### 6. `apps/renderer/src/types.ts` — MODIFY + +Add to `Thread`: + +``` +branch: string | null +worktreePath: string | null +``` + +### 7. `apps/renderer/src/persistenceSchema.ts` — MODIFY + +- Add optional `branch`/`worktreePath` to persisted thread schema (`.nullable().optional()` for backwards compat) +- Add V3 schema, update union +- Update `hydrateThread` to default new fields to `null` +- Update `toPersistedState` to serialize new fields + +### 8. `apps/renderer/src/store.ts` — MODIFY + +- Update persisted state key to v3, keep v2 as legacy fallback + +### 9. `apps/renderer/src/components/Sidebar.tsx` — MODIFY (main UI work) + +- Keep existing left-click `handleNewThread` unchanged (immediate thread creation) +- Add `onContextMenu` handler to "+ New thread" buttons (both global and per-project) +- On right-click: fetch branches via `api.git.listBranches`, show a custom context menu +- Context menu items: branch names, each with a nested option to create with worktree +- Clicking a branch → creates thread with `branch` set, title = branch name +- Clicking "with worktree" → calls `api.git.createWorktree` first, then creates thread with `worktreePath` +- Show branch badge on thread list items +- If not a git repo, show "Not a git repository" as disabled menu item + +Context menu component: a positioned `
` with `position: fixed` anchored to the click position, dismissed on click-outside or Escape. Follows the existing dropdown pattern from ChatView's model picker. + +### 10. `apps/renderer/src/components/ChatView.tsx` — MODIFY + +- Line 157: use `activeThread.worktreePath ?? activeProject.cwd` as session cwd +- Show branch/worktree badge in header bar + +## Implementation Order + +1. `packages/contracts/src/git.ts` (new schemas) +2. `packages/contracts/src/ipc.ts` + `index.ts` (wire up channels) +3. `apps/desktop/src/main.ts` (git command handlers) +4. `apps/desktop/src/preload.ts` (bridge methods) +5. `apps/renderer/src/types.ts` (Thread type update) +6. `apps/renderer/src/persistenceSchema.ts` + `store.ts` (persistence migration) +7. `apps/renderer/src/components/Sidebar.tsx` (branch picker UI) +8. `apps/renderer/src/components/ChatView.tsx` (worktree cwd + badge) + +## Edge Cases + +- **Not a git repo**: `git branch` fails → context menu shows "Not a git repository" disabled item +- **Branch has slashes**: `feature/foo` → worktree dir becomes `feature-foo` +- **Worktree exists**: git error surfaces to user via inline error message in context menu +- **No persistence breakage**: `.nullable().optional()` fields parse fine with old data + +## Verification + +1. `turbo build` — confirm contracts/desktop/renderer all compile +2. Launch app, add a project pointing to a git repo +3. Click "+ New thread" → verify branch list loads +4. Select a branch, click Start → thread created with branch in title +5. Enable worktree checkbox, pick branch, Start → verify worktree directory created on disk +6. Send a message in worktree thread → verify agent runs in worktree cwd +7. Add a non-git project → verify graceful error, can still create thread -- 2.54.0