mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-29 03:44:06 +08:00
fix(agent-manager): fall back to DOM renderer on WebGL context loss and keep hidden terminal layout size
This commit is contained in:
@@ -4878,17 +4878,18 @@ body.vscode-high-contrast-light {
|
||||
/*
|
||||
* Stacking layout for xterm terminal tabs.
|
||||
*
|
||||
* Terminals are never unmounted once mounted; inactive slots are hidden
|
||||
* with `display: none`. xterm 6's render service observes the screen
|
||||
* element and pauses hidden terminals' render loops (rAF, model
|
||||
* updates, GPU draws), then replays a full refresh when a slot becomes
|
||||
* visible again — the activation fit + refresh in the TerminalTab
|
||||
* component is insurance on top of that. The historical `display: none`
|
||||
* avoidance was for the "press Enter to see content" bug, caused by older
|
||||
* xterm versions not resuming after a paint-tree detach; opacity-only hiding
|
||||
* instead kept the render loop running at full rate for output no one
|
||||
* sees (such as background setup scripts), which is measurable on the
|
||||
* main thread.
|
||||
* Terminals are never unmounted once mounted; inactive slots hide one
|
||||
* viewport to the left while keeping their layout box. xterm 6's render
|
||||
* service observes the screen element and pauses hidden terminals'
|
||||
* render loops (rAF, model updates, GPU draws), then replays a full
|
||||
* refresh when a slot becomes visible again — the activation fit +
|
||||
* refresh in the TerminalTab component is insurance on top of that. The
|
||||
* historical `display: none` avoidance was for the "press Enter to see
|
||||
* content" bug, caused by older xterm versions not resuming after a
|
||||
* paint-tree detach; it also stops FitAddon from measuring, so hidden
|
||||
* content wraps wrong. Keeping the box fixes wrap and still lets the
|
||||
* render observer pause, saving the full-rate render cost for output no
|
||||
* one sees (such as background setup scripts).
|
||||
*
|
||||
* Stack: position:relative container hosting the main pane, review,
|
||||
* and side panel. Sits below the tab bar inside `.am-detail`.
|
||||
@@ -4926,26 +4927,28 @@ body.vscode-high-contrast-light {
|
||||
}
|
||||
|
||||
.am-terminal-slot {
|
||||
/* Hidden slots are display:none, not opacity:0. xterm 6's render
|
||||
service observes the screen element and pauses its render loop for
|
||||
non-intersecting terminals (rAF loop, model updates, WebGL draws),
|
||||
then replays a full refresh when the slot becomes visible again.
|
||||
Opacity-only hiding kept the loop running at full rate for output
|
||||
no one sees, such as background setup scripts streaming while the
|
||||
user works in another context's terminal. */
|
||||
/* Hidden slots stay in layout but are translated one viewport to the
|
||||
left, so xterm's render observer sees no intersection and pauses the
|
||||
render loop (rAF loop, model updates, WebGL draws), then replays a
|
||||
full refresh when the slot slides back in. Keeping the layout box
|
||||
(unlike display:none) lets FitAddon measure the real panel size
|
||||
while hidden — background-created terminals such as setup scripts
|
||||
wrap their output at the panel's width from the start instead of
|
||||
the 80 column default. */
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transform: translate(-100vw, 0);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.am-terminal-slot-visible {
|
||||
display: flex;
|
||||
transform: none;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
z-index: 1;
|
||||
@@ -4954,8 +4957,8 @@ body.vscode-high-contrast-light {
|
||||
/* Side terminal panel — lives inside .am-diff-panel-wrapper next to the
|
||||
diff and PR panels. Header reuses .am-diff-header metrics so the
|
||||
chrome does not shift when switching inspector modes. The panel is
|
||||
opacity-toggled, its terminal slots flip display (per-slot render
|
||||
pause, see .am-terminal-slot above). */
|
||||
opacity-toggled, its terminal slots hide by off-screen translate
|
||||
(per-slot render pause, see .am-terminal-slot above). */
|
||||
.am-side-terminal {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
|
||||
@@ -5,12 +5,14 @@
|
||||
* panels, so every mode uses the same persisted resize width. The tab row is
|
||||
* the shared inspector strip used by subagents as well.
|
||||
*
|
||||
* Visibility toggles between none and flex, never unmount: xterm keeps its
|
||||
* buffer, socket, and parser alive, while xterm's own render observer
|
||||
* (IntersectionObserver on the screen element) pauses the render loop for
|
||||
* hidden slots and replays a full refresh when a slot becomes visible
|
||||
* again. `TerminalTab` still does an explicit fit + refresh on activation
|
||||
* as insurance (see `render.tsx`).
|
||||
* Hidden slots are translated off-screen while keeping their layout
|
||||
* box, never unmounted: xterm keeps its buffer, socket, and parser
|
||||
* alive, while xterm's own render observer (IntersectionObserver on the
|
||||
* screen element) pauses the render loop for hidden slots and replays a
|
||||
* full refresh when a slot becomes visible again. Keeping the box means
|
||||
* FitAddon can measure the panel (correct wrapping) even while hidden.
|
||||
* `TerminalTab` still does an explicit fit + refresh on activation as
|
||||
* insurance (see `render.tsx`).
|
||||
*/
|
||||
|
||||
import type { Accessor, Component } from "solid-js"
|
||||
|
||||
@@ -33,8 +33,8 @@ interface Props {
|
||||
font: TerminalFont
|
||||
/** Whether this terminal is currently the focused tab.
|
||||
*
|
||||
* Inactive slots are `display: none` (see the layer / slot CSS in
|
||||
* `terminal/render.tsx` and `agent-manager.css`): xterm's render
|
||||
* Inactive slots are translated off-screen (see the layer / slot CSS
|
||||
* in `terminal/render.tsx` and `agent-manager.css`): xterm's render
|
||||
* observer pauses invisible terminals and resumes them with a full
|
||||
* refresh on activation. This prop drives that activation repaint
|
||||
* plus auto-focus on activation — xterm's own resume is primary, the
|
||||
@@ -174,7 +174,19 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
// draw batched by first glyph. Falls back to the DOM renderer when
|
||||
// WebGL2 is unavailable; the catch logs without breaking the session.
|
||||
try {
|
||||
term.loadAddon(new WebglAddon())
|
||||
const webgl = new WebglAddon()
|
||||
term.loadAddon(webgl)
|
||||
// Browsers hand out a limited number of WebGL contexts and evict
|
||||
// old ones; when a context is lost beyond recovery, dispose the
|
||||
// addon so xterm re-installs its DOM renderer for this terminal
|
||||
// instead of leaving a dead canvas.
|
||||
webgl.onContextLoss(() => {
|
||||
try {
|
||||
webgl.dispose()
|
||||
} catch (err) {
|
||||
log("webgl dispose after context loss failed", err)
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
log("webgl renderer unavailable, using DOM renderer", err)
|
||||
}
|
||||
@@ -454,10 +466,11 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
|
||||
// ---- Repaint recovery ----
|
||||
//
|
||||
// Inactive xterm slots are display:none, so their canvases are not
|
||||
// composed while hidden. xterm's render observer pauses hidden
|
||||
// terminals and replays a full refresh when they re-enter the tree,
|
||||
// but browsers still defer some canvas/render work: forcing a
|
||||
// Inactive xterm slots slide off-screen with their layout box
|
||||
// intact, so their canvases are not composed while hidden but
|
||||
// FitAddon keeps measuring. xterm's render observer pauses hidden
|
||||
// terminals and replays a full refresh when they slide back in, but
|
||||
// browsers still defer some canvas/render work: forcing a
|
||||
// `fit + refresh(0, rows-1)` once per activation reclaims paint
|
||||
// priority immediately; from then on the renderer keeps the canvas
|
||||
// live. Historically the missing insurance step here was "press
|
||||
|
||||
@@ -71,16 +71,19 @@ export function renderTerminalTab(deps: TerminalTabRenderDeps): JSX.Element {
|
||||
* ## Invariant
|
||||
*
|
||||
* **Once an xterm instance is mounted, its DOM subtree is only ever
|
||||
* hidden, never unmounted.** Inactive slots use `display: none`; xterm
|
||||
* 6's render service observes the screen element and pauses the render
|
||||
* loop (rAF, model updates, GPU draws) for non-intersecting terminals,
|
||||
* then replays a full refresh when the slot re-enters the tree. Toggling
|
||||
* visibility via opacity instead — the historical workaround for the
|
||||
* "press Enter to see content" bug, where reattachment left a stale
|
||||
* canvas — kept the render loop running at full rate for output no one
|
||||
* sees, e.g. background setup scripts streaming while the user works in
|
||||
* another context's terminal. `TerminalTab`'s activation repaint
|
||||
* (fit + refresh) remains as insurance on top of xterm's own resume.
|
||||
* hidden, never unmounted.** Inactive slots keep their layout box but
|
||||
* are translated one viewport off-screen; xterm 6's render service
|
||||
* observes the screen element and pauses the render loop (rAF, model
|
||||
* updates, GPU draws) for non-intersecting terminals, then replays a
|
||||
* full refresh when the slot slides back in. Keeping the box (unlike
|
||||
* `display: none`) also lets FitAddon measure the real panel size while
|
||||
* hidden, so background-created terminals such as setup scripts wrap
|
||||
* output at the panel's width from the start. Hiding via opacity instead
|
||||
* — the historical workaround for the "press Enter to see content" bug,
|
||||
* where reattachment left a stale canvas — kept the render loop running
|
||||
* at full rate for output no one sees. `TerminalTab`'s activation
|
||||
* repaint (fit + refresh) remains as insurance on top of xterm's own
|
||||
* resume.
|
||||
*
|
||||
* ## Design
|
||||
*
|
||||
@@ -137,9 +140,10 @@ export function renderTerminalLayer(props: {
|
||||
* Render the side-panel terminal layer inside the right-hand inspector.
|
||||
*
|
||||
* Same invariant as `renderTerminalLayer`: every side terminal stays
|
||||
* mounted, inactive slots are hidden with `display: none` so xterm's
|
||||
* render observer pauses them, and only the visible tab's slot is in the
|
||||
* tree. The layer is scoped to `contextKey` — side terminals from other
|
||||
* mounted, inactive slots are translated off-screen so xterm's render
|
||||
* observer pauses them while FitAddon keeps measuring, and only the
|
||||
* visible tab's slot is shown. The layer is scoped to `contextKey` —
|
||||
* side terminals from other
|
||||
* contexts stay paused in the background and never refit — and within a
|
||||
* context only the active strip tab's terminal is shown.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user