fix(mobile): serialize accessory key presses

This commit is contained in:
Brennan Benson
2026-08-27 13:52:14 -07:00
parent 1bde6c6f73
commit f705721fed
2 changed files with 46 additions and 2 deletions
@@ -60,6 +60,28 @@ describe('terminal accessory repeat', () => {
expect(send).toHaveBeenCalledTimes(1)
})
it('serializes a new press behind the previous press send', async () => {
vi.useFakeTimers()
const first = deferred()
const sent: string[] = []
const send = vi.fn((input: string) => {
sent.push(input)
return input === 'down' ? first.promise.then(() => true) : Promise.resolve(true)
})
const repeat = createTerminalAccessoryRepeatController<string>()
repeat.start('down', send)
repeat.stop()
repeat.start('up', send)
expect(sent).toEqual(['down'])
first.resolve()
await vi.advanceTimersByTimeAsync(0)
expect(sent).toEqual(['down', 'up'])
repeat.stop()
})
it('stops repeating when the transport rejects a send', async () => {
vi.useFakeTimers()
const send = vi.fn(() => Promise.reject(new Error('disconnected')))
@@ -19,6 +19,28 @@ export function createTerminalAccessoryRepeatSender<TInput>(
export function createTerminalAccessoryRepeatController<TInput>() {
let generation = 0
let timer: ReturnType<typeof setTimeout> | null = null
let dispatchTail: Promise<void> | null = null
const dispatch = (
input: TInput,
send: TerminalAccessoryRepeatSender<TInput>,
activeGeneration: number
) => {
const sendIfCurrent = () =>
generation === activeGeneration ? send(input) : Promise.resolve(false)
const result = dispatchTail ? dispatchTail.then(sendIfCurrent) : sendIfCurrent()
const settled = result.then(
() => undefined,
() => undefined
)
dispatchTail = settled
void settled.then(() => {
if (dispatchTail === settled) {
dispatchTail = null
}
})
return result
}
const stop = () => {
generation += 1
@@ -39,7 +61,7 @@ export function createTerminalAccessoryRepeatController<TInput>() {
if (generation !== activeGeneration) {
return
}
void send(input).then(
void dispatch(input, send, activeGeneration).then(
(sent) => {
if (sent && generation === activeGeneration) {
schedule(TERMINAL_ACCESSORY_REPEAT_INTERVAL_MS)
@@ -50,7 +72,7 @@ export function createTerminalAccessoryRepeatController<TInput>() {
}, delayMs)
}
void send(input).then(
void dispatch(input, send, activeGeneration).then(
(sent) => {
if (sent && generation === activeGeneration) {
schedule(