@elaraai/e3-ui-components 1.0.30 → 1.0.31

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.
package/dist/index.js CHANGED
@@ -59920,7 +59920,7 @@ const HELP = {
59920
59920
  },
59921
59921
  // ── Tabs ────────────────────────────────────────────────────────────
59922
59922
  tab_answer: {
59923
- term: "Answer",
59923
+ term: "The answer",
59924
59924
  gist: "The headline: did {treatment} move {outcome}, and by how much?",
59925
59925
  detail: "Shows the raw gap, the fair like-for-like estimate, and an honest verdict."
59926
59926
  },
@@ -60108,7 +60108,7 @@ const HELP = {
60108
60108
  },
60109
60109
  // ── Validate tab ────────────────────────────────────────────────────
60110
60110
  tab_validate: {
60111
- term: "Validate",
60111
+ term: "Prove it",
60112
60112
  gist: "The real experiment you’d run to prove this — for sure.",
60113
60113
  detail: "This analysis adjusted for what it could measure. A controlled trial — assigning {treatment} at random — removes any leftover doubt. This tab sizes that trial."
60114
60114
  },
@@ -60197,6 +60197,12 @@ function Help({ id: id3, extraVars, children: children2, display = "inline", gap
60197
60197
  )
60198
60198
  ) });
60199
60199
  }
60200
+ function GuidanceGist({ id: id3, extraVars }) {
60201
+ const { on: on3, vars } = useContext(GuidanceContext);
60202
+ if (!on3) return null;
60203
+ const e3 = fillHelp(HELP[id3], { ...vars, ...extraVars });
60204
+ return /* @__PURE__ */ jsx$1(Text, { textStyle: "caption", color: "fg.muted", lineHeight: "1.45", mt: "-1.5", mb: "2.5", children: e3.gist });
60205
+ }
60200
60206
  function GuidanceToggle({ on: on3, onToggle }) {
60201
60207
  const iconButton = useRecipe({ key: "iconButton" });
60202
60208
  return /* @__PURE__ */ jsx$1(Hover, { id: "guidance", trigger: /* @__PURE__ */ jsx$1(
@@ -60351,8 +60357,8 @@ function AreaRange({ lo: lo2, mid, hi, xTicks = [], yTicks = [], zero: zero2, to
60351
60357
  const atEnd = m2.at >= mid.length - 2;
60352
60358
  const atStart = m2.at <= 1;
60353
60359
  const align = atEnd ? "right" : atStart ? "left" : "center";
60354
- const labW = m2.label.length * 6 + 6;
60355
- const labX = atEnd ? -labW : atStart ? 0 : -labW / 2;
60360
+ const labW = m2.label.length * 6.5 + 8;
60361
+ const labX = atEnd ? -labW - 5 : atStart ? 5 : -labW / 2;
60356
60362
  return /* @__PURE__ */ jsxs(Group, { left: xScale(m2.at), children: [
60357
60363
  /* @__PURE__ */ jsx$1(Line, { from: { x: 0, y: -2 }, to: { x: 0, y: innerH }, stroke: mc, strokeWidth: 1, strokeDasharray: "2 3" }),
60358
60364
  /* @__PURE__ */ jsx$1(Circle, { cx: 0, cy: dotY, r: t4.dotRadius, fill: mc, stroke: t4.surface, strokeWidth: 1.5 }),
@@ -60423,6 +60429,10 @@ const unitOf = (meta3, col) => {
60423
60429
  var _a2;
60424
60430
  return getSomeorUndefined((_a2 = colMeta(meta3, col)) == null ? void 0 : _a2.unit) ?? "";
60425
60431
  };
60432
+ const labelOf = (meta3, col) => {
60433
+ var _a2;
60434
+ return getSomeorUndefined((_a2 = colMeta(meta3, col)) == null ? void 0 : _a2.label) ?? col;
60435
+ };
60426
60436
  const kindOf = (cols, name) => {
60427
60437
  var _a2;
60428
60438
  return (_a2 = cols.find((c2) => c2.name === name)) == null ? void 0 : _a2.kind;
@@ -60458,8 +60468,7 @@ function treatmentKind(cols, treatment) {
60458
60468
  return "value";
60459
60469
  }
60460
60470
  }
60461
- function deriveSpec(config2, cols, result, meta3, dataLen) {
60462
- const tKind = kindOf(cols, config2.treatment);
60471
+ function deriveSpec(config2, cols, result, meta3, dataLen, subject) {
60463
60472
  const outUnit = unitOf(meta3, config2.outcome);
60464
60473
  const balByCol = /* @__PURE__ */ new Map();
60465
60474
  for (const b2 of (result == null ? void 0 : result.balance) ?? []) {
@@ -60471,7 +60480,7 @@ function deriveSpec(config2, cols, result, meta3, dataLen) {
60471
60480
  const absStd = b2 ? Math.abs(b2.std_diff) : 0;
60472
60481
  const { level, tone } = band(absStd);
60473
60482
  const dir = b2 ? b2.treated_mean < b2.control_mean ? "lower" : "higher" : "";
60474
- const reason = b2 ? `the treated group ran ${dir} on ${col}` : "measured once you Run";
60483
+ const reason = b2 ? `the treated group ran ${dir} on ${labelOf(meta3, col)}` : "measured once you Run";
60475
60484
  return { col, reason, imbalance: clamp01(absStd), level, tone };
60476
60485
  });
60477
60486
  const inPlay = /* @__PURE__ */ new Set([config2.treatment, config2.outcome, ...config2.common_causes]);
@@ -60483,15 +60492,21 @@ function deriveSpec(config2, cols, result, meta3, dataLen) {
60483
60492
  const target = targetV && (targetV.type === "att" || targetV.type === "atc") ? "treated" : "all";
60484
60493
  return {
60485
60494
  treatment: config2.treatment,
60495
+ treatmentLabel: labelOf(meta3, config2.treatment),
60486
60496
  treatmentKind: treatmentKind(cols, config2.treatment),
60487
60497
  outcome: config2.outcome,
60498
+ outcomeLabel: labelOf(meta3, config2.outcome),
60488
60499
  outcomeKind: outUnit ? `number · ${outUnit}` : "number",
60489
- comparison: tKind === "boolean" ? "yes · vs no" : "high · vs low",
60500
+ comparison: kindOf(cols, config2.treatment) === "boolean" ? "yes · vs no" : "high · vs low",
60490
60501
  confounders,
60491
60502
  suggestion,
60492
60503
  method,
60493
60504
  target,
60494
- dataLabel: `${result ? Number(result.n_total) : dataLen} rows`
60505
+ // Always the CURRENT (filtered) dataset's row count — the result's own
60506
+ // n_total belongs to the Answer-tab counts strip; labelling the header
60507
+ // with it desyncs from the data after a population edit (and reads
60508
+ // wrong whenever the two legitimately differ).
60509
+ dataLabel: `${dataLen} ${subject.many}`
60495
60510
  };
60496
60511
  }
60497
60512
  function balanceDisplay(b2, categorical) {
@@ -60503,7 +60518,9 @@ function deriveAnswer(config2, result, adj, meta3) {
60503
60518
  const nci = getSomeorUndefined(result.naive_ci);
60504
60519
  const categorical = new Set(getSomeorUndefined(config2.categorical) ?? []);
60505
60520
  const balance = [...result.balance].sort((a2, b2) => Math.abs(b2.std_diff) - Math.abs(a2.std_diff)).map((b2) => ({
60521
+ // One-hot rows are `col=level` — label the BASE confounder, keep the level.
60506
60522
  col: b2.column,
60523
+ label: b2.column === b2.base_column ? labelOf(meta3, b2.base_column) : `${labelOf(meta3, b2.base_column)}${b2.column.startsWith(b2.base_column) ? b2.column.slice(b2.base_column.length) : ` (${b2.column})`}`,
60507
60524
  treated: b2.treated_mean,
60508
60525
  control: b2.control_mean,
60509
60526
  display: balanceDisplay(b2, categorical),
@@ -60517,8 +60534,8 @@ function deriveAnswer(config2, result, adj, meta3) {
60517
60534
  const nDropped = Number(result.n_dropped);
60518
60535
  const nTotal = Number(result.n_total);
60519
60536
  return {
60520
- treatment: config2.treatment,
60521
- outcome: config2.outcome,
60537
+ treatment: labelOf(meta3, config2.treatment),
60538
+ outcome: labelOf(meta3, config2.outcome),
60522
60539
  unit: unitOf(meta3, config2.outcome),
60523
60540
  naive: result.naive,
60524
60541
  naiveLo: (nci == null ? void 0 : nci.lower) ?? result.naive,
@@ -60537,18 +60554,63 @@ function deriveAnswer(config2, result, adj, meta3) {
60537
60554
  balance
60538
60555
  };
60539
60556
  }
60540
- function deriveRefusal(config2, result) {
60557
+ function deriveNarrative(result, a2, refute, higherBetter, subject) {
60558
+ if (a2 === null) return null;
60559
+ const unit2 = a2.unit ? ` ${a2.unit}` : "";
60560
+ const dirUp = a2.effect > 0;
60561
+ const verb = higherBetter === void 0 ? dirUp ? "raises" : "lowers" : dirUp === higherBetter ? "improves" : "worsens";
60562
+ const range = ` (likely between ${signed(a2.lo)} and ${signed(a2.hi)})`;
60563
+ const passed = (refute == null ? void 0 : refute.checks.filter((c2) => c2.passed).length) ?? 0;
60564
+ const total = (refute == null ? void 0 : refute.checks.length) ?? 0;
60565
+ const trustClause = total === 0 ? "" : passed === total ? ` It held up against all ${total} ways we tried to break it.` : ` It passed ${passed} of ${total} stress tests — see “Can we trust it?”.`;
60566
+ switch (result.verdict.type) {
60567
+ case "causal":
60568
+ return {
60569
+ tone: "pos",
60570
+ segments: [
60571
+ { text: "Yes — ", strong: true },
60572
+ { text: `${a2.treatment} genuinely ${verb} ${a2.outcome}: about ` },
60573
+ { text: `${signed(a2.effect)}${unit2}`, strong: true },
60574
+ { text: ` per ${subject.one}${range}.${trustClause}` }
60575
+ ]
60576
+ };
60577
+ case "modest": {
60578
+ const tags = new Set(result.verdict_reasons.map((v3) => v3.type));
60579
+ const why = tags.has("not_material") ? ` — the change (${signed(a2.effect)}${unit2}) is smaller than what would matter here (±${fmt$1(result.materiality_threshold)}${unit2}).` : tags.has("ci_spans_zero") ? ` — the likely range (${signed(a2.lo)} to ${signed(a2.hi)}) includes zero.` : tags.has("thin_support") ? " — too few treated and untreated look-alikes overlap to be confident." : tags.has("low_robustness") ? " — a fairly weak hidden factor could overturn it." : a2.lo <= 0 && a2.hi >= 0 ? ` — the likely range (${signed(a2.lo)} to ${signed(a2.hi)}) includes zero.` : ".";
60580
+ return {
60581
+ tone: "warn",
60582
+ segments: [
60583
+ { text: "Maybe — ", strong: true },
60584
+ { text: `there may be an effect of ${a2.treatment} on ${a2.outcome}, but it’s too small or unclear to act on` },
60585
+ { text: why }
60586
+ ]
60587
+ };
60588
+ }
60589
+ case "adjustment_insufficient":
60590
+ return {
60591
+ tone: "warn",
60592
+ segments: [
60593
+ { text: "Don’t act on this yet — ", strong: true },
60594
+ { text: `we got a number (${signed(a2.effect)}${unit2}), but a stress test failed: something we didn’t adjust for may still be driving it.` }
60595
+ ]
60596
+ };
60597
+ default:
60598
+ return null;
60599
+ }
60600
+ }
60601
+ function deriveRefusal(config2, result, meta3, subject) {
60541
60602
  const nT = Number(result.n_treated), nC = Number(result.n_control), nTot = Number(result.n_total);
60603
+ const treatment = labelOf(meta3, config2.treatment);
60542
60604
  if (result.verdict.type === "not_estimable") {
60543
60605
  const reason = result.verdict.value || "The treatment barely varies, so no comparison can be formed.";
60544
60606
  return {
60545
60607
  kind: "not_estimable",
60546
- title: `Can’t estimate the effect of ${config2.treatment}`,
60608
+ title: `Can’t estimate the effect of ${treatment}`,
60547
60609
  body: reason,
60548
60610
  evidence: [
60549
60611
  { label: "treated", value: String(nT) },
60550
60612
  { label: "untreated", value: String(nC) },
60551
- { label: "rows", value: String(nTot) }
60613
+ { label: subject.many, value: String(nTot) }
60552
60614
  ]
60553
60615
  };
60554
60616
  }
@@ -60567,12 +60629,12 @@ function deriveRefusal(config2, result) {
60567
60629
  }
60568
60630
  return {
60569
60631
  kind: "not_estimable",
60570
- title: `Can’t estimate the effect of ${config2.treatment}`,
60632
+ title: `Can’t estimate the effect of ${treatment}`,
60571
60633
  body: "The engine could not produce a like-for-like estimate for this configuration.",
60572
60634
  evidence: [
60573
60635
  { label: "treated", value: String(nT) },
60574
60636
  { label: "untreated", value: String(nC) },
60575
- { label: "rows", value: String(nTot) }
60637
+ { label: subject.many, value: String(nTot) }
60576
60638
  ]
60577
60639
  };
60578
60640
  }
@@ -60596,17 +60658,21 @@ function zeroCrossing(xs, ys) {
60596
60658
  }
60597
60659
  return null;
60598
60660
  }
60599
- function deriveRefute(r2, adj) {
60661
+ function deriveRefute(r2, adj, meta3) {
60600
60662
  const checks = [];
60601
60663
  const est = (adj == null ? void 0 : adj.effect) ?? 0;
60602
60664
  const placeboEffect = getSomeorUndefined(r2.placebo_effect);
60603
60665
  const placeboPasses = getSomeorUndefined(r2.placebo_passes);
60604
60666
  if (placeboEffect !== void 0 || placeboPasses !== void 0) {
60667
+ const passed = placeboPasses ?? (placeboEffect !== void 0 && Math.abs(placeboEffect) < Math.max(0.5, Math.abs(est) * 0.15));
60605
60668
  checks.push({
60606
60669
  name: "Shuffle test",
60670
+ short: "shuffle",
60607
60671
  desc: "Shuffle which rows were treated; a real effect should collapse to zero.",
60608
- value: placeboEffect !== void 0 ? `→ ${signed(placeboEffect)}` : placeboPasses ? "passed" : "failed",
60609
- passed: placeboPasses ?? (placeboEffect !== void 0 && Math.abs(placeboEffect) < Math.max(0.5, Math.abs(est) * 0.15)),
60672
+ // Speak the outcome, not the statistic: on fake labels the effect
60673
+ // should VANISH say whether it did.
60674
+ value: placeboEffect !== void 0 ? passed ? `vanished (${signed(placeboEffect)})` : `didn’t vanish (${signed(placeboEffect)})` : passed ? "vanished" : "didn’t vanish",
60675
+ passed,
60610
60676
  tip: some("Randomly re-label which rows were treated. A genuine effect should vanish."),
60611
60677
  help: "check_shuffle"
60612
60678
  });
@@ -60614,11 +60680,13 @@ function deriveRefute(r2, adj) {
60614
60680
  const ds = getSomeorUndefined(r2.data_subset_effect);
60615
60681
  if (ds !== void 0) {
60616
60682
  const dstd = getSomeorUndefined(r2.data_subset_std) ?? 0;
60683
+ const passed = Math.abs(ds - est) < Math.max(0.5, Math.abs(est) * 0.2);
60617
60684
  checks.push({
60618
60685
  name: "Drop-some test",
60686
+ short: "drop-some",
60619
60687
  desc: "Re-estimate on random subsamples; a trustworthy effect stays put.",
60620
- value: `${fmt$1(ds)} ± ${fmt$1(dstd)}`,
60621
- passed: Math.abs(ds - est) < Math.max(0.5, Math.abs(est) * 0.2),
60688
+ value: passed ? `stayed at ${signed(ds)} ± ${fmt$1(dstd)}` : `moved to ${signed(ds)} ± ${fmt$1(dstd)}`,
60689
+ passed,
60622
60690
  tip: none$1,
60623
60691
  help: "check_dropsome"
60624
60692
  });
@@ -60627,8 +60695,9 @@ function deriveRefute(r2, adj) {
60627
60695
  if (rcc !== void 0) {
60628
60696
  checks.push({
60629
60697
  name: "Decoy cause",
60698
+ short: "decoy",
60630
60699
  desc: "Add an irrelevant random factor; the answer should not move.",
60631
- value: rcc ? "within range" : "moved",
60700
+ value: rcc ? "didn’t move" : "moved",
60632
60701
  passed: rcc,
60633
60702
  tip: none$1,
60634
60703
  help: "check_decoy"
@@ -60636,14 +60705,18 @@ function deriveRefute(r2, adj) {
60636
60705
  }
60637
60706
  const sens = getSomeorUndefined(r2.sensitivity);
60638
60707
  const evalue = getSomeorUndefined(r2.robustness_value);
60708
+ const benchmarks = sens ? [...sens.benchmarks].sort((a2, b2) => b2.strength - a2.strength) : [];
60639
60709
  if (sens !== void 0 || evalue !== void 0) {
60640
60710
  const strengths = sens ? arr(sens.strengths) : [];
60641
60711
  const effects = sens ? arr(sens.effects) : [];
60642
60712
  const tip = sens ? zeroCrossing(strengths, effects) : null;
60643
- const value = tip !== null ? `tips at ${fmt$1(tip)}` : evalue !== void 0 ? `E-value ${fmt$1(evalue)}` : "holds throughout";
60713
+ const strongest = benchmarks[0];
60714
+ const value = tip !== null ? strongest !== void 0 ? tip > strongest.strength ? `flips only beyond ${labelOf(meta3, strongest.column)}` : `a cause weaker than ${labelOf(meta3, strongest.column)} could flip it` : `would flip at ${fmt$1(tip)}` : evalue !== void 0 ? `holds (E-value ${fmt$1(evalue)})` : "holds throughout";
60715
+ const scaleNote = strongest !== void 0 ? ` For scale, your strongest known factor (${labelOf(meta3, strongest.column)}) sits at ${fmt$1(strongest.strength)} on this axis.` : "";
60644
60716
  checks.push({
60645
60717
  name: "Hidden cause",
60646
- desc: "How strong an unrecorded common cause would need to be to overturn the result.",
60718
+ short: "hidden cause",
60719
+ desc: `How strong an unrecorded common cause would need to be to overturn the result.${scaleNote}`,
60647
60720
  value,
60648
60721
  passed: tip === null,
60649
60722
  tip: some("Something unrecorded could drive both the treatment choice and the outcome."),
@@ -60659,7 +60732,23 @@ function deriveRefute(r2, adj) {
60659
60732
  const lo2 = mid.map((m2) => m2 - half);
60660
60733
  const hi = mid.map((m2) => m2 + half);
60661
60734
  const yLo = Math.min(0, ...lo2), yHi = Math.max(...hi, 0);
60662
- sensVM = { lo: lo2, mid, hi, xTicks: ["none", "weaker", "stronger"], yTicks: [fmt$1(yLo), fmt$1((yLo + yHi) / 2), fmt$1(yHi)] };
60735
+ const strengths = arr(sens.strengths);
60736
+ const atOf = (s2) => {
60737
+ if (strengths.length < 2) return null;
60738
+ if (s2 <= strengths[0]) return 0;
60739
+ for (let i = 1; i < strengths.length; i++) {
60740
+ if (s2 <= strengths[i]) {
60741
+ const span = strengths[i] - strengths[i - 1];
60742
+ return span > 0 ? i - 1 + (s2 - strengths[i - 1]) / span : i;
60743
+ }
60744
+ }
60745
+ return strengths.length - 1;
60746
+ };
60747
+ const marks = benchmarks.slice(0, 2).flatMap((b2) => {
60748
+ const at = atOf(b2.strength);
60749
+ return at === null ? [] : [{ at, label: `as strong as ${labelOf(meta3, b2.column)}`, tone: "muted", help: "sensitivity" }];
60750
+ });
60751
+ sensVM = { lo: lo2, mid, hi, xTicks: ["none", "", "stronger"], yTicks: [fmt$1(yLo), fmt$1((yLo + yHi) / 2), fmt$1(yHi)], marks };
60663
60752
  }
60664
60753
  }
60665
60754
  return { checks, sens: sensVM };
@@ -60696,21 +60785,26 @@ function deriveDose(dose, config2, meta3) {
60696
60785
  const marks = [];
60697
60786
  if (sizes.length) marks.push({ at: here, label: "you are here", tone: "muted", help: "dose_here" });
60698
60787
  marks.push({ at: sweet, label: "sweet spot", tone: "pos", help: "dose_sweet" });
60788
+ const featureLabel = labelOf(meta3, dose.feature);
60789
+ const outcomeLabel = labelOf(meta3, config2.outcome);
60699
60790
  return {
60700
- feature: dose.feature,
60701
- outcome: config2.outcome,
60791
+ feature: featureLabel,
60792
+ outcome: outcomeLabel,
60702
60793
  lo: lo2,
60703
60794
  mid,
60704
60795
  hi,
60705
60796
  xTicks,
60706
60797
  yTicks,
60707
60798
  marks,
60708
- recoLabel: `≈ ${fmt$1(grid[sweet] ?? 0)}${featureUnit ? " " + featureUnit : ""}`,
60799
+ // Name the feature in the recommendation a bare "≈ 5.5" doesn't say
60800
+ // 5.5 of WHAT.
60801
+ recoLabel: `aim for ${featureLabel} ≈ ${fmt$1(grid[sweet] ?? 0)}${featureUnit ? " " + featureUnit : ""}`,
60709
60802
  recoEffect: mid[sweet] ?? 0,
60710
60803
  recoLo: lo2[sweet] ?? 0,
60711
60804
  recoHi: hi[sweet] ?? 0,
60712
60805
  tradeoff,
60713
- marginal
60806
+ marginal,
60807
+ framing: `A different question from the headline: how ${outcomeLabel} responds as ${featureLabel} itself moves.`
60714
60808
  };
60715
60809
  }
60716
60810
  function relTime(d2, now2) {
@@ -60727,12 +60821,12 @@ const VERDICT_WORD = {
60727
60821
  non_identifiable_positivity: "no overlap",
60728
60822
  not_estimable: "n/a"
60729
60823
  };
60730
- function deriveJournalRow(row, now2) {
60824
+ function deriveJournalRow(row, now2, meta3) {
60731
60825
  const adj = getSomeorUndefined(row.adjusted);
60732
60826
  return {
60733
- treatment: row.config.treatment,
60734
- outcome: row.config.outcome,
60735
- confounders: row.config.common_causes.join(", "),
60827
+ treatment: labelOf(meta3, row.config.treatment),
60828
+ outcome: labelOf(meta3, row.config.outcome),
60829
+ confounders: row.config.common_causes.map((c2) => labelOf(meta3, c2)).join(", "),
60736
60830
  effect: adj !== void 0 ? signed(adj) : signed(row.naive),
60737
60831
  verdict: VERDICT_WORD[row.verdict.type],
60738
60832
  verdictTone: VERDICT_TONE[row.verdict.type],
@@ -60743,22 +60837,27 @@ function deriveJournalRow(row, now2) {
60743
60837
  preset: row.preset
60744
60838
  };
60745
60839
  }
60746
- function deriveView(config2, ranConfig, cols, result, journal, meta3, dataLen, now2) {
60840
+ function deriveView(config2, ranConfig, cols, result, journal, meta3, dataLen, now2, subject = { one: "record", many: "records" }) {
60841
+ var _a2;
60747
60842
  const adj = result ? getSomeorUndefined(result.adjusted) : void 0;
60748
60843
  const refutation = result ? getSomeorUndefined(result.refutation) : void 0;
60749
60844
  const doseR = result ? getSomeorUndefined(result.dose_response) : void 0;
60845
+ const answer = result && adj ? deriveAnswer(ranConfig, result, adj, meta3) : null;
60846
+ const refute = refutation ? deriveRefute(refutation, adj, meta3) : null;
60847
+ const higherBetter = getSomeorUndefined((_a2 = colMeta(meta3, ranConfig.outcome)) == null ? void 0 : _a2.higherIsBetter);
60750
60848
  return {
60751
60849
  // The set-up rail reflects the LIVE config (the editor); the result deck
60752
60850
  // reflects RANCONFIG — the config that produced `result` — so the result
60753
60851
  // strings never drift ahead of the numbers on a live picker edit.
60754
- spec: deriveSpec(config2, cols, result, meta3, dataLen),
60852
+ spec: deriveSpec(config2, cols, result, meta3, dataLen, subject),
60755
60853
  verdict: result ? deriveVerdict(result.verdict) : null,
60756
- answer: result && adj ? deriveAnswer(ranConfig, result, adj, meta3) : null,
60757
- refusal: result && !adj ? deriveRefusal(ranConfig, result) : null,
60854
+ narrative: result ? deriveNarrative(result, answer, refute, higherBetter, subject) : null,
60855
+ answer,
60856
+ refusal: result && !adj ? deriveRefusal(ranConfig, result, meta3, subject) : null,
60758
60857
  overlap: result ? deriveOverlap(result.overlap) : null,
60759
- refute: refutation ? deriveRefute(refutation, adj) : null,
60858
+ refute,
60760
60859
  dose: doseR && doseR.effect.length ? deriveDose(doseR, ranConfig, meta3) : null,
60761
- journal: journal ? journal.map((r2) => deriveJournalRow(r2, now2)) : null
60860
+ journal: journal ? journal.map((r2) => deriveJournalRow(r2, now2, meta3)) : null
60762
60861
  };
60763
60862
  }
60764
60863
  const DESIGN_HEADLINE = {
@@ -60840,8 +60939,7 @@ function deriveDesign(d2, result, ranConfig, meta3) {
60840
60939
  faint
60841
60940
  };
60842
60941
  }
60843
- const SUBJECT_ONE = "record";
60844
- const SUBJECT_MANY = "records";
60942
+ const SUBJECT_DEFAULT = { one: "record", many: "records" };
60845
60943
  const AUTORUN_MAX_ROWS = 5e4;
60846
60944
  const TONE_TOKEN = {
60847
60945
  neg: "fg.danger",
@@ -60910,7 +61008,10 @@ function rowMatchesAll(row, preds) {
60910
61008
  }
60911
61009
  const num = (x2) => typeof x2 === "number" ? x2 : typeof x2 === "bigint" ? Number(x2) : null;
60912
61010
  function Cap({ children: children2, help }) {
60913
- return /* @__PURE__ */ jsx$1(Text, { textStyle: "caption.eyebrow", fontSize: "9px", mb: "2.5", children: help ? /* @__PURE__ */ jsx$1(Help, { id: help, children: children2 }) : children2 });
61011
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
61012
+ /* @__PURE__ */ jsx$1(Text, { textStyle: "caption.eyebrow", fontSize: "9px", mb: "2.5", children: help ? /* @__PURE__ */ jsx$1(Help, { id: help, children: children2 }) : children2 }),
61013
+ help && /* @__PURE__ */ jsx$1(GuidanceGist, { id: help })
61014
+ ] });
60914
61015
  }
60915
61016
  function Card({ children: children2, mt = "3" }) {
60916
61017
  return /* @__PURE__ */ jsx$1(Box, { layerStyle: "card", p: "3.5", borderRadius: "lg", mt, children: children2 });
@@ -61032,6 +61133,7 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61032
61133
  const data4 = useBindingValue(v3.data);
61033
61134
  const journalBind = useBindingValue(v3.journal.type === "some" ? v3.journal.value : null);
61034
61135
  const meta3 = getSomeorUndefined(v3.columnMeta);
61136
+ const subject = getSomeorUndefined(v3.subject) ?? SUBJECT_DEFAULT;
61035
61137
  const readonly = getSomeorUndefined(v3.readonly) ?? false;
61036
61138
  const configsBind = useBindingValue(v3.configs);
61037
61139
  const configs = useMemo(() => configsBind.value ?? [], [configsBind.value]);
@@ -61168,16 +61270,16 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61168
61270
  const nRows = (filteredRows == null ? void 0 : filteredRows.length) ?? 0;
61169
61271
  const helpVars = useMemo(() => {
61170
61272
  const rc = ranConfig ?? config2;
61171
- const labelOf = (col) => {
61273
+ const labelOf2 = (col) => {
61172
61274
  var _a3;
61173
61275
  return col ? getSomeorUndefined((_a3 = meta3 == null ? void 0 : meta3.get(col)) == null ? void 0 : _a3.label) ?? col : "";
61174
61276
  };
61175
- return { treatment: labelOf(rc == null ? void 0 : rc.treatment), outcome: labelOf(rc == null ? void 0 : rc.outcome), subject: SUBJECT_ONE, subjects: SUBJECT_MANY };
61176
- }, [ranConfig, config2, meta3]);
61277
+ return { treatment: labelOf2(rc == null ? void 0 : rc.treatment), outcome: labelOf2(rc == null ? void 0 : rc.outcome), subject: subject.one, subjects: subject.many };
61278
+ }, [ranConfig, config2, meta3, subject]);
61177
61279
  const view = useMemo(() => {
61178
61280
  if (!config2) return null;
61179
- return deriveView(config2, ranConfig ?? config2, columns, shownResult, journalBind.value, meta3, nRows, now2);
61180
- }, [config2, ranConfig, columns, shownResult, journalBind.value, meta3, nRows, now2]);
61281
+ return deriveView(config2, ranConfig ?? config2, columns, shownResult, journalBind.value, meta3, nRows, now2, subject);
61282
+ }, [config2, ranConfig, columns, shownResult, journalBind.value, meta3, nRows, now2, subject]);
61181
61283
  const designConfigValue = useMemo(() => ({
61182
61284
  alpha: none$1,
61183
61285
  target_power: none$1,
@@ -61232,16 +61334,18 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61232
61334
  const showResult = shownResult !== null && !experiment.pending && !failed;
61233
61335
  const anyPrecomputedDesign = configs.some((c2) => c2.design.type === "some");
61234
61336
  const showValidate = hasDesign || anyPrecomputedDesign;
61235
- const tabKeys = [...["answer", "trust", "dose"], ...showValidate ? ["validate"] : []];
61337
+ const showDose = getSomeorUndefined(config2.dose_feature) !== void 0 || (shownResult == null ? void 0 : shownResult.dose_response.type) === "some";
61338
+ const tabKeys = [...["answer", "trust"], ...showDose ? ["dose"] : [], ...showValidate ? ["validate"] : []];
61339
+ const activeTab = tabKeys.includes(tab) ? tab : "answer";
61236
61340
  return /* @__PURE__ */ jsx$1(GuidanceProvider, { on: guidance, vars: helpVars, children: /* @__PURE__ */ jsxs(Box, { layerStyle: "surface.frameless", overflow: "visible", children: [
61237
61341
  /* @__PURE__ */ jsxs(Box, { layerStyle: "header.bar", display: "flex", alignItems: "center", gap: "3.5", children: [
61238
61342
  configs.length > 1 ? /* @__PURE__ */ jsxs(Menu.Root, { children: [
61239
61343
  /* @__PURE__ */ jsx$1(Menu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(Box, { as: "button", bg: "transparent", border: "0", p: "0", cursor: "pointer", display: "inline-flex", alignItems: "center", gap: "2", textAlign: "start", children: [
61240
61344
  /* @__PURE__ */ jsxs(Text, { textStyle: "title.card", color: "fg.muted", children: [
61241
61345
  "Does ",
61242
- /* @__PURE__ */ jsx$1(Text, { as: "span", color: "brand.solid", fontWeight: "bold", children: vs.treatment }),
61346
+ /* @__PURE__ */ jsx$1(Text, { as: "span", color: "brand.solid", fontWeight: "bold", children: vs.treatmentLabel }),
61243
61347
  " change ",
61244
- /* @__PURE__ */ jsx$1(Text, { as: "span", color: "brand.solid", fontWeight: "bold", children: vs.outcome }),
61348
+ /* @__PURE__ */ jsx$1(Text, { as: "span", color: "brand.solid", fontWeight: "bold", children: vs.outcomeLabel }),
61245
61349
  "?"
61246
61350
  ] }),
61247
61351
  /* @__PURE__ */ jsx$1(Box, { as: "span", color: "fg.subtle", fontSize: "11px", lineHeight: "1", children: /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faChevronDown }) })
@@ -61255,9 +61359,9 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61255
61359
  ] }, g2.label)) }) }) })
61256
61360
  ] }) : /* @__PURE__ */ jsx$1(Text, { textStyle: "title.card", color: "fg.muted", children: /* @__PURE__ */ jsxs(Help, { id: "header", children: [
61257
61361
  "Does ",
61258
- /* @__PURE__ */ jsx$1(Text, { as: "span", color: "brand.solid", fontWeight: "bold", children: vs.treatment }),
61362
+ /* @__PURE__ */ jsx$1(Text, { as: "span", color: "brand.solid", fontWeight: "bold", children: vs.treatmentLabel }),
61259
61363
  " change ",
61260
- /* @__PURE__ */ jsx$1(Text, { as: "span", color: "brand.solid", fontWeight: "bold", children: vs.outcome }),
61364
+ /* @__PURE__ */ jsx$1(Text, { as: "span", color: "brand.solid", fontWeight: "bold", children: vs.outcomeLabel }),
61261
61365
  "?"
61262
61366
  ] }) }),
61263
61367
  /* @__PURE__ */ jsx$1(Box, { flex: "1" }),
@@ -61297,7 +61401,7 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61297
61401
  "add another"
61298
61402
  ] }) })
61299
61403
  ] }) }),
61300
- /* @__PURE__ */ jsx$1(Step, { n: 4, title: `Which ${SUBJECT_MANY}?`, help: "step_population", children: /* @__PURE__ */ jsx$1(FilterRail, { fields, population, onChange: editPopulation, chip, button, readonly }) }),
61404
+ /* @__PURE__ */ jsx$1(Step, { n: 4, title: `Which ${subject.many}?`, help: "step_population", children: /* @__PURE__ */ jsx$1(FilterRail, { fields, population, onChange: editPopulation, chip, button, readonly, subjectMany: subject.many }) }),
61301
61405
  /* @__PURE__ */ jsxs(Box, { as: "details", borderTopWidth: "1px", borderColor: "border.subtle", children: [
61302
61406
  /* @__PURE__ */ jsxs(
61303
61407
  Box,
@@ -61348,7 +61452,7 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61348
61452
  "aria-label": "Result views",
61349
61453
  onKeyDown: (e3) => {
61350
61454
  var _a3;
61351
- const i = tabKeys.indexOf(tab);
61455
+ const i = tabKeys.indexOf(activeTab);
61352
61456
  let j2 = i;
61353
61457
  if (e3.key === "ArrowRight") j2 = (i + 1) % tabKeys.length;
61354
61458
  else if (e3.key === "ArrowLeft") j2 = (i - 1 + tabKeys.length) % tabKeys.length;
@@ -61362,9 +61466,9 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61362
61466
  },
61363
61467
  children: [
61364
61468
  tabKeys.map((tk) => {
61365
- const on3 = tab === tk;
61469
+ const on3 = activeTab === tk;
61366
61470
  const tabHelp = tk === "answer" ? "tab_answer" : tk === "trust" ? "tab_trust" : tk === "dose" ? "tab_dose" : "tab_validate";
61367
- const tabLabel = tk === "answer" ? "Answer" : tk === "trust" ? "Trust" : tk === "dose" ? "Dose" : "Validate";
61471
+ const tabLabel = tk === "answer" ? "The answer" : tk === "trust" ? "Can we trust it?" : tk === "dose" ? "How much?" : "Prove it";
61368
61472
  return /* @__PURE__ */ jsx$1(
61369
61473
  Box,
61370
61474
  {
@@ -61396,7 +61500,7 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61396
61500
  ]
61397
61501
  }
61398
61502
  ),
61399
- failed ? /* @__PURE__ */ jsx$1(Box, { px: "4.5", pt: "4.5", children: /* @__PURE__ */ jsx$1(RunError, { error: experiment.error }) }) : !showResult ? /* @__PURE__ */ jsx$1(DeckSkeleton, { tab }) : /* @__PURE__ */ jsxs(Fragment, { children: [
61503
+ failed ? /* @__PURE__ */ jsx$1(Box, { px: "4.5", pt: "4.5", children: /* @__PURE__ */ jsx$1(RunError, { error: experiment.error }) }) : !showResult ? /* @__PURE__ */ jsx$1(DeckSkeleton, { tab: activeTab }) : /* @__PURE__ */ jsxs(Fragment, { children: [
61400
61504
  stale && /* @__PURE__ */ jsxs(Box, { layerStyle: "banner.stale", display: "flex", alignItems: "center", gap: "2", mx: "4.5", mt: "4.5", children: [
61401
61505
  /* @__PURE__ */ jsx$1(Box, { as: "span", color: "fg.warning", flexShrink: "0", fontSize: "12px", children: /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faTriangleExclamation }) }),
61402
61506
  /* @__PURE__ */ jsxs(Text, { textStyle: "body.sm", color: "fg.default", children: [
@@ -61404,9 +61508,22 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61404
61508
  " Hit Run to update these results for your edits."
61405
61509
  ] })
61406
61510
  ] }),
61407
- tab === "answer" && a2 && /* @__PURE__ */ jsx$1(AnswerNumeric, { a: a2, verdict, higherBetter, badge, barList }),
61408
- tab === "answer" && !a2 && ref && /* @__PURE__ */ jsx$1(RefusalZone, { refusal: ref, overlap: ov, naiveValue: (shownResult == null ? void 0 : shownResult.naive) ?? 0, outcome: vs.outcome }),
61409
- tab === "trust" && vr && /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
61511
+ activeTab === "answer" && a2 && /* @__PURE__ */ jsx$1(
61512
+ AnswerNumeric,
61513
+ {
61514
+ a: a2,
61515
+ verdict,
61516
+ narrative: view.narrative,
61517
+ checks: (vr == null ? void 0 : vr.checks) ?? null,
61518
+ onGoTrust: () => setTab("trust"),
61519
+ subjectMany: subject.many,
61520
+ higherBetter,
61521
+ badge,
61522
+ barList
61523
+ }
61524
+ ),
61525
+ activeTab === "answer" && !a2 && ref && /* @__PURE__ */ jsx$1(RefusalZone, { refusal: ref, overlap: ov, naiveValue: (shownResult == null ? void 0 : shownResult.naive) ?? 0, outcome: vs.outcomeLabel }),
61526
+ activeTab === "trust" && vr && /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
61410
61527
  /* @__PURE__ */ jsx$1(Text, { textStyle: "body.sm", color: "fg.muted", mb: "3.5", children: /* @__PURE__ */ jsx$1(Help, { id: "trust_intro", children: "Before trusting the answer we tried to break it — colour shows pass / caution." }) }),
61411
61528
  /* @__PURE__ */ jsx$1(Box, { layerStyle: "frame.flat", children: vr.checks.map((c2, i) => {
61412
61529
  const cs = statusR({ status: c2.passed ? "success" : "warning", size: "sm" });
@@ -61424,10 +61541,34 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61424
61541
  }) }),
61425
61542
  vr.sens && /* @__PURE__ */ jsxs(Card, { children: [
61426
61543
  /* @__PURE__ */ jsx$1(Cap, { help: "sensitivity", children: "Effect as a hidden cause is made stronger" }),
61427
- /* @__PURE__ */ jsx$1(AreaRange, { lo: vr.sens.lo, mid: vr.sens.mid, hi: vr.sens.hi, zero: 0, tone: "brand", xTicks: vr.sens.xTicks, yTicks: vr.sens.yTicks, height: 132 })
61544
+ /* @__PURE__ */ jsx$1(
61545
+ AreaRange,
61546
+ {
61547
+ lo: vr.sens.lo,
61548
+ mid: vr.sens.mid,
61549
+ hi: vr.sens.hi,
61550
+ zero: 0,
61551
+ tone: "brand",
61552
+ xTicks: vr.sens.xTicks,
61553
+ yTicks: vr.sens.yTicks,
61554
+ marks: vr.sens.marks,
61555
+ height: vr.sens.marks.length ? 148 : 132
61556
+ }
61557
+ )
61428
61558
  ] })
61429
61559
  ] }),
61430
- tab === "trust" && !vr && /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
61560
+ activeTab === "trust" && !vr && // Two distinct reasons the checklist is absent: the engine REFUSED
61561
+ // (no adjusted estimate to break) vs. the answer landed WITHOUT a
61562
+ // robustness summary (e.g. a precomputed result whose stress tests
61563
+ // were never run). Conflating them asserted a refusal that didn't
61564
+ // happen.
61565
+ ((shownResult == null ? void 0 : shownResult.adjusted.type) === "some" ? /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
61566
+ /* @__PURE__ */ jsxs(Box, { display: "inline-flex", alignItems: "center", gap: "2", mb: "2", color: "fg.muted", children: [
61567
+ /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faTriangleExclamation, style: { fontSize: "13px" } }),
61568
+ /* @__PURE__ */ jsx$1(Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.default", children: "Stress tests not run" })
61569
+ ] }),
61570
+ /* @__PURE__ */ jsx$1(Text, { textStyle: "body.sm", color: "fg.muted", lineHeight: "1.5", children: "This answer came without a robustness summary — the stress tests weren’t run for this question, so there’s nothing to report here." })
61571
+ ] }) : /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
61431
61572
  /* @__PURE__ */ jsxs(Box, { display: "inline-flex", alignItems: "center", gap: "2", mb: "2", color: "fg.muted", children: [
61432
61573
  /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faTriangleExclamation, style: { fontSize: "13px" } }),
61433
61574
  /* @__PURE__ */ jsx$1(Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.default", children: "Nothing to stress-test" })
@@ -61437,8 +61578,9 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61437
61578
  /* @__PURE__ */ jsx$1(Text, { as: "span", fontWeight: "semibold", color: "fg.default", children: "Answer" }),
61438
61579
  " tab for why)."
61439
61580
  ] })
61440
- ] }),
61441
- tab === "dose" && vd && /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
61581
+ ] })),
61582
+ activeTab === "dose" && vd && /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
61583
+ /* @__PURE__ */ jsx$1(Text, { textStyle: "body.sm", color: "fg.muted", mb: "3.5", children: /* @__PURE__ */ jsx$1(Help, { id: "tab_dose", children: vd.framing }) }),
61442
61584
  /* @__PURE__ */ jsxs(Card, { mt: "0", children: [
61443
61585
  /* @__PURE__ */ jsxs(Cap, { help: "dose_curve", children: [
61444
61586
  vd.outcome,
@@ -61475,7 +61617,27 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61475
61617
  ] })
61476
61618
  ] })
61477
61619
  ] }),
61478
- tab === "validate" && (design.status === "failed" && design.error ? /* @__PURE__ */ jsx$1(Box, { px: "4.5", pt: "4.5", children: /* @__PURE__ */ jsx$1(RunError, { error: design.error }) }) : designFresh && vmDesign ? /* @__PURE__ */ jsx$1(ValidatePanel, { vm: vmDesign, barList }) : /* @__PURE__ */ jsxs(Box, { p: "4.5", display: "inline-flex", alignItems: "center", gap: "2", color: "fg.muted", children: [
61620
+ activeTab === "dose" && !vd && // The tab is reachable (a dose feature is configured) but the shown
61621
+ // result has no curve — e.g. a refusal verdict, or the engine
61622
+ // returned no dose-response for this setup. Say so; never a blank pane.
61623
+ /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
61624
+ /* @__PURE__ */ jsxs(Box, { display: "inline-flex", alignItems: "center", gap: "2", mb: "2", color: "fg.muted", children: [
61625
+ /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faTriangleExclamation, style: { fontSize: "13px" } }),
61626
+ /* @__PURE__ */ jsx$1(Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.default", children: "No dose-response to show" })
61627
+ ] }),
61628
+ (shownResult == null ? void 0 : shownResult.adjusted.type) === "some" ? /* @__PURE__ */ jsx$1(Text, { textStyle: "body.sm", color: "fg.muted", lineHeight: "1.5", children: "The experiment didn’t return a how-much curve for this setup." }) : /* @__PURE__ */ jsxs(Text, { textStyle: "body.sm", color: "fg.muted", lineHeight: "1.5", children: [
61629
+ "A how-much curve needs a like-for-like estimate, and the experiment couldn’t produce one (see the ",
61630
+ /* @__PURE__ */ jsx$1(Text, { as: "span", fontWeight: "semibold", color: "fg.default", children: "Answer" }),
61631
+ " tab for why)."
61632
+ ] })
61633
+ ] }),
61634
+ activeTab === "validate" && (design.status === "failed" && design.error ? /* @__PURE__ */ jsx$1(Box, { px: "4.5", pt: "4.5", children: /* @__PURE__ */ jsx$1(RunError, { error: design.error }) }) : designFresh && vmDesign ? /* @__PURE__ */ jsx$1(ValidatePanel, { vm: vmDesign, barList }) : !hasDesign && precomputedDesign === null ? /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
61635
+ /* @__PURE__ */ jsxs(Box, { display: "inline-flex", alignItems: "center", gap: "2", mb: "2", color: "fg.muted", children: [
61636
+ /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faTriangleExclamation, style: { fontSize: "13px" } }),
61637
+ /* @__PURE__ */ jsx$1(Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.default", children: "No trial recipe for this question" })
61638
+ ] }),
61639
+ /* @__PURE__ */ jsx$1(Text, { textStyle: "body.sm", color: "fg.muted", lineHeight: "1.5", children: "This question doesn’t include a validation-trial recipe, and no design function is bound to size one." })
61640
+ ] }) : /* @__PURE__ */ jsxs(Box, { p: "4.5", display: "inline-flex", alignItems: "center", gap: "2", color: "fg.muted", children: [
61479
61641
  /* @__PURE__ */ jsx$1(Spinner, { size: "xs", borderWidth: "1.5px", color: "brand.solid" }),
61480
61642
  /* @__PURE__ */ jsx$1(Text, { textStyle: "body.sm", children: "Sizing the trial that would confirm this…" })
61481
61643
  ] }))
@@ -61485,11 +61647,11 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61485
61647
  journal && journal.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
61486
61648
  /* @__PURE__ */ jsxs(Box, { css: es.root, bg: "bg.canvas", borderTopWidth: "1px", borderColor: "border.subtle", children: [
61487
61649
  /* @__PURE__ */ jsx$1(Box, { css: es.lbl, children: /* @__PURE__ */ jsx$1(Help, { id: "journal", children: "Committed experiments" }) }),
61488
- /* @__PURE__ */ jsxs(Box, { css: es.meta, children: [
61650
+ /* @__PURE__ */ jsx$1(Box, { css: es.meta, children: /* @__PURE__ */ jsxs(Text, { as: "span", children: [
61489
61651
  /* @__PURE__ */ jsx$1(Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: journal.length }),
61490
61652
  " on record",
61491
61653
  journal.length > 50 ? " · showing newest 50" : ""
61492
- ] })
61654
+ ] }) })
61493
61655
  ] }),
61494
61656
  journal.slice(0, 50).map((r2, i) => /* @__PURE__ */ jsxs(Box, { display: "grid", gridTemplateColumns: "2fr 1fr 1fr 1fr", gap: "3", alignItems: "center", px: "4.5", py: "2.5", borderTopWidth: "1px", borderColor: "border.subtle", children: [
61495
61657
  /* @__PURE__ */ jsxs(Text, { textStyle: "body.sm", children: [
@@ -61528,24 +61690,31 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61528
61690
  ] })
61529
61691
  ] }) });
61530
61692
  }, (prev2, next2) => experimentValueEqual(prev2.value, next2.value) && prev2.storageKey === next2.storageKey);
61531
- function AnswerNumeric({ a: a2, verdict, higherBetter, badge, barList }) {
61693
+ function AnswerNumeric({ a: a2, verdict, narrative, checks, onGoTrust, subjectMany, higherBetter, badge, barList }) {
61532
61694
  const dirUp = a2.effect > 0;
61533
61695
  const statusWord = higherBetter === void 0 ? dirUp ? "Higher" : "Lower" : dirUp === higherBetter ? "Better" : "Worse";
61534
61696
  const lowerWord = a2.naive < 0 ? "lower" : "higher";
61535
- const top = a2.balance[0] ?? { col: "", display: "" };
61697
+ const top = a2.balance[0] ?? { label: "", display: "" };
61536
61698
  const kpiColor = a2.clear && !a2.cautious ? "fg.success" : "fg.warning";
61537
61699
  const badgeOk = a2.clear && !a2.cautious;
61538
61700
  const badgeText = a2.cautious ? (verdict == null ? void 0 : verdict.label) ?? "Not trustworthy yet" : a2.clear ? `${statusWord} with ${a2.treatment}` : "No clear effect";
61539
61701
  return /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
61702
+ narrative && /* @__PURE__ */ jsx$1(Text, { textStyle: "body.md", color: "fg.default", lineHeight: "1.6", mb: "3.5", children: narrative.segments.map((s2, i) => s2.strong ? /* @__PURE__ */ jsx$1(Text, { as: "span", fontWeight: "bold", color: toneToken(narrative.tone), children: s2.text }, i) : /* @__PURE__ */ jsx$1(Text, { as: "span", children: s2.text }, i)) }),
61540
61703
  /* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "center", gap: "4.5", flexWrap: "wrap", children: [
61541
61704
  /* @__PURE__ */ jsxs(Box, { display: "flex", flexDirection: "column", gap: "0.5", children: [
61542
61705
  /* @__PURE__ */ jsx$1(Text, { textStyle: "mono.sm", color: "fg.muted", children: a2.outcome }),
61543
61706
  /* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "baseline", gap: "2.5", children: [
61544
- /* @__PURE__ */ jsx$1(Text, { textStyle: "mono-kpi", fontFamily: "heading", fontSize: "32px", color: kpiColor, children: /* @__PURE__ */ jsx$1(Help, { id: "answer_effect", children: signed(a2.effect) }) }),
61707
+ /* @__PURE__ */ jsx$1(Text, { textStyle: "mono-kpi", fontFamily: "heading", fontSize: "32px", color: kpiColor, children: /* @__PURE__ */ jsxs(Help, { id: "answer_effect", children: [
61708
+ signed(a2.effect),
61709
+ a2.unit ? /* @__PURE__ */ jsxs(Text, { as: "span", fontSize: "16px", color: "fg.muted", children: [
61710
+ " ",
61711
+ a2.unit
61712
+ ] }) : null
61713
+ ] }) }),
61545
61714
  /* @__PURE__ */ jsx$1(Text, { textStyle: "mono.sm", color: "fg.muted", children: /* @__PURE__ */ jsxs(Help, { id: "answer_ci", children: [
61546
- "95% CI  ",
61715
+ "likely between ",
61547
61716
  signed(a2.lo),
61548
- " ",
61717
+ " and ",
61549
61718
  signed(a2.hi)
61550
61719
  ] }) })
61551
61720
  ] })
@@ -61555,6 +61724,42 @@ function AnswerNumeric({ a: a2, verdict, higherBetter, badge, barList }) {
61555
61724
  /* @__PURE__ */ jsx$1(Help, { id: `verdict_${(verdict == null ? void 0 : verdict.tag) ?? "modest"}`, children: badgeText })
61556
61725
  ] })
61557
61726
  ] }),
61727
+ checks && checks.length > 0 && /* @__PURE__ */ jsxs(
61728
+ Box,
61729
+ {
61730
+ as: "button",
61731
+ onClick: onGoTrust,
61732
+ display: "inline-flex",
61733
+ alignItems: "center",
61734
+ gap: "2",
61735
+ flexWrap: "wrap",
61736
+ mt: "2.5",
61737
+ bg: "transparent",
61738
+ border: "0",
61739
+ p: "0",
61740
+ cursor: "pointer",
61741
+ title: "See the full stress-test checklist",
61742
+ children: [
61743
+ /* @__PURE__ */ jsx$1(Text, { textStyle: "caption", color: "fg.muted", children: "Stress tests:" }),
61744
+ checks.map((c2, i) => /* @__PURE__ */ jsxs(
61745
+ Box,
61746
+ {
61747
+ as: "span",
61748
+ display: "inline-flex",
61749
+ alignItems: "center",
61750
+ gap: "1.5",
61751
+ color: c2.passed ? "fg.success" : "fg.warning",
61752
+ children: [
61753
+ /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: c2.passed ? faCheck : faTriangleExclamation, style: { fontSize: "10px" } }),
61754
+ /* @__PURE__ */ jsx$1(Text, { as: "span", textStyle: "caption", color: "fg.default", children: c2.short }),
61755
+ i < checks.length - 1 && /* @__PURE__ */ jsx$1(Text, { as: "span", textStyle: "caption", color: "fg.subtle", children: "·" })
61756
+ ]
61757
+ },
61758
+ i
61759
+ ))
61760
+ ]
61761
+ }
61762
+ ),
61558
61763
  a2.cautious && /* @__PURE__ */ jsxs(Box, { layerStyle: "banner.stale", display: "flex", alignItems: "flex-start", gap: "2", mt: "3", children: [
61559
61764
  /* @__PURE__ */ jsx$1(Box, { as: "span", color: "fg.warning", flexShrink: "0", mt: "0.5", fontSize: "12px", children: /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faTriangleExclamation }) }),
61560
61765
  /* @__PURE__ */ jsxs(Text, { textStyle: "body.sm", color: "fg.default", children: [
@@ -61568,15 +61773,15 @@ function AnswerNumeric({ a: a2, verdict, higherBetter, badge, barList }) {
61568
61773
  /* @__PURE__ */ jsxs(Text, { textStyle: "body.sm", color: "fg.default", children: [
61569
61774
  /* @__PURE__ */ jsx$1(Help, { id: "answer_flip", children: /* @__PURE__ */ jsx$1(Text, { as: "span", fontWeight: "bold", children: "Raw and like-for-like disagree." }) }),
61570
61775
  " In the plain average, the ",
61571
- /* @__PURE__ */ jsx$1(Text, { as: "span", fontFamily: "mono", children: a2.treatment }),
61776
+ /* @__PURE__ */ jsx$1(Text, { as: "span", fontWeight: "semibold", children: a2.treatment }),
61572
61777
  " group sits ",
61573
61778
  /* @__PURE__ */ jsx$1(Text, { as: "span", fontStyle: "italic", children: lowerWord }),
61574
61779
  " on ",
61575
- /* @__PURE__ */ jsx$1(Text, { as: "span", fontFamily: "mono", children: a2.outcome }),
61780
+ /* @__PURE__ */ jsx$1(Text, { as: "span", fontWeight: "semibold", children: a2.outcome }),
61576
61781
  " (",
61577
61782
  signed(a2.naive),
61578
61783
  ") — but they also differ most on ",
61579
- /* @__PURE__ */ jsx$1(Text, { as: "span", fontFamily: "mono", children: top.col }),
61784
+ /* @__PURE__ */ jsx$1(Text, { as: "span", fontWeight: "semibold", children: top.label }),
61580
61785
  " (",
61581
61786
  top.display,
61582
61787
  "). Adjusting for it reverses the result."
@@ -61599,11 +61804,40 @@ function AnswerNumeric({ a: a2, verdict, higherBetter, badge, barList }) {
61599
61804
  }
61600
61805
  )
61601
61806
  ] }),
61602
- /* @__PURE__ */ jsxs(Card, { children: [
61603
- /* @__PURE__ */ jsx$1(Cap, { help: "balance", children: "How unbalanced each one was — before adjusting" }),
61604
- /* @__PURE__ */ jsx$1(Box, { py: "0.5", maxH: "208px", overflowY: "auto", children: barList(a2.balance.map((b2) => ({ label: b2.col, frac: b2.frac, tone: b2.tone, value: b2.display }))) })
61807
+ /* @__PURE__ */ jsxs(Box, { as: "details", layerStyle: "card", borderRadius: "lg", mt: "3", children: [
61808
+ /* @__PURE__ */ jsxs(
61809
+ Box,
61810
+ {
61811
+ as: "summary",
61812
+ textStyle: "caption.eyebrow",
61813
+ fontSize: "9px",
61814
+ cursor: "pointer",
61815
+ p: "3.5",
61816
+ display: "flex",
61817
+ alignItems: "center",
61818
+ gap: "1.5",
61819
+ css: { listStyle: "none", "&::-webkit-details-marker": { display: "none" } },
61820
+ children: [
61821
+ /* @__PURE__ */ jsx$1(
61822
+ Box,
61823
+ {
61824
+ as: "span",
61825
+ display: "inline-flex",
61826
+ color: "fg.subtle",
61827
+ fontSize: "10px",
61828
+ transition: "transform 180ms ease",
61829
+ transform: "rotate(-90deg)",
61830
+ css: { "details[open] > summary > &": { transform: "rotate(0deg)" } },
61831
+ children: /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faChevronDown })
61832
+ }
61833
+ ),
61834
+ /* @__PURE__ */ jsx$1(Help, { id: "balance", children: "How we levelled the playing field" })
61835
+ ]
61836
+ }
61837
+ ),
61838
+ /* @__PURE__ */ jsx$1(Box, { px: "3.5", pb: "3.5", py: "0.5", maxH: "208px", overflowY: "auto", children: barList(a2.balance.map((b2) => ({ label: b2.label, frac: b2.frac, tone: b2.tone, value: b2.display }))) })
61605
61839
  ] }),
61606
- /* @__PURE__ */ jsx$1(Box, { mt: "3.5", children: /* @__PURE__ */ jsx$1(Help, { id: "counts", display: "inline-flex", gap: "4", children: [[a2.nTotal, SUBJECT_MANY], [a2.nCompared, "compared like-for-like"], [a2.nDropped, "had no fair match"]].map(([n2, label], i) => /* @__PURE__ */ jsxs(Text, { textStyle: "mono.sm", color: "fg.muted", children: [
61840
+ /* @__PURE__ */ jsx$1(Box, { mt: "3.5", children: /* @__PURE__ */ jsx$1(Help, { id: "counts", display: "inline-flex", gap: "4", children: [[a2.nTotal, subjectMany], [a2.nCompared, "compared like-for-like"], [a2.nDropped, "had no fair match"]].map(([n2, label], i) => /* @__PURE__ */ jsxs(Text, { textStyle: "mono.sm", color: "fg.muted", children: [
61607
61841
  /* @__PURE__ */ jsx$1(Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: Number(n2) }),
61608
61842
  " ",
61609
61843
  label
@@ -61707,7 +61941,7 @@ function ValidatePanel({ vm, barList }) {
61707
61941
  ] }, i)) })
61708
61942
  ] });
61709
61943
  }
61710
- function FilterRail({ fields, population, onChange: onChange2, chip, button, readonly }) {
61944
+ function FilterRail({ fields, population, onChange: onChange2, chip, button, readonly, subjectMany }) {
61711
61945
  const [open, setOpen] = useState(null);
61712
61946
  const replaceAt = (i, p2) => onChange2(population.map((f2, j2) => j2 === i ? p2 : f2));
61713
61947
  const removeAt = (i) => onChange2(population.filter((_2, j2) => j2 !== i));
@@ -61715,7 +61949,7 @@ function FilterRail({ fields, population, onChange: onChange2, chip, button, rea
61715
61949
  if (readonly) {
61716
61950
  return /* @__PURE__ */ jsx$1(Box, { display: "flex", flexWrap: "wrap", gap: "1.5", alignItems: "center", children: population.length === 0 ? /* @__PURE__ */ jsxs(Box, { css: chip({ tone: "plain", numeric: true, shape: "rounded" }), children: [
61717
61951
  "all ",
61718
- SUBJECT_MANY
61952
+ subjectMany
61719
61953
  ] }) : population.map((pred, i) => /* @__PURE__ */ jsx$1(Box, { css: chip({ tone: "brand", numeric: true, shape: "rounded" }), flexShrink: 0, children: /* @__PURE__ */ jsx$1(Box, { as: "span", whiteSpace: "nowrap", children: formatPredicate(pred) }) }, i)) });
61720
61954
  }
61721
61955
  return /* @__PURE__ */ jsxs(Box, { display: "flex", flexWrap: "wrap", gap: "1.5", alignItems: "center", children: [