Skip to content

PRs Tab Fixes#240

Merged
arul28 merged 4 commits into
mainfrom
ade/prs-tab-fixes-3c147957
May 3, 2026
Merged

PRs Tab Fixes#240
arul28 merged 4 commits into
mainfrom
ade/prs-tab-fixes-3c147957

Conversation

@arul28
Copy link
Copy Markdown
Owner

@arul28 arul28 commented May 3, 2026

Summary

Describe the change.

What Changed

Key files and behaviors.

Validation

How you tested.

Risks

Anything to watch.

Summary by CodeRabbit

  • Bug Fixes

    • Composer is disabled while a pending input/approval is awaiting answer; sending, steering, and shortcuts are blocked until resolved.
    • CI/checks list deduplicates GitHub Actions entries to avoid duplicate checks.
  • New Features

    • PR badges now surface merged PRs that still match your branch and show compact labeled badges.
    • Convergence panel shows full review-thread context and lets you expand/hide ignored comments.
    • iOS composer shows a gated placeholder/message when awaiting input.

Greptile Summary

This PR fixes several PRs-tab and chat-composer regressions: the chat composer now locks while a blocking pending-input card is active (desktop + iOS), merged PRs matching the current lane branch are surfaced in the sidebar badge and chat toolbar, GitHub Actions check-run deduplication is improved by job ID and unique-name matching, and the Convergence panel gains expandable comment context with an ignored-items toggle.

Confidence Score: 5/5

Safe to merge; all findings are P2 edge cases with no current data-loss or breakage risk.

No P0/P1 issues found. The two P2 comments cover a narrow Electron IPC race during project switch (prs-updated guard) and a clarification question about whether non-blocking freeform questions still exist in practice. Both are low-probability in a desktop app and do not affect the primary user paths changed by this PR.

apps/desktop/src/renderer/components/lanes/LanesPage.tsx (prs-updated event guard) and apps/desktop/src/renderer/components/chat/AgentChatComposer.tsx (non-blocking question submit removal)

Important Files Changed

Filename Overview
apps/desktop/src/main/services/prs/prService.ts Renamed rowMatchesCurrentLaneBranch → rowMatchesLaneBranchForDisplay (removed implicit isActivePrState gate); added getDisplayRowForCurrentLaneBranch that accepts merged PRs and removed the rows[0] stray-fallback; getForLane now routes through the new function
apps/desktop/src/renderer/components/lanes/LanesPage.tsx Adds selectLanePrTag / lanePrMatchesCurrentBranch for client-side PR badge display on lane tabs; subscribes to prs-updated / pr-notification events; prs-updated handler sets state directly without a cross-project stale guard
apps/desktop/src/renderer/components/chat/AgentChatComposer.tsx Adds composerInputLocked (blocking pending input) to disable textarea, attachment picker, command menu, and steer button; replaces the old "type in composer to answer" flow with a hard lock; old non-blocking question submit path is removed
apps/desktop/src/renderer/components/prs/detail/PrDetailPane.tsx Adds formatReviewThreadContext to enrich PanelIssueItem.body; improves buildUnifiedChecks to deduplicate check-runs that mirror Actions workflow jobs by job ID and unique name matching
apps/desktop/src/renderer/components/prs/shared/PrConvergencePanel.tsx Adds expandable comment context, show/hide ignored items toggle, and showRoundLabels distinction for manual vs auto-converge agent runs; dismiss reason updated to "Ignored from Path to Merge"
apps/ios/ADE/Views/Work/WorkChatSessionView.swift Adds hasPendingInputGate to block canCompose and canSend when any pending input exists; passes awaitingInputGate to composer card for context-sensitive placeholder
apps/desktop/src/renderer/components/chat/AgentChatPane.tsx Adds submit-time guard in submit() that checks pendingInputsBySession and session.awaitingInput before allowing a new message; moves setPromptSuggestion(null) call into submit()

Sequence Diagram

sequenceDiagram
    participant Renderer as LanesPage / ChatPane
    participant PRSvc as prService (main)
    participant DB as SQLite

    Note over Renderer,DB: Lane PR badge (sidebar)
    Renderer->>PRSvc: window.ade.prs.listAll()
    PRSvc->>DB: SELECT all PRs (lane_id, project_id)
    DB-->>PRSvc: PullRequestRow[]
    PRSvc-->>Renderer: PrSummary[] (all states incl. merged)
    Renderer->>Renderer: selectLanePrTag(lane, prs)<br/>prefer open/draft > merged > closed

    Note over Renderer,DB: Chat toolbar badge
    Renderer->>PRSvc: prs.getForLane(laneId)
    PRSvc->>DB: SELECT ORDER BY state priority + recency
    DB-->>PRSvc: PullRequestRow[]
    PRSvc->>PRSvc: rowMatchesLaneBranchForDisplay()<br/>(branch match, no state filter)
    PRSvc-->>Renderer: PrSummary | null (merged OK)

    Note over Renderer,DB: Live PR event update
    PRSvc-->>Renderer: onEvent(prs-updated, prs)
    Renderer->>Renderer: setLanePrTags(prs) no project guard

    Note over Renderer,DB: Pending input gate
    Renderer->>Renderer: composerInputLocked = pendingInput?.blocking
    Renderer->>PRSvc: sendMessage() blocked if hasLivePendingInput
Loading

Fix All in Claude Code

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

---

### Issue 1 of 2
apps/desktop/src/renderer/components/lanes/LanesPage.tsx:997-1004
**`prs-updated` handler sets state without cross-project guard**

`refreshLanePrTags` guards against cross-project stale responses with `startedRoot` / `requestId` checks, but the `prs-updated` branch calls `setLanePrTags(event.prs)` directly. If a `prs-updated` event is dispatched synchronously just before the `onEvent` cleanup runs during a project switch, the tags for the old project will briefly override the `setLanePrTags([])` reset emitted by the rootPath effect — showing wrong PR badges on the incoming project's lane list until the next refresh.

### Issue 2 of 2
apps/desktop/src/renderer/components/chat/AgentChatComposer.tsx:329-334
**Non-blocking questions no longer submitted via the composer**

`composerInputLocked` is `false` when `pendingInput?.blocking` is `false`, so the composer stays fully editable. However the old `submitComposerDraft` path that forwarded the draft as an answer (via `onApproval("accept", answer)`) has been removed entirely. If a non-blocking question-type input exists today, a user can type in the composer and submit — but the text is sent as a normal chat message, silently bypassing the pending question rather than answering it. The hint still says "Answer in the inline question card, or decline" which matches the new intent; just confirming that non-blocking freeform questions are no longer handled this way by design.

Reviews (5): Last reviewed commit: "ship: iter 3 — Show ignored toggle reach..." | Re-trigger Greptile

@vercel
Copy link
Copy Markdown

vercel Bot commented May 3, 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 3, 2026 11:14pm

@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 3, 2026

@copilot review but do not make fixes

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

📝 Walkthrough

Walkthrough

Adds session-level blocking for unresolved pending user inputs across chat service, desktop composer/pane, and iOS UI; updates lane PR selection/display to surface merged PRs when head branch matches and renders per-lane PR badges; enriches PR convergence inventory with expanded review-thread context and improves CI/check-run deduplication.

Changes

Pending-input blocking (chat service → UI wiring → tests)

Layer / File(s) Summary
Guard constant & readiness
apps/desktop/src/main/services/chat/agentChatService.ts
Introduces PENDING_INPUT_SEND_BLOCKED_MESSAGE and makes validateSessionReadyForTurn return not-ready when hasLivePendingInput(managed) is true.
Send/steer guards
apps/desktop/src/main/services/chat/agentChatService.ts
prepareSendMessage, steer, and dispatchSteer now throw the blocking message if a live pending input exists.
Composer lock state & UX
apps/desktop/src/renderer/components/chat/AgentChatComposer.tsx
Adds composerInputLocked derived from pendingInput?.blocking; disables editing/attachments/commands, updates placeholders/tooltips, clears transient UI state on lock, intercepts Escape to cancel pending approval.
Pane-level submit guard
apps/desktop/src/renderer/components/chat/AgentChatPane.tsx
submit early-returns with an error when selected session has a blocking pending input or awaitingInput.
iOS gating
apps/ios/ADE/Views/Work/WorkChatSessionView.swift
Adds hasPendingInputGate, gates canCompose/canSend, threads gate into composer components, updates placeholder/feedback messaging.
Service tests
apps/desktop/src/main/services/chat/agentChatService.test.ts
New test asserting sendMessage(...) is rejected while a pending requestChatInput(...) is unresolved and resolves after respondToInput(..., decision: "decline").
Composer / Pane tests
apps/desktop/src/renderer/components/chat/AgentChatComposer.test.tsx, apps/desktop/src/renderer/components/chat/AgentChatPane.submit.test.tsx
Updated/added tests verifying UI shows pending-card, disables composer controls, prevents Enter/send/steer calls while pending, and verifies Send is blocked when awaitingInput is true.

Lane PR display & badges (service → helpers → UI → tests)

Layer / File(s) Summary
Branch-match helper & selection logic
apps/desktop/src/main/services/prs/prService.ts
Adds rowMatchesLaneBranchForDisplay and getDisplayRowForCurrentLaneBranch, removes hard-coded “active” filtering from branch matcher. getForLane now uses display-oriented lookup.
Formatter
apps/desktop/src/renderer/components/prs/shared/prFormatters.ts
Adds formatPrBadgeLabel(pr) to produce compact badge text like MERGED #123 / `PR `#123.
Lanes helpers & state
apps/desktop/src/renderer/components/lanes/LanesPage.tsx
Adds selectLanePrTag/lanePrMatchesCurrentBranch, PR tag color mapping, state for lanePrTags, refresh effects, and a lanePrByLaneId lookup.
Lanes UI badge
apps/desktop/src/renderer/components/lanes/LanesPage.tsx
Renders clickable, colored PR badge per lane tab (uses formatPrBadgeLabel, stops event propagation, navigates to PR view).
Toolbar wiring
apps/desktop/src/renderer/components/chat/ChatGitToolbar.tsx
Uses formatPrBadgeLabel and sets tooltip title to ${label}: ${linkedPr.title}.
Service / UI tests
apps/desktop/src/main/services/prs/prService.test.ts, apps/desktop/src/renderer/components/lanes/LanesPage.test.ts
Tests updated/added to assert merged PRs are surfaced when head branch still matches, ignored when branch mismatches, and selectLanePrTag behavior including primary-lane edge cases.

PR detail convergence & checks (data mapping → unified checks → UI/tests)

Layer / File(s) Summary
Review-thread formatting & inventory mapping
apps/desktop/src/renderer/components/prs/detail/PrDetailPane.tsx
Adds formatReviewThreadContext and updates mapInventoryItems to populate body and additional metadata for review_thread items (author, counts, latest commenter/timestamp).
Unified checks correlation
apps/desktop/src/renderer/components/prs/detail/PrDetailPane.tsx
Refactors buildUnifiedChecks to normalize names, detect/extract Actions job IDs from run URLs, deduplicate runs by workflow, and use composite canonical keys so mirrored Actions check-runs aren't double-counted.
Convergence panel types & UI
apps/desktop/src/renderer/components/prs/shared/PrConvergencePanel.tsx
Extends IssueInventoryItem with optional fields (type, externalId, body, author, thread metadata); adds per-item expand/collapse to show full comment context, a "Show ignored (N)" toggle, and changes dismiss action to mark "Ignored from Path to Merge".
Convergence / Detail tests
apps/desktop/src/renderer/components/prs/shared/PrConvergencePanel.test.tsx, apps/desktop/src/renderer/components/prs/detail/PrDetailPane.issueResolver.test.tsx
Adds tests for expanded comment context, ignored-item toggling, agent-running label behavior; adds makeActionRun helper and test asserting Actions-mirrored check-runs are not double-counted.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

desktop, ios

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.00% 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 'PRs Tab Fixes' is vague and generic, using non-descriptive phrasing that does not convey meaningful information about the specific changes in this substantial PR. Replace with a more specific title that highlights the main change, such as 'Add composer input locking while pending input is active' or 'Improve PR tab sidebar badges and merged PR handling'.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
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 ade/prs-tab-fixes-3c147957

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
Contributor

Copilot AI commented May 3, 2026

@copilot review but do not make fixes

Here's a code review of the PR changes.


prService.tsgetForLane fallback behaviour

getDisplayRowForCurrentLaneBranch has an asymmetry: when laneBranch is unresolvable (empty ref), it falls back to rows[0] (any PR for that lane, any state). When laneBranch is resolved but nothing matches, it returns null instead of falling back to the most-recent row. This is intentionally stricter than the old getActiveRowForCurrentLaneBranch, but worth documenting. A lane with a stale/empty branchRef will show a potentially unrelated merged PR.

Also, rowMatchesCurrentLaneBranch now just delegates to rowMatchesLaneBranchForDisplay after the isActivePrState check — the function could be inlined or removed since it's only used internally and the only difference is the state guard. Not a bug, just some dead indirection.


PrConvergencePanel — Round label off-by-one asymmetry

  • agent_running: Math.max(1, convergence.currentRound) — guards a 0-indexed value.
  • ready: convergence.currentRound + 1 — no guard. If currentRound starts at 0, "round 1" is correct, but if currentRound is already at maxRounds, this shows "round N+1" (beyond max). The UI does hide the button when at max so this only shows in the label text while the button is disabled — low impact but inconsistent with the Math.max guard on the running label.

PrConvergencePanelshowIgnoredItems toggle scope

visibleReviewCommentItems (which respects the toggle) is only used in the main convergence view. The terminal state branch (if (terminalState)) uses the raw items prop passed in and doesn't apply showIgnoredItems filtering. Dismissed items will always be visible in the terminal summary regardless of the toggle.


PrConvergencePanelthreadLatestCommentAt unused

Added to IssueInventoryItem and threaded through mapInventoryItems, but never rendered in the expanded panel UI. It's presumably reserved for future use but is dead weight in the type surface until then.


LanesPage.tsxselectLanePrTag client-side filtering vs server

LanesPage calls window.ade.prs.listAll() to populate lanePrTags, then filters client-side with selectLanePrTag. This effectively re-implements the branch-matching logic that prService.getForLane already does server-side. The two implementations (lanePrMatchesCurrentBranch in LanesPage vs rowMatchesLaneBranchForDisplay in prService) should stay in sync — any divergence would silently show different PRs in the badge vs the detail pane.


AgentChatComposer.tsx — Three-layer pending input guard

The blocking check is enforced at three layers: compositor lock (composerInputLocked), pane submit guard (AgentChatPane.tsx), and service-level throw (agentChatService.ts). The pane guard is needed as a fallback but the composer-level and pane-level guards check slightly different conditions: the composer uses pendingInput?.blocking, while the pane checks pendingInputsBySession[id]?.length > 0 || selectedSession?.awaitingInput. A blocking: false pending input would lock the composer but not the pane — probably fine since a non-blocking pending input shouldn't stop the user.


WorkChatSessionView.swift — Indentation in struct literals

Lines added to WorkChatComposerCard and WorkChatComposerDraftInput use inconsistent indentation in the initializer call (awaitingInputGate: awaitingInputGate, has extra leading spaces vs the surrounding code). Cosmetic but could fail a SwiftFormat/SwiftLint pass.


Minor / Nit

  • GitPullRequest is imported in LanesPage.tsx and used correctly — but the icon doesn't change with PR state (merged/closed/draft all get the same icon). The color + label text carries the state signal, which is probably sufficient in that compact space.
  • formatPrBadgeLabel uses an uppercase convention (MERGED, CLOSED, DRAFT) which differs from how GitHub labels PR states in its own UI. Not wrong, just worth verifying this matches the design intent.
  • threadLatestCommentAuthor in the expanded panel defaults to the latest commenter but falls back to item.author for non-thread items — the detailAuthor alias hides the origin, which could be surprising if debugging display issues.

Copilot finished work on behalf of arul28 May 3, 2026 22:05
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: 4

🤖 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/renderer/components/chat/AgentChatComposer.tsx`:
- Around line 737-740: Re-check the composer lock and parallel-attachment limits
inside the attachment ingestion paths (e.g., addFileAttachments and the other
attachment handler near the later block) instead of relying only on the UI entry
guard; specifically, inside addFileAttachments (and the analogous handler
referenced in the comment) immediately bail out if composerInputLocked is true
and surface composerInputLockMessage (or the default "Resolve the pending
request before adding attachments.") as the rejection reason, and also re-check
parallelChatMode && attachments.length >= PARALLEL_CHAT_MAX_ATTACHMENTS to
prevent adding files past the PARALLEL_CHAT_MAX_ATTACHMENTS limit. Ensure you
update any success/error flows to respect these checks so files selected before
the lock do not get appended after lock activation.

In `@apps/desktop/src/renderer/components/chat/AgentChatPane.tsx`:
- Around line 3715-3723: The submit flow currently clears promptSuggestion too
early (onSubmit) which causes suggestions to be lost when the submit is blocked;
update the logic so promptSuggestion is only cleared after the pending-input
guard passes and a submit is actually initiated: in the submit handler (submit)
and/or onSubmit caller, move the setPromptSuggestion("") (or the prompt
suggestion clearing logic) to after the checks that reference submitInFlightRef,
busy, parallelLaunchBusy, selectedSessionId,
pendingInputsBySession[selectedSessionId], and selectedSession?.awaitingInput,
and ensure it runs only when you proceed to send (i.e., after the guard returns
false and before/after the actual send logic as appropriate).

In `@apps/desktop/src/renderer/components/lanes/LanesPage.tsx`:
- Around line 669-676: The async updater refreshLanePrTags currently sets
setLanePrTags unconditionally after awaiting window.ade.prs.listAll(), allowing
stale responses to overwrite state when project context changes; fix by adding a
cancellation token or requestId check (e.g., a useRef requestId or isMounted
ref) captured when refreshLanePrTags starts and validated before calling
setLanePrTags, and apply the same pattern to the other similar functions at the
referenced ranges; ensure the token/requestId is incremented or cleared on
context change or unmount so only the latest response updates state.

In `@apps/ios/ADE/Views/Work/WorkChatSessionView.swift`:
- Around line 148-149: The pending-input warning text is shown when
hasPendingInputGate || sessionStatus == "awaiting-input" but the composer
tint/background still only checks sessionStatus == "awaiting-input"; update the
composer styling checks (the view that sets the tint/background in
WorkChatSessionView/composer UI) to use the same combined condition
(hasPendingInputGate || sessionStatus == "awaiting-input") so the visual state
matches the blocking warning message.
🪄 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: a234f7b1-e6ae-43e9-bb4e-90b8da2abfdc

📥 Commits

Reviewing files that changed from the base of the PR and between 812254b and ad99a52.

⛔ Files ignored due to path filters (4)
  • apps/ade-cli/package-lock.json is excluded by !**/package-lock.json, !**/package-lock.json
  • docs/features/chat/composer-and-ui.md is excluded by !docs/**
  • docs/features/lanes/README.md is excluded by !docs/**
  • docs/features/pull-requests/README.md is excluded by !docs/**
📒 Files selected for processing (17)
  • apps/desktop/src/main/services/chat/agentChatService.test.ts
  • apps/desktop/src/main/services/chat/agentChatService.ts
  • apps/desktop/src/main/services/prs/prService.test.ts
  • apps/desktop/src/main/services/prs/prService.ts
  • apps/desktop/src/renderer/components/chat/AgentChatComposer.test.tsx
  • apps/desktop/src/renderer/components/chat/AgentChatComposer.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/ChatGitToolbar.tsx
  • apps/desktop/src/renderer/components/lanes/LanesPage.test.ts
  • apps/desktop/src/renderer/components/lanes/LanesPage.tsx
  • apps/desktop/src/renderer/components/prs/detail/PrDetailPane.issueResolver.test.tsx
  • apps/desktop/src/renderer/components/prs/detail/PrDetailPane.tsx
  • apps/desktop/src/renderer/components/prs/shared/PrConvergencePanel.test.tsx
  • apps/desktop/src/renderer/components/prs/shared/PrConvergencePanel.tsx
  • apps/desktop/src/renderer/components/prs/shared/prFormatters.ts
  • apps/ios/ADE/Views/Work/WorkChatSessionView.swift

Comment thread apps/desktop/src/renderer/components/chat/AgentChatComposer.tsx
Comment thread apps/desktop/src/renderer/components/chat/AgentChatPane.tsx
Comment thread apps/desktop/src/renderer/components/lanes/LanesPage.tsx
Comment thread apps/ios/ADE/Views/Work/WorkChatSessionView.swift
Comment thread apps/desktop/src/renderer/components/prs/detail/PrDetailPane.tsx
Comment thread apps/desktop/src/renderer/components/lanes/LanesPage.tsx
…nding-input lock

- prs: lane-scoped PR display falls back to merged/closed PRs; state-aware badge labels (`formatPrBadgeLabel`)
- prs: PrConvergencePanel issue rows are expandable with author/body/thread comment count, "show ignored" toggle, "Ignore comment" rename, manual-run round labels
- prs: dedupe Actions check entries; ignore/expand review items
- lanes: state-aware lane-list PR tag via `selectLanePrTag`
- chat: hard-lock composer + server-side gate while `pendingInput.blocking` is set; merged-PR display in PrDetailPane
- ios: WorkChatSessionView updates to match new chat lock contract
- docs: refresh chat/composer-and-ui, lanes/README, pull-requests/README
@arul28 arul28 force-pushed the ade/prs-tab-fixes-3c147957 branch from ad99a52 to cd92904 Compare May 3, 2026 22:11
@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 3, 2026

@copilot review but do not make fixes

Copy link
Copy Markdown
Contributor

Copilot AI commented May 3, 2026

@copilot review but do not make fixes

Here's a review of the changes, incorporating the Greptile findings now visible in the PR description.


🔴 TypeScript type error — formatReviewThreadContext(item.externalId) (Greptile P2)

File: PrDetailPane.tsx:1092

item.externalId is typed string | undefined (the ? in IssueInventoryItem), but formatReviewThreadContext declares its parameter as string. Under strict-null mode this is a compile error. Fix at the call site: formatReviewThreadContext(item.externalId ?? "") or add a guard before the call. The function safely returns null for unknown thread IDs at runtime, so the undefined case is benign — it's a type-system issue only.


🟡 Branch comparison case-sensitivity divergence — client vs server (Greptile P2)

File: LanesPage.tsx:175

normalizeLanePrBranch does branchNameFromLaneRef(ref).trim().toLowerCase(), making the client-side match case-insensitive. The server-side normalizeBranchName in prService.ts:664 only strips refs/heads/ and origin/ prefixes — no .toLowerCase(). So selectLanePrTag (sidebar badge) does case-insensitive matching while getForLane/rowMatchesLaneBranchForDisplay (toolbar badge and PR detail pane) does case-sensitive matching. In practice branch names are consistent, but this is a latent mismatch worth aligning.


Issues from the earlier review that still apply

PrConvergencePanel — round label inconsistency: agent_running uses Math.max(1, currentRound) but ready uses currentRound + 1 without a guard. Low-impact but inconsistent.

PrConvergencePanelshowIgnoredItems toggle not applied in terminal state: The terminal-state branch renders the raw items prop directly; dismissed items are always visible there regardless of the toggle.

PrConvergencePanelthreadLatestCommentAt unused: Added to the type and threaded through mapping, but never rendered.

prService.tsrowMatchesCurrentLaneBranch dead indirection: The function now just adds an isActivePrState guard then delegates to rowMatchesLaneBranchForDisplay. Could be inlined.

WorkChatSessionView.swift — indentation: awaitingInputGate: lines have inconsistent leading whitespace in the struct initializer calls.

Copilot finished work on behalf of arul28 May 3, 2026 22:13
- AgentChatPane: defer promptSuggestion clear until after pending-input guard so blocked submits don't lose the suggestion (CodeRabbit PRRT_kwDORNN9Fc5_OIgJ)
- LanesPage: guard refreshLanePrTags with requestId + project-root capture so stale listAll() responses don't overwrite state (CodeRabbit PRRT_kwDORNN9Fc5_OIgL)
- LanesPage: drop .toLowerCase() from normalizeLanePrBranch so client matching mirrors server-side normalizeBranchName (Copilot)
- prService: inline rowMatchesCurrentLaneBranch dead indirection at its 2 call sites (Copilot)
- PrConvergencePanel: render threadLatestCommentAt as "(updated <time>)"; cap ready-state round label at maxRounds; apply visibleReviewCommentItems filter in terminal-state branch (Copilot)
- WorkChatSessionView.swift: align awaitingInputGate initializer indentation (Copilot)
@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 3, 2026

@copilot review but do not make fixes

Copy link
Copy Markdown
Contributor

Copilot AI commented May 3, 2026

@copilot review but do not make fixes

Here's a review incorporating the new Greptile findings from the second pass.


🟡 Submit guard vs. composer lock inconsistency — AgentChatPane.tsx:3719

AgentChatPane.submit blocks whenever pendingInputsBySession[id]?.length > 0 || selectedSession?.awaitingInput, regardless of whether the pending inputs have blocking: true. AgentChatComposer only hard-locks the UI on pendingInput?.blocking. For a non-blocking pending input the composer stays fully interactive (textarea active, send button enabled, no visual indicator), but pressing Send silently shows the error banner — the user gets no upfront signal that send is blocked. Aligning both on the blocking flag (or surface a visual hint for non-blocking pending inputs) would make the behavior consistent.


🟡 !laneBranch fallback returns any linked PR — prService.ts:941-942

When normalizeBranchName(branchNameFromRef(lane.branch_ref ?? "")) resolves to an empty string (branch-less lane), getDisplayRowForCurrentLaneBranch returns rows[0] — the highest-priority PR ordered by state then date. The previous getActiveRowForCurrentLaneBranch returned null for the same case because rowMatchesLaneBranchForDisplay short-circuits on !laneBranch. Any lane in production with a null or empty branch_ref will now start showing a state-based badge from an arbitrary linked PR where previously nothing was shown.


Carry-forward from prior reviews

  • TypeScript type error (PrDetailPane.tsx:1092): item.externalId is string | undefined but formatReviewThreadContext declares string. Strict-null mode compile error; fix with ?? "" or an early return guard.
  • Branch case-sensitivity mismatch (LanesPage.tsx:175): normalizeLanePrBranch lowercases; server normalizeBranchName does not. Sidebar badge and toolbar badge can diverge for inconsistently-cased branch names.
  • PrConvergencePanel round-label guard asymmetry: agent_running uses Math.max(1, currentRound), ready uses currentRound + 1 without a matching guard.
  • showIgnoredItems toggle not applied in terminal state: Terminal branch renders raw items directly; toggle has no effect there.
  • threadLatestCommentAt dead weight: In the type but never rendered.
  • Swift indentation: awaitingInputGate: lines have extra leading whitespace vs surrounding struct initializer code.

Copilot finished work on behalf of arul28 May 3, 2026 22:40
- AgentChatPane.submit: only block on pending inputs whose request.blocking is true (matches AgentChatComposer's hard-lock condition); non-blocking pending inputs no longer cause silent send failures (Copilot)
- prService.getDisplayRowForCurrentLaneBranch / getActiveRowForCurrentLaneBranch: return null for branch-less lanes (lane.branch_ref empty) instead of falling back to rows[0]; restores prior contract so unbranched lanes never claim a stray PR badge (Copilot)
- prService.test: assert null for branch-less lane case
@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 3, 2026

@copilot review but do not make fixes

Copy link
Copy Markdown
Contributor

Copilot AI commented May 3, 2026

@copilot review but do not make fixes

Here's a review of the latest state of the PR after the two shipped iterations.


🟡 PrConvergencePanel — "Show ignored" toggle missing in terminal state (Greptile P1)

File: PrConvergencePanel.tsx:1228–1245

The terminal-state render path uses visibleReviewCommentItems (which filters out dismissed items when showIgnoredItems === false), but there is no toggle button in that branch to flip showIgnoredItems. Since it initialises to false, any dismissed items on a merged/closed PR are silently hidden with no way to reveal them and no indication they exist.

The "Show ignored (N)" toggle button only appears in the active-state toolbar (around line 1450). Adding a similar button above or below the "Historical review inventory" heading in the terminal-state branch, or passing showIgnoredItems and its setter in from the parent to share state across the two branches, would fix it.


Carry-forward findings (still open after iter 2)

  • PrConvergencePanel — round-label guard asymmetry: agent_running guard uses Math.max(1, currentRound), ready uses currentRound + 1 without a floor guard.
  • threadLatestCommentAt dead weight: Present in the IssueInventoryItem type and mapped through mapInventoryItems but never rendered.
  • Swift indentation in struct literals: awaitingInputGate: argument lines have extra leading whitespace vs the surrounding initializer code.

Confirmed fixed in iter 1 and iter 2

  • ✅ TypeScript type error formatReviewThreadContext(item.externalId) — fixed with ?? ""
  • AgentChatPane.submit guard aligned to pendingInput?.blocking (only blocks on blocking inputs)
  • getDisplayRowForCurrentLaneBranch!laneBranch now returns null instead of rows[0] for branch-less lanes

Copilot finished work on behalf of arul28 May 3, 2026 23:00
Terminal-state branch of PrConvergencePanel respected showIgnoredItems but
provided no toggle button to flip it, so dismissed items on merged/closed PRs
were silently hidden with no way to reveal them. Surface the toggle next to
the "Historical review inventory" heading and render the section whenever
there are visible OR dismissed items, mirroring the active-state toolbar
button (Copilot/Greptile P1).
@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 3, 2026

@copilot review but do not make fixes

Copy link
Copy Markdown
Contributor

Copilot AI commented May 3, 2026

@copilot review but do not make fixes

Here's a review of the PR after iter 3.


✅ All previously-flagged P1 / P2 issues are now resolved

Issue Status
"Show ignored" toggle missing in terminal state (PrConvergencePanel) ✅ Fixed (iter 3) — container condition widened to visibleReviewCommentItems.length > 0 || grouped.dismissed.length > 0; toggle rendered in the "Historical review inventory" header when dismissed items exist
TypeScript type error formatReviewThreadContext(item.externalId) ✅ Fixed (iter 1)
AgentChatPane.submit guard misaligned with blocking flag ✅ Fixed (iter 2)
!laneBranch fallback returning arbitrary rows[0] for branch-less lanes ✅ Fixed (iter 2)
threadLatestCommentAt unused dead weight ✅ Resolved — now rendered as (updated N ago) in the expanded IssueRow detail block (line 547)
Swift indentation in struct initializer ✅ Resolved — all parameter lines use consistent 4-level indent
Round-label guard asymmetry ✅ Mitigated — ready state now uses Math.min(maxRounds, currentRound + 1) to cap the label at max; complements the Math.max(1, ...) floor guard on the running label

🟡 One minor nit remaining

getActiveRowForCurrentLaneBranch — fallback to rows[0] for active un-archived lanes

prService.ts:890 still returns rows[0] when the lane exists, is not archived, but has no active-state PR matching the current branch. This is the pre-existing fallback behavior (unchanged by this PR), but it means active lanes without a branch-matching open/draft PR can surface an arbitrary merged PR in the PR detail toolbar. getDisplayRowForCurrentLaneBranch (used for badges) was correctly fixed to return null in this case; the detail-pane path still uses the old fallback. Low risk for production — lanes generally have matching open PRs — but worth aligning eventually.


Summary

The PR is in a clean, shippable state. The Greptile P2 regression (dismissed items silently hidden in terminal state) is fully addressed in iter 3, and all carry-forward findings from the earlier reviews are resolved. The one note above (getActiveRowForCurrentLaneBranch fallback) is pre-existing behavior, not introduced by this PR.

Copilot finished work on behalf of arul28 May 3, 2026 23:17
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

Caution

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

⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/components/chat/AgentChatComposer.tsx (1)

2739-2750: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

The steer lock is still incomplete.

This hides the draft steer button, but the staged-message strip later in the component still keeps onSendNow / onInterrupt active. With queued steers present, a blocking pending card can still be bypassed from that list.

Suggested fix
- {pendingSteers.map((steer) => (
+ {pendingSteers.map((steer) => (
     <PendingSteerItem
       key={steer.steerId}
       steer={steer}
       onCancel={() => onCancelSteer?.(steer.steerId)}
       onEdit={(text) => onEditSteer?.(steer.steerId, text)}
-      onSendNow={onDispatchSteerInline ? () => onDispatchSteerInline(steer.steerId) : undefined}
-      onInterrupt={onDispatchSteerInterrupt ? () => onDispatchSteerInterrupt(steer.steerId) : undefined}
+      onSendNow={!composerInputLocked && onDispatchSteerInline ? () => onDispatchSteerInline(steer.steerId) : undefined}
+      onInterrupt={!composerInputLocked && onDispatchSteerInterrupt ? () => onDispatchSteerInterrupt(steer.steerId) : undefined}
     />
   ))}

If you want the lock to be fully consistent, pass a disabled prop into PendingSteerItem and use it for edit/remove there too.

🤖 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
2739 - 2750, The draft-steer button is hidden when composerInputLocked is true
but the staged-message strip still allows bypass via PendingSteerItem handlers;
update the staged-message rendering to pass a disabled prop (e.g.,
disabled={composerInputLocked}) into PendingSteerItem and ensure
PendingSteerItem uses that prop to disable edit/remove and to no-op or block
calls to onSendNow and onInterrupt; locate the references to
submitComposerDraft, composerInputLocked, and PendingSteerItem in the component
and wire the disabled flag through so queued steers cannot be acted on while
locked.
♻️ Duplicate comments (2)
apps/desktop/src/renderer/components/lanes/LanesPage.tsx (1)

828-834: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Invalidate in-flight fetches when handling prs-updated events.

At Line 830-833, setLanePrTags(event.prs) does not bump lanePrTagsRequestRef. An older in-flight refreshLanePrTags() can still resolve later and overwrite this newer event payload.

💡 Proposed fix
   useEffect(() => {
     return window.ade.prs.onEvent((event) => {
       if (event.type === "prs-updated") {
+        lanePrTagsRequestRef.current += 1;
         setLanePrTags(event.prs);
       } else if (event.type === "pr-notification") {
+        lanePrTagsRequestRef.current += 1;
         void refreshLanePrTags();
       }
     });
   }, [refreshLanePrTags]);
🤖 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 828 -
834, When handling the "prs-updated" event in the window.ade.prs.onEvent
callback, also bump/invalidate the in-flight fetch token so an older
refreshLanePrTags() resolution cannot overwrite the new event data; update the
same request token/ref used by refreshLanePrTags (lanePrTagsRequestRef) inside
the event.type === "prs-updated" branch immediately before/after calling
setLanePrTags(event.prs) (e.g., increment or set to a new unique value) so
refreshLanePrTags can ignore stale responses by comparing its local token to
lanePrTagsRequestRef.
apps/desktop/src/renderer/components/chat/AgentChatComposer.tsx (1)

737-740: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

UI-only attachment locking still has a bypass.

canAttach disables the visible entry points, but the async ingestion paths can still append files if the native picker or clipboard temp-save started before pendingInput.blocking flipped. Please re-check the lock inside addFileAttachments and addNativeClipboardImageAttachment before each onAddAttachment, and surface composerInputLockMessage when rejecting.

Suggested fix
 const addFileAttachments = async (files: FileList | null | undefined) => {
   if (!files?.length) return;
+  if (composerInputLocked) {
+    setAttachError(composerInputLockMessage ?? "Resolve the pending request before adding attachments.");
+    return;
+  }
   if (parallelChatMode && attachments.length >= PARALLEL_CHAT_MAX_ATTACHMENTS) return;
   if (fileAddInProgressRef.current) return;
   fileAddInProgressRef.current = true;
   setAttachError(null);
   try {
     let addedInBatch = 0;
     for (const file of Array.from(files)) {
+      if (composerInputLocked) {
+        setAttachError(composerInputLockMessage ?? "Resolve the pending request before adding attachments.");
+        break;
+      }
       ...
       if (hasRealPath) {
+        if (composerInputLocked) break;
         onAddAttachment({ path: filePath, type: inferAttachmentType(filePath, file.type) });
         addedInBatch += 1;
         continue;
       }
       ...
       const { path: tempPath } = await window.ade.agentChat.saveTempAttachment(...);
+      if (composerInputLocked) {
+        setAttachError(composerInputLockMessage ?? "Resolve the pending request before adding attachments.");
+        break;
+      }
       onAddAttachment({ path: tempPath, type: inferAttachmentType(tempPath, file.type) });
     }
   } finally {
     fileAddInProgressRef.current = false;
   }
 };
🤖 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
737 - 740, The UI lock check (canAttach/attachBlockedReason) is insufficient
because async ingestion can start before pendingInput.blocking flips; update
addFileAttachments and addNativeClipboardImageAttachment to re-check
composerInputLocked (and parallel attachment limit via attachments.length <
PARALLEL_CHAT_MAX_ATTACHMENTS) immediately before invoking onAddAttachment, and
if blocked return/reject early while surfacing composerInputLockMessage (or the
parallel-limit message) as the rejection reason instead of proceeding. Ensure
you reference composerInputLockMessage when rejecting and keep the existing
onAddAttachment call-site unchanged except for this pre-check.
🤖 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/prs/prService.ts`:
- Around line 5239-5240: Validation failures are due to TypeScript config and
missing dev dependencies, not the getDisplayRowForCurrentLaneBranch/rowToSummary
logic; fix by adding `@types/node` to devDependencies and/or include "types":
["node"] in tsconfig's compilerOptions so TS recognizes Node types, remove or
update any deprecated tsconfig option (the reported deprecated baseUrl) to the
supported configuration, and install the missing build/lint tools (add tsup and
eslint to devDependencies and ensure eslint plugin modules used by the project
are installed) so typecheck, build, and lint succeed before approving changes
that reference getDisplayRowForCurrentLaneBranch and rowToSummary.

---

Outside diff comments:
In `@apps/desktop/src/renderer/components/chat/AgentChatComposer.tsx`:
- Around line 2739-2750: The draft-steer button is hidden when
composerInputLocked is true but the staged-message strip still allows bypass via
PendingSteerItem handlers; update the staged-message rendering to pass a
disabled prop (e.g., disabled={composerInputLocked}) into PendingSteerItem and
ensure PendingSteerItem uses that prop to disable edit/remove and to no-op or
block calls to onSendNow and onInterrupt; locate the references to
submitComposerDraft, composerInputLocked, and PendingSteerItem in the component
and wire the disabled flag through so queued steers cannot be acted on while
locked.

---

Duplicate comments:
In `@apps/desktop/src/renderer/components/chat/AgentChatComposer.tsx`:
- Around line 737-740: The UI lock check (canAttach/attachBlockedReason) is
insufficient because async ingestion can start before pendingInput.blocking
flips; update addFileAttachments and addNativeClipboardImageAttachment to
re-check composerInputLocked (and parallel attachment limit via
attachments.length < PARALLEL_CHAT_MAX_ATTACHMENTS) immediately before invoking
onAddAttachment, and if blocked return/reject early while surfacing
composerInputLockMessage (or the parallel-limit message) as the rejection reason
instead of proceeding. Ensure you reference composerInputLockMessage when
rejecting and keep the existing onAddAttachment call-site unchanged except for
this pre-check.

In `@apps/desktop/src/renderer/components/lanes/LanesPage.tsx`:
- Around line 828-834: When handling the "prs-updated" event in the
window.ade.prs.onEvent callback, also bump/invalidate the in-flight fetch token
so an older refreshLanePrTags() resolution cannot overwrite the new event data;
update the same request token/ref used by refreshLanePrTags
(lanePrTagsRequestRef) inside the event.type === "prs-updated" branch
immediately before/after calling setLanePrTags(event.prs) (e.g., increment or
set to a new unique value) so refreshLanePrTags can ignore stale responses by
comparing its local token to lanePrTagsRequestRef.
🪄 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: 0b12b9f0-95bb-4edf-b064-935a9ab82e9c

📥 Commits

Reviewing files that changed from the base of the PR and between ad99a52 and be9654b.

⛔ Files ignored due to path filters (4)
  • apps/ade-cli/package-lock.json is excluded by !**/package-lock.json, !**/package-lock.json
  • docs/features/chat/composer-and-ui.md is excluded by !docs/**
  • docs/features/lanes/README.md is excluded by !docs/**
  • docs/features/pull-requests/README.md is excluded by !docs/**
📒 Files selected for processing (17)
  • apps/desktop/src/main/services/chat/agentChatService.test.ts
  • apps/desktop/src/main/services/chat/agentChatService.ts
  • apps/desktop/src/main/services/prs/prService.test.ts
  • apps/desktop/src/main/services/prs/prService.ts
  • apps/desktop/src/renderer/components/chat/AgentChatComposer.test.tsx
  • apps/desktop/src/renderer/components/chat/AgentChatComposer.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/ChatGitToolbar.tsx
  • apps/desktop/src/renderer/components/lanes/LanesPage.test.ts
  • apps/desktop/src/renderer/components/lanes/LanesPage.tsx
  • apps/desktop/src/renderer/components/prs/detail/PrDetailPane.issueResolver.test.tsx
  • apps/desktop/src/renderer/components/prs/detail/PrDetailPane.tsx
  • apps/desktop/src/renderer/components/prs/shared/PrConvergencePanel.test.tsx
  • apps/desktop/src/renderer/components/prs/shared/PrConvergencePanel.tsx
  • apps/desktop/src/renderer/components/prs/shared/prFormatters.ts
  • apps/ios/ADE/Views/Work/WorkChatSessionView.swift
✅ Files skipped from review due to trivial changes (4)
  • apps/desktop/src/renderer/components/chat/ChatGitToolbar.tsx
  • apps/ios/ADE/Views/Work/WorkChatSessionView.swift
  • apps/desktop/src/main/services/chat/agentChatService.test.ts
  • apps/desktop/src/renderer/components/prs/shared/PrConvergencePanel.tsx
🚧 Files skipped from review as they are similar to previous changes (9)
  • apps/desktop/src/renderer/components/lanes/LanesPage.test.ts
  • apps/desktop/src/renderer/components/chat/AgentChatPane.submit.test.tsx
  • apps/desktop/src/main/services/prs/prService.test.ts
  • apps/desktop/src/renderer/components/prs/shared/PrConvergencePanel.test.tsx
  • apps/desktop/src/main/services/chat/agentChatService.ts
  • apps/desktop/src/renderer/components/chat/AgentChatComposer.test.tsx
  • apps/desktop/src/renderer/components/chat/AgentChatPane.tsx
  • apps/desktop/src/renderer/components/prs/detail/PrDetailPane.tsx
  • apps/desktop/src/renderer/components/prs/shared/prFormatters.ts

Comment thread apps/desktop/src/main/services/prs/prService.ts
@arul28 arul28 merged commit 7820970 into main May 3, 2026
25 checks passed
@arul28 arul28 deleted the ade/prs-tab-fixes-3c147957 branch May 3, 2026 23:50
@coderabbitai coderabbitai Bot mentioned this pull request May 4, 2026
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