From eccd3337d79a8cb8774ee7099a4ff5ee393ce5df Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Sat, 24 Oct 2026 18:32:46 +0000 Subject: [PATCH] fix(app): restore the native terminal cursor beside the prompt Measure the font sample outside the grid whose width is derived from those metrics. On cold Android startup the one-column grid wrapped the sample, temporarily shrinking the emulator and clamping the restored cursor before the final layout. --- .../terminal-grid-view.browser.test.tsx | 54 +++++++++++++++++++ .../terminal-grid-view.native.tsx | 7 +-- 2 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 packages/app/src/terminal/native-renderer/terminal-grid-view.browser.test.tsx diff --git a/packages/app/src/terminal/native-renderer/terminal-grid-view.browser.test.tsx b/packages/app/src/terminal/native-renderer/terminal-grid-view.browser.test.tsx new file mode 100644 index 0000000000..bf7679eb21 --- /dev/null +++ b/packages/app/src/terminal/native-renderer/terminal-grid-view.browser.test.tsx @@ -0,0 +1,54 @@ +import React, { act } from "react"; +import { createRoot } from "react-dom/client"; +import { expect, test } from "vitest"; +import { TerminalGridView } from "./terminal-grid-view.native"; +import type { TerminalViewportState } from "./headless-terminal-state"; +import type { TerminalGridCellMetrics } from "./terminal-grid-metrics"; + +test("measures a single font cell before the terminal has its viewport columns", async () => { + const container = document.createElement("div"); + container.style.cssText = "width: 400px; height: 700px; display: flex"; + document.body.appendChild(container); + const root = createRoot(container); + const measurements: TerminalGridCellMetrics[] = []; + const state: TerminalViewportState = { + rows: 1, + cols: 1, + firstRow: 0, + oldestRow: 0, + newestRow: 0, + grid: [[{ char: " " }]], + cursor: { row: 0, col: 0 }, + }; + const style = { flex: 1 }; + const recordMeasurement = (metrics: TerminalGridCellMetrics) => measurements.push(metrics); + try { + act(() => + root.render( + React.createElement(TerminalGridView, { + state, + fontSize: 12, + style, + onCellMetricsChange: recordMeasurement, + }), + ), + ); + await expect.poll(() => measurements.length).toBeGreaterThan(0); + const sample = [...container.querySelectorAll("div")].find( + (element) => element.textContent === "mmmmmmmmmm" && element.childElementCount === 0, + ); + expect(sample).toBeDefined(); + // Native Text wraps to its containing block. Font measurement must fit even + // when the terminal starts with one column, before layout determines its size. + expect(sample!.parentElement!.getBoundingClientRect().width).toBeGreaterThanOrEqual( + sample!.getBoundingClientRect().width, + ); + // The first measurement is used to size the emulator before restore data arrives. + // A wrapped ten-character sample reports a fraction of a cell and multiple lines. + expect(measurements[0].cellWidth).toBeGreaterThan(5); + expect(measurements[0].cellHeight).toBeLessThan(24); + } finally { + act(() => root.unmount()); + container.remove(); + } +}); diff --git a/packages/app/src/terminal/native-renderer/terminal-grid-view.native.tsx b/packages/app/src/terminal/native-renderer/terminal-grid-view.native.tsx index 04f82c1011..894d319b1d 100644 --- a/packages/app/src/terminal/native-renderer/terminal-grid-view.native.tsx +++ b/packages/app/src/terminal/native-renderer/terminal-grid-view.native.tsx @@ -407,10 +407,11 @@ export function TerminalGridView({ return ( + {/* Measure against the viewport, independently of the grid width derived from these metrics. */} + + {MEASURE_TEXT} + - - {MEASURE_TEXT} - {rows.map((row) => (