mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 00:03:47 +00:00
Use exact container height for mobile viewport measurement
Instead of subtracting a hardcoded row count from window.innerHeight, measure the terminal frame's pixel height via onLayout and pass it into the WebView's measureFitDimensions. This gives pixel-accurate row calculations regardless of accessory bar height or safe-area insets. Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -155,6 +155,10 @@ export default function SessionScreen() {
|
||||
const activeHandleRef = useRef<string | null>(null)
|
||||
const subscribeSeqRef = useRef<Map<string, number>>(new Map())
|
||||
const sendingRef = useRef(false)
|
||||
// Why: tracks the pixel height of the terminal frame so measureFitDimensions
|
||||
// can use the exact container height instead of relying on window.innerHeight,
|
||||
// which can overstate the visible area due to layout timing.
|
||||
const terminalFrameHeightRef = useRef<number>(0)
|
||||
|
||||
const canSend = connState === 'connected' && activeHandle != null
|
||||
|
||||
@@ -189,7 +193,9 @@ export default function SessionScreen() {
|
||||
const measureViewportOnce = useCallback(
|
||||
async (handle: string) => {
|
||||
if (viewportMeasuredRef.current) return
|
||||
const dims = await getTerminalRef(handle)?.measureFitDimensions()
|
||||
const dims = await getTerminalRef(handle)?.measureFitDimensions(
|
||||
terminalFrameHeightRef.current || undefined
|
||||
)
|
||||
if (dims) {
|
||||
viewportRef.current = dims
|
||||
viewportMeasuredRef.current = true
|
||||
@@ -255,7 +261,9 @@ export default function SessionScreen() {
|
||||
// resubscribe so the server gets the viewport and phone-fits.
|
||||
if (!viewportMeasuredRef.current) {
|
||||
void (async () => {
|
||||
const dims = await getTerminalRef(handle)?.measureFitDimensions()
|
||||
const dims = await getTerminalRef(handle)?.measureFitDimensions(
|
||||
terminalFrameHeightRef.current || undefined
|
||||
)
|
||||
if (dims && !viewportMeasuredRef.current) {
|
||||
viewportRef.current = dims
|
||||
viewportMeasuredRef.current = true
|
||||
@@ -816,7 +824,12 @@ export default function SessionScreen() {
|
||||
</Pressable>
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.terminalFrame}>
|
||||
<View
|
||||
style={styles.terminalFrame}
|
||||
onLayout={(e) => {
|
||||
terminalFrameHeightRef.current = e.nativeEvent.layout.height
|
||||
}}
|
||||
>
|
||||
{terminals.map((terminal) => (
|
||||
<TerminalPaneView
|
||||
key={terminal.handle}
|
||||
|
||||
@@ -8,7 +8,7 @@ export type TerminalWebViewHandle = {
|
||||
write: (data: string) => void
|
||||
init: (cols: number, rows: number, initialData?: string) => void
|
||||
clear: () => void
|
||||
measureFitDimensions: () => Promise<{ cols: number; rows: number } | null>
|
||||
measureFitDimensions: (containerHeight?: number) => Promise<{ cols: number; rows: number } | null>
|
||||
resetZoom: () => void
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ type TerminalMessage =
|
||||
| { type: 'write'; id?: number; data: string }
|
||||
| { type: 'init'; id?: number; cols: number; rows: number; initialData?: string }
|
||||
| { type: 'clear'; id?: number }
|
||||
| { type: 'measure'; id?: number }
|
||||
| { type: 'measure'; id?: number; containerHeight?: number }
|
||||
| { type: 'reset-zoom'; id?: number }
|
||||
|
||||
// Why: TUI apps (Claude Code / Ink) emit escape codes with absolute cursor
|
||||
@@ -287,7 +287,7 @@ const XTERM_HTML = `<!DOCTYPE html>
|
||||
}
|
||||
}
|
||||
|
||||
function measureFitDimensions() {
|
||||
function measureFitDimensions(containerHeightPx) {
|
||||
if (!term || !term.element) {
|
||||
notify({ type: 'measure-result', cols: null, rows: null });
|
||||
return;
|
||||
@@ -307,13 +307,16 @@ const XTERM_HTML = `<!DOCTYPE html>
|
||||
return;
|
||||
}
|
||||
var vpWidth = window.innerWidth;
|
||||
var vpHeight = window.innerHeight;
|
||||
// Why: prefer the container height passed from React Native over
|
||||
// window.innerHeight. The RN layout system knows the exact pixel
|
||||
// height of the terminal frame after the accessory/input bars are
|
||||
// subtracted, whereas innerHeight can overstate the visible area
|
||||
// due to layout timing or safe-area insets.
|
||||
var vpHeight = (typeof containerHeightPx === 'number' && containerHeightPx > 0)
|
||||
? containerHeightPx
|
||||
: window.innerHeight;
|
||||
var cols = Math.floor(vpWidth / cellWidth);
|
||||
// Why: subtract 2 rows so the bottom terminal line is never covered
|
||||
// by the accessory bar. The WebView's innerHeight can slightly
|
||||
// overstate the visible area due to layout timing or safe-area
|
||||
// insets, causing the last rows to render behind the overlay.
|
||||
var rows = Math.max(8, Math.floor(vpHeight / cellHeight) - 2);
|
||||
var rows = Math.max(8, Math.floor(vpHeight / cellHeight));
|
||||
notify({ type: 'measure-result', cols: cols, rows: rows });
|
||||
}
|
||||
|
||||
@@ -334,7 +337,7 @@ const XTERM_HTML = `<!DOCTYPE html>
|
||||
writesDraining = false;
|
||||
if (term) { term.clear(); term.reset(); }
|
||||
} else if (msg.type === 'measure') {
|
||||
measureFitDimensions();
|
||||
measureFitDimensions(msg.containerHeight);
|
||||
} else if (msg.type === 'reset-zoom') {
|
||||
applyFitScale();
|
||||
}
|
||||
@@ -576,12 +579,14 @@ export const TerminalWebView = forwardRef<TerminalWebViewHandle, Props>(function
|
||||
clear() {
|
||||
postMessage({ type: 'clear' })
|
||||
},
|
||||
measureFitDimensions(): Promise<{ cols: number; rows: number } | null> {
|
||||
measureFitDimensions(
|
||||
containerHeight?: number
|
||||
): Promise<{ cols: number; rows: number } | null> {
|
||||
if (!isWebReadyRef.current) return Promise.resolve(null)
|
||||
return new Promise((resolve) => {
|
||||
measureResolveRef.current?.(null)
|
||||
measureResolveRef.current = resolve
|
||||
sendToWebView({ type: 'measure' })
|
||||
sendToWebView({ type: 'measure', containerHeight })
|
||||
// Why: if the WebView doesn't respond within 2s (e.g., xterm
|
||||
// failed to load), resolve null so the caller can disable
|
||||
// Fit to Phone rather than hanging indefinitely.
|
||||
|
||||
Reference in New Issue
Block a user