From e847b41250ab5c36c1ad00c84bb69a6de9d8d80e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 14 May 2026 21:56:43 +0000 Subject: [PATCH 1/7] feat(lanes): manage lane stack position and optional reparent base branch - Extend lanes.reparent with stackBaseBranchRef (resolved in repo, prefers origin). - Skip no-op reparent when parent link and base_ref are unchanged. - Manage Lane dialog: parent lane selector, optional base branch, apply runs reparent. - Parse stackBaseBranchRef for remote lanes.reparent in ade-cli sync. ADE-29 Co-authored-by: Arul Sharma --- .../services/sync/syncRemoteCommandService.ts | 7 +- .../main/services/lanes/laneService.test.ts | 79 +++++++ .../src/main/services/lanes/laneService.ts | 34 ++- .../renderer/components/lanes/LanesPage.tsx | 1 + .../components/lanes/ManageLaneDialog.tsx | 194 +++++++++++++++++- apps/desktop/src/shared/types/lanes.ts | 6 + 6 files changed, 314 insertions(+), 7 deletions(-) diff --git a/apps/ade-cli/src/services/sync/syncRemoteCommandService.ts b/apps/ade-cli/src/services/sync/syncRemoteCommandService.ts index 6fb7ae3b3..93568f2b7 100644 --- a/apps/ade-cli/src/services/sync/syncRemoteCommandService.ts +++ b/apps/ade-cli/src/services/sync/syncRemoteCommandService.ts @@ -404,10 +404,15 @@ function parseRenameLaneArgs(value: Record): RenameLaneArgs { } function parseReparentLaneArgs(value: Record): ReparentLaneArgs { - return { + const parsed: ReparentLaneArgs = { laneId: requireString(value.laneId, "lanes.reparent requires laneId."), newParentLaneId: requireString(value.newParentLaneId, "lanes.reparent requires newParentLaneId."), }; + const stackBase = asTrimmedString(value.stackBaseBranchRef); + if (stackBase) { + parsed.stackBaseBranchRef = stackBase; + } + return parsed; } function parseUpdateLaneAppearanceArgs(value: Record): UpdateLaneAppearanceArgs { diff --git a/apps/desktop/src/main/services/lanes/laneService.test.ts b/apps/desktop/src/main/services/lanes/laneService.test.ts index 13d4178e6..9cd9ee764 100644 --- a/apps/desktop/src/main/services/lanes/laneService.test.ts +++ b/apps/desktop/src/main/services/lanes/laneService.test.ts @@ -2053,6 +2053,85 @@ describe("laneService reparent", () => { expect.objectContaining({ cwd: path.join(repoRoot, "child") }), ); }); + + it("reparents onto stackBaseBranchRef when provided", async () => { + const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), "ade-lane-service-reparent-stack-base-")); + const db = await openKvDb(path.join(repoRoot, "kv.sqlite"), createLogger()); + await seedProjectAndStack(db, { projectId: "proj-reparent-stack-base", repoRoot }); + + let childHeadReads = 0; + vi.mocked(getHeadSha).mockImplementation(async (cwd: string) => { + if (cwd.endsWith("/child")) { + childHeadReads += 1; + return childHeadReads === 1 ? "sha-child-pre" : "sha-child-post"; + } + return "sha-unused"; + }); + + vi.mocked(runGit).mockImplementation(async (args: string[]) => { + const laneBranchGitStub = defaultLaneBranchGitStub(args); + if (laneBranchGitStub) return laneBranchGitStub; + if (args[0] === "rev-parse" && args[1] === "--verify" && args[2] === "origin/develop") { + return { exitCode: 0, stdout: "sha-origin-develop\n", stderr: "" }; + } + throw new Error(`Unexpected git call: ${args.join(" ")}`); + }); + + vi.mocked(runGitOrThrow).mockImplementation(async (args: string[]) => { + if (args[0] === "rebase") { + expect(args[1]).toBe("sha-origin-develop"); + return { exitCode: 0, stdout: "", stderr: "" } as any; + } + throw new Error(`Unexpected git call: ${args.join(" ")}`); + }); + + const service = createLaneService({ + db, + projectRoot: repoRoot, + projectId: "proj-reparent-stack-base", + defaultBaseRef: "main", + worktreesDir: path.join(repoRoot, "worktrees"), + }); + + const result = await service.reparent({ + laneId: "lane-child", + newParentLaneId: "lane-main", + stackBaseBranchRef: "develop", + }); + + expect(result.newBaseRef).toBe("develop"); + expect(result.newParentLaneId).toBe("lane-main"); + expect(runGitOrThrow).toHaveBeenCalledWith( + ["rebase", "sha-origin-develop"], + expect.objectContaining({ cwd: path.join(repoRoot, "child") }), + ); + }); + + it("skips git rebase when parent and base ref are unchanged", async () => { + const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), "ade-lane-service-reparent-noop-")); + const db = await openKvDb(path.join(repoRoot, "kv.sqlite"), createLogger()); + await seedProjectAndStack(db, { projectId: "proj-reparent-noop", repoRoot }); + + vi.mocked(getHeadSha).mockResolvedValue("sha-stable"); + vi.mocked(runGit).mockImplementation(async (args: string[]) => { + const laneBranchGitStub = defaultLaneBranchGitStub(args); + if (laneBranchGitStub) return laneBranchGitStub; + throw new Error(`Unexpected git call: ${args.join(" ")}`); + }); + vi.mocked(runGitOrThrow).mockReset(); + + const service = createLaneService({ + db, + projectRoot: repoRoot, + projectId: "proj-reparent-noop", + defaultBaseRef: "main", + worktreesDir: path.join(repoRoot, "worktrees"), + }); + + await service.reparent({ laneId: "lane-child", newParentLaneId: "lane-parent" }); + + expect(runGitOrThrow).not.toHaveBeenCalled(); + }); }); describe("laneService missionId and laneRole", () => { diff --git a/apps/desktop/src/main/services/lanes/laneService.ts b/apps/desktop/src/main/services/lanes/laneService.ts index 1b6b1a149..319cd7df1 100644 --- a/apps/desktop/src/main/services/lanes/laneService.ts +++ b/apps/desktop/src/main/services/lanes/laneService.ts @@ -3361,7 +3361,7 @@ export function createLaneService({ return run ? cloneRebaseRun(run) : null; }, - async reparent({ laneId, newParentLaneId }: ReparentLaneArgs): Promise { + async reparent({ laneId, newParentLaneId, stackBaseBranchRef }: ReparentLaneArgs): Promise { const lane = getLaneRow(laneId); if (!lane) throw new Error(`Lane not found: ${laneId}`); if (lane.lane_type === "primary") throw new Error("Primary lane cannot be reparented"); @@ -3378,11 +3378,37 @@ export function createLaneService({ const previousParentLaneId = lane.parent_lane_id; const previousBaseRef = lane.base_ref; - const newBaseRef = newParent.branch_ref; const persistedParentLaneId = newParent.lane_type === "primary" ? null : newParent.id; + const stackBaseOverride = stackBaseBranchRef?.trim(); + let newBaseRef: string; + let newParentHead: string; + if (stackBaseOverride) { + const branchTarget = await resolveBranchRebaseTarget({ + projectRoot, + branchRef: stackBaseOverride, + preferRemote: true, + }); + newBaseRef = branchTarget.branchName; + newParentHead = branchTarget.headSha; + } else { + newBaseRef = newParent.branch_ref; + const newParentTarget = await resolveParentRebaseTarget({ projectRoot, parent: newParent }); + newParentHead = newParentTarget.headSha; + } + const parentUnchanged = (lane.parent_lane_id ?? null) === (persistedParentLaneId ?? null); + if (parentUnchanged && newBaseRef === lane.base_ref) { + const headSha = await getHeadSha(lane.worktree_path); + return { + laneId: lane.id, + previousParentLaneId, + newParentLaneId: newParent.id, + previousBaseRef, + newBaseRef: lane.base_ref, + preHeadSha: headSha, + postHeadSha: headSha, + }; + } const preHeadSha = await getHeadSha(lane.worktree_path); - const newParentTarget = await resolveParentRebaseTarget({ projectRoot, parent: newParent }); - const newParentHead = newParentTarget.headSha; const operation = operationService?.start({ laneId: lane.id, diff --git a/apps/desktop/src/renderer/components/lanes/LanesPage.tsx b/apps/desktop/src/renderer/components/lanes/LanesPage.tsx index b3c02328e..789726e39 100644 --- a/apps/desktop/src/renderer/components/lanes/LanesPage.tsx +++ b/apps/desktop/src/renderer/components/lanes/LanesPage.tsx @@ -3636,6 +3636,7 @@ export function LanesPage() { onArchive={() => { archiveManagedLanes().catch(() => {}); }} onDelete={() => { deleteManagedLanes().catch(() => {}); }} onAppearanceChanged={() => refreshLanes({ includeStatus: false }).catch(() => {})} + onStackReorganized={() => { void refreshLanes().catch(() => {}); }} /> diff --git a/apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx b/apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx index 9c1555be6..4aa7e375a 100644 --- a/apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx +++ b/apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx @@ -13,7 +13,8 @@ import { Cube, CheckCircle, X, - Minus + Minus, + TreeStructure } from "@phosphor-icons/react"; import { Button } from "../ui/Button"; import type { @@ -64,7 +65,8 @@ export function ManageLaneDialog({ onAdoptAttached, onArchive, onDelete, - onAppearanceChanged + onAppearanceChanged, + onStackReorganized }: { open: boolean; onOpenChange: (open: boolean) => void; @@ -88,6 +90,7 @@ export function ManageLaneDialog({ onArchive: () => void; onDelete: () => void; onAppearanceChanged?: () => void | Promise; + onStackReorganized?: () => void | Promise; }) { const lanes = managedLanes?.length ? managedLanes : managedLane ? [managedLane] : []; const isBatch = lanes.length > 1; @@ -239,6 +242,15 @@ export function ManageLaneDialog({ ) : null} + {!isBatch && lanes[0] && lanes[0].laneType !== "primary" ? ( + + ) : null} + {/* Archive */}
@@ -625,3 +637,181 @@ function AppearanceSection({
); } + +function collectDescendantLaneIds(rootId: string, all: LaneSummary[]): Set { + const childrenByParent = new Map(); + for (const row of all) { + if (!row.parentLaneId) continue; + const list = childrenByParent.get(row.parentLaneId) ?? []; + list.push(row); + childrenByParent.set(row.parentLaneId, list); + } + const out = new Set(); + const stack = [...(childrenByParent.get(rootId) ?? [])]; + while (stack.length) { + const row = stack.pop()!; + if (out.has(row.id)) continue; + out.add(row.id); + const kids = childrenByParent.get(row.id); + if (kids) stack.push(...kids); + } + return out; +} + +function StackPositionSection({ + lane, + allLanes, + disabled, + onDone, +}: { + lane: LaneSummary; + allLanes: LaneSummary[]; + disabled: boolean; + onDone?: () => void | Promise; +}) { + const primaryLane = React.useMemo( + () => allLanes.find((l) => l.laneType === "primary" && !l.archivedAt) ?? null, + [allLanes], + ); + + const effectiveCurrentParentId = lane.parentLaneId ?? primaryLane?.id ?? ""; + + const candidates = React.useMemo(() => { + const descendants = collectDescendantLaneIds(lane.id, allLanes); + const list = allLanes.filter( + (l) => !l.archivedAt && l.id !== lane.id && !descendants.has(l.id), + ); + list.sort((a, b) => { + const ap = a.laneType === "primary" ? 0 : 1; + const bp = b.laneType === "primary" ? 0 : 1; + if (ap !== bp) return ap - bp; + return a.name.localeCompare(b.name); + }); + return list; + }, [allLanes, lane.id]); + + const [stackParentId, setStackParentId] = React.useState(effectiveCurrentParentId); + const [baseBranchInput, setBaseBranchInput] = React.useState(""); + const [busy, setBusy] = React.useState(false); + const [error, setError] = React.useState(null); + const [success, setSuccess] = React.useState(null); + + React.useEffect(() => { + setStackParentId(effectiveCurrentParentId); + setBaseBranchInput(""); + setError(null); + setSuccess(null); + }, [lane.id, lane.parentLaneId, lane.baseRef, effectiveCurrentParentId]); + + const defaultBaseBranch = candidates.find((c) => c.id === stackParentId)?.branchRef ?? ""; + + const baseOverrideTrim = baseBranchInput.trim(); + const parentChanged = stackParentId !== effectiveCurrentParentId; + const baseChanged = baseOverrideTrim.length > 0 && baseOverrideTrim !== lane.baseRef; + const canApply = + !lane.status.dirty && + !lane.status.rebaseInProgress && + !busy && + !disabled && + Boolean(stackParentId) && + (parentChanged || baseChanged); + + const apply = async () => { + if (!canApply || !stackParentId) return; + setError(null); + setSuccess(null); + setBusy(true); + try { + await window.ade.lanes.reparent({ + laneId: lane.id, + newParentLaneId: stackParentId, + stackBaseBranchRef: baseOverrideTrim || null, + }); + setSuccess("Stack position updated."); + await onDone?.(); + } catch (err) { + setError(err instanceof Error ? err.message : "Could not update stack position."); + } finally { + setBusy(false); + } + }; + + return ( +
+
+ + Stack position +
+
+ Parent lane is where this lane sits in the stack (primary counts as the root). Base branch is the ref ADE uses for ahead/behind and for the rebase when you apply — leave it blank to use the parent lane's current branch. +
+ + {!primaryLane ? ( +
No primary lane found; stack changes may be limited.
+ ) : null} + + {lane.status.dirty ? ( +
+ + Commit or stash changes before changing stack position. +
+ ) : null} + + {lane.status.rebaseInProgress ? ( +
+ + Finish or abort the in-progress rebase before changing stack position. +
+ ) : null} + + Parent lane + + + Base branch (optional) + { + setBaseBranchInput(e.target.value); + setSuccess(null); + }} + placeholder={defaultBaseBranch ? `Default: ${defaultBaseBranch}` : "branch-name"} + /> + {defaultBaseBranch ? ( +
+ Selected parent is on {defaultBaseBranch} right now; that is used when the field above is empty. +
+ ) : null} + + {error ?
{error}
: null} + {success ?
{success}
: null} + +
+ +
+
+ ); +} diff --git a/apps/desktop/src/shared/types/lanes.ts b/apps/desktop/src/shared/types/lanes.ts index bbf72684d..f1d960e8e 100644 --- a/apps/desktop/src/shared/types/lanes.ts +++ b/apps/desktop/src/shared/types/lanes.ts @@ -252,6 +252,12 @@ export type RenameLaneArgs = { export type ReparentLaneArgs = { laneId: string; newParentLaneId: string; + /** + * Git branch name to stack onto (resolved in the project repo, prefers `origin/`). + * When omitted, uses the new parent lane's current branch, or for the primary lane the same + * upstream / origin resolution as graph reparent. + */ + stackBaseBranchRef?: string | null; }; export type ReparentLaneResult = { From bb55d92bce2814218000c8e4da41aeae8db3e8e3 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 14 May 2026 21:59:52 +0000 Subject: [PATCH 2/7] docs(ui): disclose git rebase when applying stack change in Manage Lane Co-authored-by: Arul Sharma --- .../src/renderer/components/lanes/ManageLaneDialog.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx b/apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx index 4aa7e375a..6e399828f 100644 --- a/apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx +++ b/apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx @@ -743,7 +743,12 @@ function StackPositionSection({ Stack position
- Parent lane is where this lane sits in the stack (primary counts as the root). Base branch is the ref ADE uses for ahead/behind and for the rebase when you apply — leave it blank to use the parent lane's current branch. + Parent lane is where this lane sits in the stack (primary counts as the root). Base branch is the ref ADE uses for ahead/behind. Leave it blank to use the parent lane's current branch. +
+ +
+ Runs git rebase. When you apply, ADE updates stack metadata then runs{" "} + git rebase in this lane's worktree onto the resolved base commit. If rebase fails, ADE aborts the rebase and restores the previous parent and base branch; the error appears below.
{!primaryLane ? ( From ee677bc4197e2c34a6bd04c68524235555ca88a2 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 14 May 2026 22:05:02 +0000 Subject: [PATCH 3/7] fix(ui): responsive lane modals and richer Manage Lane UX - LaneDialogShell: viewport-centered, max-height, scrollable body (all lane modals). - laneDialogTokens: hero and accent section styles for lane dialogs. - Manage Lane: shell description, per-section guidance, batch vs single callouts, adopt/archive/delete copy, stack accent panel and select styling. ADE-29 Co-authored-by: Arul Sharma --- .../components/lanes/LaneDialogShell.tsx | 66 ++++--- .../components/lanes/ManageLaneDialog.tsx | 170 +++++++++++++----- .../components/lanes/laneDialogTokens.ts | 6 + 3 files changed, 167 insertions(+), 75 deletions(-) diff --git a/apps/desktop/src/renderer/components/lanes/LaneDialogShell.tsx b/apps/desktop/src/renderer/components/lanes/LaneDialogShell.tsx index 7905f3ecf..0180db96b 100644 --- a/apps/desktop/src/renderer/components/lanes/LaneDialogShell.tsx +++ b/apps/desktop/src/renderer/components/lanes/LaneDialogShell.tsx @@ -24,47 +24,55 @@ export function LaneDialogShell({ onCloseAutoFocus?: (event: Event) => void; children: ReactNode; }) { + const width = widthClassName ?? "w-[min(720px,calc(100vw-1rem))]"; + return ( { if (!busy || next) onOpenChange(next); }}> - +
-
-
-
- - {Icon ? ( - - - - ) : null} - {title} - - {description ? ( - - {description} - - ) : ( - - {title} - - )} +
+
+
+
+
+ + {Icon ? ( + + + + ) : null} + {title} + + {description ? ( + + {description} + + ) : ( + + {title} + + )} +
+ + + +
+
+
+ {children}
- - -
- {children}
diff --git a/apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx b/apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx index 6e399828f..cff00217e 100644 --- a/apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx +++ b/apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx @@ -14,7 +14,8 @@ import { CheckCircle, X, Minus, - TreeStructure + TreeStructure, + Info } from "@phosphor-icons/react"; import { Button } from "../ui/Button"; import type { @@ -25,7 +26,14 @@ import type { LaneSummary } from "../../../shared/types"; import { LaneDialogShell } from "./LaneDialogShell"; -import { SECTION_CLASS_NAME, LABEL_CLASS_NAME, INPUT_CLASS_NAME } from "./laneDialogTokens"; +import { + SECTION_CLASS_NAME, + SECTION_ACCENT_CLASS_NAME, + SECTION_HERO_CLASS_NAME, + LABEL_CLASS_NAME, + INPUT_CLASS_NAME, + SELECT_CLASS_NAME +} from "./laneDialogTokens"; import { LaneColorPicker } from "./LaneColorPicker"; import { colorsInUse, laneColorName } from "./laneColorPalette"; @@ -43,6 +51,12 @@ const STEP_LABELS: Record = { database_cleanup: "Updating database" }; +const SHELL_DESCRIPTION_BATCH = + "Archive or delete the selected lanes. Stack position, color, and adopt are only available when you manage one lane at a time."; + +const SHELL_DESCRIPTION_SINGLE = + "Review lane details, then use the sections below. Stack and appearance only change this lane; archive and delete are separate flows with their own confirmations."; + export function ManageLaneDialog({ open, onOpenChange, @@ -178,22 +192,73 @@ export function ManageLaneDialog({ onOpenChange={onOpenChange} title={isBatch ? `Manage ${lanes.length} Lanes` : "Manage Lane"} icon={GitBranch} - widthClassName="w-[min(680px,calc(100vw-24px))]" + description={lanes.length === 0 ? undefined : allPrimary ? undefined : isBatch ? SHELL_DESCRIPTION_BATCH : SHELL_DESCRIPTION_SINGLE} + widthClassName="w-[calc(100vw-1rem)] max-w-[720px] sm:max-w-[min(720px,calc(100vw-2rem))]" busy={laneActionBusy} > {lanes.length === 0 ? (
Select a lane first.
) : allPrimary ? ( -
Primary lane cannot be archived or deleted.
+
+ Primary lane cannot be archived or deleted. Close this dialog or pick another lane. +
) : ( -
+
+ {!isBatch ? ( +
+
+ +
+
What each section does
+
    +
  • + Stack position + {" — "} + Change which lane is above you in the stack and optionally which branch name to stack onto. Applying updates ADE and runs{" "} + git rebase in this worktree (blocked while dirty or mid-rebase). +
  • +
  • + Appearance + {" — "} + Lane color for tabs and lists only; does not change branches or git state. +
  • +
  • + Archive + {" — "} + Hides the lane from ADE; worktree and branches stay on disk until you delete them elsewhere. +
  • +
  • + Delete + {" — "} + Destructive: remove worktree folder and optionally local and/or remote branches, with typed confirmation when risk is high. +
  • +
+
+
+
+ ) : ( +
+
Batch mode
+

+ You are managing {lanes.length} lanes together. Only archive and{" "} + delete apply to the whole selection. Open Manage Lane on one lane for stack position, color, or adopt. +

+
+ )} + {/* Lane info */} -
+
{isBatch ? "Selected lanes" : "Lane"} {isBatch ? ( -
+
{lanes.map((lane) => ( -
+
{lane.name} {lane.branchRef} @@ -205,13 +270,19 @@ export function ManageLaneDialog({
) : (
-
+
{lanes[0].name} - {lanes[0].laneType} + + {lanes[0].laneType} +
-
-
Branch: {lanes[0].branchRef}
-
Path: {lanes[0].worktreePath}
+
+
+ Branch: {lanes[0].branchRef} +
+
+ Path: {lanes[0].worktreePath} +
)} @@ -219,18 +290,19 @@ export function ManageLaneDialog({ {/* Adopt attached — single lane only */} {!isBatch && isAttached && ( -
-
-
-
- - Move to ADE-Managed Worktree +
+
+
+
+ + Move to ADE-managed worktree
-
- Move this attached worktree into .ade/worktrees for full lifecycle management. +
+ Copies registration into .ade/worktrees so ADE can manage lifecycle + (open, env, delete) the same way as other lanes. Does not rewrite git history.
-
@@ -253,30 +325,33 @@ export function ManageLaneDialog({ {/* Archive */}
-
-
+
+
- + Archive
-
+
{isBatch - ? `Hide ${lanes.length} lanes from ADE without deleting worktrees or branches.` - : "Hide from ADE without deleting worktree or branches."} + ? `Hides all ${lanes.length} lanes from ADE. Worktrees and branches stay on disk until you delete them with the section below or outside ADE.` + : "Hides this lane from ADE lists and the stack view. Worktrees and branches stay on disk until you delete them here or outside ADE."}
-
{/* Delete */} -
-
- - {hasAttached && !isBatch ? "Detach / Delete" : "Delete"} +
+
+ + {hasAttached && !isBatch ? "Detach / delete" : "Delete"}
+

+ ADE stops processes, terminals, and watchers on this lane, then removes the worktree folder and optionally deletes branches locally and/or on the remote you name. This cannot be undone from ADE. +

{hasAnyDirty && (
@@ -619,8 +694,11 @@ function AppearanceSection({ Appearance
-
- {currentName ? `Color: ${currentName}` : "Pick a color to identify this lane across the app."} +
+ Pick a color for this lane in tabs, the stack strip, and headers. This is visual only and does not rename branches or run git. +
+
+ {currentName ? `Current: ${currentName}` : "No color set yet."}
+
Stack position
-
- Parent lane is where this lane sits in the stack (primary counts as the root). Base branch is the ref ADE uses for ahead/behind. Leave it blank to use the parent lane's current branch. +
+ Parent lane is where this lane sits in the stack (primary is the root). Base branch is the ref ADE uses for ahead/behind. Leave it blank to use the parent lane's current branch.
-
- Runs git rebase. When you apply, ADE updates stack metadata then runs{" "} - git rebase in this lane's worktree onto the resolved base commit. If rebase fails, ADE aborts the rebase and restores the previous parent and base branch; the error appears below. +
+ Runs git rebase. When you apply, ADE updates stack metadata then runs{" "} + git rebase in this lane's worktree onto the resolved base commit. If rebase fails, ADE aborts the rebase and restores the previous parent and base branch; the error appears below.
{!primaryLane ? ( -
No primary lane found; stack changes may be limited.
+
No primary lane found; stack changes may be limited.
) : null} {lane.status.dirty ? ( -
+
Commit or stash changes before changing stack position.
) : null} {lane.status.rebaseInProgress ? ( -
+
Finish or abort the in-progress rebase before changing stack position.
) : null} - Parent lane + Parent lane