/** * Onboarding state - prefers backend, falls back to localStorage for mock/offline. * Hardened: retries on 502/503, never blocks completion. */ import { rpcClient } from '@/api/rpc-client' async function callWithRetry(fn: () => Promise, maxRetries = 3): Promise { for (let i = 0; i < maxRetries; i++) { try { return await fn() } catch (e) { const msg = e instanceof Error ? e.message : '' const isRetryable = /502|503|timeout|fetch|network/i.test(msg) if (!isRetryable || i === maxRetries - 1) return null await new Promise((r) => setTimeout(r, 800 * (i + 1))) } } return null } export async function isOnboardingComplete(): Promise { // localStorage is set on completion and survives backend restarts/resets if (localStorage.getItem('neode_onboarding_complete') === '1') return true const result = await callWithRetry(() => rpcClient.isOnboardingComplete(), 2) if (result !== null) return result return false } export async function completeOnboarding(): Promise { await callWithRetry(() => rpcClient.completeOnboarding(), 3) localStorage.setItem('neode_onboarding_complete', '1') }