mirror of
https://github.com/stablyai/orca.git
synced 2026-09-26 16:02:43 +00:00
* Fix mobile terminal query reply authority * fix(terminal): harden mobile query reply handoffs * fix(terminal): exclude passive mobile query responders * fix(terminal): gate mobile query replies on host capability Older hosts strip terminal.send's inputKind (zod drops unknown keys), so a forwarded xterm reply would land as ordinary floor-taking shell input. Hosts now advertise terminal.query-reply-input.v1 via status.get and mobile drops replies unless the host advertises it (pre-fix behavior). Also documents the bounded desktop-to-mobile handoff double-reply residual. Co-authored-by: Orca <help@stably.ai> * fix(terminal): advance snapshot seq across recovery snapshots The pending-overflow recovery loop trims buffered output against recovery.seq while query replay and boundary strips kept using the initial snapshot seq. Unreachable under today's control flow (no await separates the initial-overflow consume from the loop), but the stale seq would silently drop covered query replies if that ordering ever changes. Track the seq that actually covered the buffered chunks. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai>
106 lines
3.3 KiB
TypeScript
106 lines
3.3 KiB
TypeScript
import { useCallback } from 'react'
|
|
import { StyleSheet, View } from 'react-native'
|
|
import { TerminalWebView } from '../terminal/TerminalWebView'
|
|
import type {
|
|
MobileTerminalTheme,
|
|
TerminalKeyboardAvoidanceMetrics,
|
|
TerminalModes,
|
|
TerminalWebViewHandle
|
|
} from '../terminal/terminal-webview-contract'
|
|
|
|
type TerminalPaneViewProps = {
|
|
handle: string
|
|
active: boolean
|
|
keyboardLift: number
|
|
terminalTheme?: MobileTerminalTheme
|
|
textScale: number
|
|
onRef: (handle: string, ref: TerminalWebViewHandle | null) => void
|
|
onWebReady: (handle: string) => void
|
|
onSelectionMode: (handle: string, active: boolean) => void
|
|
onSelectionCopy: (handle: string, text: string) => void
|
|
onSelectionEvicted: (handle: string) => void
|
|
onModesChanged: (handle: string, modes: TerminalModes) => void
|
|
onKeyboardAvoidanceMetrics: (handle: string, metrics: TerminalKeyboardAvoidanceMetrics) => void
|
|
onHaptic: (kind: 'selection' | 'success' | 'error' | 'edge-bump') => void
|
|
onTerminalInput: (handle: string, bytes: string) => void
|
|
onTerminalQueryReply: (handle: string, bytes: string) => void
|
|
onTerminalTap: (handle: string) => void
|
|
onFileTap: (handle: string, pathText: string, line: number | null, column: number | null) => void
|
|
onOpenUrl: (handle: string, url: string) => void
|
|
onTextScaleChange: (scale: number) => void
|
|
}
|
|
|
|
export function TerminalPaneView({
|
|
handle,
|
|
active,
|
|
keyboardLift,
|
|
terminalTheme,
|
|
textScale,
|
|
onRef,
|
|
onWebReady,
|
|
onSelectionMode,
|
|
onSelectionCopy,
|
|
onSelectionEvicted,
|
|
onModesChanged,
|
|
onKeyboardAvoidanceMetrics,
|
|
onHaptic,
|
|
onTerminalInput,
|
|
onTerminalQueryReply,
|
|
onTerminalTap,
|
|
onFileTap,
|
|
onOpenUrl,
|
|
onTextScaleChange
|
|
}: TerminalPaneViewProps) {
|
|
const setRef = useCallback(
|
|
(ref: TerminalWebViewHandle | null) => {
|
|
onRef(handle, ref)
|
|
},
|
|
[handle, onRef]
|
|
)
|
|
|
|
return (
|
|
<View
|
|
// Why: inactive terminal WebViews stay mounted to preserve xterm state,
|
|
// while touch and visibility are disabled until the tab is active again.
|
|
pointerEvents={active ? 'auto' : 'none'}
|
|
style={[
|
|
styles.terminalPane,
|
|
keyboardLift > 0 && { transform: [{ translateY: -keyboardLift }] },
|
|
!active && styles.terminalPaneHidden
|
|
]}
|
|
>
|
|
<TerminalWebView
|
|
ref={setRef}
|
|
style={styles.terminalWebView}
|
|
terminalTheme={terminalTheme}
|
|
textScale={textScale}
|
|
onWebReady={() => onWebReady(handle)}
|
|
onSelectionMode={(a) => onSelectionMode(handle, a)}
|
|
onSelectionCopy={(t) => onSelectionCopy(handle, t)}
|
|
onSelectionEvicted={() => onSelectionEvicted(handle)}
|
|
onModesChanged={(m) => onModesChanged(handle, m)}
|
|
onKeyboardAvoidanceMetrics={(m) => onKeyboardAvoidanceMetrics(handle, m)}
|
|
onHaptic={onHaptic}
|
|
onTerminalInput={(bytes) => onTerminalInput(handle, bytes)}
|
|
onTerminalQueryReply={(bytes) => onTerminalQueryReply(handle, bytes)}
|
|
onTerminalTap={() => onTerminalTap(handle)}
|
|
onFileTap={(pathText, line, column) => onFileTap(handle, pathText, line, column)}
|
|
onOpenUrl={(url) => onOpenUrl(handle, url)}
|
|
onTextScaleChange={onTextScaleChange}
|
|
/>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
terminalPane: {
|
|
...StyleSheet.absoluteFillObject
|
|
},
|
|
terminalPaneHidden: {
|
|
opacity: 0
|
|
},
|
|
terminalWebView: {
|
|
flex: 1
|
|
}
|
|
})
|