Skip to content

Files tab: multi-lane open tabs and remove edit trust#671

Merged
arul28 merged 4 commits into
mainfrom
cursor/files-tab-multi-lane-6028
Jun 30, 2026
Merged

Files tab: multi-lane open tabs and remove edit trust#671
arul28 merged 4 commits into
mainfrom
cursor/files-tab-multi-lane-6028

Conversation

@arul28

@arul28 arul28 commented Jun 30, 2026

Copy link
Copy Markdown
Owner

Summary

Refactors the Files tab so open files persist across lane switches and editing is always allowed (except external mobileReadOnly workspaces).

Changes

  • Unified tab store — Project-level session key (filesProjectSessionKey) replaces per-lane tab sessions; legacy per-lane sessions are merged on first load.
  • Composite tab identity — Each tab carries workspaceId, laneId, and id (workspaceId::path) so the same relative path in different lanes can coexist.
  • Lane switch behavior — Switching lanes only updates the explorer tree; open tabs and Monaco models stay alive until explicitly closed.
  • Tab scope toggle (default: All lanes) — Toolbar control to show all open tabs with lane-colored left borders and lane-grouped ordering, or filter to the current lane only (no lane colors).
  • Per-tab editor context — Viewer, diff, save, and dirty tracking resolve workspace/root/lane per tab instead of the explorer workspace.
  • Removed edit trust UI — Dropped editOverrides, the Enable editing button, and isReadOnlyByDefault gating in the Files renderer.

Tests

  • editorGroupsStore.test.ts — composite ids, cross-workspace tabs, legacy session merge
  • tabDisplayOrder.test.ts — lane grouping and lane-only filtering
  • filesTabScope.test.ts — scope persistence
  • EditorGroup.test.tsx — updated for per-tab context props
Open in Web Open in Cursor 

ADE   Open in ADE  ·  cursor/files-tab-multi-lane-6028 branch  ·  PR #671

Summary by CodeRabbit

  • New Features

    • Added a tab-scope setting to switch file tabs between all files and the current lane.
    • Introduced lane-aware tab ordering and visual lane separation in the editor.
  • Bug Fixes

    • Improved tab tracking so files stay correctly open, dirty, and reloadable across workspaces and lanes.
    • Preserved unsaved changes when switching workspaces and refined discard confirmations.
  • Tests

    • Expanded coverage for tab scope, lane ordering, editor behavior, and workspace switch scenarios.

Greptile Summary

This PR refactors the Files tab from per-lane isolated tab sessions to a single unified project-level session, so open files persist across lane switches. It also removes the "Enable editing" opt-in flow, making every non-read-only workspace always editable.

  • Unified tab store & composite IDs — tabs now carry workspaceId, laneId, and a composite id (workspaceId::path), so the same relative path in different lanes coexists in one tab strip; a one-time migration merges legacy per-lane sessions on first load.
  • Scope toggle & lane-grouped display — a new toolbar control lets users filter the strip to the current lane only; tabs are sorted by lane with colour-coded left borders, backed by tabDisplayOrder.ts and filesTabScope.ts.
  • Per-tab editor contextresolveTabContext replaces the single explorer-level workspace props so viewer, save, diff, and dirty tracking each use their tab's own workspace, root path, and lane.

Confidence Score: 5/5

Safe to merge; the architectural shift is well-contained behind stable reducer interfaces and the migration path is covered by unit tests.

The composite-ID refactor is consistently applied across every callsite (store, registry, viewers, drag-and-drop, file watcher). The legacy session migration runs once per project and is guarded by a ref so it does not re-trigger. The two issues found are both cosmetic or low-impact and do not affect correctness or data safety.

FilesWorkbench.tsx — the openCount calculation and the openWorkspaceIds dependency in the watcher effect are the two spots worth a follow-up.

Important Files Changed

Filename Overview
apps/desktop/src/renderer/components/files/v2/editorGroupsStore.ts Core reducer layer migrated to composite workspaceId::path tab IDs; new mergeLegacyLaneSessions and upgradeLegacySession migration helpers are well-tested and correctly preserve split panes during merge.
apps/desktop/src/renderer/components/files/v2/FilesWorkbench.tsx Largest changed file; unified session key, per-tab context resolution, and workspace-switch preservation are correctly implemented. Two minor issues: openCount overcounts split tabs, and openWorkspaceIds Set identity causes unnecessary watcher effect restarts.
apps/desktop/src/renderer/components/files/v2/EditorGroup.tsx Switched to per-tab context via resolveTabContext; lane-grouped tab ordering, lane-color left-border, and scope-filtered displayTabs are all correctly computed with useMemo.
apps/desktop/src/renderer/components/files/v2/EditorGroups.tsx Adds the scope-toggle toolbar button and passes updated per-tab props; layout and resizable-panel logic unchanged and correct.
apps/desktop/src/renderer/components/files/v2/tabDisplayOrder.ts New module with orderTabsByLane, filterTabsForScope, and isLaneGroupBoundary; all pure and well-tested in tabDisplayOrder.test.ts.
apps/desktop/src/renderer/components/files/v2/filesTabScope.ts New module for persisting the All-lanes/Lane-only toggle in localStorage with a module-level cache; straightforward and covered by unit tests.
apps/desktop/src/renderer/components/files/monacoModelRegistry.ts Mechanical rename from path-keyed to modelKey-keyed (tab ID); no logic changes, correctly updated throughout.
apps/desktop/src/renderer/components/files/v2/viewers/CodeViewer.tsx Uses tab.id instead of tab.path for model registry keying and API registration; the early-return guard on readOnly save is a minor cleanup fix.
apps/desktop/src/renderer/components/files/v2/FilesWorkbench.test.tsx New integration test verifying workspace-switch preserves dirty tabs; setup is thorough and the assertion correctly verifies the key behavioral change.
apps/desktop/src/renderer/components/files/treeHelpers.ts Adds filesProjectSessionKey (JSON-stable key) alongside the legacy filesSessionKey which is kept for migration only.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant FilesWorkbench
    participant EditorGroupsStore
    participant MonacoModelRegistry
    participant FileWatcher

    User->>FilesWorkbench: Open file (workspace-a, src/a.ts)
    FilesWorkbench->>EditorGroupsStore: "openInGroup(tab id=ws-a::src/a.ts)"
    FilesWorkbench->>MonacoModelRegistry: getOrCreate(ws-a::src/a.ts, content)

    User->>FilesWorkbench: Switch lane (explorer to workspace-b)
    FilesWorkbench->>FilesWorkbench: setWorkspaceId(workspace-b)
    Note over EditorGroupsStore: Tab ws-a::src/a.ts stays open
    Note over MonacoModelRegistry: Model kept alive, dirty state preserved

    User->>FilesWorkbench: Click tab from workspace-a
    FilesWorkbench->>EditorGroupsStore: activateTab(tab.id)
    FilesWorkbench->>FilesWorkbench: setWorkspaceId(tab.workspaceId)

    User->>FilesWorkbench: Close tab
    FilesWorkbench->>EditorGroupsStore: closeTab(groupId, tabId)
    alt tab not open in any other group
        FilesWorkbench->>MonacoModelRegistry: dispose(ws-a::src/a.ts)
    end

    FileWatcher->>FilesWorkbench: onChange(workspaceId, path)
    FilesWorkbench->>FilesWorkbench: find open tab by workspaceId+path
    FilesWorkbench->>FilesWorkbench: setReloadTokensByTabId(tabId, token+1)
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 User
    participant FilesWorkbench
    participant EditorGroupsStore
    participant MonacoModelRegistry
    participant FileWatcher

    User->>FilesWorkbench: Open file (workspace-a, src/a.ts)
    FilesWorkbench->>EditorGroupsStore: "openInGroup(tab id=ws-a::src/a.ts)"
    FilesWorkbench->>MonacoModelRegistry: getOrCreate(ws-a::src/a.ts, content)

    User->>FilesWorkbench: Switch lane (explorer to workspace-b)
    FilesWorkbench->>FilesWorkbench: setWorkspaceId(workspace-b)
    Note over EditorGroupsStore: Tab ws-a::src/a.ts stays open
    Note over MonacoModelRegistry: Model kept alive, dirty state preserved

    User->>FilesWorkbench: Click tab from workspace-a
    FilesWorkbench->>EditorGroupsStore: activateTab(tab.id)
    FilesWorkbench->>FilesWorkbench: setWorkspaceId(tab.workspaceId)

    User->>FilesWorkbench: Close tab
    FilesWorkbench->>EditorGroupsStore: closeTab(groupId, tabId)
    alt tab not open in any other group
        FilesWorkbench->>MonacoModelRegistry: dispose(ws-a::src/a.ts)
    end

    FileWatcher->>FilesWorkbench: onChange(workspaceId, path)
    FilesWorkbench->>FilesWorkbench: find open tab by workspaceId+path
    FilesWorkbench->>FilesWorkbench: setReloadTokensByTabId(tabId, token+1)
Loading

Reviews (2): Last reviewed commit: "ship: address files tab review feedback" | Re-trigger Greptile

Unify editor tab state at the project level so open files persist across
lane switches. Each tab is bound to a workspace/lane with composite ids,
lane-colored borders in all-lanes mode, and a scope toggle to filter tabs
to the current lane only. Remove edit-protected Enable editing gating.

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

vercel Bot commented Jun 30, 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 30, 2026 3:24am

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR replaces path-based tab identity with composite editorTabId(workspaceId, path) keys across the editor group store, viewer components, and FilesWorkbench. It adds lane-aware tab ordering, a scope toggle ("all" / "lane"), legacy session migration, a filesTabScope preference manager, and a tabDisplayOrder utility module.

Changes

Tab-ID and Lane-Scope Editor Refactor

Layer / File(s) Summary
EditorTab identity model and core reducers
apps/desktop/src/renderer/components/files/v2/editorGroupsStore.ts
EditorTab gains id, workspaceId, laneId; editorTabId() is introduced; MRU (touchRecent), activation, close, split, move, and cycle reducers switch from path to tabId; upgradeLegacySession and mergeLegacyLaneSessions are added.
Project-level session key and legacy migration
apps/desktop/src/renderer/components/files/treeHelpers.ts, apps/desktop/src/renderer/components/files/v2/editorGroupsStore.test.ts
filesProjectSessionKey returns a __project__-scoped key; editorGroupsStore tests are updated to use editorTabId-based tab helpers and add coverage for legacy merging and isTabOpenInGroups.
Tab scope preference and lane display ordering
apps/desktop/src/renderer/components/files/v2/filesTabScope.ts, apps/desktop/src/renderer/components/files/v2/filesTabScope.test.ts, apps/desktop/src/renderer/components/files/v2/tabDisplayOrder.ts, apps/desktop/src/renderer/components/files/v2/tabDisplayOrder.test.ts
New FilesTabScope type with localStorage-backed get/set/toggle; orderTabsByLane, filterTabsForScope, and isLaneGroupBoundary utilities with Vitest tests.
Viewer callback contracts and Monaco registry key
apps/desktop/src/renderer/components/files/v2/viewers/types.ts, apps/desktop/src/renderer/components/files/v2/ViewerHost.tsx, apps/desktop/src/renderer/components/files/monacoModelRegistry.ts
ViewerProps/ViewerHostProps callbacks changed from path to tabId; non-code viewer React keys changed to tab.id; monacoModelRegistry.getOrCreate accepts modelKey instead of path.
CodeViewer and MarkdownViewer tab-id tracking
apps/desktop/src/renderer/components/files/v2/viewers/CodeViewer.tsx, apps/desktop/src/renderer/components/files/v2/viewers/MarkdownViewer.tsx
Registration, save, dirty tracking, and attachModel switch from tab.path to tab.id; MarkdownViewer registry lookup uses tab.id.
EditorGroup and EditorGroups component refactor
apps/desktop/src/renderer/components/files/v2/EditorGroup.tsx, apps/desktop/src/renderer/components/files/v2/EditorGroup.test.tsx, apps/desktop/src/renderer/components/files/v2/EditorGroups.tsx
EditorGroupProps/EditorGroupsProps replace path-based props with lane-aware tab-id props; TabWorkspaceContext is exported; scope toggle button added; TabButton gains laneAccent/showLaneDivider; ViewerHost driven from activeContext; DnD payload uses tab.id.
FilesWorkbench orchestration and dirty-tab lifecycle
apps/desktop/src/renderer/components/files/v2/FilesWorkbench.tsx, apps/desktop/src/renderer/components/files/v2/FilesWorkbench.test.tsx
Dirty state, reload tokens, before-unload, and buffer flushing switch to tab IDs; resolveTabContext added; openFile, close/rename/delete, DnD, and file-watching updated for tab IDs; legacy migration effect added; integration test verifies dirty tab preservation across workspace switches.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • arul28/ADE#626: Both PRs modify FilesWorkbench.tsx file-watching and tree-refresh logic; the overlap is in how watched workspaces and invalidated content are managed.
  • arul28/ADE#630: Touches FilesWorkbench tab-open and workspace-switching flow, which directly intersects with this PR's openFile and handleActivateTab tab-id changes.

Suggested labels

desktop

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% 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
Title check ✅ Passed The title clearly matches the main refactor: multi-lane open tabs and removal of edit trust gating.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/files-tab-multi-lane-6028

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 30, 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 30, 2026, 1:24 AM

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

- Only dispose Monaco models when tab id is closed in all groups
- Align lane-only editor body with filtered tab strip
- Filter lane scope by explorer workspace lane, not global lane
- Remove dead props, simplify lane group boundaries, fix registry bug

Co-authored-by: Arul Sharma <arul28@users.noreply.github.com>
@arul28 arul28 marked this pull request as ready for review June 30, 2026 02:51
@arul28

arul28 commented Jun 30, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cda10d6559

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const [editOverrides, setEditOverrides] = useState<Set<string>>(() => new Set());
const editOverride = workspaceId ? editOverrides.has(workspaceId) : false;
const canEdit = workspace ? (!workspace.isReadOnlyByDefault || editOverride) : false;
const canEdit = workspace != null && !workspace.mobileReadOnly;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep desktop lane workspaces editable

On desktop this makes every normal repo/lane workspace read-only: createFileService().listWorkspaces() stamps mobileReadOnly: true on each primary/worktree workspace (apps/desktop/src/main/services/files/fileService.ts:648-655), so canEdit becomes false after the real workspace list loads. That disables create/rename/delete here and the same predicate in resolveTabContext hides save/write for code tabs, contradicting the intended removal of edit-trust gating. Restrict the mobileReadOnly check to external/mobile-only workspaces, e.g. workspace.kind === "external" && workspace.mobileReadOnly.

Useful? React with 👍 / 👎.

@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: 6

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/files/v2/viewers/CodeViewer.tsx (1)

38-52: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Guard the save path when the viewer is read-only.

readOnly only skips formatting here; writeText still runs. A mobileReadOnly workspace can still hit this via Monaco Cmd/Ctrl+S or the registered editor API.

Proposed fix
     const { workspaceId: ws, tab: t, registry: reg, onDirtyChange: onDirty } = ctxRef.current;
     const editor = editorRef.current;
     if (!editor) return;
-    if (!ctxRef.current.readOnly) {
-      try {
-        await editor.getAction("editor.action.formatDocument")?.run();
-      } catch {
-        // formatter may be unavailable for this language — save unformatted
-      }
+    if (ctxRef.current.readOnly) return;
+    try {
+      await editor.getAction("editor.action.formatDocument")?.run();
+    } catch {
+      // formatter may be unavailable for this language — save unformatted
     }

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

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

In `@apps/desktop/src/renderer/components/files/v2/viewers/CodeViewer.tsx` around
lines 38 - 52, The save flow in CodeViewer’s save ref should fully short-circuit
for read-only viewers, not just skip formatting. Update the async save handler
so that when ctxRef.current.readOnly is true it returns before calling
window.ade.files.writeText, while still preserving the existing behavior for
editable tabs. Use the existing save, ctxRef, editorRef, and reg/onDirty logic
to ensure Monaco Cmd/Ctrl+S or the registered editor API cannot persist changes
in a read-only workspace.

Source: Path instructions

🧹 Nitpick comments (3)
apps/desktop/src/renderer/components/files/monacoModelRegistry.ts (1)

11-21: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Finish the key-parameter rename across the registry API.

The docs and getOrCreate now use modelKey, but the rest of this returned API still names the same cache key path. Since callers now pass tab IDs, keep the public surface consistent to avoid accidental path-keyed calls.

Proposed cleanup
-      path: string,
+      modelKey: string,
...
-      const entry = models.get(path);
+      const entry = models.get(modelKey);
...
-    markSaved(path: string): void {
-      const entry = models.get(path);
+    markSaved(modelKey: string): void {
+      const entry = models.get(modelKey);
...
-    isDirty(path: string): boolean {
-      const entry = models.get(path);
+    isDirty(modelKey: string): boolean {
+      const entry = models.get(modelKey);
...
-    getValue(path: string): string | null {
-      const entry = models.get(path);
+    getValue(modelKey: string): string | null {
+      const entry = models.get(modelKey);
...
-    has(path: string): boolean {
-      const entry = models.get(path);
+    has(modelKey: string): boolean {
+      const entry = models.get(modelKey);
...
-    dispose(path: string): void {
-      const entry = models.get(path);
+    dispose(modelKey: string): void {
+      const entry = models.get(modelKey);
       if (!entry) return;
-      models.delete(path);
+      models.delete(modelKey);

Also applies to: 47-59

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

In `@apps/desktop/src/renderer/components/files/monacoModelRegistry.ts` around
lines 11 - 21, The registry API still mixes the old `path` name with the new
`modelKey` terminology, which makes the tab-id keyed cache easy to call
incorrectly. Update the remaining public methods and related typings in
`monacoModelRegistry` so they consistently use `modelKey` across `getOrCreate`,
`dispose`, and any other exported helpers, matching the renamed tab-model key
contract. Also align any internal references in this returned API to the same
symbol names so callers continue passing tab IDs instead of file paths.
apps/desktop/src/renderer/components/files/v2/ViewerHost.tsx (1)

60-82: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Update the keying comment to match tab IDs.

Lines 60-61 still say non-code viewers are keyed by path, but the rendered elements now use key={tab.id}.

Proposed cleanup
-  // Non-code viewers carry per-file view state (zoom, page, scroll), so key them
-  // by path to reset on file change. CodeViewer is intentionally NOT keyed: one
+  // Non-code viewers carry per-tab view state (zoom, page, scroll), so key them
+  // by tab id to reset when the active tab identity changes. CodeViewer is intentionally NOT keyed: one
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/renderer/components/files/v2/ViewerHost.tsx` around lines 60
- 82, Update the keying comment in ViewerHost so it matches the actual behavior
of the switch rendering the non-code viewers. The current comment says these
viewers are keyed by path, but the ImageViewer, MarkdownViewer, CsvViewer,
PdfViewer, MediaViewer, DocumentViewer, LargeTextViewer, and BinaryViewer cases
all use key={tab.id}; revise the comment near the tab.viewerKind switch to
describe tab-id keying and the reset-on-tab-change behavior accurately.
apps/desktop/src/renderer/components/files/v2/editorGroupsStore.test.ts (1)

246-250: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a direct upgradeLegacySession() regression test.

This covers merge behavior, but the startup migration first depends on upgradeLegacySession() to rewrite legacy activeTabId/recentTabIds from paths to ids and backfill workspaceId/laneId. A focused test there would catch sessions that reopen with tabs present but no valid active tab after migration.

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

In `@apps/desktop/src/renderer/components/files/v2/editorGroupsStore.test.ts`
around lines 246 - 250, Add a focused regression test for upgradeLegacySession()
rather than only mergeLegacyLaneSessions(), since startup migration first
rewrites legacy activeTabId/recentTabIds from paths to ids and backfills
workspaceId/laneId. Use the existing helpers and symbols in
editorGroupsStore.test.ts, especially upgradeLegacySession(),
createInitialGroupsState(), openInGroup(), and tab(), to assert that a legacy
session with path-based fields is migrated into a state with a valid active tab
and preserved tab ids. Cover the case where tabs exist but activeTabId would
otherwise be invalid after migration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/desktop/src/renderer/components/files/treeHelpers.ts`:
- Around line 13-15: The project session key in filesProjectSessionKey() can
collide with a lane key because it uses the same projectRoot:: namespace as
filesSessionKey(), so make the project sentinel unambiguous by changing the
project-only suffix/prefix to a value that cannot be a valid lane id. Update any
FilesWorkbench migration/lookup logic that compares against this key so it still
recognizes the project session but never treats a lane session like
"__project__" as the merged project state.

In `@apps/desktop/src/renderer/components/files/v2/editorGroupsStore.ts`:
- Around line 352-369: Preserve split panes in mergeLegacyLaneSessions by
removing the global dedupe behavior that assumes every tab.id is unique across
sessions. Update the merge logic in mergeLegacyLaneSessions, using the existing
helpers openInGroup and activateTab, so repeated tab ids are merged in a way
that keeps split panes as separate group entries instead of collapsing later
occurrences. Ensure the fix still handles the legacy session ordering correctly
while allowing the same tab id to appear in multiple groups when splitGroup has
created that state.

In `@apps/desktop/src/renderer/components/files/v2/filesTabScope.test.ts`:
- Around line 7-18: Reset the module state between test cases because
`getFilesTabScope` caches results in `scopeByProject`, so `localStorage.clear()`
alone leaves stale in-memory data and makes the `filesTabScope.test.ts` suite
order-dependent. Update the test setup to reload the module or clear the cache
before each case, and ensure the `getFilesTabScope` and `setFilesTabScope`
assertions exercise both the cold storage read path and the per-project
persistence behavior.

In `@apps/desktop/src/renderer/components/files/v2/FilesWorkbench.tsx`:
- Around line 918-950: The renamePath and deletePath callbacks can still reach
write IPC even when the workspace is read-only, so add the same canEdit guard
used by the create flow before calling window.ade.files.rename/delete. Update
the logic in FilesWorkbench to return early when canEdit is false, keeping the
mutation handlers aligned with the existing permission checks and protecting
against stale or indirect invocation paths.
- Around line 215-237: The legacy session migration in FilesWorkbench should not
run until the workspace list is fully loaded, because the current useEffect can
mark migratedSessionsRef too early and miss lanes that arrive later from
listWorkspaces(). Update the migration guard in FilesWorkbench so it waits for
the settled workspace state before setting migratedSessionsRef and building
legacySessions. Keep the existing upgradeLegacySession and
mergeLegacyLaneSessions flow, but only trigger it once workspaces are complete
enough to include all lanes that need migration.

In `@apps/desktop/src/renderer/components/files/v2/tabDisplayOrder.ts`:
- Around line 23-29: The lane-only filtering in filterTabsForScope currently
matches tabs by laneId alone, so null-lane tabs from different workspaces get
grouped together. Update this helper and its caller in FilesWorkbench to scope
tabs by the current workspace identity in addition to laneId, and ensure the
"This lane only" branch only keeps tabs belonging to the active workspace when
currentLaneId is null.

---

Outside diff comments:
In `@apps/desktop/src/renderer/components/files/v2/viewers/CodeViewer.tsx`:
- Around line 38-52: The save flow in CodeViewer’s save ref should fully
short-circuit for read-only viewers, not just skip formatting. Update the async
save handler so that when ctxRef.current.readOnly is true it returns before
calling window.ade.files.writeText, while still preserving the existing behavior
for editable tabs. Use the existing save, ctxRef, editorRef, and reg/onDirty
logic to ensure Monaco Cmd/Ctrl+S or the registered editor API cannot persist
changes in a read-only workspace.

---

Nitpick comments:
In `@apps/desktop/src/renderer/components/files/monacoModelRegistry.ts`:
- Around line 11-21: The registry API still mixes the old `path` name with the
new `modelKey` terminology, which makes the tab-id keyed cache easy to call
incorrectly. Update the remaining public methods and related typings in
`monacoModelRegistry` so they consistently use `modelKey` across `getOrCreate`,
`dispose`, and any other exported helpers, matching the renamed tab-model key
contract. Also align any internal references in this returned API to the same
symbol names so callers continue passing tab IDs instead of file paths.

In `@apps/desktop/src/renderer/components/files/v2/editorGroupsStore.test.ts`:
- Around line 246-250: Add a focused regression test for upgradeLegacySession()
rather than only mergeLegacyLaneSessions(), since startup migration first
rewrites legacy activeTabId/recentTabIds from paths to ids and backfills
workspaceId/laneId. Use the existing helpers and symbols in
editorGroupsStore.test.ts, especially upgradeLegacySession(),
createInitialGroupsState(), openInGroup(), and tab(), to assert that a legacy
session with path-based fields is migrated into a state with a valid active tab
and preserved tab ids. Cover the case where tabs exist but activeTabId would
otherwise be invalid after migration.

In `@apps/desktop/src/renderer/components/files/v2/ViewerHost.tsx`:
- Around line 60-82: Update the keying comment in ViewerHost so it matches the
actual behavior of the switch rendering the non-code viewers. The current
comment says these viewers are keyed by path, but the ImageViewer,
MarkdownViewer, CsvViewer, PdfViewer, MediaViewer, DocumentViewer,
LargeTextViewer, and BinaryViewer cases all use key={tab.id}; revise the comment
near the tab.viewerKind switch to describe tab-id keying and the
reset-on-tab-change behavior accurately.
🪄 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: b497631b-68e0-4adc-ab76-d64a63a9457b

📥 Commits

Reviewing files that changed from the base of the PR and between 64b198d and cda10d6.

⛔ Files ignored due to path filters (1)
  • docs/features/files-and-editor/README.md is excluded by !docs/**
📒 Files selected for processing (17)
  • apps/desktop/src/renderer/components/files/monacoModelRegistry.ts
  • apps/desktop/src/renderer/components/files/treeHelpers.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.test.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/editorGroupsStore.test.ts
  • apps/desktop/src/renderer/components/files/v2/editorGroupsStore.ts
  • apps/desktop/src/renderer/components/files/v2/filesTabScope.test.ts
  • apps/desktop/src/renderer/components/files/v2/filesTabScope.ts
  • apps/desktop/src/renderer/components/files/v2/tabDisplayOrder.test.ts
  • apps/desktop/src/renderer/components/files/v2/tabDisplayOrder.ts
  • apps/desktop/src/renderer/components/files/v2/viewers/CodeViewer.tsx
  • apps/desktop/src/renderer/components/files/v2/viewers/MarkdownViewer.tsx
  • apps/desktop/src/renderer/components/files/v2/viewers/types.ts

Comment on lines +13 to +15
/** Project-level session key — unified tab store across all lanes. */
export function filesProjectSessionKey(projectRoot: string): string {
return `${projectRoot}::__project__`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Make the project session key impossible to collide with a lane key.

This shares the same ${projectRoot}::${...} namespace as filesSessionKey(). If a lane id is "__project__", FilesWorkbench will treat that legacy lane session as the already-migrated project session and skip merging the other lanes.

Possible fix
 export function filesProjectSessionKey(projectRoot: string): string {
-  return `${projectRoot}::__project__`;
+  return JSON.stringify({ kind: "files-project-session", projectRoot });
 }
📝 Committable suggestion

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

Suggested change
/** Project-level session key — unified tab store across all lanes. */
export function filesProjectSessionKey(projectRoot: string): string {
return `${projectRoot}::__project__`;
/** Project-level session key — unified tab store across all lanes. */
export function filesProjectSessionKey(projectRoot: string): string {
return JSON.stringify({ kind: "files-project-session", projectRoot });
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/renderer/components/files/treeHelpers.ts` around lines 13 -
15, The project session key in filesProjectSessionKey() can collide with a lane
key because it uses the same projectRoot:: namespace as filesSessionKey(), so
make the project sentinel unambiguous by changing the project-only suffix/prefix
to a value that cannot be a valid lane id. Update any FilesWorkbench
migration/lookup logic that compares against this key so it still recognizes the
project session but never treats a lane session like "__project__" as the merged
project state.

Comment thread apps/desktop/src/renderer/components/files/v2/editorGroupsStore.ts Outdated
Comment thread apps/desktop/src/renderer/components/files/v2/filesTabScope.test.ts
Comment thread apps/desktop/src/renderer/components/files/v2/FilesWorkbench.tsx Outdated
Comment thread apps/desktop/src/renderer/components/files/v2/FilesWorkbench.tsx Outdated
Comment thread apps/desktop/src/renderer/components/files/v2/tabDisplayOrder.ts Outdated
@arul28

arul28 commented Jun 30, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: 94687e767a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@arul28 arul28 merged commit bd5409f into main Jun 30, 2026
27 checks passed
@arul28 arul28 deleted the cursor/files-tab-multi-lane-6028 branch June 30, 2026 03:37
This was referenced Jun 30, 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