feat: replace boolean directMode with GitIsolationMode and flexible base branch#30
Conversation
… binary directMode
Replaces the binary `directMode` boolean with a `GitIsolationMode` enum ('worktree' | 'direct') and explicit `baseBranch` field, enabling worktree creation and direct work on any branch instead of just main. Unifies task creation into a single function, generalizes all git operations (merge, rebase, diff, status) to use explicit base branch, updates persistence with backward-compatible migration, and replaces UI controls with isolation mode selector and branch picker in NewTaskDialog and EditProjectDialog.
GitIsolationMode and flexible base branch
GitIsolationMode and flexible base branchdirectMode with GitIsolationMode and flexible base branch
There was a problem hiding this comment.
Thank you very much! Nice work consolidating the two task creation paths and threading baseBranch throughout – the code is cleaner now. A couple of issues to flag:
Critical: detectMergeBase cache ignores baseBranch
electron/ipc/git.ts:195-216
The cache key is just cacheKey(repoRoot), but baseBranch now varies per-task. If two tasks in the same repo use different base branches (e.g. one from main, one from develop), the second call returns the cached merge-base from the first — producing wrong diffs.
Fix: incorporate the resolved mainBranch into the cache key (e.g. ${repoRoot}:${mainBranch}).
Important: Migration fallback sets baseBranch to the task's own branch
src/store/persistence.ts:369-370 (and :389 for collapsed tasks)
baseBranch: legacy.baseBranch ?? pt.branchName,For existing worktree tasks, pt.branchName is something like task/my-feature-abc123. Setting that as baseBranch means git merge-base task/my-feature-abc123 HEAD when HEAD is on that same branch — merge-base resolves to HEAD itself, so all diffs come back empty after upgrade.
Suggestion: either make baseBranch optional on Task (so undefined triggers the existing detectMainBranch() fallback in the git layer), or resolve it asynchronously during loadState.
Minor
- EditProjectDialog: the "Worktree" button stores
undefinedrather than'worktree'— works via fallback but means "user chose worktree" is indistinguishable from "no preference set." - Arena files: the explicit
baseBranch: undefinedadditions are correct but noisy — omitting the property has the same effect. - The segmented Worktree/Direct button pattern is duplicated between NewTaskDialog and EditProjectDialog with ~30 lines of near-identical inline styles — worth extracting to a shared component.
…ntedButtons - Include resolved baseBranch in detectMergeBase cache key so tasks with different base branches in the same repo get distinct cache entries - Make baseBranch optional on Task/PersistedTask so migrated tasks without one fall back to detectMainBranch() instead of using the task's own branch name (which produced empty diffs) - Store 'worktree' explicitly in EditProjectDialog instead of undefined - Remove noisy baseBranch: undefined from arena files - Extract shared SegmentedButtons component to deduplicate ~60 lines of inline button styles between NewTaskDialog and EditProjectDialog Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Thank you for the comments, I believe I have addressed them - and thank for responding to PRs. Enjoying using the tool very much (side scrolling is the way). |
|
That's great! Thank you very much! |
Empty string '' from the UI (when branch fetch fails) would pass through `??` (nullish coalescing) without triggering the detectMainBranch() fallback, causing git commands like `git merge-base '' HEAD` to fail. Also removes unused getProjectDefaultBaseBranch/getProjectDefaultGitIsolation helper functions.
Summary
directModefield with a typedGitIsolationMode('worktree' | 'direct') across the task model, UI, and persistence layerbaseBranchfield to tasks and projects so users can branch from and diff against any local branch — not just the auto-detected mainGetBranchesIPC channel and branch picker dropdown in the New Task dialogcreateDirectTaskintocreateTaskas a single unified code pathdirectMode→gitIsolation,defaultDirectMode→defaultGitIsolation)Details
Previously, task isolation was a binary checkbox: either work in a git worktree (default) or directly on main. The branch used for diffs, merges, rebases, and worktree creation was always auto-detected via
detectMainBranch(), which looks formainormaster.This works for simple repos, but breaks down when:
develop,trunk)This PR makes two changes:
GitIsolationModetype — replaces the booleandirectModewith a string union'worktree' | 'direct'. All conditional checks (task.directMode→task.gitIsolation === 'direct') are updated throughout the codebase. The UI switches from a checkbox to a segmented button selector (Worktree / Direct).Flexible base branch — every git operation that previously called
detectMainBranch()now accepts an optionalbaseBranchparameter. When provided, it's used directly; when omitted, the existing auto-detection still runs. The New Task dialog fetches local branches via the newGetBranchesIPC channel and presents them in a<select>dropdown. Projects can also setdefaultBaseBranchin the Edit Project dialog.The two separate functions

createTaskandcreateDirectTaskare merged into a singlecreateTaskthat branches ongitIsolationinternally, reducing duplication.Changes
src/store/types.tsGitIsolationModetype; replacedirectMode?: booleanwithgitIsolation: GitIsolationMode+baseBranch: stringonTaskandPersistedTask; replacedefaultDirectModewithdefaultGitIsolation+defaultBaseBranchonProjectsrc/store/tasks.tscreateDirectTaskintocreateTask; threadbaseBranchthrough merge/push/close; renamehasDirectModeTask→hasDirectTasksrc/store/projects.tsgetProjectDefaultBaseBranch,getProjectDefaultGitIsolationhelpers; updateupdateProjectfieldssrc/store/persistence.tsdirectMode→gitIsolationanddefaultDirectMode→defaultGitIsolationon loadsrc/store/autosave.tsgitIsolation+baseBranchinstead ofdirectModesrc/store/taskStatus.tsbaseBranchtoGetWorktreeStatussrc/store/store.tscreateDirectTaskremoved,hasDirectModeTask→hasDirectTask)electron/ipc/channels.tsGetBranchesIPC channelelectron/ipc/git.tsgetBranches(); thread optionalbaseBranchthroughdetectMergeBase,createWorktree,getChangedFiles,getAllFileDiffs,getFileDiff,getWorktreeStatus,checkMergeStatus,mergeTask,getBranchLog,rebaseTask, and branch-based variantselectron/ipc/register.tsGetBrancheshandler; validate and passbaseBranchfor all git IPC handlerselectron/ipc/tasks.tsbaseBranchtocreateWorktreeelectron/preload.cjsget_branchesto preload allowlistsrc/components/NewTaskDialog.tsx<select>populated viaGetBranchesIPC; fetch branches + main branch on project changesrc/components/EditProjectDialog.tsxdefaultDirectModecheckbox with segmented selector +defaultBaseBranchtext inputsrc/components/MergeDialog.tsxbaseBranchthrough branch log, worktree status, merge status, and rebase callssrc/components/CloseTaskDialog.tsxdirectModechecks withgitIsolationcheckssrc/components/TaskPanel.tsxdirectModechecks withgitIsolationchecks; passbaseBranchto changed files list and diff viewersrc/components/DiffViewerDialog.tsxbaseBranchpropsrc/components/ScrollingDiffView.tsxbaseBranchthrough gap/trailing/file section componentssrc/components/ChangedFilesList.tsxbaseBranchto IPC callssrc/components/Sidebar.tsxdirectModechecks withgitIsolationcheckssrc/components/TilingLayout.tsxdirectModecheck withgitIsolationchecksrc/arena/ResultsScreen.tsxbaseBranch: undefinedfor arena compatibilitysrc/arena/merge.tsbaseBranch: undefinedfor arena compatibilityTest plan
npm run check(typecheck + lint + prettier) passesdirectMode: true— verify migration togitIsolation: 'direct'baseBranch: undefined)🤖 Generated with Claude Code