Skip to content

feat(editor): Global Editor — split workspace with file editor on left - #129

Merged
Juliusolsson05 merged 4 commits into
mainfrom
feat/global-editor-redo
May 14, 2026
Merged

Juliusolsson05 merged 4 commits into
mainfrom
feat/global-editor-redo

Conversation

@Juliusolsson05

Copy link
Copy Markdown
Owner

Summary

A ⌘⇧E (or palette: Global Editor) overlay that splits the workspace area:

  • LEFT: file tree + Monaco editor rooted at the active tab's project cwd
  • RIGHT: the entire normal workspace UI (dispatch / tile / spotlight / whatever) — unchanged, just shrunk

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 branch feat/code-editor were authored 2026-05-11, before:

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 lastSyncedTabIdRef at tab-change time. Within-tab pane focus changes don't reach setActiveCwd at 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 root
  • src/preload/api/editorFs.ts — preload bridge
  • src/renderer/src/features/editor/lib/{entry,fileIcon,monacoEditorTheme,path}.ts(x)
  • src/renderer/src/features/editor/types.ts
  • src/renderer/src/features/editor/ui/{EditorTabs,ExplorerPane,MonacoFileEditor}.tsx

Global Editor shell:

  • src/renderer/src/features/global-editor/store.ts — per-cwd Zustand store keyed by absolute cwd
  • src/renderer/src/features/global-editor/ui/GlobalEditorShell.tsx — split layout + draggable splitter + tab-scoped cwd sync

Files modified (wiring only)

  • src/main/ipc/index.ts — register editorFs IPC
  • src/preload/api/index.ts — spread editorFsApi
  • src/renderer/src/app-state/{types,uiShell/{types,slice}}.ts — globalEditorOpen + open/close/toggle actions
  • src/renderer/src/app/App.tsx — wrap <main> in <GlobalEditorShell>; thread state + toggle to command palette
  • src/renderer/src/features/command-palette/{types.ts,ui/CommandPalette.tsx} — palette wiring + badge
  • src/renderer/src/features/workspace/commands/layoutCommands.ts — new Global Editor command with On/Off badge
  • src/renderer/src/workspace/tile-tree/useKeybinds.ts — ⌘⇧E

Out of scope (YAGNI)

  • Disk persistence of per-cwd state across app restarts (in-memory only)
  • Search-across-project / diagnostics / LSP / multi-cursor
  • File-tree context menu (rename / delete / new file)
  • Cross-cwd unsaved-file warnings on app quit

Test plan

  • ⌘⇧E toggles the overlay. Right pane behaves identically when the overlay is off (zero-overhead pass-through).
  • File tree + editor works; ⌘S saves through editorFs:write.
  • Switch tabs to a different project → editor swaps to that project, restores its tabs.
  • Within the same tab, switch focus between two panes (same or different cwds) → editor stays put. No tab loss, no explorer reload. (This is the headline bug fix.)
  • Splitter drags smoothly between 20% and 80%; cursor doesn't desync on fast drag.
  • Command palette shows Global Editor · On/Off badge.
  • Closed-state has no DOM overhead (no Monaco mounted, no IPC subscriptions firing).
  • Build clean (npm run build — verified ✓).

🤖 Generated with Claude Code

Juliusolsson05 and others added 4 commits May 14, 2026 20:19
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>
@Juliusolsson05
Juliusolsson05 merged commit d660de6 into main May 14, 2026
@Juliusolsson05
Juliusolsson05 deleted the feat/global-editor-redo branch July 7, 2026 21:51
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