@bicharts/chart-host 0.5.101 → 0.5.103

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1440,7 +1440,7 @@ function Wt(n, s) {
1440
1440
  return { iso: i, unmatched: M4, matchedRows: h3, totalRows: n.length };
1441
1441
  }
1442
1442
 
1443
- // ../shape-core/dist/chunk-EZ56V64J.mjs
1443
+ // ../shape-core/dist/chunk-3IYFLQQR.mjs
1444
1444
  var import_papaparse = __toESM(require_papaparse_min(), 1);
1445
1445
  /*! @bicharts/shape-core — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; US Census/TIGER (public domain). Full text: NOTICE in this package. */
1446
1446
  var Ae = { "north-america": "AG AI AW BB BL BM BQ BS BZ CA CR CU CW DM DO GD GL GP GT HN HT JM KN KY LC MF MQ MS MX NI PA PM PR SV SX TC TT US VC VG VI", "south-america": "AR BO BR CL CO EC FK GF GY PE PY SR UY VE", europe: "AD AL AT AX BA BE BG BY CH CY CZ DE DK EE ES FI FO FR GB GG GI GR HR HU IE IM IS IT JE LI LT LU LV MC MD ME MK MT NL NO PL PT RO RS RU SE SI SJ SK SM UA VA", africa: "AO BF BI BJ BW CD CF CG CI CM CV DJ DZ EG EH ER ET GA GH GM GN GQ GW KE KM LR LS LY MA MG ML MR MU MW MZ NA NE NG RE RW SC SD SH SL SN SO SS ST SZ TD TG TN TZ UG YT ZA ZM ZW", asia: "AE AF AM AZ BD BH BN BT CC CN CX GE HK ID IL IN IO IQ IR JO JP KG KH KP KR KW KZ LA LB LK MM MN MO MV MY NP OM PH PK PS QA SA SG SY TH TJ TL TM TR TW UZ VN YE", oceania: "AS AU CK FJ FM GU KI MH MP NC NF NR NU NZ PF PG PN PW SB TK TO TV UM VU WF WS", antarctica: "AQ BV GS HM TF" };
@@ -1456,8 +1456,8 @@ function He(n) {
1456
1456
  }
1457
1457
  }
1458
1458
  He(N);
1459
- var Ke = ["rate", "ratio", "pct", "percent", "percentage", "rating", "share", "score", "index", "nps", "csat", "margin", "yield", "coverage", "utilization", "z[_-]?score", "average", "avg", "mean", "median", "stddev", "variance", "gpa", "percentile", "quantile", "efficiency", "accuracy", "probability", "multiplier", "coefficient", "correlation", "occupancy", "age", "bmi", "temperature", "density", "pressure", "humidity", "ph", "velocity", "speed", "elevation", "altitude", "latency", "throughput", "bandwidth", "iops", "temperatuur", "temperatur", "temperatura", "teplota", "lampotila", "homerseklet", "sicaklik", "pression", "presion", "pressao", "pressione", "druck", "tryck", "tlak", "paine", "nyomas", "presiune", "humidite", "humedad", "umidade", "umidita", "feuchtigkeit", "vochtigheid", "kosteus", "vlhkost", "vitesse", "velocidad", "velocidade", "velocita", "geschwindigkeit", "snelheid", "hastighet", "hastighed", "rychlost", "nopeus", "sebesseg", "brzina", "predkosc", "densite", "densidad", "densidade", "densita", "dichte", "dichtheid", "hustota", "tiheys", "gustoca", "moyenne", "mediane", "promedio", "mediana", "gemiddelde", "durchschnitt", "mittelwert", "genomsnitt", "gennemsnit", "gjennomsnitt", "keskiarvo", "atlag", "ortalama", "prosjek", "srednia", "prumer", "priemer", "medie", "taux", "tasa", "taxa", "tasso", "verhaltnis", "verhouding", "aandeel", "anteil", "andel", "osuus", "podil", "udzial", "udio", "pourcentage", "porcentaje", "percentual", "prozent", "procent", "prosentti", "szazalek", "yuzde", "participacion", "participacao", "pondere", "omjer", "arany", "oran", "rata", "leeftijd", "edad", "idade", "wiek", "eletkor", "varsta", "puntuacion", "pontuacao", "punteggio", "betyg", "ocena", "indice", "indeks", "wskaznik", "puan"];
1460
- var $e = ["pct", "percent", "percentage", "rate", "ratio", "rating", "share", "score", "index", "nps", "csat", "margin", "yield", "coverage", "utilization", "gpa", "percentile", "quantile", "bmi", "density", "pressure", "humidity", "velocity", "efficiency", "occupancy", "latency", "throughput", "bandwidth", "iops"];
1459
+ var Ke = ["rate", "ratio", "pct", "percent", "percentage", "rating", "share", "score", "index", "nps", "csat", "margin", "yield", "coverage", "utilization", "z[_-]?score", "average", "avg", "mean", "median", "stddev", "variance", "gpa", "percentile", "quantile", "efficiency", "accuracy", "probability", "multiplier", "coefficient", "correlation", "occupancy", "age", "bmi", "temperature", "density", "pressure", "humidity", "ph", "velocity", "speed", "elevation", "altitude", "latency", "throughput", "bandwidth", "iops", "ppm", "ppb", "dpmo", "bps", "temperatuur", "temperatur", "temperatura", "teplota", "lampotila", "homerseklet", "sicaklik", "pression", "presion", "pressao", "pressione", "druck", "tryck", "tlak", "paine", "nyomas", "presiune", "humidite", "humedad", "umidade", "umidita", "feuchtigkeit", "vochtigheid", "kosteus", "vlhkost", "vitesse", "velocidad", "velocidade", "velocita", "geschwindigkeit", "snelheid", "hastighet", "hastighed", "rychlost", "nopeus", "sebesseg", "brzina", "predkosc", "densite", "densidad", "densidade", "densita", "dichte", "dichtheid", "hustota", "tiheys", "gustoca", "moyenne", "mediane", "promedio", "mediana", "gemiddelde", "durchschnitt", "mittelwert", "genomsnitt", "gennemsnit", "gjennomsnitt", "keskiarvo", "atlag", "ortalama", "prosjek", "srednia", "prumer", "priemer", "medie", "taux", "tasa", "taxa", "tasso", "verhaltnis", "verhouding", "aandeel", "anteil", "andel", "osuus", "podil", "udzial", "udio", "pourcentage", "porcentaje", "percentual", "prozent", "procent", "prosentti", "szazalek", "yuzde", "participacion", "participacao", "pondere", "omjer", "arany", "oran", "rata", "leeftijd", "edad", "idade", "wiek", "eletkor", "varsta", "puntuacion", "pontuacao", "punteggio", "betyg", "ocena", "indice", "indeks", "wskaznik", "puan"];
1460
+ var $e = ["pct", "percent", "percentage", "rate", "ratio", "rating", "share", "score", "index", "nps", "csat", "margin", "yield", "coverage", "utilization", "gpa", "percentile", "quantile", "bmi", "density", "pressure", "humidity", "velocity", "efficiency", "occupancy", "latency", "throughput", "bandwidth", "iops", "ppm", "ppb", "dpmo", "bps"];
1461
1461
  var Ze = ["latitude", "longitude"];
1462
1462
  var Xe = ["latitude", "longitude"];
1463
1463
  function j2(n) {
@@ -2926,7 +2926,7 @@ function hexToRGB(hex2) {
2926
2926
  // src/labelContrastDom.ts
2927
2927
  var LABEL_CONTRAST_DONE_ATTR = "data-lch-contrast";
2928
2928
  var LABEL_CONTRAST_CAP = 2e3;
2929
- var EMPTY5 = { rects: 0, scanned: 0, fixed: 0, pillsBoosted: 0, offFill: 0, pageMajority: 0 };
2929
+ var EMPTY5 = { rects: 0, scanned: 0, fixed: 0, pillsBoosted: 0, offFill: 0, pageMajority: 0, paintedOver: 0 };
2930
2930
  function backedSamples(el, pts) {
2931
2931
  try {
2932
2932
  const ge = el;
@@ -2948,6 +2948,20 @@ function backedSamples(el, pts) {
2948
2948
  return null;
2949
2949
  }
2950
2950
  }
2951
+ var DOC_POSITION_FOLLOWING = 4;
2952
+ function paintRoot(el, container) {
2953
+ let root = null;
2954
+ for (let n = el.parentElement; n; n = n.parentElement) {
2955
+ if (String(n.tagName || "").toLowerCase() === "svg") root = n;
2956
+ if (n === container) break;
2957
+ }
2958
+ return root;
2959
+ }
2960
+ function drawnBeneath(shape, text, textRoot) {
2961
+ if (!shape.root || shape.root !== textRoot) return true;
2962
+ if (typeof shape.el.compareDocumentPosition !== "function") return true;
2963
+ return (shape.el.compareDocumentPosition(text) & DOC_POSITION_FOLLOWING) !== 0;
2964
+ }
2951
2965
  function applyLabelContrast(container, opts = {}) {
2952
2966
  const report = { ...EMPTY5 };
2953
2967
  if (!container) {
@@ -2972,7 +2986,15 @@ function applyLabelContrast(container, opts = {}) {
2972
2986
  if (!fill || fill === "none") return;
2973
2987
  const opAttr = el.getAttribute("fill-opacity");
2974
2988
  const op = opAttr === null ? 1 : parseFloat(opAttr);
2975
- rects.push({ r, fill, op: isFinite(op) ? op : 1, area: r.width * r.height, el, ord: ord++ });
2989
+ rects.push({
2990
+ r,
2991
+ fill,
2992
+ op: isFinite(op) ? op : 1,
2993
+ area: r.width * r.height,
2994
+ el,
2995
+ ord: ord++,
2996
+ root: paintRoot(el, container)
2997
+ });
2976
2998
  };
2977
2999
  const shapeEls = container.querySelectorAll("rect, path");
2978
3000
  if (shapeEls.length === 0) {
@@ -3008,7 +3030,15 @@ function applyLabelContrast(container, opts = {}) {
3008
3030
  if (tr.width <= 0 || tr.height <= 0) continue;
3009
3031
  const ovArea = (r) => Math.max(0, Math.min(r.right, tr.right) - Math.max(r.left, tr.left)) * Math.max(0, Math.min(r.bottom, tr.bottom) - Math.max(r.top, tr.top));
3010
3032
  const glyphArea = tr.width * tr.height;
3011
- const boxed = rects.map((mk) => ({ mk, ov: ovArea(mk.r) })).filter((x3) => x3.ov > 0);
3033
+ const txRoot = paintRoot(tx, container);
3034
+ let over = 0;
3035
+ const boxed = rects.map((mk) => ({ mk, ov: ovArea(mk.r) })).filter((x3) => {
3036
+ if (!(x3.ov > 0)) return false;
3037
+ if (drawnBeneath(x3.mk, tx, txRoot)) return true;
3038
+ if (effAlpha(x3.mk) >= PILL_MIN_ALPHA) over++;
3039
+ return false;
3040
+ });
3041
+ if (over > 0) report.paintedOver++;
3012
3042
  const pts = glyphSampleGrid({ left: tr.left, top: tr.top, width: tr.width, height: tr.height });
3013
3043
  const painted = pts.map(() => false);
3014
3044
  let unmeasured = 0;
@@ -3308,7 +3338,16 @@ function createChartHost(container, config) {
3308
3338
  if (!el) el = resolver.penetrateOverlayAt(e?.clientX, e?.clientY, clickSel);
3309
3339
  if (!el) el = resolver.resolveByGeometry(e?.clientX, e?.clientY, clickSel);
3310
3340
  if (!el) {
3311
- if (current && current.length) notify([], "user");
3341
+ if (current && current.length) {
3342
+ const slot = container[CONTAINER_SLOT_XF_CLEAR];
3343
+ if (typeof slot === "function") {
3344
+ try {
3345
+ slot();
3346
+ } catch {
3347
+ }
3348
+ }
3349
+ if (current && current.length) notify([], "user");
3350
+ }
3312
3351
  return;
3313
3352
  }
3314
3353
  const rows = parseRowIdxs(el.getAttribute(ROW_IDX_ATTR));
package/dist/index.mjs CHANGED
@@ -128,7 +128,7 @@ import {
128
128
  unpinScrolledAxis,
129
129
  valuePlacementFlag,
130
130
  x
131
- } from "./chunk-LJU47KQS.mjs";
131
+ } from "./chunk-EFOEZN4D.mjs";
132
132
  import "./chunk-A2GMXZP7.mjs";
133
133
  import "./chunk-GHODWOAL.mjs";
134
134
 
@@ -2114,6 +2114,88 @@ function sweepCandidates(seedHex) {
2114
2114
  return out;
2115
2115
  }
2116
2116
 
2117
+ // src/pendingGenerate.ts
2118
+ var PENDING_RECOVERY_WINDOW_MS = 9e5;
2119
+ var PENDING_RECOVERY_POLL_MS = 15e3;
2120
+ var PENDING_RECOVERY_FIRST_POLL_MS = 2e3;
2121
+ var PENDING_RECOVERY_FAST_PHASE_MS = 12e4;
2122
+ var PENDING_RECOVERY_MID_POLL_MS = 3e4;
2123
+ var PENDING_RECOVERY_SLOW_PHASE_MS = 3e5;
2124
+ var PENDING_RECOVERY_SLOW_POLL_MS = 6e4;
2125
+ function pendingRecoveryNextDelayMs(elapsedMs) {
2126
+ if (elapsedMs < PENDING_RECOVERY_FAST_PHASE_MS) return PENDING_RECOVERY_POLL_MS;
2127
+ if (elapsedMs < PENDING_RECOVERY_SLOW_PHASE_MS) return PENDING_RECOVERY_MID_POLL_MS;
2128
+ return PENDING_RECOVERY_SLOW_POLL_MS;
2129
+ }
2130
+ function encodePendingGenerate(m2) {
2131
+ const o2 = { v: m2.v, t: m2.t, c: m2.c, p: m2.p ?? "" };
2132
+ if (Number.isFinite(m2.h)) o2.h = Math.max(0, Math.floor(m2.h));
2133
+ return JSON.stringify(o2);
2134
+ }
2135
+ function parsePendingGenerate(raw) {
2136
+ if (typeof raw !== "string" || raw.trim() === "") return null;
2137
+ try {
2138
+ const o2 = JSON.parse(raw);
2139
+ const v2 = Number(o2?.v), t2 = Number(o2?.t);
2140
+ if (!Number.isFinite(v2) || v2 <= 0 || !Number.isFinite(t2) || t2 <= 0) return null;
2141
+ const h2 = Number(o2?.h);
2142
+ const m2 = { v: Math.floor(v2), t: Math.floor(t2), c: typeof o2.c === "string" ? o2.c : "", p: typeof o2.p === "string" ? o2.p : "" };
2143
+ if (Number.isFinite(h2) && h2 >= 0) m2.h = Math.floor(h2);
2144
+ return m2;
2145
+ } catch {
2146
+ return null;
2147
+ }
2148
+ }
2149
+ function markerArmedBlind(m2) {
2150
+ if (!m2) return false;
2151
+ return (m2.h ?? m2.v - 1) <= 0;
2152
+ }
2153
+ function isBlindMarker(m2) {
2154
+ if (!m2) return false;
2155
+ return typeof m2.c !== "string" || m2.c.trim() === "";
2156
+ }
2157
+ function servedCorrelationProves(m2, served) {
2158
+ if (!m2 || typeof m2.c !== "string" || m2.c.trim() === "") return false;
2159
+ if (typeof served !== "string" || served.trim() === "") return false;
2160
+ return served.trim().toLowerCase() === m2.c.trim().toLowerCase();
2161
+ }
2162
+ function sameMarker(a2, b2) {
2163
+ if (!a2 || !b2) return false;
2164
+ if (typeof a2.c !== "string" || typeof b2.c !== "string") return false;
2165
+ const ca = a2.c.trim().toLowerCase(), cb = b2.c.trim().toLowerCase();
2166
+ if (ca === "" || cb === "") return false;
2167
+ return ca === cb;
2168
+ }
2169
+ function decidePendingRecovery(m2, nowMs, persistedVersion) {
2170
+ if (!m2) return "none";
2171
+ if ((persistedVersion ?? 0) >= m2.v) return "delivered";
2172
+ if (nowMs - m2.t > PENDING_RECOVERY_WINDOW_MS) return "expired";
2173
+ if (isBlindMarker(m2)) return "blind";
2174
+ return "poll";
2175
+ }
2176
+ function recoveryPollRunning(d2) {
2177
+ return d2 === "poll";
2178
+ }
2179
+ function recoveryOwnsTheSentence(trigger) {
2180
+ return trigger !== "transport-lost";
2181
+ }
2182
+ function latePickupAction(m2, triedThisInstance) {
2183
+ if (!m2 || isBlindMarker(m2)) return "clear";
2184
+ return triedThisInstance ? "keep" : "fetch";
2185
+ }
2186
+ function latePickupKeepsMarker(outcome) {
2187
+ return outcome === "transport";
2188
+ }
2189
+ function pollGiveUpKeepsMarker(outcome, m2) {
2190
+ return outcome === "window-closed" && !!m2 && !isBlindMarker(m2);
2191
+ }
2192
+ function pendingRecoveryRemainingMs(m2, nowMs) {
2193
+ return Math.max(0, m2.t + PENDING_RECOVERY_WINDOW_MS - nowMs);
2194
+ }
2195
+ function transportFailureShouldRecover(headersReceived, genNew) {
2196
+ return headersReceived && genNew;
2197
+ }
2198
+
2117
2199
  // src/index.ts
2118
2200
  function registerCityTable(packed) {
2119
2201
  L3(packed);
@@ -2177,6 +2259,13 @@ export {
2177
2259
  MIN_HIT_BAND_PX,
2178
2260
  MIN_RAMP_FILLS,
2179
2261
  PAGE_MATCH_TOLERANCE,
2262
+ PENDING_RECOVERY_FAST_PHASE_MS,
2263
+ PENDING_RECOVERY_FIRST_POLL_MS,
2264
+ PENDING_RECOVERY_MID_POLL_MS,
2265
+ PENDING_RECOVERY_POLL_MS,
2266
+ PENDING_RECOVERY_SLOW_PHASE_MS,
2267
+ PENDING_RECOVERY_SLOW_POLL_MS,
2268
+ PENDING_RECOVERY_WINDOW_MS,
2180
2269
  PHANTOM_FRACTION,
2181
2270
  PILL_MIN_ALPHA,
2182
2271
  PILL_MIN_COVERAGE,
@@ -2229,7 +2318,9 @@ export {
2229
2318
  createMarkResolver,
2230
2319
  ctmScaleOf,
2231
2320
  decideLabelColor,
2321
+ decidePendingRecovery,
2232
2322
  deltaEHex,
2323
+ encodePendingGenerate,
2233
2324
  ensureCrossfilterHitTargets,
2234
2325
  explainRenderFailure,
2235
2326
  filterFitsChooser,
@@ -2245,14 +2336,18 @@ export {
2245
2336
  hitBandFlag,
2246
2337
  inlineFilterGate,
2247
2338
  isBlankRender,
2339
+ isBlindMarker,
2248
2340
  isHorizontalLabelRow,
2249
2341
  isPhantomBox,
2250
2342
  isPillBackdropAlpha,
2251
2343
  labelBand,
2344
+ latePickupAction,
2345
+ latePickupKeepsMarker,
2252
2346
  launchFavorStyle,
2253
2347
  launchGenerates,
2254
2348
  listNeedsFilter,
2255
2349
  loadGeo,
2350
+ markerArmedBlind,
2256
2351
  measureContainerBoxes,
2257
2352
  needsScroll,
2258
2353
  newQualifyGroupState,
@@ -2262,6 +2357,9 @@ export {
2262
2357
  normalizeFilterTerm,
2263
2358
  orderMostDistinct,
2264
2359
  orderRefusalsForDisplay,
2360
+ parsePendingGenerate,
2361
+ pendingRecoveryNextDelayMs,
2362
+ pendingRecoveryRemainingMs,
2265
2363
  periodTickSuppressesFeedback,
2266
2364
  pickDistinctColorFromSeed,
2267
2365
  pillBacksGlyph,
@@ -2269,6 +2367,7 @@ export {
2269
2367
  planAxisPin,
2270
2368
  planFrameGrow,
2271
2369
  planTrivialChart,
2370
+ pollGiveUpKeepsMarker,
2272
2371
  qualifyAuto,
2273
2372
  qualifyCancel,
2274
2373
  qualifyFailureFallsOpen,
@@ -2281,6 +2380,8 @@ export {
2281
2380
  rasterizeSvgToPngDataUrl,
2282
2381
  readQualifyChartRow,
2283
2382
  readQualifyRefusalRow,
2383
+ recoveryOwnsTheSentence,
2384
+ recoveryPollRunning,
2284
2385
  refusalIsSelectable,
2285
2386
  registerCityTable,
2286
2387
  registerGeo,
@@ -2289,7 +2390,9 @@ export {
2289
2390
  requiredD3Plugins,
2290
2391
  resolveAutoColorScale,
2291
2392
  resolveOptions,
2393
+ sameMarker,
2292
2394
  scrollFitFor,
2395
+ servedCorrelationProves,
2293
2396
  sessionViewStateProvider,
2294
2397
  shouldOpenChooserOnGenerate,
2295
2398
  shouldOpenInlineChooserOnGenerate,
@@ -2299,6 +2402,7 @@ export {
2299
2402
  svgNaturalSize,
2300
2403
  svgToDataUrl,
2301
2404
  toRGBA,
2405
+ transportFailureShouldRecover,
2302
2406
  unpinScrolledAxis,
2303
2407
  valuePlacementFlag,
2304
2408
  vegaSpecReadsDatesInLocalTime
package/dist/react.mjs CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  createChartHost,
5
5
  geoFromCache,
6
6
  loadGeo
7
- } from "./chunk-LJU47KQS.mjs";
7
+ } from "./chunk-EFOEZN4D.mjs";
8
8
  import "./chunk-A2GMXZP7.mjs";
9
9
  import "./chunk-GHODWOAL.mjs";
10
10
 
@@ -32,3 +32,4 @@ export { pinScrolledAxis, unpinScrolledAxis, type AxisPinReport } from "./fitDom
32
32
  export { MIN_DELTA_E, pickDistinctColorFromSeed, buildPalette, buildPaletteFromSeed, orderMostDistinct, LEGIBLE_LIGHTNESS, type PaletteResult, type SeedForSlot, type PaletteOptions, } from "./palette";
33
33
  export { deltaEHex } from "./deltaE";
34
34
  export { resolveAutoColorScale, autoRampMinStep, autoRampCanvas, AUTO_RAMP_CLASSES, AUTO_RAMP_DRAWN_OPACITY, AUTO_RAMP_MIN_STEP_DELTA_E, type AutoColorScale, type AutoColorScaleInput } from "./autoRamp";
35
+ export { PENDING_RECOVERY_WINDOW_MS, PENDING_RECOVERY_POLL_MS, PENDING_RECOVERY_FIRST_POLL_MS, PENDING_RECOVERY_FAST_PHASE_MS, PENDING_RECOVERY_MID_POLL_MS, PENDING_RECOVERY_SLOW_PHASE_MS, PENDING_RECOVERY_SLOW_POLL_MS, pendingRecoveryNextDelayMs, encodePendingGenerate, parsePendingGenerate, markerArmedBlind, isBlindMarker, servedCorrelationProves, sameMarker, decidePendingRecovery, recoveryPollRunning, recoveryOwnsTheSentence, latePickupAction, latePickupKeepsMarker, pollGiveUpKeepsMarker, pendingRecoveryRemainingMs, transportFailureShouldRecover, type PendingGenerateMarker, type PendingRecoveryDecision, type LatePickupAction, } from "./pendingGenerate";
@@ -27,6 +27,9 @@ export interface LabelContrastReport {
27
27
  offFill: number;
28
28
  /** Labels mostly over the page with one end clipping a mark - handed back to the chart. */
29
29
  pageMajority: number;
30
+ /** Labels with a painting shape over their box that was set aside because it is drawn ON TOP
31
+ * of them - an occluder, never a background. See drawnBeneath. */
32
+ paintedOver: number;
30
33
  /** Why the pass did nothing, when it did nothing. */
31
34
  skipped?: "no-container" | "no-shapes" | "too-many-shapes" | "too-many-texts" | "error";
32
35
  }
@@ -0,0 +1,163 @@
1
+ export interface PendingGenerateMarker {
2
+ /** The version this generate will produce: (version in hand || 0) + 1.
3
+ * Diagnostic only - see "WHY `v` IS NOT A PREDICTION" below before trusting it. */
4
+ v: number;
5
+ /** Epoch ms when the generate started (client clock). */
6
+ t: number;
7
+ /** Correlation id of the generate - the ONLY safe handle for recovering it. */
8
+ c: string;
9
+ /** The explicit chart-type pick the user made for this generate ("" = none). */
10
+ p: string;
11
+ /** The version the host actually HELD when the generate started (0 = none).
12
+ * Absent on markers written by older builds. Diagnostics, plus the blind test. */
13
+ h?: number;
14
+ }
15
+ export declare const PENDING_RECOVERY_WINDOW_MS = 900000;
16
+ /** Poll cadence for the first {@link PENDING_RECOVERY_FAST_PHASE_MS}. Each poll is one
17
+ * cheap fetch (and one server log row), so this is deliberately slow: the cost of finding the
18
+ * chart 15 s late is nothing next to the cost of a second generation. */
19
+ export declare const PENDING_RECOVERY_POLL_MS = 15000;
20
+ /** First poll after a re-mount fires quickly - the service may already be done. */
21
+ export declare const PENDING_RECOVERY_FIRST_POLL_MS = 2000;
22
+ /** Up to here the poll runs at {@link PENDING_RECOVERY_POLL_MS} - covers production's p50. */
23
+ export declare const PENDING_RECOVERY_FAST_PHASE_MS = 120000;
24
+ /** ...then at this cadence up to {@link PENDING_RECOVERY_SLOW_PHASE_MS}. */
25
+ export declare const PENDING_RECOVERY_MID_POLL_MS = 30000;
26
+ export declare const PENDING_RECOVERY_SLOW_PHASE_MS = 300000;
27
+ /** ...and at this one for the rest of the window. */
28
+ export declare const PENDING_RECOVERY_SLOW_POLL_MS = 60000;
29
+ /**
30
+ * How long to wait before the NEXT poll, given how long the generate has been running.
31
+ *
32
+ * Tripling the window would have tripled the poll count at a flat 15 s (60 requests, 60 server log
33
+ * rows), and every one of them after the first couple of minutes is asking about a generate that is
34
+ * plainly slow. So the cadence backs off with age while leaving the FAST phase exactly as it was - a
35
+ * chart that lands inside production's normal range is still picked up within 15 s of finishing,
36
+ * which is the case that actually happens.
37
+ *
38
+ * 0 - 2 min every 15 s (9 polls) production p50 82 s lands here
39
+ * 2 - 5 min every 30 s (6 polls) production p99 383 s lands here
40
+ * 5 - 15 min every 60 s (10 polls) the long tail
41
+ *
42
+ * ~25 polls across 15 minutes, against 60 for a flat cadence over the same window.
43
+ */
44
+ export declare function pendingRecoveryNextDelayMs(elapsedMs: number): number;
45
+ export declare function encodePendingGenerate(m: PendingGenerateMarker): string;
46
+ export declare function parsePendingGenerate(raw: unknown): PendingGenerateMarker | null;
47
+ export type PendingRecoveryDecision = "none" | "delivered" | "expired" | "blind" | "poll";
48
+ /**
49
+ * What to do with a marker found at mount (or after a transport failure).
50
+ * @param persistedVersion the version the host HAS, 0/null = none
51
+ */
52
+ /**
53
+ * Was this marker armed holding NO version? If so its `v` is 1, which the service reads as "the
54
+ * shape's first chart" rather than "this host's first chart". `h` is the explicit signal; the
55
+ * `v - 1` fallback says the same thing for markers written by older builds.
56
+ */
57
+ export declare function markerArmedBlind(m: PendingGenerateMarker | null | undefined): boolean;
58
+ /**
59
+ * Can this marker be recovered AT ALL? Only by CORRELATION, so only if it carries one.
60
+ *
61
+ * A version number is never a safe handle, not even when we held one: the number we hold came FROM
62
+ * the service and is therefore a shape version, so "mine + 1" is still a guess about what everyone
63
+ * else did next. The poll consequently asks by correlation ALONE and sends no version at all - which
64
+ * is also what makes this safe against a service too old to understand the field, because such a
65
+ * service refuses a fetchOnly request with no version and can serve nothing. A marker with no
66
+ * correlation (written by an older build) has no usable handle and is not polled.
67
+ *
68
+ * One definition, shared by the mount-time decision and the transport-lost message.
69
+ */
70
+ export declare function isBlindMarker(m: PendingGenerateMarker | null | undefined): boolean;
71
+ /**
72
+ * Does `served` (the service's statement of whose generation it returned) prove this marker's
73
+ * chart? Absence is REFUSAL, not assent: an older service says nothing here, and a recovery that
74
+ * cannot tell your chart from a stranger's is worse than no recovery.
75
+ */
76
+ export declare function servedCorrelationProves(m: PendingGenerateMarker | null | undefined, served: string | null | undefined): boolean;
77
+ /**
78
+ * Are these the SAME pending generate? By correlation, never by version.
79
+ *
80
+ * Found 2026-08-30, and the log showed the bug in two adjacent lines. A poll was running for one
81
+ * correlation when the user clicked Generate again, arming a second. The poll's "has the marker
82
+ * changed under me?" guard compared `m.v` - and EVERY marker armed by a host holding no chart is
83
+ * `v: 1`, so `1 !== 1` was false and the stale poll did not notice it had been superseded. Four
84
+ * minutes later its window closed and it cleared the marker belonging to the generate that was
85
+ * still running:
86
+ *
87
+ * pending-gen-poll-end {"outcome":"window-closed","c":"<the first correlation>"}
88
+ * pending-gen-cleared {"reason":"poll-window-closed","c":"<the second correlation>"}
89
+ *
90
+ * The live generate lost its recovery marker 35 s after starting, and the user was told "nothing
91
+ * was delivered" about a chart that was still being written. The same lesson as `v` above: a shape
92
+ * version that the whole account moves can never identify one host's request. The correlation can.
93
+ *
94
+ * Two markers with no correlation are never "the same" - there is nothing to compare, and a false
95
+ * match here is what clears a stranger's marker.
96
+ */
97
+ export declare function sameMarker(a: PendingGenerateMarker | null | undefined, b: PendingGenerateMarker | null | undefined): boolean;
98
+ export declare function decidePendingRecovery(m: PendingGenerateMarker | null, nowMs: number, persistedVersion: number | null | undefined): PendingRecoveryDecision;
99
+ /**
100
+ * Did the recovery actually START A PICKUP? Only "poll" does - "expired", "delivered", "blind" and
101
+ * "none" all clear the marker and walk away.
102
+ *
103
+ * ONE definition, because the caller's SENTENCE has to agree with it. Found 2026-08-28: a generate
104
+ * ran nearly seventeen minutes, the transport died, and the transport-lost branch chose its wording
105
+ * from `isBlindMarker` alone - true enough on its own terms, the marker did carry a correlation -
106
+ * while the recovery had already returned "expired" against the window and polled for nothing. The
107
+ * user was told the chart was being checked for and would appear, with nothing checking for
108
+ * anything. A property of the MARKER cannot answer a question about the DECISION; only the decision
109
+ * can.
110
+ */
111
+ export declare function recoveryPollRunning(d: PendingRecoveryDecision): boolean;
112
+ /**
113
+ * Does the RECOVERY PATH own the sentence for this trigger, or does its caller?
114
+ *
115
+ * On "transport-lost" the host's retry loop is mid-render and already owns the message, which it
116
+ * words from the decision (recoveryPollRunning above). Anything the recovery says on top of that
117
+ * stacks a second sentence stating the same fact, and when both are painted the reader sees it twice
118
+ * in slightly different words - seen 2026-09-04, where the connection-dropped explanation appeared
119
+ * back to back with itself.
120
+ *
121
+ * On a MOUNT (or any other trigger) nothing else speaks, so the recovery must.
122
+ */
123
+ export declare function recoveryOwnsTheSentence(trigger: string): boolean;
124
+ /**
125
+ * THE ONE-SHOT LATE PICKUP.
126
+ *
127
+ * An EXPIRED marker used to be cleared on sight. But "expired" is a fact about the POLL WINDOW, not
128
+ * about the chart: the service writes the generation when it finishes and serves it by correlation
129
+ * for good. Seen in production: a corporate egress proxy cut the stream at 66 s, the poll armed, the
130
+ * host stopped reporting within seconds, the service finished at 75 s - and a reader who reopened
131
+ * the report later would have found the marker, called it expired, and cleared a finished, charged
132
+ * chart.
133
+ *
134
+ * So a MOUNT that finds an expired marker with a correlation fetches ONCE before letting go:
135
+ * "fetch" - a correlation to ask by, and this host instance has not asked for it yet;
136
+ * "keep" - this instance already asked (its own poll ran out, or a pickup already tried), so the
137
+ * marker waits for the NEXT mount - a data refresh seconds after the window closed must not
138
+ * spend the one fetch before a slow generate has finished;
139
+ * "clear" - no marker, or no correlation to ask by (a version is never a safe handle).
140
+ */
141
+ export type LatePickupAction = "fetch" | "keep" | "clear";
142
+ export declare function latePickupAction(m: PendingGenerateMarker | null | undefined, triedThisInstance: boolean): LatePickupAction;
143
+ /**
144
+ * After the late pickup's one fetch: only a TRANSPORT failure keeps the marker - the question never
145
+ * reached the service, so a later mount may still ask. An answer of not-found, or a definitive
146
+ * refusal, is the service saying there is nothing to deliver, and the marker goes.
147
+ */
148
+ export declare function latePickupKeepsMarker(outcome: "found" | "not-yet" | "hard-error" | "transport"): boolean;
149
+ /**
150
+ * When the 15-minute poll runs out ("window-closed"), the marker STAYS for the next mount's late
151
+ * pickup, as long as it carries a correlation. A definitive refusal ("hard-error") still clears it.
152
+ */
153
+ export declare function pollGiveUpKeepsMarker(outcome: string, m: PendingGenerateMarker | null | undefined): boolean;
154
+ /** Milliseconds left in the recovery window from `nowMs`, floored at 0. */
155
+ export declare function pendingRecoveryRemainingMs(m: PendingGenerateMarker, nowMs: number): number;
156
+ /**
157
+ * Whether a transport failure on a generate should hand off to the recovery poll (true) or fall back
158
+ * to the ordinary retry (false). The discriminator is whether the service ACCEPTED the request: a
159
+ * streaming endpoint commits its 200 headers the moment it starts, so headers-received plus a later
160
+ * failure means the generate is running server-side and a retry would make a SECOND chart. No
161
+ * headers means the service never saw it, and retrying is the right move.
162
+ */
163
+ export declare function transportFailureShouldRecover(headersReceived: boolean, genNew: boolean): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bicharts/chart-host",
3
- "version": "0.5.101",
3
+ "version": "0.5.103",
4
4
  "description": "Run a BIC-generated D3 chart in any web host: compiles the generated render() function, applies the shared option defaults, resolves mark clicks (through tooltip overlays), owns the selection affordance, and translates row indices between cross-filtered charts. The same contract the BIC Power BI visual implements, minus Power BI. React bindings at @bicharts/chart-host/react.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",