Skip to content

Cursor/multiple model prompt lanes bb23#136

Closed
arul28 wants to merge 4 commits intomainfrom
cursor/multiple-model-prompt-lanes-bb23
Closed

Cursor/multiple model prompt lanes bb23#136
arul28 wants to merge 4 commits intomainfrom
cursor/multiple-model-prompt-lanes-bb23

Conversation

@arul28
Copy link
Copy Markdown
Owner

@arul28 arul28 commented Apr 5, 2026

Summary by CodeRabbit

  • New Features

    • Added AI-generated lane name suggestions from chat prompts
    • Enabled parallel chat mode to launch multiple child lanes with different models simultaneously
    • Introduced work-focus tiling layout for lanes page
    • Added custom instructions support for conflict resolution
    • Added merge-into lane selection for integration PRs with sequential merge preview
  • Enhancements

    • Improved integration PR flow with preferred lane adoption and dirty worktree confirmations

cursoragent and others added 4 commits April 3, 2026 05:26
- Add IPC suggestLaneName for AI-derived lane name prefixes from the prompt
- Work new-chat draft: optional parallel mode with per-model configure (model, reasoning, permissions, Codex execution mode)
- On send: create child lanes per model, open chats, send prompt, navigate to Lanes with multi-select
- Lanes: laneIds + workFocus query for split columns and work-emphasis tiling (layout v6)

Co-authored-by: Arul Sharma <arul28@users.noreply.github.com>
- Allow images in parallel mode (same refs sent to every child session); cap via PARALLEL_CHAT_MAX_IMAGES
- Filter @ picker to images; file input accept=image/*; validate paste/drop
- Refined parallel launch card, entry button, send enables with images-only prompt
- Steer fallback includes attachment paths; image-only send uses short default line

Co-authored-by: Arul Sharma <arul28@users.noreply.github.com>
- Replace image-only parallel rules with same file/image support as single chat
- Cap parallel attachments via PARALLEL_CHAT_MAX_ATTACHMENTS (12)
- Naming seed and empty-message default text cover mixed attachments

Co-authored-by: Arul Sharma <arul28@users.noreply.github.com>
…structions (#135)

* Add integration proposal preferred lane column and resolver prompt types

- Store preferred_integration_lane_id on integration_proposals
- Extend SimulateIntegrationArgs, CommitIntegrationArgs, CreateIntegrationPrArgs
- Add PrAiResolutionStartArgs.additionalInstructions and PrepareResolverSessionArgs field

Co-authored-by: Arul Sharma <arul28@users.noreply.github.com>

* Integrate into existing lanes, simulate parent-head conflicts, append resolver notes

- createIntegrationPr: optional existingIntegrationLaneId merges into that worktree
- simulateIntegration: mergeIntoLaneId for merge-tree vs parent head and sequential preview from that HEAD
- createIntegrationLaneForProposal: use preferred_integration_lane_id when no binding exists
- commitIntegration: dirty-check preferred lane; persist preferred id override
- prepareResolverSession: append operator instructions to prompt.md
- IPC: pass additionalInstructions from aiResolutionStart

Co-authored-by: Arul Sharma <arul28@users.noreply.github.com>

* UI: merge-into lane picker, re-simulate flow, optional resolver instructions

- CreatePrModal: merge-into select, simulation and save proposal preference
- IntegrationTab: apply merge-into with proposal replace, advisories, live PR simulate uses integration lane head
- PrAiResolverPanel: optional instructions textarea sent as additionalInstructions

Co-authored-by: Arul Sharma <arul28@users.noreply.github.com>

* Address PR review: merge-into HEAD persistence, dirty checks, safer proposal swap

- Fail simulateIntegration when merge-into lane HEAD cannot be resolved (Codex)
- Store merge_into_head_sha on proposals; warn on drift when adopting preferred lane
- createIntegrationLaneForProposal: assertDirtyWorktrees for sources + preferred lane; optional allowDirtyWorktree
- IntegrationTab: reorder apply-merge flow (update before delete), gate actions on mergeIntoLaneBusy,
  include liveIntegrationLaneId in simulation reset key, filter source lanes from merge-into picker,
  lane terminology and advisory copy, dirty-worktree confirm for prepare lane
- CreatePrModal: runWithDirtyWorktreeConfirmation around createIntegrationLaneForProposal
- Fix SimulateIntegrationArgs JSDoc (pairwise vs sequential merge base)

Co-authored-by: Arul Sharma <arul28@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Arul Sharma <arul28@users.noreply.github.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 5, 2026

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

Project Deployment Actions Updated (UTC)
ade Ready Ready Preview, Comment Apr 5, 2026 8:12pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 5, 2026

📝 Walkthrough

Walkthrough

This PR introduces lane name suggestion from prompts via AI, extends integration proposals to support merge-into lanes with conflict analysis, adds custom instructions to conflict resolution, implements parallel chat mode in the UI with work-focus tiling, and extends the database schema with integration tracking fields.

Changes

Cohort / File(s) Summary
Lane Name Suggestion
apps/desktop/src/main/services/chat/agentChatService.ts, apps/desktop/src/preload/preload.ts, apps/desktop/src/shared/types/chat.ts, apps/desktop/src/preload/global.d.ts, apps/desktop/src/shared/ipc.ts, apps/desktop/src/main/services/ipc/registerIpc.ts
New suggestLaneNameFromPrompt capability that generates lane names using AI models; includes system prompt, fallback slug generation from prompt text, model resolution, and sanitization. Exported via IPC and preload API.
Merge-Into Integration Lanes
apps/desktop/src/main/services/prs/prService.ts, apps/desktop/src/shared/types/prs.ts, apps/desktop/src/renderer/components/prs/tabs/IntegrationTab.tsx, apps/desktop/src/renderer/components/prs/CreatePrModal.tsx
Extended integration proposals with preferred_integration_lane_id and merge_into_head_sha fields for adopting/reusing existing lanes; added merge-tree conflict analysis between merge-into head and source lanes; simulation, creation, and proposal updates support the new merge-into targeting.
Conflict Resolver Instructions
apps/desktop/src/main/services/conflicts/conflictService.ts, apps/desktop/src/shared/types/conflicts.ts, apps/desktop/src/renderer/components/prs/shared/PrAiResolverPanel.tsx
Added additionalInstructions field to conflict resolver sessions appended as an "Operator instructions" section; UI textarea allows users to customize resolution prompts before launching.
Parallel Chat Mode
apps/desktop/src/renderer/components/chat/AgentChatComposer.tsx, apps/desktop/src/renderer/components/chat/AgentChatPane.tsx
Added parallel multi-lane chat launch with per-slot model/reasoning/execution mode configuration, PARALLEL_CHAT_MAX_ATTACHMENTS limit, parallel submission flow creating child lanes/sessions, and control-slot state cloning logic.
Work-Focus Tiling Layout
apps/desktop/src/renderer/components/lanes/laneUtils.ts, apps/desktop/src/renderer/components/lanes/LanesPage.tsx, apps/desktop/src/renderer/components/lanes/laneUtils.test.ts
Introduced LANES_TILING_WORK_FOCUS_TREE layout for work-focused tiling mode; updated layout version from v5 to v6; extended lane URL handling to support workFocus=1 and multi-lane laneIds CSV parameters.
Database Schema
apps/desktop/src/main/services/state/kvDb.ts, apps/desktop/src/main/services/state/kvDb.missionsMigration.test.ts, apps/ios/ADE/Resources/DatabaseBootstrap.sql
Added preferred_integration_lane_id and merge_into_head_sha columns to integration_proposals table with optional fields and safe migration path.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related PRs

  • Integration: merge into any lane, parent-head simulation, resolver instructions #135: Both PRs modify integration proposal workflow with merge-into lane targeting, preferred_integration_lane_id/merge_into_head_sha fields, and additionalInstructions for conflict resolution across the same service and UI files.
  • pr-convergence #117: Both PRs extend createAgentChatService return object with new exported capabilities; the main PR adds suggestLaneNameFromPrompt alongside existing chat service exports.
  • chat tool calling #111: Both PRs modify AgentChatComposer and AgentChatPane components with new props and state management for chat UI features.

Suggested labels

desktop, ios

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Cursor/multiple model prompt lanes bb23' is vague and does not clearly convey the main purpose of the changeset. While it hints at Cursor and multiple models, it lacks specificity about the actual feature being implemented. Revise the title to be more descriptive of the primary change, such as 'Add parallel chat mode with multiple model support and merge-into lane targeting' or similar, removing the version suffix 'bb23'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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/multiple-model-prompt-lanes-bb23

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 13

Caution

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

⚠️ Outside diff range comments (2)
apps/desktop/src/renderer/components/lanes/LanesPage.tsx (1)

1147-1176: ⚠️ Potential issue | 🟡 Minor

Adding lanesById to dependencies may cause unintended effect re-runs.

The eslint-disable comment on line 1146 was originally to suppress warnings about omitting prepareCreateDialog to prevent dialog resets on lane refresh. However, adding lanesById to the dependency array (line 1176) now causes this effect to re-run whenever any lane data changes.

This could lead to:

  1. Unexpected lane re-selection when lanes refresh in the background
  2. setPinnedLaneIds(new Set()) clearing pins on every lane update when laneIds param is present

Consider extracting the URL parameter handling into a separate effect that only depends on params, or using a ref to track whether the URL params have already been processed.

🛠️ Suggested approach: split the effect or guard against re-processing
+ const processedLaneIdsRef = useRef<string | null>(null);
+
  useEffect(() => {
    const laneIdsRaw = params.get("laneIds");
    const laneId = params.get("laneId");
    const sessionId = params.get("sessionId");
    const inspectorTabParam = params.get("inspectorTab");
    if (params.get("action") === "create") {
      prepareCreateDialog();
    }
    if (laneIdsRaw) {
+     // Skip re-processing if we already handled this exact laneIds value
+     if (processedLaneIdsRef.current === laneIdsRaw) return;
      const parsed = laneIdsRaw.split(",").map((s) => s.trim()).filter(Boolean);
      const valid = parsed.filter((id) => lanesById.has(id));
      if (valid.length) {
+       processedLaneIdsRef.current = laneIdsRaw;
        selectLane(valid[0]!);
        setActiveLaneIds(valid);
        setPinnedLaneIds(new Set());
        // ...
      }
    }
    // ...
  }, [params, selectLane, focusSession, setLaneInspectorTab, lanesById]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/lanes/LanesPage.tsx` around lines 1147 -
1176, The current useEffect couples URL param processing with lanesById, causing
re-runs on lane updates; extract the URL-params logic into a new effect that
only depends on params (handling laneIds/laneId/inspectorTab/sessionId and
calling selectLane, setActiveLaneIds, setPinnedLaneIds, setLaneInspectorTab,
focusSession) and remove lanesById from that effect, and keep
prepareCreateDialog either in a separate effect that runs only when
params.get("action") === "create" (dependent on params) or use a ref guard to
ensure prepareCreateDialog is only invoked once per navigation; ensure any
validation that needs lanesById (e.g., filtering valid ids) is performed via a
short-lived lookup inside the params-only effect or moved to a second effect
that runs when lanesById changes but does not re-run prepareCreateDialog or
clear pinned IDs.
apps/desktop/src/renderer/components/chat/AgentChatComposer.tsx (1)

1058-1078: ⚠️ Potential issue | 🟠 Major

Revalidate the parallel attachment cap before submit.

The new limit is only enforced while adding files. If a user attaches more than PARALLEL_CHAT_MAX_ATTACHMENTS in single-model mode, switches to parallel mode, and sends, this branch still launches the run.

🐛 Suggested fix
     if (parallelChatMode) {
       if (busy || parallelLaunchBusy) return;
       if (parallelModelSlots.length < 2) return;
+      if (attachments.length > PARALLEL_CHAT_MAX_ATTACHMENTS) {
+        setAttachError(`Remove files until only ${PARALLEL_CHAT_MAX_ATTACHMENTS} remain for parallel launch.`);
+        return;
+      }
       const hasPrompt = draft.trim().length > 0;
       const hasAttachments = attachments.length > 0;
       if (!hasPrompt && !hasAttachments) return;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/chat/AgentChatComposer.tsx` around lines
1058 - 1078, In submitComposerDraft, revalidate the parallel-mode attachment cap
before allowing send: inside the parallelChatMode branch (function
submitComposerDraft) check attachments.length against
PARALLEL_CHAT_MAX_ATTACHMENTS and bail out (same as the existing
hasPrompt/hasAttachments checks) if it exceeds the limit so onSubmit() cannot be
called when attachments.length > PARALLEL_CHAT_MAX_ATTACHMENTS; keep the other
guards (busy, parallelLaunchBusy, parallelModelSlots.length) intact and ensure
the check references the attachments array and PARALLEL_CHAT_MAX_ATTACHMENTS
constant.
🧹 Nitpick comments (2)
apps/desktop/src/main/services/ipc/registerIpc.ts (1)

4788-4790: Bound additionalInstructions length before forwarding to resolver session prep.

This field is forwarded into prompt generation. Add a max length guard to prevent oversized prompt artifacts and avoid avoidable token/cost spikes.

✂️ Suggested guard
-    const additionalInstructions = typeof arg?.additionalInstructions === "string" && arg.additionalInstructions.trim().length > 0
-      ? arg.additionalInstructions.trim()
-      : null;
+    const MAX_ADDITIONAL_INSTRUCTIONS_CHARS = 4_000;
+    const rawAdditionalInstructions =
+      typeof arg?.additionalInstructions === "string" ? arg.additionalInstructions.trim() : "";
+    if (rawAdditionalInstructions.length > MAX_ADDITIONAL_INSTRUCTIONS_CHARS) {
+      throw new Error(`Additional instructions must be ${MAX_ADDITIONAL_INSTRUCTIONS_CHARS} characters or fewer.`);
+    }
+    const additionalInstructions = rawAdditionalInstructions.length > 0 ? rawAdditionalInstructions : null;

As per coding guidelines: Electron desktop app — check for IPC security, proper main/renderer process separation, and React best practices.

Also applies to: 4847-4847

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/services/ipc/registerIpc.ts` around lines 4788 - 4790,
The current assignment for additionalInstructions can accept arbitrarily long
strings and is later forwarded into prompt generation; add a max-length guard
(e.g., MAX_ADDITIONAL_INSTRUCTIONS = 1000 or a project-consistent limit) and
enforce it when setting additionalInstructions in registerIpc.ts: if
arg?.additionalInstructions is a string, trim it and then either truncate to the
max length or reject/normalize it before assigning to additionalInstructions so
oversized input cannot be forwarded to the resolver session prep/prompt
generation; update any callers that expect null vs truncated string accordingly
(refer to the additionalInstructions variable and the code path that forwards it
to the resolver session prep).
apps/desktop/src/renderer/components/prs/CreatePrModal.tsx (1)

799-805: Either store the preparation result here or drop the unreachable merge-results branch.

integrationResult is only ever reset to null in this component, so the Step 3 merge-results panel at Lines 2198-2238 cannot render. If createIntegrationLaneForProposal still returns merge details, this is the spot to capture them.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/prs/CreatePrModal.tsx` around lines 799
- 805, The Step 3 "merge-results" UI never shows because the code calls
runWithDirtyWorktreeConfirmation(... run: async (...) =>
window.ade.prs.createIntegrationLaneForProposal(...)) but never captures or
stores the returned merge details into the component state (integrationResult),
so integrationResult stays null; either remove the unreachable merge-results
branch or store the API response here by awaiting the
createIntegrationLaneForProposal call inside run and calling
setIntegrationResult(response) (or the appropriate state setter) so the existing
merge-results panel can render; reference runWithDirtyWorktreeConfirmation,
createIntegrationLaneForProposal, and the integrationResult state setter when
making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/main/services/chat/agentChatService.ts`:
- Around line 4426-4438: The current guard returns fallback() when
requestedModelId or sourceLaneId are empty, disabling AI suggestions; change the
initial check to only require prompt (i.e., remove requestedModelId/sourceLaneId
from the if that calls fallback()), keep the existing try/catch that attempts
resolveLaneLaunchContext to set cwd (laneWorktreePath) and fall back to
projectRoot on error, and ensure any later code that actually needs a model or
lane uses the existing model-fallback chain rather than this early return; refer
to prompt, requestedModelId, sourceLaneId, fallback(), resolveLaneLaunchContext
and the cwd/laneWorktreePath assignment when making the change.
- Around line 4476-4479: The returned auto-title must be run through the
lane-safe normalizer and validated before returning: after calling
sanitizeAutoTitle(result.text.trim(), 56) run the project’s lane-safe normalizer
(e.g., normalizeLaneName or ensureLaneSafe) on the sanitized value and verify it
produces a non-empty, valid lane string; if the normalized result is
empty/invalid, call fallback() instead; update the return path in the function
containing sanitizeAutoTitle(...) and the existing fallback() usage to perform
this post-normalization and validation.

In `@apps/desktop/src/main/services/ipc/registerIpc.ts`:
- Around line 3800-3803: Add runtime validation at the IPC boundary for
IPC.agentChatSuggestLaneName by parsing the incoming payload before calling the
service: implement or reuse a validator named parseAgentChatSuggestLaneNameArgs
that accepts the raw arg and throws on invalid shape, invoke it inside the
handler to produce a typed value, then pass that validated result to
ctx.agentChatService.suggestLaneNameFromPrompt; update the handler signature to
accept the raw arg and remove direct reliance on static typing (keep references
to IPC.agentChatSuggestLaneName, AgentChatSuggestLaneNameArgs,
parseAgentChatSuggestLaneNameArgs, and suggestLaneNameFromPrompt to locate the
change).

In `@apps/desktop/src/renderer/components/chat/AgentChatPane.tsx`:
- Around line 1721-1738: The effect currently re-seeds lanes whenever
parallelModelSlots.length < 2, which wipes user changes on remove; change it to
only seed when enabling or when the list is empty: in the useEffect that
references parallelChatMode, parallelModelSlots, currentNativeControls, modelId,
reasoningEffort, executionMode, replace the guard "if (parallelModelSlots.length
>= 2) return" with "if (parallelModelSlots.length > 0) return" (or explicitly
"if (parallelModelSlots.length !== 0) return") so it only creates slots when
parallelChatMode becomes true and there are no slots; keep cloning via
cloneParallelSlotFromComposer with currentNativeControls, modelId,
reasoningEffort, executionMode and call setParallelModelSlots as before, and
ensure the dependency list still includes the relevant props; alternatively, if
you need to seed only on the transition from false->true, use a ref to track
previous parallelChatMode and only seed when prev was false and current is true
and parallelModelSlots.length === 0.
- Around line 1919-2030: The catch block must handle partial successes by either
compensating (best-effort cleanup) or routing the user to the lanes/sessions
that were created; update the catch for submitError to inspect createdLaneIds
and sessionByLane and (a) if a lanes delete API exists (use
window.ade.lanes.delete or deleteChild) attempt to delete each created child
lane inside a try/catch (log but suppress errors) and clear sessionByLane, or
(b) if you choose not to delete, navigate the user to the created lanes and open
the created sessions by reusing the existing code that sets work view state and
calls navigate with q.set("laneIds", createdLaneIds.join(",")); ensure you run
this compensation/routing before restoring draft/attachments and only then
setError(message) so retries won’t silently duplicate orphan lanes.
- Around line 1876-1885: The code permits empty modelId values in
parallelModelSlots, so add a validation before creating child lanes: check
parallelModelSlots for any empty or falsy s.modelId (e.g.,
parallelModelSlots.some(s => !s.modelId)) and if found call setError with a
clear message (e.g., "All parallel lanes must have a model selected.") and
return; place this check near the existing duplicate-model and attachments
checks to prevent agentChat.create(...) from being invoked with blank modelIds.
- Around line 828-830: onClaudeModeChange currently only sets
claudePermissionMode via patchParallelSlot(idx, { claudePermissionMode: mode })
but the launch path uses slot.interactionMode to decide how to create/send
Claude sessions; update onClaudeModeChange to patch both claudePermissionMode
and interactionMode (e.g., patchParallelSlot(idx, { claudePermissionMode: mode,
interactionMode: mode })) so selecting a Claude mode keeps the parallel slot's
interactionMode in sync with claudePermissionMode; mirror the same behavior for
onClaudePermissionModeChange if needed so both handlers maintain consistency.
- Around line 2748-2752: The onParallelChatModeChange handler currently calls
setAttachments(...slice(...)) which silently drops files; instead, in the
onParallelChatModeChange function check attachments.length against
PARALLEL_CHAT_MAX_ATTACHMENTS and if enabling would exceed the limit, prevent
calling setParallelChatMode(true) and surface an explicit UI message
(toast/alert/modal) that states how many attachments must be removed and offers
a confirmation to proceed (or a button to open attachments manager); only call
setParallelChatMode(true) and then setAttachments(...) to trim after the user
confirms, otherwise leave attachments unchanged — adjust code paths around
setParallelChatMode, setAttachments, PARALLEL_CHAT_MAX_ATTACHMENTS and add the
user prompt/notification logic.

In `@apps/desktop/src/renderer/components/prs/CreatePrModal.tsx`:
- Around line 1541-1556: Update the user-facing copy to use "lane" terminology
and explicitly state the action on save: replace the option text "New
integration branch" with something like "New integration lane" (in the dropdown
rendering inside CreatePrModal where integrationMergeIntoOptions is mapped) and
rewrite the helper text below the select so it explicitly states what ADE will
do on save — e.g., "When set, simulation includes conflicts against that lane's
current HEAD. On save, ADE will prepare merges into that lane instead of
creating a new lane." Ensure the changes are applied to the JSX around
integrationMergeIntoOptions and integrationSources so the option label and the
explanatory div reflect the new lane-centric, stateful wording.

In `@apps/desktop/src/renderer/components/prs/tabs/IntegrationTab.tsx`:
- Around line 2895-2898: The Create PR button's disabled logic (variable
isDisabled in IntegrationTab.tsx) doesn't account for resimulation activity so a
fast click can commit a proposal that's about to be replaced; update the
isDisabled expression (the same place that checks commitBusy, mergeIntoLaneBusy,
selectedProposal.overallOutcome, hasUnresolved, and
selectedProposalTargetLaneId) to also include resimBusy (or whatever boolean
tracks handleResimulate activity) so the button is disabled while
handleResimulate() is running.
- Around line 3168-3169: The memo's dependency array for the useMemo that
renders IntegrationTab is missing mergeIntoLaneDraft, causing stale UI; add
mergeIntoLaneDraft to that dependency list (the same array that currently
includes prs, selectedPr, selectedPrId, mergeContextByPrId, laneById, ...
nextManualResolutionLaneId) so the memo recalculates when the draft changes.

In `@apps/desktop/src/shared/types/conflicts.ts`:
- Line 411: The renderer call sites in ResolverTerminalModal.tsx are not passing
the newly-added additionalInstructions field through to prepareResolverSession;
update the UI flow so that wherever prepareResolverSession (or the IPC wrapper
that calls it) is invoked from ResolverTerminalModal, you include the
additionalInstructions value from the component/state/props and forward it in
the payload and any typed IPC call. Concretely, locate the two invocation points
in ResolverTerminalModal.tsx that build the resolver request and add
additionalInstructions?: string | null to that object (and to any intermediary
handler/prop signatures) so the value flows into prepareResolverSession with the
correct type. Ensure TypeScript types remain consistent with the shared
conflicts type.

In `@apps/ios/ADE/Resources/DatabaseBootstrap.sql`:
- Around line 548-550: The DB schema added two TEXT columns
(preferred_integration_lane_id and merge_into_head_sha); update the iOS model
and DAO to surface them: add optional String? properties named
preferredIntegrationLaneId and mergeIntoHeadSha to IntegrationProposal
(RemoteModels.swift) and the corresponding fields to IntegrationProposalRow
(Database.swift), update the fetchIntegrationProposals SELECT to include
preferred_integration_lane_id and merge_into_head_sha and map those columns into
the new properties, and ensure any initializer/decoder and database-to-model
mapping code converts snake_case column names to the camelCase property names
and safely handles nulls.

---

Outside diff comments:
In `@apps/desktop/src/renderer/components/chat/AgentChatComposer.tsx`:
- Around line 1058-1078: In submitComposerDraft, revalidate the parallel-mode
attachment cap before allowing send: inside the parallelChatMode branch
(function submitComposerDraft) check attachments.length against
PARALLEL_CHAT_MAX_ATTACHMENTS and bail out (same as the existing
hasPrompt/hasAttachments checks) if it exceeds the limit so onSubmit() cannot be
called when attachments.length > PARALLEL_CHAT_MAX_ATTACHMENTS; keep the other
guards (busy, parallelLaunchBusy, parallelModelSlots.length) intact and ensure
the check references the attachments array and PARALLEL_CHAT_MAX_ATTACHMENTS
constant.

In `@apps/desktop/src/renderer/components/lanes/LanesPage.tsx`:
- Around line 1147-1176: The current useEffect couples URL param processing with
lanesById, causing re-runs on lane updates; extract the URL-params logic into a
new effect that only depends on params (handling
laneIds/laneId/inspectorTab/sessionId and calling selectLane, setActiveLaneIds,
setPinnedLaneIds, setLaneInspectorTab, focusSession) and remove lanesById from
that effect, and keep prepareCreateDialog either in a separate effect that runs
only when params.get("action") === "create" (dependent on params) or use a ref
guard to ensure prepareCreateDialog is only invoked once per navigation; ensure
any validation that needs lanesById (e.g., filtering valid ids) is performed via
a short-lived lookup inside the params-only effect or moved to a second effect
that runs when lanesById changes but does not re-run prepareCreateDialog or
clear pinned IDs.

---

Nitpick comments:
In `@apps/desktop/src/main/services/ipc/registerIpc.ts`:
- Around line 4788-4790: The current assignment for additionalInstructions can
accept arbitrarily long strings and is later forwarded into prompt generation;
add a max-length guard (e.g., MAX_ADDITIONAL_INSTRUCTIONS = 1000 or a
project-consistent limit) and enforce it when setting additionalInstructions in
registerIpc.ts: if arg?.additionalInstructions is a string, trim it and then
either truncate to the max length or reject/normalize it before assigning to
additionalInstructions so oversized input cannot be forwarded to the resolver
session prep/prompt generation; update any callers that expect null vs truncated
string accordingly (refer to the additionalInstructions variable and the code
path that forwards it to the resolver session prep).

In `@apps/desktop/src/renderer/components/prs/CreatePrModal.tsx`:
- Around line 799-805: The Step 3 "merge-results" UI never shows because the
code calls runWithDirtyWorktreeConfirmation(... run: async (...) =>
window.ade.prs.createIntegrationLaneForProposal(...)) but never captures or
stores the returned merge details into the component state (integrationResult),
so integrationResult stays null; either remove the unreachable merge-results
branch or store the API response here by awaiting the
createIntegrationLaneForProposal call inside run and calling
setIntegrationResult(response) (or the appropriate state setter) so the existing
merge-results panel can render; reference runWithDirtyWorktreeConfirmation,
createIntegrationLaneForProposal, and the integrationResult state setter when
making the change.
🪄 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: 2c52c463-ee9d-464b-bb0e-019a54186137

📥 Commits

Reviewing files that changed from the base of the PR and between 4502724 and 9eb8c73.

📒 Files selected for processing (21)
  • apps/desktop/src/main/services/chat/agentChatService.ts
  • apps/desktop/src/main/services/conflicts/conflictService.ts
  • apps/desktop/src/main/services/ipc/registerIpc.ts
  • apps/desktop/src/main/services/prs/prService.ts
  • apps/desktop/src/main/services/state/kvDb.missionsMigration.test.ts
  • apps/desktop/src/main/services/state/kvDb.ts
  • apps/desktop/src/preload/global.d.ts
  • apps/desktop/src/preload/preload.ts
  • apps/desktop/src/renderer/components/chat/AgentChatComposer.tsx
  • apps/desktop/src/renderer/components/chat/AgentChatPane.tsx
  • apps/desktop/src/renderer/components/lanes/LanesPage.tsx
  • apps/desktop/src/renderer/components/lanes/laneUtils.test.ts
  • apps/desktop/src/renderer/components/lanes/laneUtils.ts
  • apps/desktop/src/renderer/components/prs/CreatePrModal.tsx
  • apps/desktop/src/renderer/components/prs/shared/PrAiResolverPanel.tsx
  • apps/desktop/src/renderer/components/prs/tabs/IntegrationTab.tsx
  • apps/desktop/src/shared/ipc.ts
  • apps/desktop/src/shared/types/chat.ts
  • apps/desktop/src/shared/types/conflicts.ts
  • apps/desktop/src/shared/types/prs.ts
  • apps/ios/ADE/Resources/DatabaseBootstrap.sql

Comment on lines +4426 to +4438
if (!prompt.length || !requestedModelId.length || !sourceLaneId.length) {
return fallback();
}

let cwd = projectRoot;
try {
({ laneWorktreePath: cwd } = resolveLaneLaunchContext({
laneService,
laneId: sourceLaneId,
purpose: "name a lane from prompt",
}));
} catch {
cwd = projectRoot;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove the hard requirement on modelId/laneId before AI suggestion.

Line 4426 currently forces fallback unless prompt, modelId, and laneId are all present. That bypasses your own model fallback chain and cwd fallback path, so AI suggestion can be unintentionally disabled.

💡 Suggested fix
-    if (!prompt.length || !requestedModelId.length || !sourceLaneId.length) {
+    if (!prompt.length) {
       return fallback();
     }

     let cwd = projectRoot;
-    try {
-      ({ laneWorktreePath: cwd } = resolveLaneLaunchContext({
-        laneService,
-        laneId: sourceLaneId,
-        purpose: "name a lane from prompt",
-      }));
-    } catch {
-      cwd = projectRoot;
-    }
+    if (sourceLaneId.length) {
+      try {
+        ({ laneWorktreePath: cwd } = resolveLaneLaunchContext({
+          laneService,
+          laneId: sourceLaneId,
+          purpose: "name a lane from prompt",
+        }));
+      } catch {
+        cwd = projectRoot;
+      }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/services/chat/agentChatService.ts` around lines 4426 -
4438, The current guard returns fallback() when requestedModelId or sourceLaneId
are empty, disabling AI suggestions; change the initial check to only require
prompt (i.e., remove requestedModelId/sourceLaneId from the if that calls
fallback()), keep the existing try/catch that attempts resolveLaneLaunchContext
to set cwd (laneWorktreePath) and fall back to projectRoot on error, and ensure
any later code that actually needs a model or lane uses the existing
model-fallback chain rather than this early return; refer to prompt,
requestedModelId, sourceLaneId, fallback(), resolveLaneLaunchContext and the
cwd/laneWorktreePath assignment when making the change.

Comment on lines +4476 to +4479
const sanitized = sanitizeAutoTitle(result.text.trim(), 56);
if (!sanitized) return fallback();
return sanitized;
} catch (error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Enforce lane-safe normalization on model output before returning.

sanitizeAutoTitle(...) does not guarantee lane-safe characters across the full string (e.g., internal /, :), so this can return values that violate the lane naming contract while fallback returns a strict slug.

💡 Suggested fix
+      const normalizeLaneBase = (value: string): string | null => {
+        const normalized = value
+          .toLowerCase()
+          .replace(/[^a-z0-9 -]+/g, " ")
+          .replace(/\s+/g, " ")
+          .trim();
+        if (!normalized.length) return null;
+        const slug = normalized.replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
+        return slug.length ? slug.slice(0, 56) : null;
+      };
+
       const sanitized = sanitizeAutoTitle(result.text.trim(), 56);
-      if (!sanitized) return fallback();
-      return sanitized;
+      const laneBase = sanitized ? normalizeLaneBase(sanitized) : null;
+      if (!laneBase) return fallback();
+      return laneBase;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/services/chat/agentChatService.ts` around lines 4476 -
4479, The returned auto-title must be run through the lane-safe normalizer and
validated before returning: after calling sanitizeAutoTitle(result.text.trim(),
56) run the project’s lane-safe normalizer (e.g., normalizeLaneName or
ensureLaneSafe) on the sanitized value and verify it produces a non-empty, valid
lane string; if the normalized result is empty/invalid, call fallback() instead;
update the return path in the function containing sanitizeAutoTitle(...) and the
existing fallback() usage to perform this post-normalization and validation.

Comment on lines +3800 to +3803
ipcMain.handle(IPC.agentChatSuggestLaneName, async (_event, arg: AgentChatSuggestLaneNameArgs): Promise<string> => {
const ctx = getCtx();
return await ctx.agentChatService.suggestLaneNameFromPrompt(arg);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 1) Confirm the new IPC handler currently forwards payload directly.
rg -n -C3 'IPC\.agentChatSuggestLaneName|suggestLaneNameFromPrompt\('

# 2) Inspect whether service-level validation exists for this arg type.
fd -i 'agentChatService.ts' --exec rg -n -C5 'suggestLaneNameFromPrompt|AgentChatSuggestLaneNameArgs' {}

Repository: arul28/ADE

Length of output: 2780


🏁 Script executed:

# Find AgentChatSuggestLaneNameArgs type definition
rg -n 'AgentChatSuggestLaneNameArgs\s*=' --type ts --type tsx

Repository: arul28/ADE

Length of output: 81


🏁 Script executed:

# Check if validation patterns exist at IPC boundary by looking at other handlers
rg -n 'ipcMain\.handle' apps/desktop/src/main/services/ipc/registerIpc.ts | head -20

Repository: arul28/ADE

Length of output: 1730


🏁 Script executed:

# Look for requireRecord or parseRecord patterns to see if validation is a standard pattern
rg -n 'requireRecord|parseRecord' apps/desktop/src/main/services/ipc/registerIpc.ts | head -10

Repository: arul28/ADE

Length of output: 677


🏁 Script executed:

# Check the service implementation more thoroughly to understand validation approach
sed -n '4414,4450p' apps/desktop/src/main/services/ipc/registerIpc.ts

Repository: arul28/ADE

Length of output: 1465


🏁 Script executed:

# View requireRecord implementation
sed -n '3494,3510p' apps/desktop/src/main/services/ipc/registerIpc.ts

Repository: arul28/ADE

Length of output: 690


🏁 Script executed:

# View full suggestLaneNameFromPrompt service method
sed -n '4414,4470p' apps/desktop/src/main/services/ipc/registerIpc.ts

Repository: arul28/ADE

Length of output: 2163


🏁 Script executed:

# Find AgentChatSuggestLaneNameArgs type definition
rg -n 'AgentChatSuggestLaneNameArgs' -A 3 --type ts

Repository: arul28/ADE

Length of output: 3567


🏁 Script executed:

# Get the full AgentChatSuggestLaneNameArgs type definition
sed -n '615,625p' apps/desktop/src/shared/types/chat.ts

Repository: arul28/ADE

Length of output: 479


🏁 Script executed:

# Check if there are similar agent chat IPC handlers and how they're validated
rg -n 'ipcMain\.handle\(IPC\.agentChat' apps/desktop/src/main/services/ipc/registerIpc.ts -A 3 | head -40

Repository: arul28/ADE

Length of output: 2037


🏁 Script executed:

# Look at patterns: how many IPC handlers use requireRecord vs direct typing
rg -n 'const parse.*Args.*=|ipcMain\.handle' apps/desktop/src/main/services/ipc/registerIpc.ts | grep -A1 'parse.*Args' | head -20

Repository: arul28/ADE

Length of output: 427


Add runtime validation for IPC.agentChatSuggestLaneName payloads at the IPC boundary.

The handler accepts arg: AgentChatSuggestLaneNameArgs with static typing only. While the service layer performs String coercion on the received fields, runtime validation should occur at the IPC boundary before dispatch. This aligns with the established pattern in this file (e.g., agentChatCancelSteer uses parseAgentChatCancelSteerArgs) and hardens against malformed renderer payloads.

Suggested fix
+  const parseAgentChatSuggestLaneNameArgs = (value: unknown): AgentChatSuggestLaneNameArgs => {
+    const record = requireRecord(value, "Agent chat suggest lane name request");
+    if (typeof record.prompt !== "string" || !record.prompt.trim()) {
+      throw new Error("Agent chat suggest lane name prompt must be a non-empty string");
+    }
+    if (typeof record.modelId !== "string" || !record.modelId.trim()) {
+      throw new Error("Agent chat suggest lane name model ID must be a non-empty string");
+    }
+    if (typeof record.laneId !== "string" || !record.laneId.trim()) {
+      throw new Error("Agent chat suggest lane name lane ID must be a non-empty string");
+    }
+    return {
+      prompt: record.prompt.trim(),
+      modelId: record.modelId.trim(),
+      laneId: record.laneId.trim(),
+    } as AgentChatSuggestLaneNameArgs;
+  };
+
-  ipcMain.handle(IPC.agentChatSuggestLaneName, async (_event, arg: AgentChatSuggestLaneNameArgs): Promise<string> => {
+  ipcMain.handle(IPC.agentChatSuggestLaneName, async (_event, arg: unknown): Promise<string> => {
     const ctx = getCtx();
-    return await ctx.agentChatService.suggestLaneNameFromPrompt(arg);
+    return await ctx.agentChatService.suggestLaneNameFromPrompt(parseAgentChatSuggestLaneNameArgs(arg));
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ipcMain.handle(IPC.agentChatSuggestLaneName, async (_event, arg: AgentChatSuggestLaneNameArgs): Promise<string> => {
const ctx = getCtx();
return await ctx.agentChatService.suggestLaneNameFromPrompt(arg);
});
const parseAgentChatSuggestLaneNameArgs = (value: unknown): AgentChatSuggestLaneNameArgs => {
const record = requireRecord(value, "Agent chat suggest lane name request");
if (typeof record.prompt !== "string" || !record.prompt.trim()) {
throw new Error("Agent chat suggest lane name prompt must be a non-empty string");
}
if (typeof record.modelId !== "string" || !record.modelId.trim()) {
throw new Error("Agent chat suggest lane name model ID must be a non-empty string");
}
if (typeof record.laneId !== "string" || !record.laneId.trim()) {
throw new Error("Agent chat suggest lane name lane ID must be a non-empty string");
}
return {
prompt: record.prompt.trim(),
modelId: record.modelId.trim(),
laneId: record.laneId.trim(),
} as AgentChatSuggestLaneNameArgs;
};
ipcMain.handle(IPC.agentChatSuggestLaneName, async (_event, arg: unknown): Promise<string> => {
const ctx = getCtx();
return await ctx.agentChatService.suggestLaneNameFromPrompt(parseAgentChatSuggestLaneNameArgs(arg));
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/services/ipc/registerIpc.ts` around lines 3800 - 3803,
Add runtime validation at the IPC boundary for IPC.agentChatSuggestLaneName by
parsing the incoming payload before calling the service: implement or reuse a
validator named parseAgentChatSuggestLaneNameArgs that accepts the raw arg and
throws on invalid shape, invoke it inside the handler to produce a typed value,
then pass that validated result to
ctx.agentChatService.suggestLaneNameFromPrompt; update the handler signature to
accept the raw arg and remove direct reliance on static typing (keep references
to IPC.agentChatSuggestLaneName, AgentChatSuggestLaneNameArgs,
parseAgentChatSuggestLaneNameArgs, and suggestLaneNameFromPrompt to locate the
change).

Comment on lines +828 to +830
onInteractionModeChange: (mode) => patchParallelSlot(idx, { interactionMode: mode }),
onClaudeModeChange: (mode) => patchParallelSlot(idx, { claudePermissionMode: mode }),
onClaudePermissionModeChange: (mode) => patchParallelSlot(idx, { claudePermissionMode: mode }),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep Claude mode and interaction mode in sync for parallel lanes.

onClaudeModeChange only patches claudePermissionMode, but the launch path later reads slot.interactionMode when creating/sending Claude sessions. Selecting plan here can therefore still launch the lane with interactionMode: "default".

Suggested fix
-      onClaudeModeChange: (mode) => patchParallelSlot(idx, { claudePermissionMode: mode }),
+      onClaudeModeChange: (mode) => patchParallelSlot(idx, {
+        interactionMode: mode === "plan" ? "plan" : "default",
+        claudePermissionMode: mode,
+      }),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/chat/AgentChatPane.tsx` around lines 828
- 830, onClaudeModeChange currently only sets claudePermissionMode via
patchParallelSlot(idx, { claudePermissionMode: mode }) but the launch path uses
slot.interactionMode to decide how to create/send Claude sessions; update
onClaudeModeChange to patch both claudePermissionMode and interactionMode (e.g.,
patchParallelSlot(idx, { claudePermissionMode: mode, interactionMode: mode }))
so selecting a Claude mode keeps the parallel slot's interactionMode in sync
with claudePermissionMode; mirror the same behavior for
onClaudePermissionModeChange if needed so both handlers maintain consistency.

Comment on lines +1721 to +1738
useEffect(() => {
if (!parallelChatMode) return;
if (parallelModelSlots.length >= 2) return;
setParallelModelSlots([
cloneParallelSlotFromComposer({
native: currentNativeControls,
modelId,
reasoningEffort,
executionMode,
}),
cloneParallelSlotFromComposer({
native: currentNativeControls,
modelId,
reasoningEffort,
executionMode,
}),
]);
}, [parallelChatMode, parallelModelSlots.length, currentNativeControls, modelId, reasoningEffort, executionMode]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t re-seed configured slots after a remove.

This effect runs any time the slot count drops below two. If the user removes one lane, the remaining configured slot gets replaced with two fresh clones from the composer, which wipes any per-lane model or permission changes they already made. Seed only on the initial enable (or when the list is empty), and guard the remove action instead of resetting from scratch.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/chat/AgentChatPane.tsx` around lines
1721 - 1738, The effect currently re-seeds lanes whenever
parallelModelSlots.length < 2, which wipes user changes on remove; change it to
only seed when enabling or when the list is empty: in the useEffect that
references parallelChatMode, parallelModelSlots, currentNativeControls, modelId,
reasoningEffort, executionMode, replace the guard "if (parallelModelSlots.length
>= 2) return" with "if (parallelModelSlots.length > 0) return" (or explicitly
"if (parallelModelSlots.length !== 0) return") so it only creates slots when
parallelChatMode becomes true and there are no slots; keep cloning via
cloneParallelSlotFromComposer with currentNativeControls, modelId,
reasoningEffort, executionMode and call setParallelModelSlots as before, and
ensure the dependency list still includes the relevant props; alternatively, if
you need to seed only on the transition from false->true, use a ref to track
previous parallelChatMode and only seed when prev was false and current is true
and parallelModelSlots.length === 0.

Comment on lines +1541 to +1556
<option value="">New integration branch</option>
{integrationMergeIntoOptions.map((lane) => (
<option key={lane.id} value={lane.id} disabled={integrationSources.includes(lane.id)}>
{lane.name}{integrationSources.includes(lane.id) ? " (source)" : ""}
</option>
))}
</select>
<div style={{
marginTop: 6,
fontSize: 10,
fontFamily: "var(--font-sans)",
color: C.textMuted,
lineHeight: "14px",
}}>
When set, simulation includes conflicts against that lane&apos;s current HEAD. Commit prepares merges there instead of creating a new lane.
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Rename this option/helper copy to lane language and state the action explicitly.

New integration branch conflicts with ADE's lane terminology, and Commit prepares merges there... doesn't say what action changes state. This is the main inline explanation of the new flow, so the copy should say exactly what ADE does on save.

💬 Suggested copy tweak
-                        <option value="">New integration branch</option>
+                        <option value="">New integration lane</option>

-                        When set, simulation includes conflicts against that lane&apos;s current HEAD. Commit prepares merges there instead of creating a new lane.
+                        When set, preview includes conflicts against the selected lane&apos;s current HEAD. When you save, ADE prepares merges on that lane instead of creating a new integration lane.

As per coding guidelines Use 'lane' terminology for ADE worktrees/branches and Keep user-facing copy concrete and stateful: say what changed, what is blocked, and what the next action is.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<option value="">New integration branch</option>
{integrationMergeIntoOptions.map((lane) => (
<option key={lane.id} value={lane.id} disabled={integrationSources.includes(lane.id)}>
{lane.name}{integrationSources.includes(lane.id) ? " (source)" : ""}
</option>
))}
</select>
<div style={{
marginTop: 6,
fontSize: 10,
fontFamily: "var(--font-sans)",
color: C.textMuted,
lineHeight: "14px",
}}>
When set, simulation includes conflicts against that lane&apos;s current HEAD. Commit prepares merges there instead of creating a new lane.
</div>
<option value="">New integration lane</option>
{integrationMergeIntoOptions.map((lane) => (
<option key={lane.id} value={lane.id} disabled={integrationSources.includes(lane.id)}>
{lane.name}{integrationSources.includes(lane.id) ? " (source)" : ""}
</option>
))}
</select>
<div style={{
marginTop: 6,
fontSize: 10,
fontFamily: "var(--font-sans)",
color: C.textMuted,
lineHeight: "14px",
}}>
When set, preview includes conflicts against the selected lane&apos;s current HEAD. When you save, ADE prepares merges on that lane instead of creating a new integration lane.
</div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/prs/CreatePrModal.tsx` around lines 1541
- 1556, Update the user-facing copy to use "lane" terminology and explicitly
state the action on save: replace the option text "New integration branch" with
something like "New integration lane" (in the dropdown rendering inside
CreatePrModal where integrationMergeIntoOptions is mapped) and rewrite the
helper text below the select so it explicitly states what ADE will do on save —
e.g., "When set, simulation includes conflicts against that lane's current HEAD.
On save, ADE will prepare merges into that lane instead of creating a new lane."
Ensure the changes are applied to the JSX around integrationMergeIntoOptions and
integrationSources so the option label and the explanatory div reflect the new
lane-centric, stateful wording.

Comment on lines 2895 to 2898
{(() => {
const hasUnresolved = selectedProposal.overallOutcome !== "clean" && !allStepsResolved;
const isDisabled = commitBusy || selectedProposal.overallOutcome === "blocked" || hasUnresolved || !selectedProposalTargetLaneId;
const isDisabled = commitBusy || mergeIntoLaneBusy || selectedProposal.overallOutcome === "blocked" || hasUnresolved || !selectedProposalTargetLaneId;
return (
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Disable Create PR while re-simulation is replacing the proposal.

handleResimulate() writes a replacement proposal row and then deletes the current one, but isDisabled still ignores resimBusy. A fast click here can commit the stale proposal just before it is removed.

🐛 Suggested fix
-              const isDisabled = commitBusy || mergeIntoLaneBusy || selectedProposal.overallOutcome === "blocked" || hasUnresolved || !selectedProposalTargetLaneId;
+              const isDisabled = commitBusy || resimBusy || mergeIntoLaneBusy || selectedProposal.overallOutcome === "blocked" || hasUnresolved || !selectedProposalTargetLaneId;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/prs/tabs/IntegrationTab.tsx` around
lines 2895 - 2898, The Create PR button's disabled logic (variable isDisabled in
IntegrationTab.tsx) doesn't account for resimulation activity so a fast click
can commit a proposal that's about to be replaced; update the isDisabled
expression (the same place that checks commitBusy, mergeIntoLaneBusy,
selectedProposal.overallOutcome, hasUnresolved, and
selectedProposalTargetLaneId) to also include resimBusy (or whatever boolean
tracks handleResimulate activity) so the button is disabled while
handleResimulate() is running.

Comment on lines 3168 to +3169
// eslint-disable-next-line react-hooks/exhaustive-deps
}), [prs, selectedPr, selectedPrId, mergeContextByPrId, laneById, mergeSourcesResolved, liveIntegrationLaneId, liveIntegrationRebaseNeed, liveSimulationLaneIds, resolverTargetLaneId, simulateResult, simulateBusy, simulateError, resolverOpen, proposalResolverConfig, deleteConfirm, deleteBusy, deleteCloseGh, hasConflicts, rebaseNeeds, rebaseNeedByLaneId, autoRebaseStatuses, setActiveTab, onSelectPr, onRefresh, proposals, proposalsLoaded, selectedProposal, selectedProposalId, selectedProposalRebaseLaneIds, selectedPrLiveModel, commitBusy, commitError, resimBusy, deleteProposalBusy, expandedPairKeys, resolutionState, activeWorkerStepId, createLaneBusy, resolvingLaneId, resolutionPanelDismissed, allStepsResolved, proposalLaneCards, proposalConflictingPairs, proposalConflictSteps, totalProposalConflictFiles, urlProposalId, conflictPairCountByLaneId, isLegacySequentialProposal, nextManualResolutionLaneId]);
}), [prs, selectedPr, selectedPrId, mergeContextByPrId, laneById, mergeSourcesResolved, liveIntegrationLaneId, liveIntegrationRebaseNeed, liveSimulationLaneIds, liveSimulationKey, resolverTargetLaneId, simulateResult, simulateBusy, simulateError, resolverOpen, proposalResolverConfig, deleteConfirm, deleteBusy, deleteCloseGh, hasConflicts, rebaseNeeds, rebaseNeedByLaneId, autoRebaseStatuses, setActiveTab, onSelectPr, onRefresh, proposals, proposalsLoaded, selectedProposal, selectedProposalId, selectedProposalRebaseLaneIds, selectedPrLiveModel, commitBusy, commitError, resimBusy, mergeIntoLaneBusy, deleteProposalBusy, expandedPairKeys, resolutionState, activeWorkerStepId, createLaneBusy, resolvingLaneId, resolutionPanelDismissed, allStepsResolved, proposalLaneCards, proposalConflictingPairs, proposalConflictSteps, totalProposalConflictFiles, urlProposalId, conflictPairCountByLaneId, isLegacySequentialProposal, nextManualResolutionLaneId]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's check the file exists and get its line count
wc -l apps/desktop/src/renderer/components/prs/tabs/IntegrationTab.tsx

Repository: arul28/ADE

Length of output: 123


🏁 Script executed:

# Now let's read the area around lines 3168-3169 to see the memo definition
sed -n '3160,3180p' apps/desktop/src/renderer/components/prs/tabs/IntegrationTab.tsx | cat -n

Repository: arul28/ADE

Length of output: 1500


🏁 Script executed:

# Let's get more context - read the full memo that likely starts before line 3168
sed -n '3100,3180p' apps/desktop/src/renderer/components/prs/tabs/IntegrationTab.tsx | cat -n

Repository: arul28/ADE

Length of output: 3767


🏁 Script executed:

# Search for mergeIntoLaneDraft usage in the file
rg "mergeIntoLaneDraft" apps/desktop/src/renderer/components/prs/tabs/IntegrationTab.tsx -n

Repository: arul28/ADE

Length of output: 297


🏁 Script executed:

# Also search for where detailPane is defined
rg "detailPane\s*=" apps/desktop/src/renderer/components/prs/tabs/IntegrationTab.tsx -n -A 2 -B 2

Repository: arul28/ADE

Length of output: 299


🏁 Script executed:

# Let's see what's around line 2105 where mergeIntoLaneDraft is used
sed -n '2090,2120p' apps/desktop/src/renderer/components/prs/tabs/IntegrationTab.tsx | cat -n

Repository: arul28/ADE

Length of output: 1871


🏁 Script executed:

# Also check what's at line 1269 to understand how mergeIntoLaneDraft is used
sed -n '1260,1320p' apps/desktop/src/renderer/components/prs/tabs/IntegrationTab.tsx | cat -n

Repository: arul28/ADE

Length of output: 3304


🏁 Script executed:

# Let's look at the handleApplyMergeIntoTarget button to see how it uses the callback
sed -n '2130,2160p' apps/desktop/src/renderer/components/prs/tabs/IntegrationTab.tsx | cat -n

Repository: arul28/ADE

Length of output: 1609


Add mergeIntoLaneDraft to the memo's dependency list.

The merge-target select element and its apply handler both depend on mergeIntoLaneDraft, but the memo doesn't include it in dependencies. This causes the UI to display stale values when the draft changes until an unrelated state update triggers a re-render.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/prs/tabs/IntegrationTab.tsx` around
lines 3168 - 3169, The memo's dependency array for the useMemo that renders
IntegrationTab is missing mergeIntoLaneDraft, causing stale UI; add
mergeIntoLaneDraft to that dependency list (the same array that currently
includes prs, selectedPr, selectedPrId, mergeContextByPrId, laneById, ...
nextManualResolutionLaneId) so the memo recalculates when the draft changes.

originMissionId?: string | null;
originRunId?: string | null;
originLabel?: string | null;
additionalInstructions?: string | null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Wire additionalInstructions through the renderer call path.

Line 411 extends the contract, but the provided call sites in apps/desktop/src/renderer/components/shared/conflictResolver/ResolverTerminalModal.tsx (lines 197-235 and 590-615) still omit additionalInstructions, so custom operator instructions won’t reach prepareResolverSession from that UI flow.

As per coding guidelines "Keep IPC contracts, preload types, shared types, and renderer usage in sync whenever an interface changes."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/shared/types/conflicts.ts` at line 411, The renderer call
sites in ResolverTerminalModal.tsx are not passing the newly-added
additionalInstructions field through to prepareResolverSession; update the UI
flow so that wherever prepareResolverSession (or the IPC wrapper that calls it)
is invoked from ResolverTerminalModal, you include the additionalInstructions
value from the component/state/props and forward it in the payload and any typed
IPC call. Concretely, locate the two invocation points in
ResolverTerminalModal.tsx that build the resolver request and add
additionalInstructions?: string | null to that object (and to any intermediary
handler/prop signatures) so the value flows into prepareResolverSession with the
correct type. Ensure TypeScript types remain consistent with the shared
conflicts type.

Comment on lines +548 to +550
alter table integration_proposals add column preferred_integration_lane_id text;

alter table integration_proposals add column merge_into_head_sha text;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Update iOS DAO/model mapping for the new columns.

Line 548 and Line 550 add new schema fields, but the provided snippets show apps/ios/ADE/Services/Database.swift (IntegrationProposalRow + fetchIntegrationProposals SELECT) and apps/ios/ADE/Models/RemoteModels.swift (IntegrationProposal) are not yet carrying preferred_integration_lane_id / merge_into_head_sha. This leaves the new data unsurfaced in iOS flows.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ios/ADE/Resources/DatabaseBootstrap.sql` around lines 548 - 550, The DB
schema added two TEXT columns (preferred_integration_lane_id and
merge_into_head_sha); update the iOS model and DAO to surface them: add optional
String? properties named preferredIntegrationLaneId and mergeIntoHeadSha to
IntegrationProposal (RemoteModels.swift) and the corresponding fields to
IntegrationProposalRow (Database.swift), update the fetchIntegrationProposals
SELECT to include preferred_integration_lane_id and merge_into_head_sha and map
those columns into the new properties, and ensure any initializer/decoder and
database-to-model mapping code converts snake_case column names to the camelCase
property names and safely handles nulls.

@arul28 arul28 closed this Apr 6, 2026
@arul28 arul28 deleted the cursor/multiple-model-prompt-lanes-bb23 branch April 6, 2026 02:04
@arul28 arul28 restored the cursor/multiple-model-prompt-lanes-bb23 branch April 6, 2026 02:17
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.

2 participants