fix(mobile): recheck queued tap delivery context

This commit is contained in:
Brennan Benson
2026-08-27 15:19:14 -07:00
parent 04bc32905a
commit 7a805da421
3 changed files with 42 additions and 12 deletions
+15 -9
View File
@@ -2999,12 +2999,16 @@ export default function SessionScreen() {
async function handleAccessoryKey(
input: ReturnType<typeof createTerminalLiveAccessoryInput>,
targetHandle = activeHandleRef.current
targetHandle = activeHandleRef.current,
isDeliveryTargetCurrent: () => boolean = () => targetHandle === activeHandleRef.current
) {
if (!client || !targetHandle || targetHandle !== activeHandleRef.current || !canSend) {
if (!client || !targetHandle || !isDeliveryTargetCurrent() || !canSend) {
return false
}
const accessoryCommit = await handleLiveInputAccessoryBytes(input)
if (!isDeliveryTargetCurrent()) {
return false
}
if (accessoryCommit.kind !== 'allow-raw') {
return accessoryCommit.kind === 'handled'
}
@@ -3419,17 +3423,19 @@ export default function SessionScreen() {
// Why: the controller queue sits outside RPC, so fence pre-outage taps from a later connection.
const targetClient = clientRef.current
const targetConnectedAt = targetClient?.getLastConnectedAt() ?? null
const isDeliveryTargetCurrent = (targetHandle: string) =>
activeHandleRef.current === targetHandle &&
targetClient !== null &&
clientRef.current === targetClient &&
connStateRef.current === 'connected' &&
targetClient.getLastConnectedAt() === targetConnectedAt
accessoryRepeatRef.current.start(
input,
createTerminalAccessoryRepeatSender(
activeHandleRef.current,
(targetHandle) =>
activeHandleRef.current === targetHandle &&
targetClient !== null &&
clientRef.current === targetClient &&
connStateRef.current === 'connected' &&
targetClient.getLastConnectedAt() === targetConnectedAt,
(nextInput, targetHandle) => handleAccessoryKeyRef.current(nextInput, targetHandle)
isDeliveryTargetCurrent,
(nextInput, targetHandle, isTargetCurrent) =>
handleAccessoryKeyRef.current(nextInput, targetHandle, isTargetCurrent)
)
)
},
@@ -164,7 +164,26 @@ describe('terminal accessory repeat', () => {
await expect(send('down')).resolves.toBe(false)
expect(sendToTerminal).toHaveBeenCalledTimes(1)
expect(sendToTerminal).toHaveBeenCalledWith('down', 'terminal-a')
expect(sendToTerminal).toHaveBeenCalledWith('down', 'terminal-a', expect.any(Function))
})
it('provides a live target guard for async send preflight', async () => {
let targetCurrent = true
const preflight = deferred()
const send = createTerminalAccessoryRepeatSender(
'terminal-a',
() => targetCurrent,
async (_input, _targetHandle, isDeliveryTargetCurrent) => {
await preflight.promise
return isDeliveryTargetCurrent()
}
)
const result = send('down')
targetCurrent = false
preflight.resolve()
await expect(result).resolves.toBe(false)
})
it('drops a queued send when its press-time connection is no longer current', async () => {
@@ -2,17 +2,22 @@ export const TERMINAL_ACCESSORY_REPEAT_DELAY_MS = 400
export const TERMINAL_ACCESSORY_REPEAT_INTERVAL_MS = 45
type TerminalAccessoryRepeatSender<TInput> = (input: TInput) => Promise<boolean>
type TerminalAccessoryRepeatSendToTerminal<TInput> = (
input: TInput,
targetHandle: string,
isDeliveryTargetCurrent: () => boolean
) => Promise<boolean>
export function createTerminalAccessoryRepeatSender<TInput>(
targetHandle: string | null,
isDeliveryTargetCurrent: (targetHandle: string) => boolean,
sendToTerminal: (input: TInput, targetHandle: string) => Promise<boolean>
sendToTerminal: TerminalAccessoryRepeatSendToTerminal<TInput>
): TerminalAccessoryRepeatSender<TInput> {
return (input) => {
if (!targetHandle || !isDeliveryTargetCurrent(targetHandle)) {
return Promise.resolve(false)
}
return sendToTerminal(input, targetHandle)
return sendToTerminal(input, targetHandle, () => isDeliveryTargetCurrent(targetHandle))
}
}