perf(mobile): dedupe in-flight relay diagnostics sends

Send to Orca had no busy state, so repeat clicks each spawned a gh api user
subprocess against the shared gh concurrency limit and filed a duplicate report.

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Neil
2026-08-04 00:35:38 -07:00
co-authored by Orca
parent e853f2f418
commit 7a5e705fe7
2 changed files with 66 additions and 1 deletions
@@ -0,0 +1,54 @@
// @vitest-environment happy-dom
import { cleanup, render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { useSendRelayMintFailureFeedback } from './relay-mint-failure-feedback'
import type { MobileRelayMintFailure } from '../../../../shared/mobile-relay-mint-failure'
vi.mock('sonner', () => ({
toast: { success: vi.fn(), error: vi.fn() }
}))
const failure: MobileRelayMintFailure = {
code: 'relay_binding_failed',
stage: 'binding_failed',
message: 'Could not bind the relay session.'
}
function SendButton(): React.JSX.Element {
const send = useSendRelayMintFailureFeedback()
return (
<button type="button" onClick={() => void send({ failure, preferredConnectionMode: 'relay' })}>
send
</button>
)
}
afterEach(() => {
cleanup()
vi.unstubAllGlobals()
})
describe('useSendRelayMintFailureFeedback', () => {
it('drops repeat clicks while a send is in flight so one gh spawn and one report go out', async () => {
const user = userEvent.setup()
let resolveViewer: (value: null) => void = () => {}
const viewer = vi.fn(() => new Promise<null>((resolve) => (resolveViewer = resolve)))
const submit = vi.fn(async () => ({ ok: true as const }))
vi.stubGlobal(
'window',
Object.assign(window, { api: { gh: { viewer }, feedback: { submit } } })
)
render(<SendButton />)
const button = screen.getByRole('button', { name: 'send' })
await user.click(button)
await user.click(button)
await user.click(button)
expect(viewer).toHaveBeenCalledTimes(1)
resolveViewer(null)
await vi.waitFor(() => expect(submit).toHaveBeenCalledTimes(1))
})
})
@@ -1,4 +1,4 @@
import { useCallback } from 'react'
import { useCallback, useRef } from 'react'
import { toast } from 'sonner'
import { translate } from '@/i18n/i18n'
import type { MobileRelayMintFailure } from '../../../../shared/mobile-relay-mint-failure'
@@ -30,7 +30,16 @@ export function buildRelayMintFailureFeedback(args: RelayMintFailureFeedbackArgs
export function useSendRelayMintFailureFeedback(): (
args: RelayMintFailureFeedbackArgs
) => Promise<void> {
// Why: the button has no busy state and the send is a `gh` subprocess plus an
// HTTPS post, so repeat clicks would file duplicate reports and queue extra
// spawns behind the shared gh concurrency limit.
const inFlightRef = useRef(false)
return useCallback(async (args: RelayMintFailureFeedbackArgs): Promise<void> => {
if (inFlightRef.current) {
return
}
inFlightRef.current = true
try {
// Why: identity is best-effort; a missing gh session must not block the report.
const viewer = await window.api.gh.viewer().catch(() => null)
@@ -57,6 +66,8 @@ export function useSendRelayMintFailureFeedback(): (
)
)
console.error('Failed to send relay pairing diagnostics:', error)
} finally {
inFlightRef.current = false
}
}, [])
}