Skip to content

Fix ADE-77 TUI papercuts and ADE-70 Files Workbench gaps#646

Merged
arul28 merged 1 commit into
mainfrom
ade/linear-validation-audit-047d470e
Jun 24, 2026
Merged

Fix ADE-77 TUI papercuts and ADE-70 Files Workbench gaps#646
arul28 merged 1 commit into
mainfrom
ade/linear-validation-audit-047d470e

Conversation

@arul28

@arul28 arul28 commented Jun 24, 2026

Copy link
Copy Markdown
Owner

Summary

  • finish ADE-77 TUI papercuts: full approval option rendering, debounced/cached @-mentions, no-match palette rows, startup retry UI, distinct image-chip glyphs, and terminal mode restore on exit paths
  • finish ADE-70 Files Workbench v2 blockers: dirty-buffer publication, dirty-state guards on workspace/window/rename/delete, clean-editor reloads on watcher changes, persisted/pruned recents, diff-mode save/error handling, and clearer empty/history states
  • fix the pre-launch audit flake in remote runtime port-forward disconnect tests by awaiting forward-server teardown

Validation

  • node scripts/validate-docs.mjs
  • npm --prefix apps/ade-cli run typecheck
  • npm --prefix apps/ade-cli run test
  • npm --prefix apps/ade-cli run build
  • npm --prefix apps/ade-cli exec -- vitest run src/tuiClient/tests/appPolling.test.tsx src/tuiClient/tests/appInput.test.ts src/tuiClient/tests/ApprovalPrompt.test.tsx src/tuiClient/tests/Palettes.test.tsx
  • npm --prefix apps/desktop run typecheck
  • npm --prefix apps/desktop run lint
  • npm --prefix apps/desktop run build
  • npm --prefix apps/desktop exec -- vitest run src/renderer/components/files/v2/recentFiles.test.ts src/renderer/components/files/monacoModelRegistry.test.ts src/renderer/lib/dirtyWorkspaceBuffers.test.ts src/renderer/components/history/historyGitActions.test.ts src/main/services/remoteRuntime/remoteConnectionPool.test.ts
  • npm run test:desktop:sharded

Greptile Summary

This PR closes two feature-gap milestones (ADE-77 TUI papercuts and ADE-70 Files Workbench v2 blockers) across the CLI and desktop apps, plus a flaky test fix in the remote-runtime connection pool.

  • TUI (ADE-77): Adds debounced/cached @-mention remote fetches, startup auto-retry UI with r-key shortcut, terminal interactive-mode restore on every exit path, distinct selected/unselected image-chip glyphs, and no-match palette rows for both slash and mention palettes. Both empty-state palette components render a redundant extra body line, making the palette one row taller than the populated state and displaying two overlapping "no results" messages.
  • Files Workbench (ADE-70): Introduces dirty-buffer publication, reloadTokensByPath for clean-editor watcher reloads, dirty-state confirmation guards before workspace switch/rename/delete, diff-mode save via registry.getValue, localStorage-persisted recents, and a beforeunload gate. The reload flow calls refreshClean before disposing the previous onDidChangeContent subscription; the stale listener invokes onEdit, silently promoting preview tabs to permanent on external file changes.
  • Remote runtime (test fix): closeLocalPortForwardsForTarget is now async and awaits net.Server.close callbacks, eliminating the audit-flake in port-forward disconnect tests.

Confidence Score: 4/5

Safe to merge with awareness of three cosmetic/behavioral regressions introduced alongside the larger fixes.

The palette empty-state rendering emits a duplicate 'no results' row in both MentionPalette and SlashPalette, making those widgets visually inconsistent. The refreshClean→setValue→stale-listener chain silently promotes preview tabs to permanent on external file reloads. A chat submit error is no longer surfaced to the user. None of these are data-destroying, and the core dirty-buffer, watcher-reload, and terminal-restore logic is well-structured, but three separate behavioral regressions in the changed paths warrant attention before shipping.

MentionPalette.tsx and SlashPalette.tsx (duplicate empty-state row); CodeViewer.tsx (subscription disposal ordering around refreshClean); app.tsx (removed chat error display).

Important Files Changed

Filename Overview
apps/ade-cli/src/tuiClient/app.tsx Large set of TUI fixes: debounced/cached @-mention remote fetches, startup retry UI with auto-retry timer, terminal mode restore on all exit paths, palette open/close logic corrected, image chip glyph distinction fixed. A chat submit error is now silently swallowed after removing setError.
apps/ade-cli/src/tuiClient/components/MentionPalette.tsx Adds empty-state rendering when suggestions are absent. Contains a duplicate 'no results' body line that extends the palette height beyond the normal VISIBLE_ROWS count.
apps/ade-cli/src/tuiClient/components/SlashPalette.tsx Adds empty-state rendering when no slash commands match. Has the same duplicate extra body line issue as MentionPalette.
apps/desktop/src/renderer/components/files/v2/FilesWorkbench.tsx Substantial new guards: dirty-buffer publication via dirtyBufferRevision, dirty-state checks before workspace switch/window reload/rename/delete, reloadTokensByPath for watcher-triggered clean reloads, handleCloseOthers with dirty confirmation, and beforeunload protection. Logic is largely correct.
apps/desktop/src/renderer/components/files/v2/viewers/CodeViewer.tsx Wires onError, adds content.languageId dep, and calls refreshClean before getOrCreate. The ordering of refreshClean before disposing the old changeSubRef listener can silently promote preview tabs on external reloads.
apps/desktop/src/renderer/components/files/monacoModelRegistry.ts Adds refreshClean and setLanguage helpers. refreshClean correctly guards on getAlternativeVersionId equality before updating clean models; logic is sound.
apps/desktop/src/renderer/components/files/v2/recentFiles.ts Migrates recents from in-memory to localStorage with safe JSON parsing, quota-error handling, and new forgetRecentFilesUnder for recursive path removal. Logic is clean.
apps/desktop/src/main/services/remoteRuntime/remoteConnectionPool.ts Converts disconnect/dispose/closeLocalPortForwardsForTarget to async, awaiting net.Server.close callbacks so tests can reliably detect teardown completion. Change is self-contained and correctly propagated.
apps/desktop/src/renderer/lib/dirtyWorkspaceBuffers.ts Factors out replaceDirtyBufferValuesForWorkspace from replaceDirtyBuffersForWorkspace so the new Files v2 code can publish dirty content without needing a savedContent baseline. Logic is correct.
apps/desktop/src/renderer/components/files/v2/EditorGroup.tsx Adds global keydown save handler, diff-mode save fallback via registry.getValue, onError propagation, and reloadToken pass-through. Guards against Monaco editor and text inputs are correct.
apps/desktop/src/renderer/components/history/CommitDetailPanel.tsx Surfaces the first actionDisabledReason in a small amber banner above git actions, prioritizing lane-related reasons. Clean and isolated change.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Watcher as File Watcher
    participant FW as FilesWorkbench
    participant VH as ViewerHost
    participant CV as CodeViewer
    participant Reg as ModelRegistry

    Watcher->>FW: file changed (workspaceId, path)
    FW->>FW: "tab open & not dirty?"
    FW->>FW: "setReloadTokensByPath({ [path]: token+1 })"
    FW->>VH: reloadToken prop changes
    VH->>VH: useFileContent refetches content
    VH->>CV: content.content changes
    CV->>Reg: refreshClean(path, newContent)
    Reg->>Reg: model clean? setValue(newContent)
    Note over CV,Reg: stale onDidChangeContent fires here calling onEdit() which promotes preview tab
    CV->>CV: changeSubRef.current?.dispose()
    CV->>CV: subscribe new onDidChangeContent
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Watcher as File Watcher
    participant FW as FilesWorkbench
    participant VH as ViewerHost
    participant CV as CodeViewer
    participant Reg as ModelRegistry

    Watcher->>FW: file changed (workspaceId, path)
    FW->>FW: "tab open & not dirty?"
    FW->>FW: "setReloadTokensByPath({ [path]: token+1 })"
    FW->>VH: reloadToken prop changes
    VH->>VH: useFileContent refetches content
    VH->>CV: content.content changes
    CV->>Reg: refreshClean(path, newContent)
    Reg->>Reg: model clean? setValue(newContent)
    Note over CV,Reg: stale onDidChangeContent fires here calling onEdit() which promotes preview tab
    CV->>CV: changeSubRef.current?.dispose()
    CV->>CV: subscribe new onDidChangeContent
Loading

Comments Outside Diff (2)

  1. apps/desktop/src/renderer/components/files/v2/viewers/CodeViewer.tsx, line 158-165 (link)

    P2 Stale onDidChangeContent listener fires during refreshClean, promoting preview tabs

    attachModel calls registry.refreshClean(...) before disposing the old changeSubRef.current. When refreshClean determines the model is clean and calls model.setValue(newContent), Monaco fires onDidChangeContent synchronously — against the still-live old listener. That listener calls onEditCb?.(t.path), which invokes onPromoteTab(group.id, path) and permanently promotes the tab. changeSubRef.current?.dispose() runs only afterward, too late to suppress the event.

    Concretely: a user single-clicks a file (preview tab), an external tool modifies it, the watcher increments reloadToken, content.content changes, attachModel runs, setValue fires the stale listener, and the preview tab becomes permanent silently. A simple fix is to dispose the old subscription before calling refreshClean.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/desktop/src/renderer/components/files/v2/viewers/CodeViewer.tsx
    Line: 158-165
    
    Comment:
    **Stale `onDidChangeContent` listener fires during `refreshClean`, promoting preview tabs**
    
    `attachModel` calls `registry.refreshClean(...)` before disposing the old `changeSubRef.current`. When `refreshClean` determines the model is clean and calls `model.setValue(newContent)`, Monaco fires `onDidChangeContent` synchronously — against the still-live old listener. That listener calls `onEditCb?.(t.path)`, which invokes `onPromoteTab(group.id, path)` and permanently promotes the tab. `changeSubRef.current?.dispose()` runs only afterward, too late to suppress the event.
    
    Concretely: a user single-clicks a file (preview tab), an external tool modifies it, the watcher increments `reloadToken`, `content.content` changes, `attachModel` runs, `setValue` fires the stale listener, and the preview tab becomes permanent silently. A simple fix is to dispose the old subscription before calling `refreshClean`.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

  2. apps/ade-cli/src/tuiClient/components/SlashPalette.tsx, line 148-165 (link)

    P2 Redundant extra body line in slash-palette empty state

    Same pattern as MentionPalette.tsx: after mapping rowLines (which already fills visibleRows body slots with " No matching commands" at index 0), an additional standalone paletteLine(bodyLine("No command matches this query.", ...)) is rendered. This produces a palette that is one row taller than the populated state and displays two "no results" messages simultaneously.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/ade-cli/src/tuiClient/components/SlashPalette.tsx
    Line: 148-165
    
    Comment:
    **Redundant extra body line in slash-palette empty state**
    
    Same pattern as `MentionPalette.tsx`: after mapping `rowLines` (which already fills `visibleRows` body slots with `" No matching commands"` at index 0), an additional standalone `paletteLine(bodyLine("No command matches this query.", ...))` is rendered. This produces a palette that is one row taller than the populated state and displays two "no results" messages simultaneously.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

Fix All in Claude Code

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

---

### Issue 1 of 4
apps/ade-cli/src/tuiClient/components/MentionPalette.tsx:116-134
**Redundant "no results" line in empty-state palette**

The empty-state branch renders `VISIBLE_ROWS` body lines (the first already containing `" No references found"`) and then an extra `paletteLine(bodyLine("No reference matches this query.", ...))` outside the mapped rows. This adds an unwanted extra body line, making the empty-state palette one row taller than the populated one, and displays two overlapping "no results" messages. The identical pattern exists in `SlashPalette.tsx` (`" No matching commands"` + `"No command matches this query."`). The standalone `paletteLine` call after the map should be removed.

### Issue 2 of 4
apps/desktop/src/renderer/components/files/v2/viewers/CodeViewer.tsx:158-165
**Stale `onDidChangeContent` listener fires during `refreshClean`, promoting preview tabs**

`attachModel` calls `registry.refreshClean(...)` before disposing the old `changeSubRef.current`. When `refreshClean` determines the model is clean and calls `model.setValue(newContent)`, Monaco fires `onDidChangeContent` synchronously — against the still-live old listener. That listener calls `onEditCb?.(t.path)`, which invokes `onPromoteTab(group.id, path)` and permanently promotes the tab. `changeSubRef.current?.dispose()` runs only afterward, too late to suppress the event.

Concretely: a user single-clicks a file (preview tab), an external tool modifies it, the watcher increments `reloadToken`, `content.content` changes, `attachModel` runs, `setValue` fires the stale listener, and the preview tab becomes permanent silently. A simple fix is to dispose the old subscription before calling `refreshClean`.

### Issue 3 of 4
apps/ade-cli/src/tuiClient/app.tsx:9757-9760
**Chat submit errors are now silently discarded**

`setError(message)` was removed from the chat submit catch block. After this change, when a chat send fails the prompt is restored (so the user can retry) but there is no visible indication of what went wrong — the error is swallowed. If the intent was to stop using the connection-error banner for transient chat errors, a toast or inline per-message error indicator should replace it; otherwise users are left wondering why their message did not go through.

### Issue 4 of 4
apps/ade-cli/src/tuiClient/components/SlashPalette.tsx:148-165
**Redundant extra body line in slash-palette empty state**

Same pattern as `MentionPalette.tsx`: after mapping `rowLines` (which already fills `visibleRows` body slots with `" No matching commands"` at index 0), an additional standalone `paletteLine(bodyLine("No command matches this query.", ...))` is rendered. This produces a palette that is one row taller than the populated state and displays two "no results" messages simultaneously.

Reviews (1): Last reviewed commit: "Fix TUI papercuts and Files Workbench da..." | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

@vercel

vercel Bot commented Jun 24, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
ade Ignored Ignored Preview Jun 24, 2026 2:11pm

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@arul28, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 38 minutes and 14 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9d3171b1-ede1-4735-8c2f-1da6bd6e1349

📥 Commits

Reviewing files that changed from the base of the PR and between 96260d8 and 41f1900.

⛔ Files ignored due to path filters (3)
  • docs/features/ade-code/README.md is excluded by !docs/**
  • docs/features/files-and-editor/README.md is excluded by !docs/**
  • docs/features/files-and-editor/editor-surfaces.md is excluded by !docs/**
📒 Files selected for processing (26)
  • apps/ade-cli/src/tuiClient/__tests__/ApprovalPrompt.test.tsx
  • apps/ade-cli/src/tuiClient/__tests__/Palettes.test.tsx
  • apps/ade-cli/src/tuiClient/__tests__/appInput.test.ts
  • apps/ade-cli/src/tuiClient/__tests__/appPolling.test.tsx
  • apps/ade-cli/src/tuiClient/app.tsx
  • apps/ade-cli/src/tuiClient/components/ApprovalPrompt.tsx
  • apps/ade-cli/src/tuiClient/components/MentionPalette.tsx
  • apps/ade-cli/src/tuiClient/components/SlashPalette.tsx
  • apps/desktop/src/main/services/remoteRuntime/remoteConnectionPool.test.ts
  • apps/desktop/src/main/services/remoteRuntime/remoteConnectionPool.ts
  • apps/desktop/src/renderer/components/files/monacoModelRegistry.test.ts
  • apps/desktop/src/renderer/components/files/monacoModelRegistry.ts
  • apps/desktop/src/renderer/components/files/v2/EditorGroup.test.tsx
  • apps/desktop/src/renderer/components/files/v2/EditorGroup.tsx
  • apps/desktop/src/renderer/components/files/v2/EditorGroups.tsx
  • apps/desktop/src/renderer/components/files/v2/FilesWorkbench.tsx
  • apps/desktop/src/renderer/components/files/v2/ViewerHost.tsx
  • apps/desktop/src/renderer/components/files/v2/WarmEmptyState.tsx
  • apps/desktop/src/renderer/components/files/v2/overlays.tsx
  • apps/desktop/src/renderer/components/files/v2/recentFiles.test.ts
  • apps/desktop/src/renderer/components/files/v2/recentFiles.ts
  • apps/desktop/src/renderer/components/files/v2/viewers/CodeViewer.tsx
  • apps/desktop/src/renderer/components/files/v2/viewers/types.ts
  • apps/desktop/src/renderer/components/history/CommitDetailPanel.tsx
  • apps/desktop/src/renderer/lib/dirtyWorkspaceBuffers.test.ts
  • apps/desktop/src/renderer/lib/dirtyWorkspaceBuffers.ts
📝 Walkthrough

Walkthrough

The PR adds terminal restore safety and connection-retry UX to the ADE CLI TUI, debounces and caches remote @ mention RPCs, renders palette empty states instead of returning null, and removes the 6-option cap in ApprovalPrompt. On the desktop side it introduces dirty-buffer tracking with localStorage-backed recent files persistence, selective per-path reload tokens, an onError callback chain from ViewerProps up through EditorGroups to FilesWorkbench, a refreshClean method in the Monaco model registry, async SSH port-forward teardown in RemoteConnectionPool, and a disabled-reason notice in CommitDetailPanel.

Changes

ADE CLI TUI hardening and UX improvements

Layer / File(s) Summary
Terminal sequence helpers, process-restore hook, and exit wiring
apps/ade-cli/src/tuiClient/app.tsx, apps/ade-cli/src/tuiClient/__tests__/appInput.test.ts
Extracts alternate-screen/scroll sequences into exported pure helpers, composes them into terminalInteractiveRestoreSequence, registers a mount-once process-exit handler, and calls restore on heartbeat shutdown and requestAppExit. Tests assert the composed sequence.
Startup connection retry with timer and keyboard handler
apps/ade-cli/src/tuiClient/app.tsx, apps/ade-cli/src/tuiClient/__tests__/appPolling.test.tsx
Adds connectionRetrySeq state and retryStartupConnection; on failure stops the heartbeat, sets error with retry hint, schedules an unref'd timer; r/R key retries when disconnected. Footer shows "r retry now · Ctrl+C quit". Tests simulate the failure-then-retry cycle.
Debounced and cached remote mention RPCs
apps/ade-cli/src/tuiClient/app.tsx, apps/ade-cli/src/tuiClient/__tests__/appPolling.test.tsx
Adds MENTION_REMOTE_DEBOUNCE_MS and a lane-scoped mentionRemoteCacheRef; mention effect merges local + attached suggestions immediately, delays remote file/commit/PR RPC calls with per-lane/query caching, and cancels timers on cleanup. Tests assert debounce call-counts.
Palette empty states, open logic, and ApprovalPrompt fix
apps/ade-cli/src/tuiClient/components/MentionPalette.tsx, apps/ade-cli/src/tuiClient/components/SlashPalette.tsx, apps/ade-cli/src/tuiClient/components/ApprovalPrompt.tsx, apps/ade-cli/src/tuiClient/__tests__/Palettes.test.tsx, apps/ade-cli/src/tuiClient/__tests__/ApprovalPrompt.test.tsx
MentionPalette and SlashPalette render "0 matches" empty states instead of returning null. Palette-open detection keys off activeMentionRange != null. ApprovalPrompt removes the 6-option limit. Tests cover all three behaviors.

Desktop files workbench and supporting services

Layer / File(s) Summary
Dirty buffer workspace storage and recentFiles localStorage persistence
apps/desktop/src/renderer/lib/dirtyWorkspaceBuffers.ts, apps/desktop/src/renderer/lib/dirtyWorkspaceBuffers.test.ts, apps/desktop/src/renderer/components/files/v2/recentFiles.ts, apps/desktop/src/renderer/components/files/v2/recentFiles.test.ts
Adds replaceDirtyBufferValuesForWorkspace for path-normalized buffer replacement. recentFiles switches to localStorage-backed storage with bounded de-duplication, subtree forgetting, and safe parse/quota guards. Tests cover both.
Monaco model registry refreshClean and setLanguage helper
apps/desktop/src/renderer/components/files/monacoModelRegistry.ts, apps/desktop/src/renderer/components/files/monacoModelRegistry.test.ts, apps/desktop/src/renderer/components/files/v2/viewers/CodeViewer.tsx
Extracts a setLanguage guard helper; refreshClean validates model cleanliness via getAlternativeVersionId before overwriting content. CodeViewer calls refreshClean in attachModel and extends its effect dependency on languageId. Tests verify clean vs. dirty refresh behavior.
onError propagation: ViewerProps → CodeViewer → ViewerHost → EditorGroup → EditorGroups
apps/desktop/src/renderer/components/files/v2/viewers/types.ts, apps/desktop/src/renderer/components/files/v2/viewers/CodeViewer.tsx, apps/desktop/src/renderer/components/files/v2/ViewerHost.tsx, apps/desktop/src/renderer/components/files/v2/EditorGroup.tsx, apps/desktop/src/renderer/components/files/v2/EditorGroups.tsx
Adds optional onError to ViewerProps and ViewerHostProps; CodeViewer stores it in ctxRef and forwards save failures; EditorGroup implements a useCallback-based saveActive with registry fallback plus useEffect Cmd/Ctrl+S listener; EditorGroups forwards onError and reloadTokensByPath to each group.
FilesWorkbench dirty-state lifecycle, reload tokens, workspace/rename/delete guards
apps/desktop/src/renderer/components/files/v2/FilesWorkbench.tsx
Adds dirtyBufferRevision, reloadTokensByPath, and openTabPaths state; implements dirty-path prune helpers, beforeunload guard, and workspace-scoped buffer persistence. Workspace switch prompts before discard; disk-change events increment reload tokens only for open non-dirty paths; rename/delete confirm subtree discard.
WarmEmptyState visual refresh, overlay font, and CommitDetailPanel disabled reason
apps/desktop/src/renderer/components/files/v2/WarmEmptyState.tsx, apps/desktop/src/renderer/components/files/v2/overlays.tsx, apps/desktop/src/renderer/components/history/CommitDetailPanel.tsx
WarmEmptyState switches to drop-shadow accent logo styling and updates subtitle copy/color. Search overlay preview changes from font-mono to font-sans. CommitDetailPanel computes and renders an amber disabled-reason notice for Git actions.
Async SSH port-forward teardown in RemoteConnectionPool
apps/desktop/src/main/services/remoteRuntime/remoteConnectionPool.ts, apps/desktop/src/main/services/remoteRuntime/remoteConnectionPool.test.ts
disconnect and dispose become async; closeLocalPortForwardsForTarget awaits each server close with optional closeAllConnections; eviction triggers async forward cleanup. Test replaces network-socket failure check with ensureLocalPortForward rejection and forwardOut call-count assertions.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related issues

Suggested labels

desktop

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.37% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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.
Title check ✅ Passed The title matches the PR’s main themes: TUI fixes and Files Workbench gaps tied to ADE-77 and ADE-70.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ade/linear-validation-audit-047d470e

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.

@mintlify

mintlify Bot commented Jun 24, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
ade-ac1c6011 🟢 Ready View Preview Jun 24, 2026, 1:58 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@arul28 arul28 force-pushed the ade/linear-validation-audit-047d470e branch from 96260d8 to e36c66a Compare June 24, 2026 13:59
@arul28 arul28 force-pushed the ade/linear-validation-audit-047d470e branch from e36c66a to 26a85a7 Compare June 24, 2026 14:00
@arul28 arul28 changed the title Linear Validation Audit Fix ADE-77 TUI papercuts and ADE-70 Files Workbench gaps Jun 24, 2026
@linear-code

linear-code Bot commented Jun 24, 2026

Copy link
Copy Markdown

ADE-77

ADE-70

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (3)
apps/ade-cli/src/tuiClient/components/ApprovalPrompt.tsx (1)

352-359: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use a collision-safe React key for legacy options.

Now that all options render, key={option.value} can collide when values repeat, which can cause unstable row updates.

🐛 Proposed fix
-              <Box key={option.value} flexDirection="row">
+              <Box key={`${option.value}:${index}`} flexDirection="row">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ade-cli/src/tuiClient/components/ApprovalPrompt.tsx` around lines 352 -
359, The legacy options list in ApprovalPrompt now renders every entry, so using
key={option.value} can create duplicate React keys when values repeat. Update
the key generation in the options.map block to use a collision-safe identifier
that is unique per rendered row, such as the existing optionId or another stable
composite based on the option’s position and identity. Keep the change localized
to the ApprovalPrompt rendering logic so each Box gets a unique key.
apps/desktop/src/main/services/remoteRuntime/remoteConnectionPool.ts (1)

772-817: 🩺 Stability & Availability | 🟠 Major

Await async dispose() in both disconnect branches

entry.dispose(true, false) now returns Promise<void> here, but both paths fire-and-forget it. Rejected disposals can escape the local .catch(), and disconnect() can finish before teardown completes. Await or return the promise in both branches, and in the resolved-entry path wait for it alongside forwardsClosed.

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

In `@apps/desktop/src/main/services/remoteRuntime/remoteConnectionPool.ts` around
lines 772 - 817, The disconnect flow in remoteConnectionPool’s disconnect() is
not awaiting async cleanup from entry.dispose(true, false), so teardown can
finish early and disposal rejections can bypass the local catch. Update both the
resolved-entry branch and the pending-entry promise handler to await or return
the dispose promise, and make the resolved-entry path wait for that cleanup
together with forwardsClosed before exiting.
apps/ade-cli/src/tuiClient/app.tsx (1)

6843-6935: 🎯 Functional Correctness | 🟡 Minor

Add retry coverage for embedded ADE CLI launches. The startup-reconnect test exercises only one mocked connectToAde path; add a forceEmbedded/headless case so retry behavior is covered in both embedded and socket-backed modes.

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

In `@apps/ade-cli/src/tuiClient/app.tsx` around lines 6843 - 6935, The startup
retry coverage in app.tsx only exercises the socket-backed connectToAde flow, so
add a test case that runs the same retry path with forceEmbedded enabled and
headless mode to cover embedded ADE CLI launches as well. Update the existing
startup-reconnect test setup around connectToAde and the retry effect in
retryStartupConnection/useEffect so both embedded and socket-backed startup
failures verify the automatic reconnect behavior.

Source: Coding guidelines

🧹 Nitpick comments (2)
apps/ade-cli/src/tuiClient/components/SlashPalette.tsx (1)

137-139: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Short-circuit before building rows.

When the query is not slash-prefixed, the component returns null anyway, so computing paletteCommands(...) first is avoidable work.

♻️ Proposed change
-  const rows = paletteCommands(query, userCommands, { provider });
-  if (!query.startsWith("/")) return null;
+  if (!query.startsWith("/")) return null;
+  const rows = paletteCommands(query, userCommands, { provider });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ade-cli/src/tuiClient/components/SlashPalette.tsx` around lines 137 -
139, Move the non-slash guard in SlashPalette before the paletteCommands call so
the component returns null immediately when query does not start with "/". Use
the existing query.startsWith("/") check in SlashPalette to short-circuit first,
then build rows only for valid slash queries to avoid unnecessary work.
apps/ade-cli/src/tuiClient/__tests__/appPolling.test.tsx (1)

244-249: 📐 Maintainability & Code Quality | 🔵 Trivial

Use the shared reconnect delay constant here.

3_000 matches STARTUP_RECONNECT_DELAY_MS in apps/ade-cli/src/tuiClient/app.tsx. Export that constant and use it in this test so the assertion stays aligned if the reconnect schedule changes.

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

In `@apps/ade-cli/src/tuiClient/__tests__/appPolling.test.tsx` around lines 244 -
249, The test is hardcoding the reconnect wait instead of using the shared
startup reconnect delay, so update the polling test to reference the exported
reconnect delay constant from app.tsx rather than 3_000. Expose
STARTUP_RECONNECT_DELAY_MS from app.tsx if needed, then use that symbol in
appPolling.test.tsx so the expectation stays aligned with reconnect timing
changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/ade-cli/src/tuiClient/app.tsx`:
- Around line 10765-10775: While disconnected in app.tsx, the input guard in the
connection error branch should swallow every key except the explicit exit and
retry actions handled by requestAppExit, retryStartupConnection, and isCtrlInput
for Ctrl+C. Update the early-return logic around connectionRef.current and error
so keys like Esc, Tab, and other global shortcuts do not fall through and mutate
hidden TUI state; keep only the allowed retry/exit paths and block everything
else until the connection is restored.

In `@apps/desktop/src/renderer/components/files/v2/EditorGroup.tsx`:
- Around line 104-114: The global keydown listener in EditorGroup’s useEffect is
too broad and can intercept Cmd/Ctrl+S from unrelated inputs while the group is
active. Update the EditorGroup shortcut handling so saveActive() only fires when
focus is within this group and not inside Monaco or other text inputs/dialog
controls, using the existing activeTab, canEdit, and props.isActiveGroup checks
plus a focus/container-based guard around the onKey handler.

---

Outside diff comments:
In `@apps/ade-cli/src/tuiClient/app.tsx`:
- Around line 6843-6935: The startup retry coverage in app.tsx only exercises
the socket-backed connectToAde flow, so add a test case that runs the same retry
path with forceEmbedded enabled and headless mode to cover embedded ADE CLI
launches as well. Update the existing startup-reconnect test setup around
connectToAde and the retry effect in retryStartupConnection/useEffect so both
embedded and socket-backed startup failures verify the automatic reconnect
behavior.

In `@apps/ade-cli/src/tuiClient/components/ApprovalPrompt.tsx`:
- Around line 352-359: The legacy options list in ApprovalPrompt now renders
every entry, so using key={option.value} can create duplicate React keys when
values repeat. Update the key generation in the options.map block to use a
collision-safe identifier that is unique per rendered row, such as the existing
optionId or another stable composite based on the option’s position and
identity. Keep the change localized to the ApprovalPrompt rendering logic so
each Box gets a unique key.

In `@apps/desktop/src/main/services/remoteRuntime/remoteConnectionPool.ts`:
- Around line 772-817: The disconnect flow in remoteConnectionPool’s
disconnect() is not awaiting async cleanup from entry.dispose(true, false), so
teardown can finish early and disposal rejections can bypass the local catch.
Update both the resolved-entry branch and the pending-entry promise handler to
await or return the dispose promise, and make the resolved-entry path wait for
that cleanup together with forwardsClosed before exiting.

---

Nitpick comments:
In `@apps/ade-cli/src/tuiClient/__tests__/appPolling.test.tsx`:
- Around line 244-249: The test is hardcoding the reconnect wait instead of
using the shared startup reconnect delay, so update the polling test to
reference the exported reconnect delay constant from app.tsx rather than 3_000.
Expose STARTUP_RECONNECT_DELAY_MS from app.tsx if needed, then use that symbol
in appPolling.test.tsx so the expectation stays aligned with reconnect timing
changes.

In `@apps/ade-cli/src/tuiClient/components/SlashPalette.tsx`:
- Around line 137-139: Move the non-slash guard in SlashPalette before the
paletteCommands call so the component returns null immediately when query does
not start with "/". Use the existing query.startsWith("/") check in SlashPalette
to short-circuit first, then build rows only for valid slash queries to avoid
unnecessary work.
🪄 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: feac72d9-1d64-481f-adc0-a3558a510f34

📥 Commits

Reviewing files that changed from the base of the PR and between 919be29 and 96260d8.

⛔ Files ignored due to path filters (7)
  • CHANGELOG.md is excluded by !*.md
  • changelog/index.mdx is excluded by !changelog/**
  • changelog/v1.2.9.mdx is excluded by !changelog/**
  • docs.json is excluded by !docs.json
  • docs/features/ade-code/README.md is excluded by !docs/**
  • docs/features/files-and-editor/README.md is excluded by !docs/**
  • docs/features/files-and-editor/editor-surfaces.md is excluded by !docs/**
📒 Files selected for processing (25)
  • apps/ade-cli/src/tuiClient/__tests__/ApprovalPrompt.test.tsx
  • apps/ade-cli/src/tuiClient/__tests__/Palettes.test.tsx
  • apps/ade-cli/src/tuiClient/__tests__/appInput.test.ts
  • apps/ade-cli/src/tuiClient/__tests__/appPolling.test.tsx
  • apps/ade-cli/src/tuiClient/app.tsx
  • apps/ade-cli/src/tuiClient/components/ApprovalPrompt.tsx
  • apps/ade-cli/src/tuiClient/components/MentionPalette.tsx
  • apps/ade-cli/src/tuiClient/components/SlashPalette.tsx
  • apps/desktop/src/main/services/remoteRuntime/remoteConnectionPool.test.ts
  • apps/desktop/src/main/services/remoteRuntime/remoteConnectionPool.ts
  • apps/desktop/src/renderer/components/files/monacoModelRegistry.test.ts
  • apps/desktop/src/renderer/components/files/monacoModelRegistry.ts
  • apps/desktop/src/renderer/components/files/v2/EditorGroup.tsx
  • apps/desktop/src/renderer/components/files/v2/EditorGroups.tsx
  • apps/desktop/src/renderer/components/files/v2/FilesWorkbench.tsx
  • apps/desktop/src/renderer/components/files/v2/ViewerHost.tsx
  • apps/desktop/src/renderer/components/files/v2/WarmEmptyState.tsx
  • apps/desktop/src/renderer/components/files/v2/overlays.tsx
  • apps/desktop/src/renderer/components/files/v2/recentFiles.test.ts
  • apps/desktop/src/renderer/components/files/v2/recentFiles.ts
  • apps/desktop/src/renderer/components/files/v2/viewers/CodeViewer.tsx
  • apps/desktop/src/renderer/components/files/v2/viewers/types.ts
  • apps/desktop/src/renderer/components/history/CommitDetailPanel.tsx
  • apps/desktop/src/renderer/lib/dirtyWorkspaceBuffers.test.ts
  • apps/desktop/src/renderer/lib/dirtyWorkspaceBuffers.ts

Comment thread apps/ade-cli/src/tuiClient/app.tsx
Comment thread apps/desktop/src/renderer/components/files/v2/EditorGroup.tsx
@arul28 arul28 force-pushed the ade/linear-validation-audit-047d470e branch from 26a85a7 to 41f1900 Compare June 24, 2026 14:11
@arul28 arul28 merged commit 7768a64 into main Jun 24, 2026
27 checks passed
@arul28 arul28 deleted the ade/linear-validation-audit-047d470e branch June 24, 2026 14:18
Comment on lines +116 to 134
...Array.from({ length: Math.max(0, VISIBLE_ROWS - 1) }, () => bodyLine("", paletteWidth)),
];
const footer = bottomLine("Type to filter · Esc close", paletteWidth);
return (
<Box width={paletteWidth} flexDirection="column">
{paletteLine(header, theme.color.violet)}
{rowLines.map((line, index) => (
<React.Fragment key={index}>
{paletteLine(line, index === 0 ? theme.color.t3 : theme.color.t2)}
</React.Fragment>
))}
{paletteLine(bodyLine("No reference matches this query.", paletteWidth), theme.color.t3)}
{paletteLine(footer, theme.color.t4)}
</Box>
);
}
const total = suggestions.length;
const safeIndex = Math.max(0, Math.min(selectedIndex, total - 1));
const half = Math.floor(VISIBLE_ROWS / 2);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Redundant "no results" line in empty-state palette

The empty-state branch renders VISIBLE_ROWS body lines (the first already containing " No references found") and then an extra paletteLine(bodyLine("No reference matches this query.", ...)) outside the mapped rows. This adds an unwanted extra body line, making the empty-state palette one row taller than the populated one, and displays two overlapping "no results" messages. The identical pattern exists in SlashPalette.tsx (" No matching commands" + "No command matches this query."). The standalone paletteLine call after the map should be removed.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/ade-cli/src/tuiClient/components/MentionPalette.tsx
Line: 116-134

Comment:
**Redundant "no results" line in empty-state palette**

The empty-state branch renders `VISIBLE_ROWS` body lines (the first already containing `" No references found"`) and then an extra `paletteLine(bodyLine("No reference matches this query.", ...))` outside the mapped rows. This adds an unwanted extra body line, making the empty-state palette one row taller than the populated one, and displays two overlapping "no results" messages. The identical pattern exists in `SlashPalette.tsx` (`" No matching commands"` + `"No command matches this query."`). The standalone `paletteLine` call after the map should be removed.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code

Comment on lines 9757 to 9760
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
setStreaming(false);
setError(message);
if (submittedValue.trim() || promptAttachments.length) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Chat submit errors are now silently discarded

setError(message) was removed from the chat submit catch block. After this change, when a chat send fails the prompt is restored (so the user can retry) but there is no visible indication of what went wrong — the error is swallowed. If the intent was to stop using the connection-error banner for transient chat errors, a toast or inline per-message error indicator should replace it; otherwise users are left wondering why their message did not go through.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/ade-cli/src/tuiClient/app.tsx
Line: 9757-9760

Comment:
**Chat submit errors are now silently discarded**

`setError(message)` was removed from the chat submit catch block. After this change, when a chat send fails the prompt is restored (so the user can retry) but there is no visible indication of what went wrong — the error is swallowed. If the intent was to stop using the connection-error banner for transient chat errors, a toast or inline per-message error indicator should replace it; otherwise users are left wondering why their message did not go through.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code

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.

1 participant