doxa-maps/library/composables/retryableAsyncComponent.js
doxa-maps-88655c70 8578854cbc
Some checks are pending
build-gate / build (push) Waiting to run
Search tier 2: a misspelled country resolves to THE country (best match, 2-edit cap)
Driver: 'if I search the name of the country and add n to it, like
Austrian or Canadian, I would like for there to be a selection of that
country -- the table under the map shows the result for that country.'

Three changes, one ladder:
1. resolveFuzzyValues(): WHOLE-VALUE Levenshtein against the DISTINCT
   values of every search key -- 'Sudann' is a misspelling of the
   VALUE 'Sudan', so the rows returned are exactly Sudan's, identical
   to picking it as a Place. The windowed matcher stays only as the
   final rescue tier.
2. Best match wins: only the minimal-distance value(s) resolve. The
   under-the-cap union admitted 47 rows for 'Canadian'.
3. Both fuzzy tiers cap at 2 edits absolute (the did-you-mean bound);
   threshold*len alone reached 3 errors at 8+ chars, which is where
   the cross-facet noise lived.

Measured (dashboard scope / grid count agree): Sudann -> 105/105
Sudan only; Indonesa -> 36/36 Indonesia; Banladesh -> 3/3 Bangladesh;
Bhutann -> 13/13 Bhutan; exact terms unchanged (tier 1).

NOTE: Canada/Austria themselves have NO rows in this dataset (it
holds only the 175 countries with unreached people groups), so
'Canadian'/'Austrian' cannot select them -- a handful of genuine
2-edit neighbours return instead.

Also ships @map/composables/retryableAsyncComponent.js wired at every
defineAsyncComponent site (ProfileLoader chokepoint + 8 lazy
components): a transient ERR_NETWORK_CHANGED no longer permanently
kills a lazy plugin (3 retries, growing backoff). Live fault-injection
proof pending; the wrapper is the documented Vue onError contract.
2026-09-24 00:34:19 +00:00

30 lines
1.4 KiB
JavaScript

/**
* 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 Driver 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()
},
})
}