Split VS Code inspector runtime from webview controller
Problem
Decompose apps/ide/vscode-extension/src/inspector/inspector-panel.ts by extracting server/runtime orchestration, navigation bridge, and inspect state handling into focused modules so the webview provider stops owning process lifecycle, retry logic, and rendering concerns.
Context
inspector-panel.ts (862 lines) is a monolith owning three distinct concerns:
- Server/runtime orchestration — process lifecycle (
spawn,kill), health checks, connection/dialect detection fromprofiles.yml, retry with backoff, CLI command building. - Navigation bridge — JS injection into webview HTML to intercept
<a>clicks and<form>submissions, routing them back throughpostMessageto the extension host. - Panel state/rendering —
WebviewViewProviderimplementation, state machine (idle|loading|error|ready), editor change listeners, table name extraction, webview HTML generation.
All three are tangled inside one class + module-level globals. The server globals (serverProcess, serverProjectDir, serverConnection) are particularly problematic — they're free variables mutated by both the class and top-level functions.
Key files:
- apps/ide/vscode-extension/src/inspector/inspector-panel.ts — the monolith
- apps/ide/vscode-extension/src/inspector/inspector-errors.ts — already extracted error helpers
- apps/ide/vscode-extension/src/extension.ts — imports InspectorPanelProvider and killInspectServer
Constraints: preserve the public API (InspectorPanelProvider class, killInspectServer export) so extension.ts changes are minimal.
Possible Solutions
A. Extract two focused modules, keep panel as orchestrator
Extract inspector-runtime.ts (server lifecycle, connection detection, CLI commands, retry) and inspector-navigation.ts (script injection, URL routing). The panel file becomes a slim webview provider that delegates to these modules.
Trade-offs: Clean separation with minimal risk. Each module has a single reason to change. The panel class shrinks from ~550 to ~300 lines. Recommended.
B. Full class decomposition (runtime class + navigation class + panel class)
Wrap runtime state in a class with proper encapsulation. More OOP, but adds indirection without clear benefit — the runtime is inherently singleton (one server process per extension).
Trade-offs: More abstraction than needed. Module-level state for a singleton is fine.
C. Event-based architecture
Runtime emits events, panel subscribes. Over-engineered for three consumers in one extension.
Plan
Approach A — extract two modules, re-export from panel for backward compat.
- Create
inspector-runtime.ts: move constants (INSPECT_SERVER_PORT,INSPECT_SERVER_URL, retry constants), server globals,sleep,detectConnectionFromProfiles,detectDialectFromProfiles,isServerRunning,ensureServerRunning,killInspectServer,CommandOption,buildInspectCommands,didCommandTimeout,executeWithRetry,buildInspectorErrorMarkdown. - Create
inspector-navigation.ts: moveattachInspectorNavigationas a standalone function. - Slim
inspector-panel.ts: import from the new modules, keep only theInspectorPanelProviderclass with its state machine, editor listeners, and webview rendering. Re-exportkillInspectServersoextension.tsdoesn't need changes. - Update
extension.tsimport if needed (prefer re-export to avoid changes). - Compile and run unit tests (
npm run test:unit).
Implementation Progress
Files created
inspector-runtime.ts(~250 lines) — all server/process lifecycle, connection detection, CLI command building, retry logic, error markdown builder. Owns the singleton server state (serverProcess,serverProjectDir,serverConnection).inspector-navigation.ts(~55 lines) —attachInspectorNavigation()function that injects the click/submit interception script into server-rendered HTML.
Files modified
inspector-panel.ts— reduced from 862 to ~410 lines. Now imports from the two new modules. Re-exportskillInspectServersoextension.tsneeds no changes. Contains only theInspectorPanelProviderclass (state machine, editor listeners, webview rendering, message dispatch).
Files unchanged
extension.ts— no changes needed thanks to re-export.inspector-errors.ts— already well-factored, untouched.inspector-panel.test.ts— tests import frominspector-errorsdirectly, still pass.
Key decisions
- Kept
didCommandTimeoutprivate toinspector-runtime.ts(not exported) since it's only used internally byexecuteWithRetry. - Used re-export (
export { killInspectServer }) in panel to preserve the existing public API surface. DEBOUNCE_DELAY_MSstays ininspector-panel.ts— it's a UI concern, not a runtime concern.
Validation
- TypeScript compilation: clean (0 errors)
- vitest unit tests: 84/84 passed (7 test files, including
inspector-panel.test.ts)
QA Exploration
- [x] QA exploration completed (or N/A for non-UI tasks)
N/A — refactor-only task, no UI changes. Behavior preserved by keeping all function signatures identical.
Review Feedback
- Manager review (
just review) approved the refactor structure. - Removed the unnecessary
exportsurface for runtime-only constants andisServerRunning. - Re-ran focused validation after the review cleanup:
npm run compilenpm run test:unit -- src/inspector/inspector-panel.test.ts- [x] Review cleared