export interface ShowcaseRetryOptions { readonly isCancelled: () => boolean; readonly attemptTimeoutMs?: number; readonly retryDelayMs?: number; } const DEFAULT_ATTEMPT_TIMEOUT_MS = 10_000; const DEFAULT_RETRY_DELAY_MS = 500; function delay(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } async function runAttemptWithTimeout( operation: () => Promise, timeoutMs: number, ): Promise { let timeout: ReturnType | null = null; try { return await Promise.race([ operation(), new Promise((resolve) => { timeout = setTimeout(() => resolve(false), timeoutMs); }), ]); } catch { return false; } finally { if (timeout !== null) clearTimeout(timeout); } } /** Retry transient showcase setup work until it succeeds or the owning effect unmounts. */ export async function retryShowcaseOperation( operation: () => Promise, options: ShowcaseRetryOptions, ): Promise { const attemptTimeoutMs = options.attemptTimeoutMs ?? DEFAULT_ATTEMPT_TIMEOUT_MS; const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS; while (!options.isCancelled()) { if (await runAttemptWithTimeout(operation, attemptTimeoutMs)) return true; if (!options.isCancelled()) await delay(retryDelayMs); } return false; }