Files tab: multi-lane open tabs and remove edit trust#671
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
📝 WalkthroughWalkthroughThe PR replaces path-based tab identity with composite ChangesTab-ID and Lane-Scope Editor Refactor
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 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>
|
@codex review |
There was a problem hiding this comment.
💡 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; |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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 winGuard the save path when the viewer is read-only.
readOnlyonly skips formatting here;writeTextstill runs. AmobileReadOnlyworkspace 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 winFinish the key-parameter rename across the registry API.
The docs and
getOrCreatenow usemodelKey, but the rest of this returned API still names the same cache keypath. 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 winUpdate 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 winAdd a direct
upgradeLegacySession()regression test.This covers merge behavior, but the startup migration first depends on
upgradeLegacySession()to rewrite legacyactiveTabId/recentTabIdsfrom paths to ids and backfillworkspaceId/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
⛔ Files ignored due to path filters (1)
docs/features/files-and-editor/README.mdis excluded by!docs/**
📒 Files selected for processing (17)
apps/desktop/src/renderer/components/files/monacoModelRegistry.tsapps/desktop/src/renderer/components/files/treeHelpers.tsapps/desktop/src/renderer/components/files/v2/EditorGroup.test.tsxapps/desktop/src/renderer/components/files/v2/EditorGroup.tsxapps/desktop/src/renderer/components/files/v2/EditorGroups.tsxapps/desktop/src/renderer/components/files/v2/FilesWorkbench.test.tsxapps/desktop/src/renderer/components/files/v2/FilesWorkbench.tsxapps/desktop/src/renderer/components/files/v2/ViewerHost.tsxapps/desktop/src/renderer/components/files/v2/editorGroupsStore.test.tsapps/desktop/src/renderer/components/files/v2/editorGroupsStore.tsapps/desktop/src/renderer/components/files/v2/filesTabScope.test.tsapps/desktop/src/renderer/components/files/v2/filesTabScope.tsapps/desktop/src/renderer/components/files/v2/tabDisplayOrder.test.tsapps/desktop/src/renderer/components/files/v2/tabDisplayOrder.tsapps/desktop/src/renderer/components/files/v2/viewers/CodeViewer.tsxapps/desktop/src/renderer/components/files/v2/viewers/MarkdownViewer.tsxapps/desktop/src/renderer/components/files/v2/viewers/types.ts
| /** Project-level session key — unified tab store across all lanes. */ | ||
| export function filesProjectSessionKey(projectRoot: string): string { | ||
| return `${projectRoot}::__project__`; |
There was a problem hiding this comment.
🗄️ 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.
| /** 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.
|
@codex review |
|
Codex Review: Didn't find any major issues. Keep it up! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
Summary
Refactors the Files tab so open files persist across lane switches and editing is always allowed (except external
mobileReadOnlyworkspaces).Changes
filesProjectSessionKey) replaces per-lane tab sessions; legacy per-lane sessions are merged on first load.workspaceId,laneId, andid(workspaceId::path) so the same relative path in different lanes can coexist.editOverrides, the Enable editing button, andisReadOnlyByDefaultgating in the Files renderer.Tests
editorGroupsStore.test.ts— composite ids, cross-workspace tabs, legacy session mergetabDisplayOrder.test.ts— lane grouping and lane-only filteringfilesTabScope.test.ts— scope persistenceEditorGroup.test.tsx— updated for per-tab context propsSummary by CodeRabbit
New Features
Bug Fixes
Tests
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.
workspaceId,laneId, and a compositeid(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.tabDisplayOrder.tsandfilesTabScope.ts.resolveTabContextreplaces 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
workspaceId::pathtab IDs; newmergeLegacyLaneSessionsandupgradeLegacySessionmigration helpers are well-tested and correctly preserve split panes during merge.resolveTabContext; lane-grouped tab ordering, lane-color left-border, and scope-filtereddisplayTabsare all correctly computed withuseMemo.orderTabsByLane,filterTabsForScope, andisLaneGroupBoundary; all pure and well-tested in tabDisplayOrder.test.ts.tab.idinstead oftab.pathfor model registry keying and API registration; the early-return guard onreadOnlysave is a minor cleanup fix.filesProjectSessionKey(JSON-stable key) alongside the legacyfilesSessionKeywhich 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)%%{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)Reviews (2): Last reviewed commit: "ship: address files tab review feedback" | Re-trigger Greptile