feat(simulation): record viewport resizes (#36306)

This commit is contained in:
James Long 2026-07-10 15:08:43 -04:00 committed by GitHub
commit be18f22842
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 90 additions and 11 deletions

View file

@ -14,6 +14,7 @@ export interface Harness {
readonly renderer: CliRenderer
readonly mockInput: MockInput
readonly mockMouse: MockMouse
readonly resize: (cols: number, rows: number) => void
readonly renderOnce: () => Promise<void>
readonly screen: () => string
}
@ -61,15 +62,20 @@ export function createHarness(renderer: CliRenderer): Harness {
renderer,
mockInput: setup?.mockInput ?? createMockKeys(renderer),
mockMouse: setup?.mockMouse ?? createMockMouse(renderer),
resize: setup?.resize ?? ((cols, rows) => renderer.resize(cols, rows)),
renderOnce:
setup?.renderOnce ??
(async () => {
renderer.requestRender()
await renderer.idle()
}),
screen:
setup?.captureCharFrame ??
(() => decoder.decode((Reflect.get(renderer, "currentRenderBuffer") as RenderBuffer).getRealCharBytes(true))),
// captureCharFrame follows the test renderer's output sink. Recording
// redirects that sink to the timeline, so read the live render buffer
// instead; it is also the source used by screenshots.
screen: () =>
decoder.decode(
(Reflect.get(renderer, "currentRenderBuffer") as RenderBuffer).getRealCharBytes(),
),
}
}
@ -151,6 +157,13 @@ export async function execute(harness: Harness, action: Action) {
case "ui.click":
await harness.mockMouse.click(action.x, action.y)
break
case "ui.resize":
if (!Number.isSafeInteger(action.cols) || action.cols <= 0 || !Number.isSafeInteger(action.rows) || action.rows <= 0) {
throw new Error("resize cols and rows must be positive integers")
}
harness.resize(action.cols, action.rows)
SimulationRenderer.recordResize(harness.renderer, action.cols, action.rows)
break
}
await harness.renderOnce()
return state(harness)

View file

@ -11,21 +11,28 @@ const recordings = new WeakMap<CliRenderer, Timeline>()
* module-side so the harness can use supported testing APIs without app
* code carrying it around.
*/
export async function create(options: CliRendererConfig, path?: string): Promise<CliRenderer> {
export interface Viewport {
readonly cols: number
readonly rows: number
}
export async function create(options: CliRendererConfig, path?: string, viewport?: Viewport): Promise<CliRenderer> {
const cols = viewport?.cols ?? 100
const rows = viewport?.rows ?? 40
if (!path) {
const setup = await createTestRenderer({
...options,
width: 100,
height: 40,
width: cols,
height: rows,
})
setups.set(setup.renderer, setup)
return setup.renderer
}
const recording = await Timeline.create(path, 100, 40)
const recording = await Timeline.create(path, cols, rows)
const setup = await createTestRenderer({
...options,
width: 100,
height: 40,
width: cols,
height: rows,
stdout: recording as unknown as NodeJS.WriteStream,
bufferedOutput: "stdout",
onDestroy: () => {
@ -41,6 +48,10 @@ export async function create(options: CliRendererConfig, path?: string): Promise
return setup.renderer
}
export function recordResize(renderer: CliRenderer, cols: number, rows: number) {
recordings.get(renderer)?.resize(cols, rows)
}
export function setupFor(renderer: CliRenderer): TestRendererSetup | undefined {
return setups.get(renderer)
}

View file

@ -47,6 +47,8 @@ async function handle(
x: request.params.x,
y: request.params.y,
})
case "ui.resize":
return SimulationActions.execute(harness, { type: "ui.resize", cols: request.params.cols, rows: request.params.rows })
}
}

View file

@ -16,8 +16,9 @@ export async function create(options: CliRendererConfig): Promise<CliRenderer> {
const headless = process.env.OPENCODE_DRIVE_RENDERER === "headless"
const manifest = DriveManifest.resolve()
const renderer = headless
? await SimulationRenderer.create(options, manifest.recording?.timeline)
? await SimulationRenderer.create(options, manifest.recording?.timeline, manifest.viewport)
: await createCliRenderer(options)
if (!headless && manifest.viewport) renderer.resize(manifest.viewport.cols, manifest.viewport.rows)
const server = SimulationServer.start(
SimulationActions.createHarness(renderer),
manifest.endpoints.ui,