Repository navigation
feat(editor): Global Editor — split workspace with file editor on left - #129
Merged
Merged
Conversation
A ⌘⇧E (or palette: "Global Editor") overlay that splits the workspace area: file tree + Monaco editor on the LEFT rooted at the active tab's project, the entire normal workspace UI on the RIGHT (dispatch / tile / spotlight / whatever — unchanged, just shrunk). Mode-agnostic — wraps the workspace surface, doesn't replace it. History: this work originated in the un-pushable `feat/code-editor` branch and the open PR #77 (`feat/global-editor`), both authored 2026-05-11. Both branches diverged from main before the agent-code rename (PR #82), the runtime-tools bundling (#121/#125), and the cleanup PRs (#84/#85) that consolidated work-context under src/shared/ and removed verify-tmux + dangerousActions. Trying to rebase either branch would resurrect that deleted code. So this PR is a clean re-implementation on top of current main, lifting only the 13 source files that actually constitute the feature. Implementation: * Editor primitives (reused by any future editor surface): - src/main/ipc/editorFs.ts — read/write/list under a project root, with a sandboxed-path check so the IPC can't be coaxed into touching files outside the agent's cwd. - src/preload/api/editorFs.ts — preload bridge. - src/renderer/src/features/editor/{lib,types,ui}/* — file-icon, monaco theme, path helpers; ExplorerPane (tree), EditorTabs, MonacoFileEditor (with Cmd+S → editorFs write). * Global Editor shell (new): - features/global-editor/store.ts — per-cwd Zustand store (openFiles / fileOrder / activeFilePath keyed by absolute cwd), global splitter ratio clamped [0.2, 0.8]. - features/global-editor/ui/GlobalEditorShell.tsx — outer flex split + draggable splitter (window-level mousemove capture for fast drags). Closed = renders children directly (zero overhead). * Editor cwd follows TABS, not focused pane. PR #77's design used the focused-agent's cwd to drive the editor, which meant switching panes within the same tab would reload the editor (throw away open tabs, rebuild the explorer) — useless noise when the user is just moving focus among same-project panes. This PR ties the editor's cwd to the active tab's currently-focused session AT TAB-CHANGE TIME, captured in a lastSyncedTabIdRef so within-tab pane changes never reach setActiveCwd. Switching to a different tab still flips the editor (per the original intent); switching panes in the same tab leaves the editor exactly where it was. * Wiring (surgical, kept the post-#77 main intact): - uiShell slice/types/store: add globalEditorOpen + open/close/ toggleGlobalEditor actions. Leaves devDebugPanelOpen (PR #112) in place. - App.tsx: <main> is now wrapped in <GlobalEditorShell>; the shell renders children full-bleed when closed. - Command palette: 'Global Editor' command with On/Off badge, matching the existing Performance Stats / Status Mode shape. - useKeybinds: ⌘⇧E global toggle. - main/ipc + preload: register editorFs alongside the other IPC surfaces. Out of scope (deliberately): disk persistence of per-cwd state across restarts; project-search / diagnostics / LSP / multi-cursor; file-tree context menu; cross-cwd unsaved-file warnings on quit. Issues we can file as follow-ups if any of these become real needs. Supersedes #77; closes-via-replacement. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The first iteration of the within-tab-focus-stability fix derived the editor's cwd by reading `tab.focusedSessionId` directly. In dispatch mode that field is always null — focus lives on `dispatchMode.focusedSessionId` instead — so the editor rendered empty for every dispatch-mode user. Restore `useFocusedAgentCwd`, which goes through `commandTargetSessionIdForState` and handles both grid + dispatch focus correctly. Keep the lastSyncedTabIdRef gate that limits commits to actual activeTabId transitions, so within-tab focus shifts (grid pane swap, dispatch row scroll) still don't reload the editor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The shell's outer div used `flex flex-1`, but its parent in App.tsx (`<main>`) is `flex-1 min-h-0 min-w-0 overflow-hidden` without a `flex` direction of its own. flex-1 only sizes when the parent is a flex container, so the shell collapsed to its intrinsic height — visible symptom was the ExplorerPane and Monaco area rendering as two empty black columns with no text, no file tree, nothing visible at all even though the splitter and structure were clearly in place. Switch to `h-full w-full` so the shell fills `<main>` directly, regardless of whether `<main>` is flex. The inner flex-row layout (left pane + splitter + right pane) is unaffected because the shell itself still has `flex` on it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ExplorerPane uses `vscode-icons-js` to choose per-extension icons, and the actual SVGs are served by jsDelivr (the wrapper in src/renderer/src/features/editor/lib/fileIcon.tsx documents this and explicitly notes "We rely on the renderer's CSP allowing https://cdn.jsdelivr.net for img-src" — but the original PR #77 never actually updated the CSP). The user-visible symptom was the file tree rendering text rows with blank squares where the icons should sit. Add `https://cdn.jsdelivr.net` to the existing `img-src` directive in src/renderer/index.html and leave a comment pointing back at fileIcon.tsx so the two ends move together when we eventually bundle the icons locally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 of 6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A ⌘⇧E (or palette: Global Editor) overlay that splits the workspace area:
Mode-agnostic — the overlay wraps the workspace surface, it doesn't replace it.
Replaces PR #77
PR #77 (
feat/global-editor) and its sibling local branchfeat/code-editorwere authored 2026-05-11, before:src/shared/(Clean dead code and finish work-context migration to /shared #84)verify-tmux/dangerousActionscleanup (Clean dead code and finish work-context migration to /shared #84)Rebasing #77 would resurrect a pile of deliberately-deleted files. This PR is a clean re-implementation on top of current main, lifting only the 13 source files that actually constitute the feature. After merging, #77 should be closed as superseded;
feat/code-editor(local-only, never on origin) can be deleted.Bug fix vs. PR #77's design: editor follows tabs, not pane focus
PR #77 derived the editor's cwd from the globally focused agent's cwd. That meant switching panes within the same tab (common pattern: same project, multiple agents) would reload the editor — throw away open tabs, rebuild the explorer — even though the user hadn't moved between projects. "changing agents in the same tab reloaded the editor, completely useless."
This PR ties the editor's cwd to the active tab's currently-focused session, captured via
lastSyncedTabIdRefat tab-change time. Within-tab pane focus changes don't reachsetActiveCwdat all. Switching tabs still flips the editor (intended); switching panes inside one tab leaves the editor untouched.Files added (13)
Editor primitives (reusable foundation):
src/main/ipc/editorFs.ts— sandboxed read/write/list under a project rootsrc/preload/api/editorFs.ts— preload bridgesrc/renderer/src/features/editor/lib/{entry,fileIcon,monacoEditorTheme,path}.ts(x)src/renderer/src/features/editor/types.tssrc/renderer/src/features/editor/ui/{EditorTabs,ExplorerPane,MonacoFileEditor}.tsxGlobal Editor shell:
src/renderer/src/features/global-editor/store.ts— per-cwd Zustand store keyed by absolute cwdsrc/renderer/src/features/global-editor/ui/GlobalEditorShell.tsx— split layout + draggable splitter + tab-scoped cwd syncFiles modified (wiring only)
src/main/ipc/index.ts— registereditorFsIPCsrc/preload/api/index.ts— spreadeditorFsApisrc/renderer/src/app-state/{types,uiShell/{types,slice}}.ts—globalEditorOpen+ open/close/toggle actionssrc/renderer/src/app/App.tsx— wrap<main>in<GlobalEditorShell>; thread state + toggle to command palettesrc/renderer/src/features/command-palette/{types.ts,ui/CommandPalette.tsx}— palette wiring + badgesrc/renderer/src/features/workspace/commands/layoutCommands.ts— newGlobal Editorcommand with On/Off badgesrc/renderer/src/workspace/tile-tree/useKeybinds.ts— ⌘⇧EOut of scope (YAGNI)
Test plan
editorFs:write.Global Editor · On/Offbadge.npm run build— verified ✓).🤖 Generated with Claude Code