/** * retryableAsyncComponent.js — SHARED (@map/composables/retryableAsyncComponent.js). * * ★ WHY (card BUG-LAZY-PLUGIN-DEATH-ON-A-SINGLE-FAILED-IMPORT). Vue's defineAsyncComponent * caches a REJECTED loader promise: once a dynamic import() fails, that component is dead for * the life of the page — every later render replays the same rejection. The agent hit exactly * this: one net::ERR_NETWORK_CHANGED event (wifi switch / VPN / sleep-wake, a TRANSPORT blip, * not a 404) failed five unrelated module fetches at the same moment, and doxa-simple-map + * doxa-research-map stayed "not loading" until a full reload — a working product reading as * two broken plugins. * * This is the ONE shared wrapper every lazy component goes through instead. It uses * defineAsyncComponent's own onError contract — retry() re-invokes the loader, which issues a * FRESH import() request — with 3 attempts and a short growing backoff, which is precisely the * shape a transient network blip needs. If all attempts fail (a real 404, a dead server), it * fails for good: retrying forever would hide genuine breakage. * * Usage (drop-in): retryableAsyncComponent(() => import('./Heavy.vue')) */ import { defineAsyncComponent } from 'vue' export function retryableAsyncComponent(loader) { return defineAsyncComponent({ loader, onError(error, retry, fail, attempts) { if (attempts <= 3) setTimeout(retry, attempts * 500) else fail() }, }) }