Skip to content

ADE-29: Reorganize lane stack from Manage Lane#311

Merged
arul28 merged 7 commits into
mainfrom
cursor/reorganize-lanes-manage-ade29-7d0c
May 15, 2026
Merged

ADE-29: Reorganize lane stack from Manage Lane#311
arul28 merged 7 commits into
mainfrom
cursor/reorganize-lanes-manage-ade29-7d0c

Conversation

@arul28
Copy link
Copy Markdown
Owner

@arul28 arul28 commented May 14, 2026

Summary

Implements ADE-29 (reorganize lane stack) via Manage Lane, with responsive lane modals and clearer Manage Lane UX (what each section does, rebase disclosure, ADE-aligned panels).

Demo / screen capture (honest status)

An earlier PR revision attached an empty screen recording — that was not useful. Root cause: the recorder ran against a static desktop with no Electron window in focus, so the capture was blank.

Follow-up work in the agent environment:

  • ADE Electron + Vite were started for real (ADE_DISABLE_HARDWARE_ACCEL=1, ADE_PROJECT_ROOT=/workspace, DISPLAY=:1).
  • Computer-use drove the app to the Lanes tab and captured static screenshots under /tmp/computer-use/ (e.g. lanes surface + command palette). Those are not a full reparent video.
  • A second full-session screen recording hit an ffmpeg stop timeout in this environment, so there is no reliable MP4 to attach here.

Practical takeaway: the best proof of stack moves is still on your Mac with a real repo (see steps below) — the cloud Linux lane is missing the “already seeded multi-lane + git remotes” comfort of a dev laptop, and GUI automation did not finish Create lane → Manage lane → reparent → stack dropdown end-to-end in one pass.

Steps to reproduce on your machine (recommended)

  1. Open ADE on a real project with primary on a normal branch.
  2. Create lane → child A from primary; Create lane → child B from A.
  3. Expand the stack control in the lanes header — confirm Primary → A → B.
  4. Right-click BManage laneStack position → parent PrimaryApply stack change.
  5. Expand the stack again — B should sit directly under Primary.

Parent lane vs base branch

  • Parent lane: ADE stack link (parent_lane_id; null when rooted on primary).
  • Base branch (base_ref): git branch name for ahead/behind and rebase target; optional override in Manage lane.

Code changes

  • ReparentLaneArgs.stackBaseBranchRef; laneService.reparent + no-op skip; tests.
  • Manage Lane: stack UI + guidance + LaneDialogShell layout (centered, max-h, scroll).
  • laneDialogTokens: SECTION_HERO_CLASS_NAME, SECTION_ACCENT_CLASS_NAME.
  • ade-cli: parse stackBaseBranchRef for lanes.reparent.

Tests

npx vitest run src/main/services/lanes/laneService.test.ts -t "laneService reparent"

Open in Web Open in Cursor 

Summary by CodeRabbit

Release Notes

  • New Features

    • Added lane reparenting capability with optional base branch override for more flexible stack reorganization
    • GitHub pull request snapshots now support filtering external closed PR history
    • Improved chat session history tracking and refresh behavior
  • Improvements

    • Enhanced project switching stability and binding management
    • Refined chat composer availability during project transitions with clearer user feedback

Greptile Summary

This PR implements ADE-29 lane stack reorganization via Manage Lane, adding stackBaseBranchRef support to laneService.reparent, a new StackPositionSection UI on both desktop and iOS, and a suite of supporting fixes (stale-closure in TopBar, recentOutputTail memory cleanup, chat steer/send retry hardening, project-switch binding reset, and external PR history lazy-loading on iOS).

  • Core reparent: laneService.reparent accepts an optional stackBaseBranchRef; a no-op early-return skips git rebase when parent and base are unchanged, and upsertBranchProfileForRow is properly rolled back on rebase failure. Frontend/iOS baseChanged/reparentBaseChanged logic correctly normalizes branch refs before comparison, fixing the previously reported always-enabled Apply button.
  • Desktop fixes: TopBar tab-close callback reads latest state from refs/getState() instead of stale closures; AgentChatPane adds robust send\u2192steer retry with stale-turn recovery; ptyService clears the 2 MB recentOutputTail buffer on session dispose.
  • iOS: LaneManageSheet mirrors the desktop stack-position UI; PrsRootScreen adds lazy external-history loading with dedup guards.

Confidence Score: 5/5

Safe to merge; all core reparent paths are correct and well-tested, and the one remaining finding is a cosmetic self-correcting race.

All core reparent paths — service, IPC, desktop UI, iOS sheet, CLI parsing — look correct and are backed by four new unit tests. Previously reported issues (always-enabled Apply button, recentOutputTail leak) are properly addressed. The only remaining finding is a brief overwrite of the iOS external PR history snapshot that self-corrects within the next poll cycle.

apps/ios/ADE/Views/PRs/PrsRootScreen.swift — concurrent snapshot-write race between reload and loadGitHubExternalHistoryIfNeeded

Important Files Changed

Filename Overview
apps/desktop/src/main/services/lanes/laneService.ts Adds stackBaseBranchRef to reparent: normalizes override, resolves rebase target, adds no-op early-return when parent+base unchanged, and correctly rolls back upsertBranchProfileForRow on rebase failure.
apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx Adds StackPositionSection with parent-lane select and optional base-branch override; baseChanged correctly normalizes via normalizeBranchRefForCompare to mirror backend.
apps/desktop/src/main/services/ipc/registerIpc.ts Adds getCachedProjectDetail with 10 s TTL, in-flight dedup, and .catch eviction of failed entries. Wires new lanes.reparent IPC handler for stackBaseBranchRef.
apps/desktop/src/main/services/pty/ptyService.ts Adds recentOutputTail live-buffer per PtyEntry, cleared on dispose to prevent 2 MB-per-session leak; exposes readTranscriptTail with KMP overlap dedup.
apps/ios/ADE/Views/Lanes/LaneManageSheet.swift Adds stack-position reparent UI mirroring desktop; reparentBaseChanged now compares normalized values against normalizedDefault, fixing the previously reported always-true bug.
apps/ios/ADE/Views/PRs/PrsRootScreen.swift Adds isLoadingExternalHistory + loadGitHubExternalHistoryIfNeeded for lazy external PR history; narrow race allows reload to briefly overwrite external history, self-correcting on next poll.
apps/desktop/src/renderer/components/app/TopBar.tsx Fixes stale-closure bug in tab-close handler by reading latest state via refs and useAppStore.getState().
apps/desktop/src/renderer/components/chat/AgentChatPane.tsx Adds projectTransitionBlocksChat placeholder, clearSessionView helper, robust send→steer retry logic, and sessionFound guard in loadHistory.
apps/desktop/src/main/services/lanes/laneService.test.ts Adds four focused reparent tests: primary-lane remote-tracking target, stackBaseBranchRef override, rebase-failure rollback with branch-profile restoration, and no-op skip.

Sequence Diagram

sequenceDiagram
    participant UI as ManageLaneDialog
    participant IPC as registerIpc
    participant SVC as laneService.reparent
    participant GIT as git
    participant DB as SQLite
    UI->>IPC: "lanes.reparent({laneId, newParentLaneId, stackBaseBranchRef?})"
    IPC->>SVC: reparent(args)
    SVC->>SVC: normalize stackBaseBranchRef
    alt parent+base unchanged
        SVC-->>IPC: no-op result
    else change detected
        SVC->>GIT: resolveBranchRebaseTarget
        GIT-->>SVC: targetSha
        SVC->>DB: UPDATE lanes + upsertBranchProfile
        SVC->>GIT: git rebase targetSha
        alt success
            SVC-->>IPC: ReparentLaneResult
        else failure
            SVC->>DB: ROLLBACK lanes + upsertBranchProfile
            SVC-->>IPC: throw error
        end
    end
    IPC-->>UI: result or error
Loading

Fix All in Claude Code

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
apps/ios/ADE/Views/PRs/PrsRootScreen.swift:1193-1218
**`reload` can briefly overwrite the external-history snapshot**

When `loadGitHubExternalHistoryIfNeeded` and `reload` run concurrently: (1) `loadGitHubExternalHistoryIfNeeded` sets `isLoadingExternalHistory = true` and starts its external-history fetch; (2) `reload` sees `suppressExternalClosed = true`, fires a non-external-closed fetch, and — when that resolves — may overwrite `githubSnapshot` with a result that lacks external closed PRs. `githubExternalHistoryLoaded` stays `true`, so the next poll cycle (~10 s) re-fetches with external history and restores the correct view, making the gap cosmetic and self-correcting.

Reviews (5): Last reviewed commit: "ship: iter 4 — exempt read-only chat fro..." | Re-trigger Greptile

@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 14, 2026

ADE-29

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 14, 2026

📝 Walkthrough

Walkthrough

This PR integrates lane reparenting with optional base-branch customization, GitHub snapshot support for closed external PRs, PTY transcript tail merging, agent chat event history snapshots with session-found tracking, project binding lifecycle management during transitions, subagent parent/child linkage tracking, and comprehensive UI/scroll-event handling improvements across CLI, desktop, and iOS platforms.

Changes

Unified feature cohort: lane reparenting, GitHub snapshots, chat history, and infra

Layer / File(s) Summary
Type definitions and shared contracts
apps/desktop/src/shared/types/chat.ts, apps/desktop/src/shared/types/lanes.ts, apps/ade-cli/src/tuiClient/types.ts
AgentChatEventHistorySnapshot introduced with sessionFound: boolean field; ReparentLaneArgs extended with optional stackBaseBranchRef; ChatSubagentSnapshot gains optional turnId field.
PTY transcript tail buffering and merging
apps/desktop/src/main/services/pty/ptyService.ts
PtyEntry adds recentOutputTail buffer capped at LIVE_TRANSCRIPT_TAIL_BUFFER_CHARS; new helpers (computeSuffixPrefixOverlap, mergeTranscriptTailWithLiveOutput) deduplicate live/persisted tail overlap; readTranscriptTail merges and returns raw or ANSI-stripped text; buffers released on PTY teardown.
PTY transcript integration in RPC and sync layers
apps/ade-cli/src/adeRpcServer.ts, apps/ade-cli/src/services/sync/syncHostService.ts, apps/desktop/src/main/services/ipc/registerIpc.ts, tests
RPC server, sync host, and IPC handlers switch from sessionService.readTranscriptTail(path) to ptyService.readTranscriptTail({ sessionId, maxBytes, raw, alignToLineBoundary }).
Lane reparenting with stackBaseBranchRef support
apps/desktop/src/main/services/lanes/laneService.ts, tests
Service accepts optional stackBaseBranchRef override; derives newBaseRef from override or parent branch_ref; returns early (no-op) if parent/base already match; uses resolveBranchRebaseTarget for override or resolveParentRebaseTarget otherwise; upserts branch profile on success/rollback.
Lane reparent CLI and remote command wiring
apps/ade-cli/src/cli.ts, apps/ade-cli/src/services/sync/syncRemoteCommandService.ts, tests
CLI parses --parent and --stack-base-branch flags, builds reparentArgs payload; remote command reads optional stackBaseBranchRef from payload and passes to service with updated signature.
GitHub snapshot external-closed PR history support
apps/ade-cli/src/cli.ts, apps/ade-cli/src/services/sync/syncRemoteCommandService.ts, apps/ios/ADE/Services/SyncService.swift, apps/ios/ADE/Views/PRs/PrsRootScreen.swift
CLI builds snapshotArgs with conditional includeExternalClosed flag; remote command passes flag to service; iOS PRsTabView tracks external-history loading state and conditionally fetches based on status filter.
Agent chat event history snapshot with sessionFound
apps/desktop/src/main/services/chat/agentChatService.ts, apps/desktop/src/main/services/ipc/registerIpc.ts, apps/desktop/src/preload/*, apps/ade-cli/src/tuiClient/types.ts
Service updates getChatEventHistory to return AgentChatEventHistorySnapshot with sessionFound flag; validates blank sessionId and invalid tool types; IPC handler returns snapshot shape; preload/renderer call sites use typed snapshot.
Project binding lifecycle during transitions
apps/desktop/src/preload/preload.ts, apps/desktop/src/renderer/state/appStore.ts, tests
Preload tracks projectBindingVersion, single-flight projectBindingRefreshPromise, and projectRuntimeTransitionDepth to guard chat actions during transitions; app store clears projectBinding: null when starting transitions (openRepo, switchToPath, switchRemoteProject, closeProject).
Subagent parent/child linkage via parentToolUseId
apps/desktop/src/main/services/chat/agentChatService.ts, apps/desktop/src/renderer/components/chat/chatExecutionSummary.ts, apps/ade-cli/src/tuiClient/app.tsx, tests
Service extends activeSubagents map values with parentToolUseId (string | null); tracks linkage through Codex spawn/collab/close; emits parentToolUseId in subagent events; renderer snapshots track turnId and coalesce spawn placeholders with later threads.
Claude subagent stopping and interruption
apps/desktop/src/main/services/chat/agentChatService.ts, tests
Service adds CLAUDE_STOP_TASK_TIMEOUT_MS (2 sec), binds Claude stopTask control, implements stopActiveClaudeSubagents with timeout-based Promise.race, emits subagent_result events with final summaries, integrates into interrupt/termination flows.
Codex shutdown and runtime workload detection
apps/desktop/src/main/services/chat/agentChatService.ts, apps/desktop/src/main/main.ts
Expands Codex runtime interruption to explicitly clean resources, cancel pending followups, clear approvals, emit system/status/done events; adds hasRuntimeActiveWorkload function; exports hasActiveWorkloads for session/runtime activity checking.
Chat UI gating during project transitions and scroll event handling
apps/desktop/src/renderer/components/chat/AgentChatPane.tsx, apps/desktop/src/renderer/components/chat/AgentChatMessageList.tsx, tests
Renderer adds projectTransitionBlocksChat flag to gate send/steer, disables composer during transitions; adds shouldAbsorbProgrammaticScrollEvent helper and programmaticScrollTargetRef to replace counter-based scroll tracking.
Chat send/steer error recovery and history hydration
apps/desktop/src/renderer/components/chat/AgentChatPane.tsx
Adds errorMessage() helper and "turn already active"/"no active turn to steer" detectors; implements steer-fallback for Codex control and main send paths; adds clearSessionView() to reset per-session UI state when history indicates sessionFound === false.
Lane dialog shell and action updates
apps/desktop/src/renderer/components/lanes/LaneDialogShell.tsx, apps/desktop/src/renderer/components/lanes/laneDialogTokens.ts, apps/ade-cli/src/tuiClient/components/RightPane.tsx
Dialog shell refactors with computed width/maxHeight, centers with top-1/2/-translate, updates overlay background, restructures header+body layout; lane dialog tokens add section accent/hero variants; RightPane updates key r action to /reparent.
Manage lane dialog stack-position reparenting UI
apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx, apps/desktop/src/renderer/components/lanes/LanesPage.tsx
ManageLaneDialog adds onStackReorganized callback; StackPositionSection refactored with branch-ref normalization, collectDescendantLaneIds for parent filtering, updated base-branch selection, canApply gating; passes stackBaseBranchRef as null when empty.
TopBar tab management with ref-based state sync
apps/desktop/src/renderer/components/app/TopBar.tsx, tests
TopBar maintains openProjectTabRootsRef and openRemoteProjectTabsRef synced via useEffect; uses refs in async handlers to avoid stale closures; handleRemoveTab uses ref-based root list; handleDragEnd awaits openProjectInNewWindow and switches/closes via latest store state.
iOS lane reparenting with stack-base override
apps/ios/ADE/Services/SyncService.swift, apps/ios/ADE/Views/Lanes/LaneManageSheet.swift, tests
iOS adds makeLanesReparentArgs to build reparent payload; updates reparentLane to require parent and accept base ref; LaneManageSheet adds baseBranchOverride state, derives primary/effective parent, detects parent/base changes, calls service with typed arguments.
Project browser and detail caching
apps/desktop/src/main/services/projects/projectBrowserService.ts, apps/desktop/src/main/services/ipc/registerIpc.ts
ProjectBrowserService updates resolveOpenableProjectRoot to map managed worktrees and walk upward probing .git; RegisterIpc adds TTL-based caching for projectGetDetail and projectListRecent with size caps/eviction.
AppShell binding update during project transitions
apps/desktop/src/renderer/components/app/AppShell.tsx
AppShell's onProjectBindingChanged now applies incoming binding even during projectTransition, ensuring store updates reflect remote binding changes during in-progress transitions.
CLI and TUI client reparent support
apps/ade-cli/src/tuiClient/commands.ts, apps/ade-cli/src/tuiClient/app.tsx, tests
CLI adds /reparent built-in command with right-pane placement; TUI app.tsx implements /reparent handler that lists targets when no args, resolves parent+base-ref when provided, dispatches lane reparent action.
TUI client subagent snapshot and session history handling
apps/ade-cli/src/tuiClient/app.tsx, tests
App.tsx adds findSubagentSnapshotByParent to resolve snapshots by parentToolUseId; updates subagent event processing with parent-based matching and prioritized summary selection; adds post-pass to mark running snapshots as stopped when turn ends; chat history refresh checks sessionFound.
CLI surface documentation
apps/ade-cli/README.md
README adds two example commands: ade lanes reparent lane-child --parent lane-parent --stack-base-branch main and ade prs github-snapshot --include-external-closed.
Comprehensive test coverage across layers
apps/ade-cli/src/cli.test.ts, apps/desktop/src/main/services/lanes/laneService.test.ts, apps/desktop/src/main/services/pty/ptyService.test.ts, apps/desktop/src/main/services/chat/agentChatService.test.ts, apps/desktop/src/preload/preload.test.ts, apps/desktop/src/renderer/components/app/TopBar.test.tsx, apps/desktop/src/renderer/components/chat/AgentChatPane.submit.test.tsx, apps/desktop/src/renderer/components/chat/chatExecutionSummary.test.ts, apps/ios/ADETests/ADETests.swift
Extensive test additions covering reparent stack-base/rollback/no-op, github-snapshot external-closed, ptyService transcript-tail merging, agentChatService Claude alias handling/hasActiveWorkloads/shutdown/subagent lifecycle, preload binding/chat-action transitions, TopBar tab management, drawer behavior, and parent-linkage coalescing.
Type and API signature consolidation
apps/ade-cli/src/tuiClient/types.ts, apps/desktop/src/preload/global.d.ts, apps/desktop/src/preload/preload.ts
ADE-CLI tuiClient types: ChatHistorySnapshot becomes alias of AgentChatEventHistorySnapshot; preload typings and API updated to use AgentChatEventHistorySnapshot for getEventHistory return type; shared lanes types extended with stackBaseBranchRef documentation.

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • arul28/ADE#112: Overlap in agentChatService.ts Claude subagent stopping (emitting subagent_result events and stopTask support) and interrupt/termination flows.
  • arul28/ADE#289: Related wiring of includeExternalClosed flag through CLI, remote commands, sync, and iOS PR services.
  • arul28/ADE#175: Overlapping agent-chat event history surface (AgentChatEventHistorySnapshot, getChatEventHistory IPC, preload/renderer call sites).

Suggested labels

desktop, ios

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/reorganize-lanes-manage-ade29-7d0c

@vercel
Copy link
Copy Markdown

vercel Bot commented May 14, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
ade Ignored Ignored Preview May 15, 2026 10:09am

@arul28 arul28 marked this pull request as ready for review May 15, 2026 08:22
@capy-ai
Copy link
Copy Markdown

capy-ai Bot commented May 15, 2026

Capy auto-review is paused for this organization because the monthly auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews.

cursoragent and others added 4 commits May 15, 2026 04:23
…anch

- 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 <arul28@users.noreply.github.com>
Co-authored-by: Arul Sharma <arul28@users.noreply.github.com>
- 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 <arul28@users.noreply.github.com>
Picks up uncommitted lane work across desktop, ade-cli, ios, and docs:
ManageLaneDialog refactor (singleLane), chat pane turn-active error
handling, iOS sync command helpers (lanes.reparent), sync host /
remote command updates, project transition state, PR detail/route
state cleanup, and broad doc refreshes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx Outdated
Comment thread apps/desktop/src/main/services/pty/ptyService.ts
@arul28 arul28 force-pushed the cursor/reorganize-lanes-manage-ade29-7d0c branch from e93f67c to 56a8469 Compare May 15, 2026 08:32
@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 15, 2026

@copilot review but do not make fixes

Copy link
Copy Markdown
Contributor

Copilot AI commented May 15, 2026

@copilot review but do not make fixes

Reviewed only (no fixes) on commit e93f67c.

Findings:

  • ManageLaneDialog.tsx issue is valid: baseChanged compares raw input (baseOverrideTrim) vs lane.baseRef without branch normalization, so inputs like refs/heads/main can show a false-positive change and success toast on backend no-op.
  • ptyService.ts memory-leak claim appears not valid in current code: both closeEntry and dispose call ptys.delete(ptyId) (around lines 2065 and 3340), so disposed entries are removed from the map rather than retained indefinitely.

No code changes were made.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
apps/desktop/src/main/services/lanes/laneService.ts (2)

3434-3443: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Wrap the rollback-path DB updates in a transaction.

Similar to the success path, the rollback restores the lane's parent/base (line 3434) and branch profile (line 3438) in two separate DB operations. If the profile upsert fails after the lane restore succeeds, the rollback itself will be incomplete, leaving inconsistent state after a failed rebase.

🔒 Proposed fix to add transaction
+  db.run("begin");
+  try {
     db.run(
       "update lanes set parent_lane_id = ?, base_ref = ? where id = ? and project_id = ?",
       [previousParentLaneId, previousBaseRef, lane.id, projectId]
     );
     upsertBranchProfileForRow(lane, {
       branchRef: lane.branch_ref,
       baseRef: previousBaseRef,
       parentLaneId: previousParentLaneId,
     });
+    db.run("commit");
+  } catch (err) {
+    try { db.run("rollback"); } catch { /* keep the original rebase error */ }
+    // Fall through to throw the original rebase error below
+  }
   invalidateLaneListCache();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/main/services/lanes/laneService.ts` around lines 3434 -
3443, The rollback path currently performs two separate DB ops (db.run("update
lanes ...", [...]) and upsertBranchProfileForRow(...)) then calls
invalidateLaneListCache(), which can leave the DB inconsistent if the second op
fails; wrap the rollback updates in a single transaction so both the lane
restore and the branch-profile upsert succeed or both rollback: begin a
transaction, perform db.run(...) for restoring lane (use lane.id and projectId),
call upsertBranchProfileForRow(lane, { branchRef: lane.branch_ref, baseRef:
previousBaseRef, parentLaneId: previousParentLaneId }) inside the same
transaction, on success commit and then call invalidateLaneListCache(), and on
any error rollback and rethrow/log so the rollback is atomic.

3415-3424: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Wrap the success-path DB updates in a transaction.

The lane parent/base update (line 3415) and branch profile upsert (line 3419) are two separate DB operations. If the profile upsert fails after the lane update succeeds, the lane and branch profile tables will be inconsistent. Other methods in this file use transactions for multi-step updates (e.g., switchBranch at lines 2743-2798).

🔒 Proposed fix to add transaction
+  db.run("begin");
+  try {
     db.run(
       "update lanes set parent_lane_id = ?, base_ref = ? where id = ? and project_id = ?",
       [persistedParentLaneId, newBaseRef, lane.id, projectId]
     );
     upsertBranchProfileForRow(lane, {
       branchRef: lane.branch_ref,
       baseRef: newBaseRef,
       parentLaneId: persistedParentLaneId,
     });
+    db.run("commit");
+  } catch (err) {
+    try { db.run("rollback"); } catch { /* swallow rollback failures */ }
+    throw err;
+  }
   invalidateLaneListCache();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/main/services/lanes/laneService.ts` around lines 3415 -
3424, The update to the lanes table (db.run(...)) and the subsequent
upsertBranchProfileForRow(...) must be executed inside a single DB transaction
so they either both succeed or both roll back; wrap the sequence (start
transaction, perform db.run update, call upsertBranchProfileForRow, call
invalidateLaneListCache, commit) and ensure any thrown error causes a rollback
and the error to be propagated; use the same DB connection used for db.run and
upsertBranchProfileForRow, and handle errors to call rollback before rethrowing.
apps/ade-cli/src/cli.ts (1)

2486-2512: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Enforce required parent for lanes reparent.

newParentLaneId can still be omitted, which defers validation to downstream services and yields less actionable CLI errors. Validate it here to match the command contract.

💡 Suggested fix
     const reparentArgs: JsonObject = {
       laneId,
-      newParentLaneId:
-        readValue(args, [
-          "--parent",
-          "--parent-lane",
-          "--parent-lane-id",
-        ]) ?? firstPositional(args),
+      newParentLaneId: requireValue(
+        readValue(args, [
+          "--parent",
+          "--parent-lane",
+          "--parent-lane-id",
+        ]) ?? firstPositional(args),
+        "newParentLaneId",
+      ),
     };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ade-cli/src/cli.ts` around lines 2486 - 2512, The reparent flow allows
newParentLaneId to be omitted (it’s set from readValue(...) ??
firstPositional(...)) which defers validation; update the CLI handler so that
after building reparentArgs you validate that reparentArgs.newParentLaneId is
present and, if not, fail fast with a clear CLI error (e.g., throw or return a
user-facing error/exit) before creating the execute step; modify the logic
around reparentArgs, readValue, firstPositional, and before the
actionStep/collectGenericObjectArgs call to enforce the required parent and
surface a concise message like "missing required --parent/--parent-lane-id"
instead of letting downstream services handle it.
apps/desktop/src/renderer/components/chat/AgentChatPane.tsx (1)

5199-5210: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clear the optimistic bubble before falling back to steer().

This branch keeps the optimistic user_message in state, then sends a steer. Steer-backed events carry steerId, and isMatchingOptimisticUserMessage() explicitly refuses to reconcile those, so the duplicate bubble can stick around until remount.

Proposed fix
         } catch (sendError) {
           // Race condition: the turn may have started between our state check
           // and the backend call. If so, automatically fall back to steer
           // instead of surfacing a confusing error to the user.
           if (isTurnAlreadyActiveError(sendError)) {
+            setOptimisticOutgoingMessageSynced(null);
             try {
               await steerMessage();
             } catch (steerError) {
               if (!isNoActiveTurnToSteerError(steerError) || !retryOnStaleSteer) throw steerError;
               setTurnActiveBySession((prev) => ({ ...prev, [sessionId]: false }));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/renderer/components/chat/AgentChatPane.tsx` around lines
5199 - 5210, The code falls back to steerMessage() when
isTurnAlreadyActiveError(sendError) occurs but leaves the optimistic
user_message bubble in state so steer events (which carry steerId) won't
reconcile it (see isMatchingOptimisticUserMessage()); before calling
steerMessage() clear the optimistic user_message/optimistic bubble from state
(invoke the existing state setter that removes the optimistic user message) so
the steer-backed events can render correctly, then proceed with steerMessage()
and the existing error handling (references: isTurnAlreadyActiveError,
steerMessage, isNoActiveTurnToSteerError, setTurnActiveBySession,
sendMessageOrSteerIfBusy, isMatchingOptimisticUserMessage).
apps/desktop/src/main/services/chat/agentChatService.ts (1)

12366-12404: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Stale placeholder leak on spawn_agent failure.

When spawnStatus === "failed" and receiverIds is non-empty (and doesn't include itemId), the placeholder entry keyed by itemId is read via the ?? runtime.activeSubagents.get(itemId) fallback but never removed from runtime.activeSubagents. The success branch right below explicitly handles this with runtime.activeSubagents.delete(itemId); the failure branch should do the same so the placeholder doesn't survive a failed spawn.

🔧 Proposed fix
         if (spawnStatus === "failed") {
+          const placeholder = runtime.activeSubagents.get(itemId);
           for (const taskId of receiverIds.length ? receiverIds : [itemId]) {
             const existing = runtime.activeSubagents.get(taskId) ?? runtime.activeSubagents.get(itemId);
             runtime.activeSubagents.delete(taskId);
             emitChatEvent(managed, {
               type: "subagent_result",
               taskId,
               parentToolUseId: existing?.parentToolUseId ?? itemId,
               status: "failed",
               summary: String(item.error ?? item.result ?? "Agent spawn failed"),
               turnId,
             });
           }
+          if (placeholder && receiverIds.length && !receiverIds.includes(itemId)) {
+            runtime.activeSubagents.delete(itemId);
+          }
         } else {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/main/services/chat/agentChatService.ts` around lines 12366 -
12404, The failure branch for spawn_agent (when
mapCodexCollabAgentStatus(item.status) === "failed") leaks the placeholder
stored under runtime.activeSubagents keyed by itemId because it only deletes per
taskId and uses runtime.activeSubagents.get(itemId) as a fallback; ensure the
placeholder is removed: before emitting subagent_result events, delete the
itemId key from runtime.activeSubagents (runtime.activeSubagents.delete(itemId))
so the placeholder doesn't persist when receiverIds is non-empty and spawn
fails; adjust the logic in the failed branch that iterates receiverIds/itemId
accordingly (functions/vars: spawn_agent, mapCodexCollabAgentStatus,
runtime.activeSubagents, itemId, receiverIds, emitChatEvent).
🧹 Nitpick comments (4)
apps/desktop/src/main/services/lanes/laneService.ts (1)

3384-3395: 💤 Low value

Consider adding a comment to explain the no-op optimization.

The early return when the parent and base are already set to the target values is a good optimization, but would benefit from a brief comment explaining that we skip the rebase entirely when nothing would change.

💡 Suggested comment
+  // No-op fast path: if the requested parent and base already match the current
+  // values, skip the rebase and return immediately with unchanged HEAD.
   if (lane.parent_lane_id === persistedParentLaneId && newBaseRef === previousBaseRef) {
     const headSha = await getHeadSha(lane.worktree_path);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/main/services/lanes/laneService.ts` around lines 3384 -
3395, Add a brief inline comment above the early-return block that explains this
is a no-op optimization: when lane.parent_lane_id equals persistedParentLaneId
and newBaseRef equals previousBaseRef we skip doing any rebase/parent-change
work and simply return the current head SHA; place the comment near the block
referencing lane.parent_lane_id, persistedParentLaneId, newBaseRef,
previousBaseRef and the getHeadSha call so future readers understand why we
return unchanged preHeadSha/postHeadSha.
apps/ios/ADE/Views/PRs/PrsRootScreen.swift (1)

397-400: ⚡ Quick win

Consider task deduplication for rapid filter changes.

The onChange handler creates a new Task on each filter change without storing or cancelling previous tasks. If the user rapidly toggles between filter options, multiple concurrent tasks may spawn and execute loadGitHubExternalHistoryIfNeeded before the flag check can prevent duplicates.

While the githubExternalHistoryLoaded guard provides some protection, consider storing the task (e.g., @State private var externalHistoryLoadTask: Task<Void, Never>?) and cancelling it before creating a new one, or using a dedicated actor/serial queue to serialize external history loads.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ios/ADE/Views/PRs/PrsRootScreen.swift` around lines 397 - 400, The
onChange handler spawns an untracked Task for every githubStatusFilterRawValue
change which can lead to concurrent loadGitHubExternalHistoryIfNeeded calls; add
a cancellable Task holder (e.g., `@State` private var externalHistoryLoadTask:
Task<Void, Never>?) and before creating a new Task cancel the previous one,
assign the new Task to externalHistoryLoadTask, and ensure you clear
externalHistoryLoadTask when the Task completes or is cancelled; alternatively
serialize calls via an actor or dedicated queue in the code path that runs
loadGitHubExternalHistoryIfNeeded and reference githubStatusFilterRawValue and
selectedGitHubStatusFilter to keep existing guard logic.
apps/desktop/src/main/services/pty/ptyService.ts (1)

99-99: ⚡ Quick win

Hot-path string allocation in appendRecentOutput.

pty.onData fires on every raw PTY chunk (before the PTY_DATA_BATCH_INTERVAL_MS coalescing) and each call does tailString(${entry.recentOutputTail}${data}, 2_000_000). Once the buffer is full this allocates a ~2 M-char string (≈ 4 MB UTF-16) and slices it on every chunk, so heavy producers (build/install/log tailing) pay a sizeable per-chunk copy. Across many concurrent terminals the steady-state resident cost of the buffer alone is also substantial.

If the hydration window only needs to cover the first TUI frame before the WriteStream flushes, a much smaller cap (e.g. 256–512 KB) would be sufficient and would bound both the per-call copy and the per-session memory. If you want to keep the larger window, consider a chunk-list buffer that only flattens on read:

♻️ Sketch — chunk list, flatten on read
-  recentOutputTail: string;
+  recentOutputChunks: string[];
+  recentOutputChars: number;
-  const appendRecentOutput = (entry: PtyEntry, data: string) => {
-    if (!data) return;
-    entry.recentOutputTail = tailString(`${entry.recentOutputTail}${data}`, LIVE_TRANSCRIPT_TAIL_BUFFER_CHARS);
-  };
+  const appendRecentOutput = (entry: PtyEntry, data: string) => {
+    if (!data) return;
+    entry.recentOutputChunks.push(data);
+    entry.recentOutputChars += data.length;
+    while (
+      entry.recentOutputChunks.length > 1 &&
+      entry.recentOutputChars - (entry.recentOutputChunks[0]?.length ?? 0) >= LIVE_TRANSCRIPT_TAIL_BUFFER_CHARS
+    ) {
+      entry.recentOutputChars -= entry.recentOutputChunks.shift()!.length;
+    }
+  };
+
+  const readRecentOutputTail = (entry: PtyEntry): string =>
+    tailString(entry.recentOutputChunks.join(""), LIVE_TRANSCRIPT_TAIL_BUFFER_CHARS);

Then in readTranscriptTail:

-  const live = liveEntryBySessionId(sessionId)?.[1].recentOutputTail ?? "";
+  const liveEntry = liveEntryBySessionId(sessionId)?.[1] ?? null;
+  const live = liveEntry ? readRecentOutputTail(liveEntry) : "";

Also applies to: 2093-2096

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/main/services/pty/ptyService.ts` at line 99, The
LIVE_TRANSCRIPT_TAIL_BUFFER_CHARS constant is too large and causes heavy
per-chunk string allocations in appendRecentOutput (called from pty.onData);
either reduce the cap (e.g., set LIVE_TRANSCRIPT_TAIL_BUFFER_CHARS to
256*1024–512*1024) to bound per-call copies and per-session memory, or replace
the simple string-slice approach in appendRecentOutput and readTranscriptTail
with a chunk-list buffer that appends incoming chunks to an array and only
flattens (joins) to a single string when readTranscriptTail is called, ensuring
you update appendRecentOutput, the buffer storage used by
entry.recentOutputTail, and readTranscriptTail to manage chunk eviction to keep
the total buffered size under the new cap.
apps/desktop/src/renderer/components/chat/AgentChatPane.submit.test.tsx (1)

384-384: ⚡ Quick win

Module-level test state reduces test isolation.

The iosEventListener variable is declared at module scope and mutated by individual tests. While beforeEach resets it to null, this pattern can cause issues:

  1. Test isolation: If a test fails before cleanup, subsequent tests may see stale state.
  2. Future-proofing: Parallel test execution would create race conditions.
  3. Clarity: Tests directly inspecting module variables are harder to understand than explicit returns from setup functions.

Consider refactoring to return the listener reference from installAdeMocks (similar to emitChatEvent, emitSessionChanged), or using a test-scoped closure.

♻️ Proposed refactor
-let iosEventListener: ((event: { type: string; chatSessionId?: string; laneId?: string; mode?: string }) => void) | null = null;
-
 beforeEach(() => {
   invalidateAiDiscoveryCache();
   window.localStorage.clear();
   window.sessionStorage.clear();
-  iosEventListener = null;
   Object.defineProperty(window.navigator, "platform", {
     configurable: true,
     value: "MacIntel",
   });
   resetChatTestStore();
 });

Inside installAdeMocks:

+  let iosEventListener: ((event: { type: string; chatSessionId?: string; laneId?: string; mode?: string }) => void) | null = null;
+
   // ... existing code ...
   
   return {
     send,
     steer,
     // ... other mocks ...
+    getIosEventListener: () => iosEventListener,
     emitChatEvent: (event: AgentChatEventEnvelope) => {
       for (const listener of chatEventListeners) {
         listener(event);
       }
     },

In tests:

- await waitFor(() => {
-   expect(typeof iosEventListener).toBe("function");
- });
- act(() => {
-   iosEventListener?.({
+ const mocks = installAdeMocks({ sessions: [session] });
+ await waitFor(() => {
+   expect(typeof mocks.getIosEventListener()).toBe("function");
+ });
+ act(() => {
+   mocks.getIosEventListener()?.({

Also applies to: 390-390, 324-327

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/renderer/components/chat/AgentChatPane.submit.test.tsx` at
line 384, The module-level mutable variable iosEventListener breaks test
isolation; instead refactor installAdeMocks to return the listener (or an
accessor) so tests receive a test-scoped reference rather than mutating
iosEventListener directly; update tests that currently read/write
iosEventListener to call installAdeMocks() and use its returned listener
(similar to existing emitChatEvent and emitSessionChanged), remove the
module-scope iosEventListener declaration, and ensure beforeEach/afterEach use
the returned reference for cleanup.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/ade-cli/src/tuiClient/app.tsx`:
- Around line 620-622: The current summary selection uses summaryFromEvent =
[event.summary, event.finalSummary, event.text, event.description].find((value):
value is string => typeof value === "string") which treats empty strings as
valid and blocks later non-empty values; update the predicate used by find to
only accept non-empty strings (e.g., typeof value === "string" && value.trim()
!== "") so summaryFromEvent will skip blank summaries and allow populated
finalSummary/text/description to be chosen before falling back to
existing?.summary or "".
- Around line 3156-3181: When history.sessionFound === false we clear session UI
state but don't reset the interrupted flag, which can leave a stale interrupted
state shown; update the branch that handles history.sessionFound === false (the
if branch using selectedSessionFound, setCurrentGoal, setContextPercent,
setTokenSummary, setStatusLineStats, eventCountRef, loadedSessionIdRef,
nextEvents) to also call setInterrupted(false) so interrupted is cleared
whenever the selected session is missing; keep the existing later logic that
sets setInterrupted(false) for the active selected session but ensure this
immediate branch explicitly resets interrupted to false.
- Around line 714-719: The resolveLaneReference function currently falls back to
the first partial match which can silently pick the wrong lane; update it so
after exact id/name matches are attempted, collect all lanes where
lane.name.toLowerCase().includes(normalized) into an array and only return that
lane if the array length is exactly 1, otherwise return null (i.e., reject
ambiguous partial matches); keep the initial exact-match checks (id/name)
unchanged and reference resolveLaneReference and LaneSummary when making this
change.

In `@apps/desktop/src/main/services/ipc/registerIpc.ts`:
- Around line 3767-3784: The cache currently sets expiresAtMs when the request
starts so slow in-flight getProjectDetail requests can expire and be re-fetched;
change getCachedProjectDetail to keep in-flight promises cached by inserting the
entry with expiresAtMs = Infinity (or a sentinel) when creating promise, then on
promise.then set expiresAtMs = Date.now() + PROJECT_DETAIL_CACHE_TTL_MS and
update the cached.promise if still the same, and on promise.catch keep the
existing check that deletes the cache only if
projectDetailCache.get(rootPath)?.promise === promise; reference
getCachedProjectDetail, projectDetailCache, getProjectDetail,
PROJECT_DETAIL_CACHE_TTL_MS and the existing promise.catch to locate where to
apply these changes.

In `@apps/desktop/src/main/services/pty/ptyService.ts`:
- Around line 3224-3247: Add unit tests in ptyService.test.ts covering two
missing cases: (1) disk-only path where liveEntryBySessionId returns undefined
or no live entry exists — mock sessionService.get to return a session and mock
sessionService.readTranscriptTail to return a non-empty disk tail, call
readTranscriptTail and assert the returned merged content equals the disk tail
(respecting maxBytes/clamping if relevant); (2) ANSI stripping when raw is
false/undefined — mock a disk tail (or merged result via
mergeTranscriptTailWithLiveOutput) containing ANSI sequences, ensure
liveEntryBySessionId is set or unset appropriately, call readTranscriptTail
without raw or with raw: false and assert the result has ANSI removed (i.e.,
compare to stripAnsi(diskTail)). Use the same helpers/mocks in existing tests
and reference readTranscriptTail, sessionService.readTranscriptTail,
liveEntryBySessionId, mergeTranscriptTailWithLiveOutput, and stripAnsi to locate
the code under test.

In `@apps/desktop/src/preload/preload.ts`:
- Around line 2592-2601: The post-action cleanup for openRepo (and switchToPath)
is calling rememberProjectBinding(null) because clearAround's cleanup runs both
before and after the action; remove or move that nulling so it only runs before
the action. Specifically, update the clearAround calls that wrap
runProjectRuntimeTransition(() => ipcRenderer.invoke(IPC.projectOpenRepo, ...))
and the analogous switchToPath call so the pre-action callback still calls
rememberProjectBinding(null) and clearProjectScopedReadCaches(), but the
post-action callback does NOT call rememberProjectBinding(null) (leave other
safe post-cleanup tasks intact), preventing clobbering of a freshly updated
binding and restoring runtime routing/event pumping.

In `@apps/desktop/src/renderer/components/chat/chatExecutionSummary.ts`:
- Around line 84-90: The code is collapsing merged subagent snapshots by
overwriting snapshot.taskId with the latest event.taskId (via adoptEventTaskId),
which loses merged identity used by deriveSubagentTimeline; change the
assignment in snapshots.set so that when a parent-based match exists you
preserve the stable merged identity (use existing?.taskId or parentMatch's
taskId) instead of adopting event.taskId, and apply the same fix in the other
similar block (the later snapshot update around the second occurrence). Locate
findSnapshotByParent, snapshots, key, event.taskId, adoptEventTaskId and ensure
snapshot.taskId remains the merged id (existing?.taskId ?? event.taskId) rather
than replacing it when parentMatch is present.

In `@apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx`:
- Around line 791-800: The logic for baseChanged ignores the user clearing the
base field because it only considers non-empty input; update the computation so
an empty trimmed baseBranchInput counts as a change when the lane currently has
a base override. For example, change baseChanged (used by canApply) to true when
(1) baseOverrideTrim is non-empty and differs from lane.baseRef, or (2)
baseOverrideTrim is empty but lane.baseRef is set (i.e. clearing will remove the
override). Refer to baseOverrideTrim, baseBranchInput, baseChanged, canApply,
and lane.baseRef when making this change.

In `@apps/ios/ADE/Views/Lanes/LaneManageSheet.swift`:
- Around line 91-100: reparentBaseChanged currently ignores empty
trimmedBaseOverride so clearing the override doesn't count as a change; update
its logic to simply compare trimmedBaseOverride to snapshot.lane.baseRef (i.e.,
change reparentBaseChanged to "trimmedBaseOverride != snapshot.lane.baseRef") so
clearing the field is treated as a change and canApplyReparent will allow
resetting the base; apply the same fix to the duplicated logic mentioned (lines
270-277) and keep references to selectedParentLaneId and reparentParentChanged
unchanged.

In `@apps/ios/ADE/Views/PRs/PrsRootScreen.swift`:
- Around line 397-400: The onChange handler for githubStatusFilterRawValue and
the reload task both check githubExternalHistoryLoaded and can concurrently call
fetchGitHubPullRequestSnapshot(includeExternalClosed: true); to fix, add a
synchronous guard flag (e.g., isLoadingExternalHistory) that is set true before
awaiting any async fetch and cleared on completion, and update
loadGitHubExternalHistoryIfNeeded, reload (task with id:
prsProjectionReloadKey), and any other paths (e.g., the blocks around lines
referencing githubExternalHistoryLoaded and fetchGitHubPullRequestSnapshot) to
check/set this flag atomically so only one fetchGitHubPullRequestSnapshot runs
at a time; alternatively consolidate external history loading into a single
deduplicated function that uses the same isLoadingExternalHistory and
githubExternalHistoryLoaded flags.

---

Outside diff comments:
In `@apps/ade-cli/src/cli.ts`:
- Around line 2486-2512: The reparent flow allows newParentLaneId to be omitted
(it’s set from readValue(...) ?? firstPositional(...)) which defers validation;
update the CLI handler so that after building reparentArgs you validate that
reparentArgs.newParentLaneId is present and, if not, fail fast with a clear CLI
error (e.g., throw or return a user-facing error/exit) before creating the
execute step; modify the logic around reparentArgs, readValue, firstPositional,
and before the actionStep/collectGenericObjectArgs call to enforce the required
parent and surface a concise message like "missing required
--parent/--parent-lane-id" instead of letting downstream services handle it.

In `@apps/desktop/src/main/services/chat/agentChatService.ts`:
- Around line 12366-12404: The failure branch for spawn_agent (when
mapCodexCollabAgentStatus(item.status) === "failed") leaks the placeholder
stored under runtime.activeSubagents keyed by itemId because it only deletes per
taskId and uses runtime.activeSubagents.get(itemId) as a fallback; ensure the
placeholder is removed: before emitting subagent_result events, delete the
itemId key from runtime.activeSubagents (runtime.activeSubagents.delete(itemId))
so the placeholder doesn't persist when receiverIds is non-empty and spawn
fails; adjust the logic in the failed branch that iterates receiverIds/itemId
accordingly (functions/vars: spawn_agent, mapCodexCollabAgentStatus,
runtime.activeSubagents, itemId, receiverIds, emitChatEvent).

In `@apps/desktop/src/main/services/lanes/laneService.ts`:
- Around line 3434-3443: The rollback path currently performs two separate DB
ops (db.run("update lanes ...", [...]) and upsertBranchProfileForRow(...)) then
calls invalidateLaneListCache(), which can leave the DB inconsistent if the
second op fails; wrap the rollback updates in a single transaction so both the
lane restore and the branch-profile upsert succeed or both rollback: begin a
transaction, perform db.run(...) for restoring lane (use lane.id and projectId),
call upsertBranchProfileForRow(lane, { branchRef: lane.branch_ref, baseRef:
previousBaseRef, parentLaneId: previousParentLaneId }) inside the same
transaction, on success commit and then call invalidateLaneListCache(), and on
any error rollback and rethrow/log so the rollback is atomic.
- Around line 3415-3424: The update to the lanes table (db.run(...)) and the
subsequent upsertBranchProfileForRow(...) must be executed inside a single DB
transaction so they either both succeed or both roll back; wrap the sequence
(start transaction, perform db.run update, call upsertBranchProfileForRow, call
invalidateLaneListCache, commit) and ensure any thrown error causes a rollback
and the error to be propagated; use the same DB connection used for db.run and
upsertBranchProfileForRow, and handle errors to call rollback before rethrowing.

In `@apps/desktop/src/renderer/components/chat/AgentChatPane.tsx`:
- Around line 5199-5210: The code falls back to steerMessage() when
isTurnAlreadyActiveError(sendError) occurs but leaves the optimistic
user_message bubble in state so steer events (which carry steerId) won't
reconcile it (see isMatchingOptimisticUserMessage()); before calling
steerMessage() clear the optimistic user_message/optimistic bubble from state
(invoke the existing state setter that removes the optimistic user message) so
the steer-backed events can render correctly, then proceed with steerMessage()
and the existing error handling (references: isTurnAlreadyActiveError,
steerMessage, isNoActiveTurnToSteerError, setTurnActiveBySession,
sendMessageOrSteerIfBusy, isMatchingOptimisticUserMessage).

---

Nitpick comments:
In `@apps/desktop/src/main/services/lanes/laneService.ts`:
- Around line 3384-3395: Add a brief inline comment above the early-return block
that explains this is a no-op optimization: when lane.parent_lane_id equals
persistedParentLaneId and newBaseRef equals previousBaseRef we skip doing any
rebase/parent-change work and simply return the current head SHA; place the
comment near the block referencing lane.parent_lane_id, persistedParentLaneId,
newBaseRef, previousBaseRef and the getHeadSha call so future readers understand
why we return unchanged preHeadSha/postHeadSha.

In `@apps/desktop/src/main/services/pty/ptyService.ts`:
- Line 99: The LIVE_TRANSCRIPT_TAIL_BUFFER_CHARS constant is too large and
causes heavy per-chunk string allocations in appendRecentOutput (called from
pty.onData); either reduce the cap (e.g., set LIVE_TRANSCRIPT_TAIL_BUFFER_CHARS
to 256*1024–512*1024) to bound per-call copies and per-session memory, or
replace the simple string-slice approach in appendRecentOutput and
readTranscriptTail with a chunk-list buffer that appends incoming chunks to an
array and only flattens (joins) to a single string when readTranscriptTail is
called, ensuring you update appendRecentOutput, the buffer storage used by
entry.recentOutputTail, and readTranscriptTail to manage chunk eviction to keep
the total buffered size under the new cap.

In `@apps/desktop/src/renderer/components/chat/AgentChatPane.submit.test.tsx`:
- Line 384: The module-level mutable variable iosEventListener breaks test
isolation; instead refactor installAdeMocks to return the listener (or an
accessor) so tests receive a test-scoped reference rather than mutating
iosEventListener directly; update tests that currently read/write
iosEventListener to call installAdeMocks() and use its returned listener
(similar to existing emitChatEvent and emitSessionChanged), remove the
module-scope iosEventListener declaration, and ensure beforeEach/afterEach use
the returned reference for cleanup.

In `@apps/ios/ADE/Views/PRs/PrsRootScreen.swift`:
- Around line 397-400: The onChange handler spawns an untracked Task for every
githubStatusFilterRawValue change which can lead to concurrent
loadGitHubExternalHistoryIfNeeded calls; add a cancellable Task holder (e.g.,
`@State` private var externalHistoryLoadTask: Task<Void, Never>?) and before
creating a new Task cancel the previous one, assign the new Task to
externalHistoryLoadTask, and ensure you clear externalHistoryLoadTask when the
Task completes or is cancelled; alternatively serialize calls via an actor or
dedicated queue in the code path that runs loadGitHubExternalHistoryIfNeeded and
reference githubStatusFilterRawValue and selectedGitHubStatusFilter to keep
existing guard logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f94d4f31-db36-47e3-bee8-dce4dcf1393c

📥 Commits

Reviewing files that changed from the base of the PR and between 690554b and e93f67c.

⛔ Files ignored due to path filters (12)
  • docs/ARCHITECTURE.md is excluded by !docs/**
  • docs/codex migration plan.md is excluded by !docs/**
  • docs/features/chat/README.md is excluded by !docs/**
  • docs/features/chat/composer-and-ui.md is excluded by !docs/**
  • docs/features/lanes/README.md is excluded by !docs/**
  • docs/features/lanes/stacking.md is excluded by !docs/**
  • docs/features/project-home/README.md is excluded by !docs/**
  • docs/features/pull-requests/README.md is excluded by !docs/**
  • docs/features/sync-and-multi-device/README.md is excluded by !docs/**
  • docs/features/sync-and-multi-device/ios-companion.md is excluded by !docs/**
  • docs/features/sync-and-multi-device/remote-commands.md is excluded by !docs/**
  • docs/features/terminals-and-sessions/README.md is excluded by !docs/**
📒 Files selected for processing (53)
  • apps/ade-cli/README.md
  • apps/ade-cli/src/adeRpcServer.test.ts
  • apps/ade-cli/src/adeRpcServer.ts
  • apps/ade-cli/src/cli.test.ts
  • apps/ade-cli/src/cli.ts
  • apps/ade-cli/src/services/sync/syncHostService.test.ts
  • apps/ade-cli/src/services/sync/syncHostService.ts
  • apps/ade-cli/src/services/sync/syncRemoteCommandService.ts
  • apps/ade-cli/src/tuiClient/__tests__/appInput.test.ts
  • apps/ade-cli/src/tuiClient/__tests__/commands.test.ts
  • apps/ade-cli/src/tuiClient/app.tsx
  • apps/ade-cli/src/tuiClient/commands.ts
  • apps/ade-cli/src/tuiClient/components/RightPane.tsx
  • apps/ade-cli/src/tuiClient/types.ts
  • apps/desktop/src/main/main.ts
  • apps/desktop/src/main/services/chat/agentChatService.test.ts
  • apps/desktop/src/main/services/chat/agentChatService.ts
  • apps/desktop/src/main/services/ipc/registerIpc.ts
  • apps/desktop/src/main/services/lanes/laneService.test.ts
  • apps/desktop/src/main/services/lanes/laneService.ts
  • apps/desktop/src/main/services/projects/projectBrowserService.ts
  • apps/desktop/src/main/services/pty/ptyService.test.ts
  • apps/desktop/src/main/services/pty/ptyService.ts
  • apps/desktop/src/main/services/sync/syncHostService.test.ts
  • apps/desktop/src/preload/global.d.ts
  • apps/desktop/src/preload/preload.test.ts
  • apps/desktop/src/preload/preload.ts
  • apps/desktop/src/renderer/components/app/AppShell.tsx
  • apps/desktop/src/renderer/components/app/TopBar.test.tsx
  • apps/desktop/src/renderer/components/app/TopBar.tsx
  • apps/desktop/src/renderer/components/chat/AgentChatMessageList.test.tsx
  • apps/desktop/src/renderer/components/chat/AgentChatMessageList.tsx
  • apps/desktop/src/renderer/components/chat/AgentChatPane.companionDrawers.test.tsx
  • apps/desktop/src/renderer/components/chat/AgentChatPane.submit.test.tsx
  • apps/desktop/src/renderer/components/chat/AgentChatPane.tsx
  • apps/desktop/src/renderer/components/chat/chatExecutionSummary.test.ts
  • apps/desktop/src/renderer/components/chat/chatExecutionSummary.ts
  • apps/desktop/src/renderer/components/lanes/LaneDialogShell.tsx
  • apps/desktop/src/renderer/components/lanes/LanesPage.tsx
  • apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx
  • apps/desktop/src/renderer/components/lanes/laneDialogTokens.ts
  • apps/desktop/src/renderer/components/prs/detail/PrDetailPane.tsx
  • apps/desktop/src/renderer/components/prs/prsRouteState.ts
  • apps/desktop/src/renderer/components/prs/state/PrsContext.tsx
  • apps/desktop/src/renderer/state/appStore.test.ts
  • apps/desktop/src/renderer/state/appStore.ts
  • apps/desktop/src/shared/types/chat.ts
  • apps/desktop/src/shared/types/lanes.ts
  • apps/ios/ADE/Services/SyncService.swift
  • apps/ios/ADE/Views/Lanes/LaneManageSheet.swift
  • apps/ios/ADE/Views/PRs/PrDetailScreen.swift
  • apps/ios/ADE/Views/PRs/PrsRootScreen.swift
  • apps/ios/ADETests/ADETests.swift
💤 Files with no reviewable changes (1)
  • apps/desktop/src/renderer/components/chat/AgentChatPane.companionDrawers.test.tsx

Comment thread apps/ade-cli/src/tuiClient/app.tsx
Comment thread apps/ade-cli/src/tuiClient/app.tsx Outdated
Comment thread apps/ade-cli/src/tuiClient/app.tsx
Comment thread apps/desktop/src/main/services/ipc/registerIpc.ts
Comment thread apps/desktop/src/main/services/pty/ptyService.ts
Comment thread apps/desktop/src/preload/preload.ts Outdated
Comment thread apps/desktop/src/renderer/components/chat/chatExecutionSummary.ts Outdated
Comment thread apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx
Comment thread apps/ios/ADE/Views/Lanes/LaneManageSheet.swift
Comment thread apps/ios/ADE/Views/PRs/PrsRootScreen.swift
Copilot finished work on behalf of arul28 May 15, 2026 08:36
Greptile P1/P2 + CodeRabbit 10 + Copilot review-only on commit 56a8469.

- ManageLaneDialog + LaneManageSheet (iOS): normalize baseChanged compare
  (strip refs/heads/, origin/), and treat empty input as a change when an
  existing override is set so users can clear the base override.
- ptyService: zero out recentOutputTail in closeEntry + dispose to release
  up-to-2MB tail buffer when a session ends.
- ptyService.test: add coverage for disk-only readTranscriptTail and
  stripAnsi invocation on merged tail.
- ade-cli app.tsx: require trimmed summary length > 0; reject ambiguous
  partial lane reference in resolveLaneReference; clear interrupted state
  when sessionFound === false.
- registerIpc: keep in-flight projectGetDetail entries non-expiring until
  the promise settles, then start TTL from resolve time.
- preload: do not null rememberProjectBinding inside clearAround's twice-
  running cleanup for openRepo / switchToPath.
- chatExecutionSummary: preserve merged taskId on subagent_progress /
  subagent_result so timeline doesn't collapse to the latest entry.
- PrsRootScreen (iOS): guard concurrent external history fetches behind
  isLoadingExternalHistory flag to remove race on filter change.

addressed: 3246905124 3246905210 3246917318 3246917328 3246917331
3246917349 3246917363 3246917372 3246917378 3246917382 3246917386
3246917389

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 15, 2026

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

Comment thread apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx Outdated
Comment thread apps/ios/ADE/Views/Lanes/LaneManageSheet.swift Outdated
Iter 2's baseChanged/reparentBaseChanged change made Apply enable on
initial dialog open whenever lane.baseRef was non-empty, because
lane.baseRef is a non-nullable string. That could fire an unintended
rebase on an unsuspecting click.

Now the empty-override branch flips true only when lane.baseRef
actually diverges from the selected parent's default branch (compared
with the existing normalizeBranchRefForCompare helper). Three states:

  - empty override, lane.baseRef == default → baseChanged false
  - empty override, lane.baseRef != default → baseChanged true (clear)
  - non-empty override differing from existing → baseChanged true

Same fix applied to apps/ios/ADE/Views/Lanes/LaneManageSheet.swift.

addressed: 3247162053 3247162141

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 15, 2026

@copilot review but do not make fixes

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/desktop/src/preload/preload.ts`:
- Around line 1214-1225: The current guard that throws when
projectRuntimeTransitionDepth > 0 is applied unconditionally for domain ===
"chat" (freshBinding), which blocks read-only chat operations; update the logic
in the helper around freshBinding/domain === "chat" so it does not throw for
read-only chat calls: either remove the transition guard here and relocate it
into mutating chat entrypoints (e.g., send, respondToInput, approve) or detect
read-only actions and immediately return { handled: false } so
callRemoteProjectActionIfBound and callLocalProjectActionIfBound can fallback to
IPC; keep references to freshBinding, projectRuntimeTransitionDepth, domain ===
"chat", callRemoteProjectActionIfBound, and callLocalProjectActionIfBound to
locate the code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6309f680-4840-4204-95b1-7f721944f5a9

📥 Commits

Reviewing files that changed from the base of the PR and between e93f67c and 624c5c1.

⛔ Files ignored due to path filters (9)
  • docs/ARCHITECTURE.md is excluded by !docs/**
  • docs/features/chat/README.md is excluded by !docs/**
  • docs/features/lanes/README.md is excluded by !docs/**
  • docs/features/lanes/stacking.md is excluded by !docs/**
  • docs/features/project-home/README.md is excluded by !docs/**
  • docs/features/sync-and-multi-device/README.md is excluded by !docs/**
  • docs/features/sync-and-multi-device/ios-companion.md is excluded by !docs/**
  • docs/features/sync-and-multi-device/remote-commands.md is excluded by !docs/**
  • docs/features/terminals-and-sessions/README.md is excluded by !docs/**
📒 Files selected for processing (49)
  • apps/ade-cli/README.md
  • apps/ade-cli/src/adeRpcServer.test.ts
  • apps/ade-cli/src/adeRpcServer.ts
  • apps/ade-cli/src/cli.test.ts
  • apps/ade-cli/src/cli.ts
  • apps/ade-cli/src/services/sync/syncHostService.test.ts
  • apps/ade-cli/src/services/sync/syncHostService.ts
  • apps/ade-cli/src/services/sync/syncRemoteCommandService.ts
  • apps/ade-cli/src/tuiClient/__tests__/appInput.test.ts
  • apps/ade-cli/src/tuiClient/__tests__/commands.test.ts
  • apps/ade-cli/src/tuiClient/app.tsx
  • apps/ade-cli/src/tuiClient/commands.ts
  • apps/ade-cli/src/tuiClient/components/RightPane.tsx
  • apps/ade-cli/src/tuiClient/types.ts
  • apps/desktop/src/main/main.ts
  • apps/desktop/src/main/services/chat/agentChatService.test.ts
  • apps/desktop/src/main/services/chat/agentChatService.ts
  • apps/desktop/src/main/services/ipc/registerIpc.ts
  • apps/desktop/src/main/services/lanes/laneService.test.ts
  • apps/desktop/src/main/services/lanes/laneService.ts
  • apps/desktop/src/main/services/projects/projectBrowserService.ts
  • apps/desktop/src/main/services/pty/ptyService.test.ts
  • apps/desktop/src/main/services/pty/ptyService.ts
  • apps/desktop/src/main/services/sync/syncHostService.test.ts
  • apps/desktop/src/preload/global.d.ts
  • apps/desktop/src/preload/preload.test.ts
  • apps/desktop/src/preload/preload.ts
  • apps/desktop/src/renderer/components/app/AppShell.tsx
  • apps/desktop/src/renderer/components/app/TopBar.test.tsx
  • apps/desktop/src/renderer/components/app/TopBar.tsx
  • apps/desktop/src/renderer/components/chat/AgentChatMessageList.test.tsx
  • apps/desktop/src/renderer/components/chat/AgentChatMessageList.tsx
  • apps/desktop/src/renderer/components/chat/AgentChatPane.companionDrawers.test.tsx
  • apps/desktop/src/renderer/components/chat/AgentChatPane.submit.test.tsx
  • apps/desktop/src/renderer/components/chat/AgentChatPane.tsx
  • apps/desktop/src/renderer/components/chat/chatExecutionSummary.test.ts
  • apps/desktop/src/renderer/components/chat/chatExecutionSummary.ts
  • apps/desktop/src/renderer/components/lanes/LaneDialogShell.tsx
  • apps/desktop/src/renderer/components/lanes/LanesPage.tsx
  • apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx
  • apps/desktop/src/renderer/components/lanes/laneDialogTokens.ts
  • apps/desktop/src/renderer/state/appStore.test.ts
  • apps/desktop/src/renderer/state/appStore.ts
  • apps/desktop/src/shared/types/chat.ts
  • apps/desktop/src/shared/types/lanes.ts
  • apps/ios/ADE/Services/SyncService.swift
  • apps/ios/ADE/Views/Lanes/LaneManageSheet.swift
  • apps/ios/ADE/Views/PRs/PrsRootScreen.swift
  • apps/ios/ADETests/ADETests.swift
💤 Files with no reviewable changes (1)
  • apps/desktop/src/renderer/components/chat/AgentChatPane.companionDrawers.test.tsx
✅ Files skipped from review due to trivial changes (3)
  • apps/ade-cli/README.md
  • apps/ade-cli/src/tuiClient/commands.ts
  • apps/ade-cli/src/services/sync/syncHostService.test.ts
🚧 Files skipped from review as they are similar to previous changes (41)
  • apps/desktop/src/shared/types/chat.ts
  • apps/desktop/src/main/services/projects/projectBrowserService.ts
  • apps/desktop/src/renderer/components/lanes/LanesPage.tsx
  • apps/ade-cli/src/adeRpcServer.test.ts
  • apps/desktop/src/renderer/components/app/AppShell.tsx
  • apps/ade-cli/src/tuiClient/tests/commands.test.ts
  • apps/desktop/src/preload/preload.test.ts
  • apps/desktop/src/shared/types/lanes.ts
  • apps/ade-cli/src/tuiClient/types.ts
  • apps/desktop/src/renderer/components/chat/chatExecutionSummary.test.ts
  • apps/desktop/src/renderer/components/lanes/LaneDialogShell.tsx
  • apps/ade-cli/src/services/sync/syncHostService.ts
  • apps/ade-cli/src/cli.test.ts
  • apps/desktop/src/renderer/state/appStore.test.ts
  • apps/ade-cli/src/adeRpcServer.ts
  • apps/desktop/src/renderer/components/chat/AgentChatMessageList.test.tsx
  • apps/ade-cli/src/tuiClient/components/RightPane.tsx
  • apps/desktop/src/main/services/sync/syncHostService.test.ts
  • apps/desktop/src/main/main.ts
  • apps/desktop/src/renderer/components/app/TopBar.tsx
  • apps/ios/ADE/Views/Lanes/LaneManageSheet.swift
  • apps/ade-cli/src/services/sync/syncRemoteCommandService.ts
  • apps/ios/ADE/Views/PRs/PrsRootScreen.swift
  • apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx
  • apps/ade-cli/src/tuiClient/tests/appInput.test.ts
  • apps/desktop/src/renderer/components/app/TopBar.test.tsx
  • apps/desktop/src/main/services/pty/ptyService.ts
  • apps/desktop/src/renderer/state/appStore.ts
  • apps/desktop/src/renderer/components/chat/chatExecutionSummary.ts
  • apps/ios/ADETests/ADETests.swift
  • apps/desktop/src/main/services/lanes/laneService.test.ts
  • apps/desktop/src/renderer/components/chat/AgentChatMessageList.tsx
  • apps/ade-cli/src/cli.ts
  • apps/ios/ADE/Services/SyncService.swift
  • apps/desktop/src/main/services/lanes/laneService.ts
  • apps/desktop/src/main/services/ipc/registerIpc.ts
  • apps/desktop/src/renderer/components/chat/AgentChatPane.submit.test.tsx
  • apps/desktop/src/renderer/components/chat/AgentChatPane.tsx
  • apps/ade-cli/src/tuiClient/app.tsx
  • apps/desktop/src/main/services/chat/agentChatService.test.ts
  • apps/desktop/src/main/services/chat/agentChatService.ts

Comment thread apps/desktop/src/preload/preload.ts
…t Major)

callProjectRuntimeActionIfBound previously threw for every chat-domain
call when projectRuntimeTransitionDepth > 0, including reads like list,
getSummary, getAvailableModels, and getEventHistory. That blocked the UI
from rendering chat summaries/history during a project switch.

Now only state-changing chat actions (MUTATING_CHAT_ACTIONS allowlist:
sendMessage, respondToInput, approveToolUse, interrupt, steer family,
session lifecycle, ensureCtoSession, ensureAgentIdentitySession, etc.)
throw the transition error. Read-only chat actions return
{handled:false} so the caller falls through to the IPC-backed read APIs.

Added preload.test coverage for read-only chat falling through to IPC
during an in-flight switchToPath.

addressed: 3247309433

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 15, 2026

@copilot review but do not make fixes

@arul28 arul28 deleted the cursor/reorganize-lanes-manage-ade29-7d0c branch May 15, 2026 10:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants