feat(render): RenderableSurface registry as recovery dispatch hub - #181
feat(render): RenderableSurface registry as recovery dispatch hub#181blueberrycongee wants to merge 1 commit into
Conversation
…walk Recovery dispatch was hardwired to runtimeRegistry — only terminals got refreshed/reset on `visibility_change_to_visible`, `page_lifecycle_resume`, and the lifecycle IPC. Other GPU surfaces (the canvas graph layer, Monaco editor, the Pet sprite) lost their framebuffers in the same events but had no path to recover. Introduce `RenderableSurface` (`setVisible`, `forceRepaint`, `getHealth`) and a `surfaceRegistry`. Recovery now calls `dispatchSurfaceRecovery`, which iterates every registered surface — terminal-specific repaint logic moved into the per-terminal adapter (`terminalSurface.ts`). Non-terminal surfaces wire in via PR 7. Per-terminal lifecycle: - `createTerminalRenderer` constructs the surface adapter once xterm is ready and registers it. - `destroyTerminalRuntime` disposes the adapter and unregisters. - `focusTerminalRuntime` flips the surface's visibility hint to true. Blur intentionally does not — a focused-elsewhere terminal can still be visible in its tile and should keep painting. The adapter subscribes to `xterm.onRender` so `getHealth().lastPaintAt` is accurate without callers having to mark paints. PR 5's heartbeat watchdog will read this field to detect renderers that stopped producing frames while their surface is still considered visible. Tests cover the registry contract (dispatch tally, error isolation, duplicate-id replacement) and the terminal adapter (visibility gating, paint subscription, dispose teardown). Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e44a982b97
ℹ️ 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".
| registry_size: surfaces.size, | ||
| }, | ||
| }); | ||
| return () => unregisterSurface(surface.id); |
There was a problem hiding this comment.
Guard disposer from removing a replacement surface
registerSurface can replace an existing entry with the same id, but the returned cleanup always calls unregisterSurface(surface.id) unconditionally. If the old cleanup runs after a replacement registration (the exact duplicate-registration scenario this function handles), it will delete the newer surface and remove it from recovery/health dispatch unexpectedly. The cleanup should only unregister when the map still points to the same surface instance.
Useful? React with 👍 / 👎.
| if (!runtime.surfaceHandle) { | ||
| const view = createTerminalSurfaceRuntimeView(runtime); | ||
| runtime.surfaceHandle = createTerminalSurface(view); | ||
| registerSurface(runtime.surfaceHandle.surface); |
There was a problem hiding this comment.
Recreate surface handle when xterm instance is recreated
createTerminalSurface binds onPaint once at construction time, but createTerminalRenderer skips rebuilding the surface whenever runtime.surfaceHandle already exists. In the evict/reattach path (detachTerminalRenderer sets runtime.xterm = null, later attachTerminalContainer calls createTerminalRenderer), the new xterm instance reuses an old surface handle whose paint subscription is tied to the disposed xterm, so lastPaintAt (and context-loss clearing) stop updating after reattach.
Useful? React with 👍 / 👎.
Why
PR 1 (#162) centralized recovery triggers in `VisibilityObserver`, but the listener still walked `runtimeRegistry` directly — only terminals participated. Monaco / canvas-backed graph layer / Pet are also GPU surfaces that lose framebuffers on `visibility hidden→visible`, sleep/wake, and the macOS Space-switch IPC. They had no path back.
This is render-recovery v2 PR 3, the foundation for PR 5 (paint heartbeat) and PR 7 (non-terminal surfaces). Independent of PR 6 (#180).
What
`xterm.onRender` feeds `lastPaintAt` automatically, so PR 5's heartbeat will read accurate paint timestamps without any new instrumentation in the runtime.
Files
Verification
macOS QA (cannot run here)
Risks
🤖 Generated with Claude Code