AGENTS.md@packages/client/src/components/editor-pane · git:20260808.b28f645 · 2026-08-08 · sha256 1abe5a17a1cefd05

AGENTS.md@packages/client/src/components/editor-pane git:20260808.b28f645A

Immutable. This exact content is served forever at /api/v1/blob/1abe5a17a1cefd05.

# DOX — packages/client/src/components/editor-pane

Files in this directory. One row per source file.

| File | Purpose |
|------|---------|
| `BinaryWarn.tsx` | Binary-file tab. Shows a "binary file" notice (path only). No content fetched. Native-editor buttons / `fetchEditors` removed. See change: add-internal-monaco-editor-pane, remove-external-editor-integration. |
| `CappedViewer.tsx` | Large-file cap gate wrapping the rich viewers: resolves the component from `viewerRegistry` (half a) and, for every non-`monaco` kind, reads `size` from `/api/file` metadata, mounting `TooLargePreview` above `MAX_PREVIEW_BYTES`. `monaco` bypasses (own large-file handling). `viewer` narrowed to `OpenPathViewer`; pseudo-tabs bypass this gate (their probe always missed → removal is a no-op). No casts at call sites. See change: cleanup-import-cycles. |
| `ChangedOnDiskBanner.tsx` | Per-tab changed-on-disk banner. Refresh re-fetches; Dismiss keeps stale view. No auto-reload. See change: split-editor-workspace. |
| `ChangesRailSection.tsx` | Slim summary bar atop the rail: `Changes (N) · +X −Y`, `summed` badge (non-git), `this session only` toggle. Props `{sessionOnly, onSessionOnlyChange}` (rail-local, owned by `EditorPane`, D3 — not context; lifting breaks `FileDiffView` takeover). No per-file list / `DiffFileTree`. Reads `SessionDiffContext`; null when no `files`+`otherChanges`. See change: add-change-summary-table, detect-tool-created-files, collapse-diff-file-tree. |
| `DiffViewer.tsx` | `diff` viewer tab. Strips `diff:` prefix from virtual path, reads file `gitDiff` from `SessionDiffContext`, delegates render to `DiffPanel`. See change: add-change-summary-table. |
| `EditorFileTree.tsx` | Lazy file-tree rail rooted at session cwd. Expands one level per click. Lists `{name,isDir}` from single `GET /api/file/tree` (hidden included) — replaces the `/api/file`+`/api/browse` merge that mislabelled `.git`/`.pi` as files (#1). File rows show per-kind icon via `fileIcon()`. Clicking file calls `onOpenFile(rel, viewer)` via shared `fileKind`. `treeOpenRoots` persisted. Each row (file+dir) gains hover-revealed `RowCopyAffordance` copy glyph → anchored popup: Copy full path (`absOf`), relative (`rel`), file name (basename); guarded `navigator.clipboard?.writeText`, ✓ ~1.5s, flips above near rail bottom (`data-file-rail`); dismiss on outside-click/rail-scroll/Escape (Escape refocuses glyph); glyph `stopPropagation` — no open/toggle. Consumes `useOptionalSessionDiff()` (`buildDiffIndex`): changed `FileRow` shows per-kind icon + status `●`/`+` (`statusIndicator`; edit/tool/mixed→●), `CountBadges`, hover `diff` chip (`onOpenDiff`→`diff:` tab), >1-event `✏️/📝` expander; changed-descendant dirs show `folder-dot` (path-prefix, no fetch); NO auto-expand. `OtherChangesGroup` (muted, `<button>` rows) renders `otherChanges` at rail bottom unless `sessionOnly`. Null diff → plain tree. See change: add-internal-monaco-editor-pane, improve-content-editor, copy-file-path, collapse-diff-file-tree. |
| `EditorPane.tsx` | Editor pane shell. Composes `EditorTabs` + collapsible `EditorFileTree` + active viewer (`viewerRegistry`) + keep-alive `TerminalPaneLayer` (mounted in the body region beside the file viewer). Header adds `+ Terminal` (`data-testid=new-terminal-launch`)→`terminal.createTerminal()`; closing a `term:` tab routes to `terminal.closeTerminalTab` (kills PTY, D4); passes `terminalTitle` resolver to `EditorTabs`. See change: terminals-in-tabbed-panes. Replaces ChatView in content area, mirrors FileDiffView. Read-only v1. Header: back, tree-toggle, refresh. Footer: viewer kind, language, path. State via `useEditorPaneState(sessionId)`. See change: add-internal-monaco-editor-pane. Now controlled by `SplitWorkspaceContext`. Adds inner rail divider, search panel, changed-on-disk banner, unsplit button, Cmd-P/Cmd-Shift-F. See change: split-editor-workspace. Rail is a vertical stack: `ChangesRailSection` above the project tree. See change: add-change-summary-table. Rail toggle now a labelled "Files" control (aria-pressed, `data-testid=tree-toggle`), visibility persisted via `useTreeVisible(sessionId)` (#6). Header `mdiWeb` launcher (`data-testid=live-preview-launch`) opens a `live-server` tab. See change: improve-content-editor. Now dispatches by open path: `isPseudoTabViewer(activeTab.viewer)` → render `pseudoTabRegistry` directly, else `CappedViewer`. Discriminator MUST be `activeTab.viewer` (NOT `fileKind().viewer`, wrong for pseudo-paths). `tabActionTarget` tests `url` BEFORE the guard — `url` is the one pseudo-tab kind with an action, and it keeps `path.replace(/^url:/, "")`. See change: cleanup-import-cycles. |
| `EditorSearchPanel.tsx` | Dual-mode search. Filenames=bridge walk, Contents=`GET /api/grep`. Regexp toggle, min-3-char + debounce, keyboard nav ↑↓/↵/Esc. Injected search fns. See change: split-editor-workspace. |
| `EditorTabs.tsx` | Horizontal tab strip. Click activate, `×`/middle-click/Ctrl+Cmd-W close, drag reorder. Per-kind icon via `fileIcon()` + label = basename, tooltip = rel path. Tags `diff` tab kind. `terminal` tabs (`term:<id>`) render a console icon + `terminalTitle(id)` label (shell/title) via optional `terminalTitle` prop. See change: add-internal-monaco-editor-pane. See change: improve-content-editor. See change: add-change-summary-table. See change: terminals-in-tabbed-panes. |
| `MarkdownEditor.tsx` | Controlled editable Monaco buffer. Props `{value,onChange,readOnly}`. language=markdown, wordWrap on, minimap off. Lazy-mounts behind Suspense in InstructionsPage. Theme via shared `useThemeContext()` (recolors live on theme switch). See change: directory-settings-page-and-scoped-md-editing. See change: improve-content-editor. |
| `LiveServerViewer.tsx` | Live-server-preview tab (viewer kind `live-server`). Allowlist picker (`GET /api/live-server/list`) + confirmed URL entry → `startLiveServer` (client+server loopback SSRF check) → iframe proxied `/live/<id>/` on MAIN origin with `sandbox="allow-scripts allow-forms allow-popups"` (NO `allow-same-origin` — opaque origin D7). Remote hosts refused. Preset `ViewerProps.path` of form `live:<loopback-url>` auto-launches on mount (skips picker) and preserves the URL's `pathname+search` into the iframe `src` (deep link); `live:preview`/empty → picker. See change: improve-content-editor; open-loopback-links-in-split-viewer. |
| `MermaidViewer.tsx` | Mermaid tab. Fetches `.mmd`/`.mermaid` source via `/api/file/raw`, renders through shared `MermaidBlock` (theme-aware, zoom/pan). Loading/error states. See change: improve-content-editor. |
| `MarkdownViewer.tsx` | Markdown tab, Preview (default) / Edit. Fetches content+mtime via `/api/file`. Preview→`MarkdownContent` (frontmatter=properties, `pi-asset:` via `SessionAssetsContext`). Preview passes `imageBase={{cwd, dir: absOf(cwd, dirname(path))}}` so local images resolve. Edit (gated on `fileKind.editable` `.md`/`.mdx`)→controlled `MarkdownEditor`; Save→`POST /api/file/write` w/ loaded mtime, 200 clears dirty, 409→`ChangedOnDiskBanner`. See change: add-internal-monaco-editor-pane. See change: improve-content-editor. See change: fix-markdown-preview-relative-images. |
| `monaco-setup.ts` | Shared Monaco bootstrap. Worker `?worker` imports + MonacoEnvironment + loader.config. Side-effect import by MonacoBuffer + MarkdownEditor. See change: directory-settings-page-and-scoped-md-editing. |
| `MonacoBuffer.tsx` | Heavy lazy Monaco chunk. Imports `monaco-editor` + `@monaco-editor/react`. Bundles editor/json/css/html workers via `?worker`; ts.worker omitted (read-only, no LSP); ts/js diagnostics disabled. Read-only. Theme via `buildMonacoTheme`, recolors on theme/mode change. Fetches content via `/api/file`. Scrolls to line. Theme via shared `useThemeContext()` (recolors live on theme switch). See change: add-internal-monaco-editor-pane. Worker bootstrap moved to monaco-setup.ts. See change: directory-settings-page-and-scoped-md-editing. See change: improve-content-editor. |
| `pseudo-tab-registry.tsx` | Half (b) of the registry split: `pseudoTabRegistry` for diff/url/live-server/terminal. Owns the `DiffViewer` import, so only `EditorPane` may import it. See change: cleanup-import-cycles. |
| `TerminalPaneLayer.tsx` | Keep-alive terminal layer. Mounts one `TerminalView` per open `term:<id>` tab, visibility toggled by the active tab — switching to a file tab and back does NOT tear down + reconnect the xterm WS (single-mount-per-id contract, avoids fix-terminal-half-height-dual-mount). Fills the file-viewer body region; hidden when a file tab active. Reads `paneState`+`terminal` from `useSplitWorkspace`. Exports `TerminalPaneLayer`. See change: terminals-in-tabbed-panes. |
| `types.ts` | `ViewerProps` contract `{ cwd, path, kind, mimeType, size, line? }`. Shared by all viewers. See change: add-internal-monaco-editor-pane. |
| `viewer-kinds.ts` | Component-free leaf partitioning `ViewerKind` (18) into `OPEN_PATH_VIEWERS` (14) + `PSEUDO_TAB_VIEWERS` (4) with guard `isPseudoTabViewer`. Compile-time totality via `_AssertNever` (NOT `const _x: T[] = []`, which is vacuous). Must import no viewer. See change: cleanup-import-cycles. |
| `viewer-registry.tsx` | `viewerRegistry` maps `ViewerKind`→component. monaco=`React.lazy(MonacoBuffer)` boundary. Rich kinds delegate to shared `preview/*` (thin `ViewerProps`→`{target}` adapters): pdf→`PdfPreview` (pdfjs, replaces broken `<object>`), html→`HtmlPreview` (sandboxed, no scripts), video→`VideoPreview`, image→`ImagePreview` variant=full (pan/zoom), audio→`AudioPreview`, mermaid→`MermaidViewer`, markdown/binary-warn eager. `line` reaches Monaco only. Since the D3 split it maps ONLY the 14 `fileKind()`-returnable kinds; `diff`/`terminal`/`url`/`live-server` moved to `pseudo-tab-registry.tsx`. See change: add-internal-monaco-editor-pane. See change: improve-content-editor. See change: add-change-summary-table. See change: terminals-in-tabbed-panes. **Split (half a):** now `Record<OpenPathViewer, …>` over the 14 `fileKind()`-returnable kinds only; diff/terminal/url/live-server moved to `pseudo-tab-registry.tsx`. Do NOT import a pseudo-tab viewer here — keeping `DiffViewer` out is what breaks the cycle. See change: cleanup-import-cycles. |