@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.cjs CHANGED
@@ -59938,7 +59938,7 @@ const HELP = {
59938
59938
  },
59939
59939
  // ── Tabs ────────────────────────────────────────────────────────────
59940
59940
  tab_answer: {
59941
- term: "Answer",
59941
+ term: "The answer",
59942
59942
  gist: "The headline: did {treatment} move {outcome}, and by how much?",
59943
59943
  detail: "Shows the raw gap, the fair like-for-like estimate, and an honest verdict."
59944
59944
  },
@@ -60126,7 +60126,7 @@ const HELP = {
60126
60126
  },
60127
60127
  // ── Validate tab ────────────────────────────────────────────────────
60128
60128
  tab_validate: {
60129
- term: "Validate",
60129
+ term: "Prove it",
60130
60130
  gist: "The real experiment you’d run to prove this — for sure.",
60131
60131
  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."
60132
60132
  },
@@ -60215,6 +60215,12 @@ function Help({ id: id3, extraVars, children: children2, display = "inline", gap
60215
60215
  )
60216
60216
  ) });
60217
60217
  }
60218
+ function GuidanceGist({ id: id3, extraVars }) {
60219
+ const { on: on3, vars } = React.useContext(GuidanceContext);
60220
+ if (!on3) return null;
60221
+ const e3 = fillHelp(HELP[id3], { ...vars, ...extraVars });
60222
+ return /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption", color: "fg.muted", lineHeight: "1.45", mt: "-1.5", mb: "2.5", children: e3.gist });
60223
+ }
60218
60224
  function GuidanceToggle({ on: on3, onToggle }) {
60219
60225
  const iconButton = react.useRecipe({ key: "iconButton" });
60220
60226
  return /* @__PURE__ */ jsxRuntime.jsx(Hover, { id: "guidance", trigger: /* @__PURE__ */ jsxRuntime.jsx(
@@ -60369,8 +60375,8 @@ function AreaRange({ lo: lo2, mid, hi, xTicks = [], yTicks = [], zero: zero2, to
60369
60375
  const atEnd = m2.at >= mid.length - 2;
60370
60376
  const atStart = m2.at <= 1;
60371
60377
  const align = atEnd ? "right" : atStart ? "left" : "center";
60372
- const labW = m2.label.length * 6 + 6;
60373
- const labX = atEnd ? -labW : atStart ? 0 : -labW / 2;
60378
+ const labW = m2.label.length * 6.5 + 8;
60379
+ const labX = atEnd ? -labW - 5 : atStart ? 5 : -labW / 2;
60374
60380
  return /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: xScale(m2.at), children: [
60375
60381
  /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: -2 }, to: { x: 0, y: innerH }, stroke: mc, strokeWidth: 1, strokeDasharray: "2 3" }),
60376
60382
  /* @__PURE__ */ jsxRuntime.jsx(Circle, { cx: 0, cy: dotY, r: t4.dotRadius, fill: mc, stroke: t4.surface, strokeWidth: 1.5 }),
@@ -60441,6 +60447,10 @@ const unitOf = (meta3, col) => {
60441
60447
  var _a2;
60442
60448
  return eastUiComponents.getSomeorUndefined((_a2 = colMeta(meta3, col)) == null ? void 0 : _a2.unit) ?? "";
60443
60449
  };
60450
+ const labelOf = (meta3, col) => {
60451
+ var _a2;
60452
+ return eastUiComponents.getSomeorUndefined((_a2 = colMeta(meta3, col)) == null ? void 0 : _a2.label) ?? col;
60453
+ };
60444
60454
  const kindOf = (cols, name) => {
60445
60455
  var _a2;
60446
60456
  return (_a2 = cols.find((c2) => c2.name === name)) == null ? void 0 : _a2.kind;
@@ -60476,8 +60486,7 @@ function treatmentKind(cols, treatment) {
60476
60486
  return "value";
60477
60487
  }
60478
60488
  }
60479
- function deriveSpec(config2, cols, result, meta3, dataLen) {
60480
- const tKind = kindOf(cols, config2.treatment);
60489
+ function deriveSpec(config2, cols, result, meta3, dataLen, subject) {
60481
60490
  const outUnit = unitOf(meta3, config2.outcome);
60482
60491
  const balByCol = /* @__PURE__ */ new Map();
60483
60492
  for (const b2 of (result == null ? void 0 : result.balance) ?? []) {
@@ -60489,7 +60498,7 @@ function deriveSpec(config2, cols, result, meta3, dataLen) {
60489
60498
  const absStd = b2 ? Math.abs(b2.std_diff) : 0;
60490
60499
  const { level, tone } = band(absStd);
60491
60500
  const dir = b2 ? b2.treated_mean < b2.control_mean ? "lower" : "higher" : "";
60492
- const reason = b2 ? `the treated group ran ${dir} on ${col}` : "measured once you Run";
60501
+ const reason = b2 ? `the treated group ran ${dir} on ${labelOf(meta3, col)}` : "measured once you Run";
60493
60502
  return { col, reason, imbalance: clamp01(absStd), level, tone };
60494
60503
  });
60495
60504
  const inPlay = /* @__PURE__ */ new Set([config2.treatment, config2.outcome, ...config2.common_causes]);
@@ -60501,15 +60510,21 @@ function deriveSpec(config2, cols, result, meta3, dataLen) {
60501
60510
  const target = targetV && (targetV.type === "att" || targetV.type === "atc") ? "treated" : "all";
60502
60511
  return {
60503
60512
  treatment: config2.treatment,
60513
+ treatmentLabel: labelOf(meta3, config2.treatment),
60504
60514
  treatmentKind: treatmentKind(cols, config2.treatment),
60505
60515
  outcome: config2.outcome,
60516
+ outcomeLabel: labelOf(meta3, config2.outcome),
60506
60517
  outcomeKind: outUnit ? `number · ${outUnit}` : "number",
60507
- comparison: tKind === "boolean" ? "yes · vs no" : "high · vs low",
60518
+ comparison: kindOf(cols, config2.treatment) === "boolean" ? "yes · vs no" : "high · vs low",
60508
60519
  confounders,
60509
60520
  suggestion,
60510
60521
  method,
60511
60522
  target,
60512
- dataLabel: `${result ? Number(result.n_total) : dataLen} rows`
60523
+ // Always the CURRENT (filtered) dataset's row count — the result's own
60524
+ // n_total belongs to the Answer-tab counts strip; labelling the header
60525
+ // with it desyncs from the data after a population edit (and reads
60526
+ // wrong whenever the two legitimately differ).
60527
+ dataLabel: `${dataLen} ${subject.many}`
60513
60528
  };
60514
60529
  }
60515
60530
  function balanceDisplay(b2, categorical) {
@@ -60521,7 +60536,9 @@ function deriveAnswer(config2, result, adj, meta3) {
60521
60536
  const nci = eastUiComponents.getSomeorUndefined(result.naive_ci);
60522
60537
  const categorical = new Set(eastUiComponents.getSomeorUndefined(config2.categorical) ?? []);
60523
60538
  const balance = [...result.balance].sort((a2, b2) => Math.abs(b2.std_diff) - Math.abs(a2.std_diff)).map((b2) => ({
60539
+ // One-hot rows are `col=level` — label the BASE confounder, keep the level.
60524
60540
  col: b2.column,
60541
+ 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})`}`,
60525
60542
  treated: b2.treated_mean,
60526
60543
  control: b2.control_mean,
60527
60544
  display: balanceDisplay(b2, categorical),
@@ -60535,8 +60552,8 @@ function deriveAnswer(config2, result, adj, meta3) {
60535
60552
  const nDropped = Number(result.n_dropped);
60536
60553
  const nTotal = Number(result.n_total);
60537
60554
  return {
60538
- treatment: config2.treatment,
60539
- outcome: config2.outcome,
60555
+ treatment: labelOf(meta3, config2.treatment),
60556
+ outcome: labelOf(meta3, config2.outcome),
60540
60557
  unit: unitOf(meta3, config2.outcome),
60541
60558
  naive: result.naive,
60542
60559
  naiveLo: (nci == null ? void 0 : nci.lower) ?? result.naive,
@@ -60555,18 +60572,63 @@ function deriveAnswer(config2, result, adj, meta3) {
60555
60572
  balance
60556
60573
  };
60557
60574
  }
60558
- function deriveRefusal(config2, result) {
60575
+ function deriveNarrative(result, a2, refute, higherBetter, subject) {
60576
+ if (a2 === null) return null;
60577
+ const unit2 = a2.unit ? ` ${a2.unit}` : "";
60578
+ const dirUp = a2.effect > 0;
60579
+ const verb = higherBetter === void 0 ? dirUp ? "raises" : "lowers" : dirUp === higherBetter ? "improves" : "worsens";
60580
+ const range = ` (likely between ${signed(a2.lo)} and ${signed(a2.hi)})`;
60581
+ const passed = (refute == null ? void 0 : refute.checks.filter((c2) => c2.passed).length) ?? 0;
60582
+ const total = (refute == null ? void 0 : refute.checks.length) ?? 0;
60583
+ 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?”.`;
60584
+ switch (result.verdict.type) {
60585
+ case "causal":
60586
+ return {
60587
+ tone: "pos",
60588
+ segments: [
60589
+ { text: "Yes — ", strong: true },
60590
+ { text: `${a2.treatment} genuinely ${verb} ${a2.outcome}: about ` },
60591
+ { text: `${signed(a2.effect)}${unit2}`, strong: true },
60592
+ { text: ` per ${subject.one}${range}.${trustClause}` }
60593
+ ]
60594
+ };
60595
+ case "modest": {
60596
+ const tags = new Set(result.verdict_reasons.map((v3) => v3.type));
60597
+ 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.` : ".";
60598
+ return {
60599
+ tone: "warn",
60600
+ segments: [
60601
+ { text: "Maybe — ", strong: true },
60602
+ { text: `there may be an effect of ${a2.treatment} on ${a2.outcome}, but it’s too small or unclear to act on` },
60603
+ { text: why }
60604
+ ]
60605
+ };
60606
+ }
60607
+ case "adjustment_insufficient":
60608
+ return {
60609
+ tone: "warn",
60610
+ segments: [
60611
+ { text: "Don’t act on this yet — ", strong: true },
60612
+ { 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.` }
60613
+ ]
60614
+ };
60615
+ default:
60616
+ return null;
60617
+ }
60618
+ }
60619
+ function deriveRefusal(config2, result, meta3, subject) {
60559
60620
  const nT = Number(result.n_treated), nC = Number(result.n_control), nTot = Number(result.n_total);
60621
+ const treatment = labelOf(meta3, config2.treatment);
60560
60622
  if (result.verdict.type === "not_estimable") {
60561
60623
  const reason = result.verdict.value || "The treatment barely varies, so no comparison can be formed.";
60562
60624
  return {
60563
60625
  kind: "not_estimable",
60564
- title: `Can’t estimate the effect of ${config2.treatment}`,
60626
+ title: `Can’t estimate the effect of ${treatment}`,
60565
60627
  body: reason,
60566
60628
  evidence: [
60567
60629
  { label: "treated", value: String(nT) },
60568
60630
  { label: "untreated", value: String(nC) },
60569
- { label: "rows", value: String(nTot) }
60631
+ { label: subject.many, value: String(nTot) }
60570
60632
  ]
60571
60633
  };
60572
60634
  }
@@ -60585,12 +60647,12 @@ function deriveRefusal(config2, result) {
60585
60647
  }
60586
60648
  return {
60587
60649
  kind: "not_estimable",
60588
- title: `Can’t estimate the effect of ${config2.treatment}`,
60650
+ title: `Can’t estimate the effect of ${treatment}`,
60589
60651
  body: "The engine could not produce a like-for-like estimate for this configuration.",
60590
60652
  evidence: [
60591
60653
  { label: "treated", value: String(nT) },
60592
60654
  { label: "untreated", value: String(nC) },
60593
- { label: "rows", value: String(nTot) }
60655
+ { label: subject.many, value: String(nTot) }
60594
60656
  ]
60595
60657
  };
60596
60658
  }
@@ -60614,17 +60676,21 @@ function zeroCrossing(xs, ys) {
60614
60676
  }
60615
60677
  return null;
60616
60678
  }
60617
- function deriveRefute(r2, adj) {
60679
+ function deriveRefute(r2, adj, meta3) {
60618
60680
  const checks = [];
60619
60681
  const est = (adj == null ? void 0 : adj.effect) ?? 0;
60620
60682
  const placeboEffect = eastUiComponents.getSomeorUndefined(r2.placebo_effect);
60621
60683
  const placeboPasses = eastUiComponents.getSomeorUndefined(r2.placebo_passes);
60622
60684
  if (placeboEffect !== void 0 || placeboPasses !== void 0) {
60685
+ const passed = placeboPasses ?? (placeboEffect !== void 0 && Math.abs(placeboEffect) < Math.max(0.5, Math.abs(est) * 0.15));
60623
60686
  checks.push({
60624
60687
  name: "Shuffle test",
60688
+ short: "shuffle",
60625
60689
  desc: "Shuffle which rows were treated; a real effect should collapse to zero.",
60626
- value: placeboEffect !== void 0 ? `→ ${signed(placeboEffect)}` : placeboPasses ? "passed" : "failed",
60627
- passed: placeboPasses ?? (placeboEffect !== void 0 && Math.abs(placeboEffect) < Math.max(0.5, Math.abs(est) * 0.15)),
60690
+ // Speak the outcome, not the statistic: on fake labels the effect
60691
+ // should VANISH say whether it did.
60692
+ value: placeboEffect !== void 0 ? passed ? `vanished (${signed(placeboEffect)})` : `didn’t vanish (${signed(placeboEffect)})` : passed ? "vanished" : "didn’t vanish",
60693
+ passed,
60628
60694
  tip: east.some("Randomly re-label which rows were treated. A genuine effect should vanish."),
60629
60695
  help: "check_shuffle"
60630
60696
  });
@@ -60632,11 +60698,13 @@ function deriveRefute(r2, adj) {
60632
60698
  const ds = eastUiComponents.getSomeorUndefined(r2.data_subset_effect);
60633
60699
  if (ds !== void 0) {
60634
60700
  const dstd = eastUiComponents.getSomeorUndefined(r2.data_subset_std) ?? 0;
60701
+ const passed = Math.abs(ds - est) < Math.max(0.5, Math.abs(est) * 0.2);
60635
60702
  checks.push({
60636
60703
  name: "Drop-some test",
60704
+ short: "drop-some",
60637
60705
  desc: "Re-estimate on random subsamples; a trustworthy effect stays put.",
60638
- value: `${fmt$1(ds)} ± ${fmt$1(dstd)}`,
60639
- passed: Math.abs(ds - est) < Math.max(0.5, Math.abs(est) * 0.2),
60706
+ value: passed ? `stayed at ${signed(ds)} ± ${fmt$1(dstd)}` : `moved to ${signed(ds)} ± ${fmt$1(dstd)}`,
60707
+ passed,
60640
60708
  tip: east.none,
60641
60709
  help: "check_dropsome"
60642
60710
  });
@@ -60645,8 +60713,9 @@ function deriveRefute(r2, adj) {
60645
60713
  if (rcc !== void 0) {
60646
60714
  checks.push({
60647
60715
  name: "Decoy cause",
60716
+ short: "decoy",
60648
60717
  desc: "Add an irrelevant random factor; the answer should not move.",
60649
- value: rcc ? "within range" : "moved",
60718
+ value: rcc ? "didn’t move" : "moved",
60650
60719
  passed: rcc,
60651
60720
  tip: east.none,
60652
60721
  help: "check_decoy"
@@ -60654,14 +60723,18 @@ function deriveRefute(r2, adj) {
60654
60723
  }
60655
60724
  const sens = eastUiComponents.getSomeorUndefined(r2.sensitivity);
60656
60725
  const evalue = eastUiComponents.getSomeorUndefined(r2.robustness_value);
60726
+ const benchmarks = sens ? [...sens.benchmarks].sort((a2, b2) => b2.strength - a2.strength) : [];
60657
60727
  if (sens !== void 0 || evalue !== void 0) {
60658
60728
  const strengths = sens ? arr(sens.strengths) : [];
60659
60729
  const effects = sens ? arr(sens.effects) : [];
60660
60730
  const tip = sens ? zeroCrossing(strengths, effects) : null;
60661
- const value = tip !== null ? `tips at ${fmt$1(tip)}` : evalue !== void 0 ? `E-value ${fmt$1(evalue)}` : "holds throughout";
60731
+ const strongest = benchmarks[0];
60732
+ 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";
60733
+ const scaleNote = strongest !== void 0 ? ` For scale, your strongest known factor (${labelOf(meta3, strongest.column)}) sits at ${fmt$1(strongest.strength)} on this axis.` : "";
60662
60734
  checks.push({
60663
60735
  name: "Hidden cause",
60664
- desc: "How strong an unrecorded common cause would need to be to overturn the result.",
60736
+ short: "hidden cause",
60737
+ desc: `How strong an unrecorded common cause would need to be to overturn the result.${scaleNote}`,
60665
60738
  value,
60666
60739
  passed: tip === null,
60667
60740
  tip: east.some("Something unrecorded could drive both the treatment choice and the outcome."),
@@ -60677,7 +60750,23 @@ function deriveRefute(r2, adj) {
60677
60750
  const lo2 = mid.map((m2) => m2 - half);
60678
60751
  const hi = mid.map((m2) => m2 + half);
60679
60752
  const yLo = Math.min(0, ...lo2), yHi = Math.max(...hi, 0);
60680
- sensVM = { lo: lo2, mid, hi, xTicks: ["none", "weaker", "stronger"], yTicks: [fmt$1(yLo), fmt$1((yLo + yHi) / 2), fmt$1(yHi)] };
60753
+ const strengths = arr(sens.strengths);
60754
+ const atOf = (s2) => {
60755
+ if (strengths.length < 2) return null;
60756
+ if (s2 <= strengths[0]) return 0;
60757
+ for (let i = 1; i < strengths.length; i++) {
60758
+ if (s2 <= strengths[i]) {
60759
+ const span = strengths[i] - strengths[i - 1];
60760
+ return span > 0 ? i - 1 + (s2 - strengths[i - 1]) / span : i;
60761
+ }
60762
+ }
60763
+ return strengths.length - 1;
60764
+ };
60765
+ const marks = benchmarks.slice(0, 2).flatMap((b2) => {
60766
+ const at = atOf(b2.strength);
60767
+ return at === null ? [] : [{ at, label: `as strong as ${labelOf(meta3, b2.column)}`, tone: "muted", help: "sensitivity" }];
60768
+ });
60769
+ sensVM = { lo: lo2, mid, hi, xTicks: ["none", "", "stronger"], yTicks: [fmt$1(yLo), fmt$1((yLo + yHi) / 2), fmt$1(yHi)], marks };
60681
60770
  }
60682
60771
  }
60683
60772
  return { checks, sens: sensVM };
@@ -60714,21 +60803,26 @@ function deriveDose(dose, config2, meta3) {
60714
60803
  const marks = [];
60715
60804
  if (sizes.length) marks.push({ at: here, label: "you are here", tone: "muted", help: "dose_here" });
60716
60805
  marks.push({ at: sweet, label: "sweet spot", tone: "pos", help: "dose_sweet" });
60806
+ const featureLabel = labelOf(meta3, dose.feature);
60807
+ const outcomeLabel = labelOf(meta3, config2.outcome);
60717
60808
  return {
60718
- feature: dose.feature,
60719
- outcome: config2.outcome,
60809
+ feature: featureLabel,
60810
+ outcome: outcomeLabel,
60720
60811
  lo: lo2,
60721
60812
  mid,
60722
60813
  hi,
60723
60814
  xTicks,
60724
60815
  yTicks,
60725
60816
  marks,
60726
- recoLabel: `≈ ${fmt$1(grid[sweet] ?? 0)}${featureUnit ? " " + featureUnit : ""}`,
60817
+ // Name the feature in the recommendation a bare "≈ 5.5" doesn't say
60818
+ // 5.5 of WHAT.
60819
+ recoLabel: `aim for ${featureLabel} ≈ ${fmt$1(grid[sweet] ?? 0)}${featureUnit ? " " + featureUnit : ""}`,
60727
60820
  recoEffect: mid[sweet] ?? 0,
60728
60821
  recoLo: lo2[sweet] ?? 0,
60729
60822
  recoHi: hi[sweet] ?? 0,
60730
60823
  tradeoff,
60731
- marginal
60824
+ marginal,
60825
+ framing: `A different question from the headline: how ${outcomeLabel} responds as ${featureLabel} itself moves.`
60732
60826
  };
60733
60827
  }
60734
60828
  function relTime(d2, now2) {
@@ -60745,12 +60839,12 @@ const VERDICT_WORD = {
60745
60839
  non_identifiable_positivity: "no overlap",
60746
60840
  not_estimable: "n/a"
60747
60841
  };
60748
- function deriveJournalRow(row, now2) {
60842
+ function deriveJournalRow(row, now2, meta3) {
60749
60843
  const adj = eastUiComponents.getSomeorUndefined(row.adjusted);
60750
60844
  return {
60751
- treatment: row.config.treatment,
60752
- outcome: row.config.outcome,
60753
- confounders: row.config.common_causes.join(", "),
60845
+ treatment: labelOf(meta3, row.config.treatment),
60846
+ outcome: labelOf(meta3, row.config.outcome),
60847
+ confounders: row.config.common_causes.map((c2) => labelOf(meta3, c2)).join(", "),
60754
60848
  effect: adj !== void 0 ? signed(adj) : signed(row.naive),
60755
60849
  verdict: VERDICT_WORD[row.verdict.type],
60756
60850
  verdictTone: VERDICT_TONE[row.verdict.type],
@@ -60761,22 +60855,27 @@ function deriveJournalRow(row, now2) {
60761
60855
  preset: row.preset
60762
60856
  };
60763
60857
  }
60764
- function deriveView(config2, ranConfig, cols, result, journal, meta3, dataLen, now2) {
60858
+ function deriveView(config2, ranConfig, cols, result, journal, meta3, dataLen, now2, subject = { one: "record", many: "records" }) {
60859
+ var _a2;
60765
60860
  const adj = result ? eastUiComponents.getSomeorUndefined(result.adjusted) : void 0;
60766
60861
  const refutation = result ? eastUiComponents.getSomeorUndefined(result.refutation) : void 0;
60767
60862
  const doseR = result ? eastUiComponents.getSomeorUndefined(result.dose_response) : void 0;
60863
+ const answer = result && adj ? deriveAnswer(ranConfig, result, adj, meta3) : null;
60864
+ const refute = refutation ? deriveRefute(refutation, adj, meta3) : null;
60865
+ const higherBetter = eastUiComponents.getSomeorUndefined((_a2 = colMeta(meta3, ranConfig.outcome)) == null ? void 0 : _a2.higherIsBetter);
60768
60866
  return {
60769
60867
  // The set-up rail reflects the LIVE config (the editor); the result deck
60770
60868
  // reflects RANCONFIG — the config that produced `result` — so the result
60771
60869
  // strings never drift ahead of the numbers on a live picker edit.
60772
- spec: deriveSpec(config2, cols, result, meta3, dataLen),
60870
+ spec: deriveSpec(config2, cols, result, meta3, dataLen, subject),
60773
60871
  verdict: result ? deriveVerdict(result.verdict) : null,
60774
- answer: result && adj ? deriveAnswer(ranConfig, result, adj, meta3) : null,
60775
- refusal: result && !adj ? deriveRefusal(ranConfig, result) : null,
60872
+ narrative: result ? deriveNarrative(result, answer, refute, higherBetter, subject) : null,
60873
+ answer,
60874
+ refusal: result && !adj ? deriveRefusal(ranConfig, result, meta3, subject) : null,
60776
60875
  overlap: result ? deriveOverlap(result.overlap) : null,
60777
- refute: refutation ? deriveRefute(refutation, adj) : null,
60876
+ refute,
60778
60877
  dose: doseR && doseR.effect.length ? deriveDose(doseR, ranConfig, meta3) : null,
60779
- journal: journal ? journal.map((r2) => deriveJournalRow(r2, now2)) : null
60878
+ journal: journal ? journal.map((r2) => deriveJournalRow(r2, now2, meta3)) : null
60780
60879
  };
60781
60880
  }
60782
60881
  const DESIGN_HEADLINE = {
@@ -60858,8 +60957,7 @@ function deriveDesign(d2, result, ranConfig, meta3) {
60858
60957
  faint
60859
60958
  };
60860
60959
  }
60861
- const SUBJECT_ONE = "record";
60862
- const SUBJECT_MANY = "records";
60960
+ const SUBJECT_DEFAULT = { one: "record", many: "records" };
60863
60961
  const AUTORUN_MAX_ROWS = 5e4;
60864
60962
  const TONE_TOKEN = {
60865
60963
  neg: "fg.danger",
@@ -60928,7 +61026,10 @@ function rowMatchesAll(row, preds) {
60928
61026
  }
60929
61027
  const num = (x2) => typeof x2 === "number" ? x2 : typeof x2 === "bigint" ? Number(x2) : null;
60930
61028
  function Cap({ children: children2, help }) {
60931
- return /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", fontSize: "9px", mb: "2.5", children: help ? /* @__PURE__ */ jsxRuntime.jsx(Help, { id: help, children: children2 }) : children2 });
61029
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
61030
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", fontSize: "9px", mb: "2.5", children: help ? /* @__PURE__ */ jsxRuntime.jsx(Help, { id: help, children: children2 }) : children2 }),
61031
+ help && /* @__PURE__ */ jsxRuntime.jsx(GuidanceGist, { id: help })
61032
+ ] });
60932
61033
  }
60933
61034
  function Card({ children: children2, mt = "3" }) {
60934
61035
  return /* @__PURE__ */ jsxRuntime.jsx(react.Box, { layerStyle: "card", p: "3.5", borderRadius: "lg", mt, children: children2 });
@@ -61050,6 +61151,7 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
61050
61151
  const data4 = useBindingValue(v3.data);
61051
61152
  const journalBind = useBindingValue(v3.journal.type === "some" ? v3.journal.value : null);
61052
61153
  const meta3 = eastUiComponents.getSomeorUndefined(v3.columnMeta);
61154
+ const subject = eastUiComponents.getSomeorUndefined(v3.subject) ?? SUBJECT_DEFAULT;
61053
61155
  const readonly = eastUiComponents.getSomeorUndefined(v3.readonly) ?? false;
61054
61156
  const configsBind = useBindingValue(v3.configs);
61055
61157
  const configs = React.useMemo(() => configsBind.value ?? [], [configsBind.value]);
@@ -61186,16 +61288,16 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
61186
61288
  const nRows = (filteredRows == null ? void 0 : filteredRows.length) ?? 0;
61187
61289
  const helpVars = React.useMemo(() => {
61188
61290
  const rc = ranConfig ?? config2;
61189
- const labelOf = (col) => {
61291
+ const labelOf2 = (col) => {
61190
61292
  var _a3;
61191
61293
  return col ? eastUiComponents.getSomeorUndefined((_a3 = meta3 == null ? void 0 : meta3.get(col)) == null ? void 0 : _a3.label) ?? col : "";
61192
61294
  };
61193
- return { treatment: labelOf(rc == null ? void 0 : rc.treatment), outcome: labelOf(rc == null ? void 0 : rc.outcome), subject: SUBJECT_ONE, subjects: SUBJECT_MANY };
61194
- }, [ranConfig, config2, meta3]);
61295
+ return { treatment: labelOf2(rc == null ? void 0 : rc.treatment), outcome: labelOf2(rc == null ? void 0 : rc.outcome), subject: subject.one, subjects: subject.many };
61296
+ }, [ranConfig, config2, meta3, subject]);
61195
61297
  const view = React.useMemo(() => {
61196
61298
  if (!config2) return null;
61197
- return deriveView(config2, ranConfig ?? config2, columns, shownResult, journalBind.value, meta3, nRows, now2);
61198
- }, [config2, ranConfig, columns, shownResult, journalBind.value, meta3, nRows, now2]);
61299
+ return deriveView(config2, ranConfig ?? config2, columns, shownResult, journalBind.value, meta3, nRows, now2, subject);
61300
+ }, [config2, ranConfig, columns, shownResult, journalBind.value, meta3, nRows, now2, subject]);
61199
61301
  const designConfigValue = React.useMemo(() => ({
61200
61302
  alpha: east.none,
61201
61303
  target_power: east.none,
@@ -61250,16 +61352,18 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
61250
61352
  const showResult = shownResult !== null && !experiment.pending && !failed;
61251
61353
  const anyPrecomputedDesign = configs.some((c2) => c2.design.type === "some");
61252
61354
  const showValidate = hasDesign || anyPrecomputedDesign;
61253
- const tabKeys = [...["answer", "trust", "dose"], ...showValidate ? ["validate"] : []];
61355
+ const showDose = eastUiComponents.getSomeorUndefined(config2.dose_feature) !== void 0 || (shownResult == null ? void 0 : shownResult.dose_response.type) === "some";
61356
+ const tabKeys = [...["answer", "trust"], ...showDose ? ["dose"] : [], ...showValidate ? ["validate"] : []];
61357
+ const activeTab = tabKeys.includes(tab) ? tab : "answer";
61254
61358
  return /* @__PURE__ */ jsxRuntime.jsx(GuidanceProvider, { on: guidance, vars: helpVars, children: /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "surface.frameless", overflow: "visible", children: [
61255
61359
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "header.bar", display: "flex", alignItems: "center", gap: "3.5", children: [
61256
61360
  configs.length > 1 ? /* @__PURE__ */ jsxRuntime.jsxs(react.Menu.Root, { children: [
61257
61361
  /* @__PURE__ */ jsxRuntime.jsx(react.Menu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { as: "button", bg: "transparent", border: "0", p: "0", cursor: "pointer", display: "inline-flex", alignItems: "center", gap: "2", textAlign: "start", children: [
61258
61362
  /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "title.card", color: "fg.muted", children: [
61259
61363
  "Does ",
61260
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "brand.solid", fontWeight: "bold", children: vs.treatment }),
61364
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "brand.solid", fontWeight: "bold", children: vs.treatmentLabel }),
61261
61365
  " change ",
61262
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "brand.solid", fontWeight: "bold", children: vs.outcome }),
61366
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "brand.solid", fontWeight: "bold", children: vs.outcomeLabel }),
61263
61367
  "?"
61264
61368
  ] }),
61265
61369
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", color: "fg.subtle", fontSize: "11px", lineHeight: "1", children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faChevronDown }) })
@@ -61273,9 +61377,9 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
61273
61377
  ] }, g2.label)) }) }) })
61274
61378
  ] }) : /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "title.card", color: "fg.muted", children: /* @__PURE__ */ jsxRuntime.jsxs(Help, { id: "header", children: [
61275
61379
  "Does ",
61276
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "brand.solid", fontWeight: "bold", children: vs.treatment }),
61380
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "brand.solid", fontWeight: "bold", children: vs.treatmentLabel }),
61277
61381
  " change ",
61278
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "brand.solid", fontWeight: "bold", children: vs.outcome }),
61382
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "brand.solid", fontWeight: "bold", children: vs.outcomeLabel }),
61279
61383
  "?"
61280
61384
  ] }) }),
61281
61385
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { flex: "1" }),
@@ -61315,7 +61419,7 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
61315
61419
  "add another"
61316
61420
  ] }) })
61317
61421
  ] }) }),
61318
- /* @__PURE__ */ jsxRuntime.jsx(Step, { n: 4, title: `Which ${SUBJECT_MANY}?`, help: "step_population", children: /* @__PURE__ */ jsxRuntime.jsx(FilterRail, { fields, population, onChange: editPopulation, chip, button, readonly }) }),
61422
+ /* @__PURE__ */ jsxRuntime.jsx(Step, { n: 4, title: `Which ${subject.many}?`, help: "step_population", children: /* @__PURE__ */ jsxRuntime.jsx(FilterRail, { fields, population, onChange: editPopulation, chip, button, readonly, subjectMany: subject.many }) }),
61319
61423
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { as: "details", borderTopWidth: "1px", borderColor: "border.subtle", children: [
61320
61424
  /* @__PURE__ */ jsxRuntime.jsxs(
61321
61425
  react.Box,
@@ -61366,7 +61470,7 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
61366
61470
  "aria-label": "Result views",
61367
61471
  onKeyDown: (e3) => {
61368
61472
  var _a3;
61369
- const i = tabKeys.indexOf(tab);
61473
+ const i = tabKeys.indexOf(activeTab);
61370
61474
  let j2 = i;
61371
61475
  if (e3.key === "ArrowRight") j2 = (i + 1) % tabKeys.length;
61372
61476
  else if (e3.key === "ArrowLeft") j2 = (i - 1 + tabKeys.length) % tabKeys.length;
@@ -61380,9 +61484,9 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
61380
61484
  },
61381
61485
  children: [
61382
61486
  tabKeys.map((tk) => {
61383
- const on3 = tab === tk;
61487
+ const on3 = activeTab === tk;
61384
61488
  const tabHelp = tk === "answer" ? "tab_answer" : tk === "trust" ? "tab_trust" : tk === "dose" ? "tab_dose" : "tab_validate";
61385
- const tabLabel = tk === "answer" ? "Answer" : tk === "trust" ? "Trust" : tk === "dose" ? "Dose" : "Validate";
61489
+ const tabLabel = tk === "answer" ? "The answer" : tk === "trust" ? "Can we trust it?" : tk === "dose" ? "How much?" : "Prove it";
61386
61490
  return /* @__PURE__ */ jsxRuntime.jsx(
61387
61491
  react.Box,
61388
61492
  {
@@ -61414,7 +61518,7 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
61414
61518
  ]
61415
61519
  }
61416
61520
  ),
61417
- failed ? /* @__PURE__ */ jsxRuntime.jsx(react.Box, { px: "4.5", pt: "4.5", children: /* @__PURE__ */ jsxRuntime.jsx(RunError, { error: experiment.error }) }) : !showResult ? /* @__PURE__ */ jsxRuntime.jsx(DeckSkeleton, { tab }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
61521
+ failed ? /* @__PURE__ */ jsxRuntime.jsx(react.Box, { px: "4.5", pt: "4.5", children: /* @__PURE__ */ jsxRuntime.jsx(RunError, { error: experiment.error }) }) : !showResult ? /* @__PURE__ */ jsxRuntime.jsx(DeckSkeleton, { tab: activeTab }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
61418
61522
  stale && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "banner.stale", display: "flex", alignItems: "center", gap: "2", mx: "4.5", mt: "4.5", children: [
61419
61523
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", color: "fg.warning", flexShrink: "0", fontSize: "12px", children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation }) }),
61420
61524
  /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "body.sm", color: "fg.default", children: [
@@ -61422,9 +61526,22 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
61422
61526
  " Hit Run to update these results for your edits."
61423
61527
  ] })
61424
61528
  ] }),
61425
- tab === "answer" && a2 && /* @__PURE__ */ jsxRuntime.jsx(AnswerNumeric, { a: a2, verdict, higherBetter, badge, barList }),
61426
- tab === "answer" && !a2 && ref && /* @__PURE__ */ jsxRuntime.jsx(RefusalZone, { refusal: ref, overlap: ov, naiveValue: (shownResult == null ? void 0 : shownResult.naive) ?? 0, outcome: vs.outcome }),
61427
- tab === "trust" && vr && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
61529
+ activeTab === "answer" && a2 && /* @__PURE__ */ jsxRuntime.jsx(
61530
+ AnswerNumeric,
61531
+ {
61532
+ a: a2,
61533
+ verdict,
61534
+ narrative: view.narrative,
61535
+ checks: (vr == null ? void 0 : vr.checks) ?? null,
61536
+ onGoTrust: () => setTab("trust"),
61537
+ subjectMany: subject.many,
61538
+ higherBetter,
61539
+ badge,
61540
+ barList
61541
+ }
61542
+ ),
61543
+ activeTab === "answer" && !a2 && ref && /* @__PURE__ */ jsxRuntime.jsx(RefusalZone, { refusal: ref, overlap: ov, naiveValue: (shownResult == null ? void 0 : shownResult.naive) ?? 0, outcome: vs.outcomeLabel }),
61544
+ activeTab === "trust" && vr && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
61428
61545
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", color: "fg.muted", mb: "3.5", children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "trust_intro", children: "Before trusting the answer we tried to break it — colour shows pass / caution." }) }),
61429
61546
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { layerStyle: "frame.flat", children: vr.checks.map((c2, i) => {
61430
61547
  const cs = statusR({ status: c2.passed ? "success" : "warning", size: "sm" });
@@ -61442,10 +61559,34 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
61442
61559
  }) }),
61443
61560
  vr.sens && /* @__PURE__ */ jsxRuntime.jsxs(Card, { children: [
61444
61561
  /* @__PURE__ */ jsxRuntime.jsx(Cap, { help: "sensitivity", children: "Effect as a hidden cause is made stronger" }),
61445
- /* @__PURE__ */ jsxRuntime.jsx(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 })
61562
+ /* @__PURE__ */ jsxRuntime.jsx(
61563
+ AreaRange,
61564
+ {
61565
+ lo: vr.sens.lo,
61566
+ mid: vr.sens.mid,
61567
+ hi: vr.sens.hi,
61568
+ zero: 0,
61569
+ tone: "brand",
61570
+ xTicks: vr.sens.xTicks,
61571
+ yTicks: vr.sens.yTicks,
61572
+ marks: vr.sens.marks,
61573
+ height: vr.sens.marks.length ? 148 : 132
61574
+ }
61575
+ )
61446
61576
  ] })
61447
61577
  ] }),
61448
- tab === "trust" && !vr && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
61578
+ activeTab === "trust" && !vr && // Two distinct reasons the checklist is absent: the engine REFUSED
61579
+ // (no adjusted estimate to break) vs. the answer landed WITHOUT a
61580
+ // robustness summary (e.g. a precomputed result whose stress tests
61581
+ // were never run). Conflating them asserted a refusal that didn't
61582
+ // happen.
61583
+ ((shownResult == null ? void 0 : shownResult.adjusted.type) === "some" ? /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
61584
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "inline-flex", alignItems: "center", gap: "2", mb: "2", color: "fg.muted", children: [
61585
+ /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation, style: { fontSize: "13px" } }),
61586
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.default", children: "Stress tests not run" })
61587
+ ] }),
61588
+ /* @__PURE__ */ jsxRuntime.jsx(react.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." })
61589
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
61449
61590
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "inline-flex", alignItems: "center", gap: "2", mb: "2", color: "fg.muted", children: [
61450
61591
  /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation, style: { fontSize: "13px" } }),
61451
61592
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.default", children: "Nothing to stress-test" })
@@ -61455,8 +61596,9 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
61455
61596
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "semibold", color: "fg.default", children: "Answer" }),
61456
61597
  " tab for why)."
61457
61598
  ] })
61458
- ] }),
61459
- tab === "dose" && vd && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
61599
+ ] })),
61600
+ activeTab === "dose" && vd && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
61601
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", color: "fg.muted", mb: "3.5", children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "tab_dose", children: vd.framing }) }),
61460
61602
  /* @__PURE__ */ jsxRuntime.jsxs(Card, { mt: "0", children: [
61461
61603
  /* @__PURE__ */ jsxRuntime.jsxs(Cap, { help: "dose_curve", children: [
61462
61604
  vd.outcome,
@@ -61493,7 +61635,27 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
61493
61635
  ] })
61494
61636
  ] })
61495
61637
  ] }),
61496
- tab === "validate" && (design.status === "failed" && design.error ? /* @__PURE__ */ jsxRuntime.jsx(react.Box, { px: "4.5", pt: "4.5", children: /* @__PURE__ */ jsxRuntime.jsx(RunError, { error: design.error }) }) : designFresh && vmDesign ? /* @__PURE__ */ jsxRuntime.jsx(ValidatePanel, { vm: vmDesign, barList }) : /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", display: "inline-flex", alignItems: "center", gap: "2", color: "fg.muted", children: [
61638
+ activeTab === "dose" && !vd && // The tab is reachable (a dose feature is configured) but the shown
61639
+ // result has no curve — e.g. a refusal verdict, or the engine
61640
+ // returned no dose-response for this setup. Say so; never a blank pane.
61641
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
61642
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "inline-flex", alignItems: "center", gap: "2", mb: "2", color: "fg.muted", children: [
61643
+ /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation, style: { fontSize: "13px" } }),
61644
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.default", children: "No dose-response to show" })
61645
+ ] }),
61646
+ (shownResult == null ? void 0 : shownResult.adjusted.type) === "some" ? /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", color: "fg.muted", lineHeight: "1.5", children: "The experiment didn’t return a how-much curve for this setup." }) : /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "body.sm", color: "fg.muted", lineHeight: "1.5", children: [
61647
+ "A how-much curve needs a like-for-like estimate, and the experiment couldn’t produce one (see the ",
61648
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "semibold", color: "fg.default", children: "Answer" }),
61649
+ " tab for why)."
61650
+ ] })
61651
+ ] }),
61652
+ activeTab === "validate" && (design.status === "failed" && design.error ? /* @__PURE__ */ jsxRuntime.jsx(react.Box, { px: "4.5", pt: "4.5", children: /* @__PURE__ */ jsxRuntime.jsx(RunError, { error: design.error }) }) : designFresh && vmDesign ? /* @__PURE__ */ jsxRuntime.jsx(ValidatePanel, { vm: vmDesign, barList }) : !hasDesign && precomputedDesign === null ? /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
61653
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "inline-flex", alignItems: "center", gap: "2", mb: "2", color: "fg.muted", children: [
61654
+ /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation, style: { fontSize: "13px" } }),
61655
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.default", children: "No trial recipe for this question" })
61656
+ ] }),
61657
+ /* @__PURE__ */ jsxRuntime.jsx(react.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." })
61658
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", display: "inline-flex", alignItems: "center", gap: "2", color: "fg.muted", children: [
61497
61659
  /* @__PURE__ */ jsxRuntime.jsx(react.Spinner, { size: "xs", borderWidth: "1.5px", color: "brand.solid" }),
61498
61660
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", children: "Sizing the trial that would confirm this…" })
61499
61661
  ] }))
@@ -61503,11 +61665,11 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
61503
61665
  journal && journal.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
61504
61666
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: es.root, bg: "bg.canvas", borderTopWidth: "1px", borderColor: "border.subtle", children: [
61505
61667
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: es.lbl, children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "journal", children: "Committed experiments" }) }),
61506
- /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: es.meta, children: [
61668
+ /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: es.meta, children: /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { as: "span", children: [
61507
61669
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: journal.length }),
61508
61670
  " on record",
61509
61671
  journal.length > 50 ? " · showing newest 50" : ""
61510
- ] })
61672
+ ] }) })
61511
61673
  ] }),
61512
61674
  journal.slice(0, 50).map((r2, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "grid", gridTemplateColumns: "2fr 1fr 1fr 1fr", gap: "3", alignItems: "center", px: "4.5", py: "2.5", borderTopWidth: "1px", borderColor: "border.subtle", children: [
61513
61675
  /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "body.sm", children: [
@@ -61546,24 +61708,31 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
61546
61708
  ] })
61547
61709
  ] }) });
61548
61710
  }, (prev2, next2) => experimentValueEqual(prev2.value, next2.value) && prev2.storageKey === next2.storageKey);
61549
- function AnswerNumeric({ a: a2, verdict, higherBetter, badge, barList }) {
61711
+ function AnswerNumeric({ a: a2, verdict, narrative, checks, onGoTrust, subjectMany, higherBetter, badge, barList }) {
61550
61712
  const dirUp = a2.effect > 0;
61551
61713
  const statusWord = higherBetter === void 0 ? dirUp ? "Higher" : "Lower" : dirUp === higherBetter ? "Better" : "Worse";
61552
61714
  const lowerWord = a2.naive < 0 ? "lower" : "higher";
61553
- const top = a2.balance[0] ?? { col: "", display: "" };
61715
+ const top = a2.balance[0] ?? { label: "", display: "" };
61554
61716
  const kpiColor = a2.clear && !a2.cautious ? "fg.success" : "fg.warning";
61555
61717
  const badgeOk = a2.clear && !a2.cautious;
61556
61718
  const badgeText = a2.cautious ? (verdict == null ? void 0 : verdict.label) ?? "Not trustworthy yet" : a2.clear ? `${statusWord} with ${a2.treatment}` : "No clear effect";
61557
61719
  return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
61720
+ narrative && /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.md", color: "fg.default", lineHeight: "1.6", mb: "3.5", children: narrative.segments.map((s2, i) => s2.strong ? /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "bold", color: toneToken(narrative.tone), children: s2.text }, i) : /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", children: s2.text }, i)) }),
61558
61721
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "center", gap: "4.5", flexWrap: "wrap", children: [
61559
61722
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", flexDirection: "column", gap: "0.5", children: [
61560
61723
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono.sm", color: "fg.muted", children: a2.outcome }),
61561
61724
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "baseline", gap: "2.5", children: [
61562
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono-kpi", fontFamily: "heading", fontSize: "32px", color: kpiColor, children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "answer_effect", children: signed(a2.effect) }) }),
61725
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono-kpi", fontFamily: "heading", fontSize: "32px", color: kpiColor, children: /* @__PURE__ */ jsxRuntime.jsxs(Help, { id: "answer_effect", children: [
61726
+ signed(a2.effect),
61727
+ a2.unit ? /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { as: "span", fontSize: "16px", color: "fg.muted", children: [
61728
+ " ",
61729
+ a2.unit
61730
+ ] }) : null
61731
+ ] }) }),
61563
61732
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono.sm", color: "fg.muted", children: /* @__PURE__ */ jsxRuntime.jsxs(Help, { id: "answer_ci", children: [
61564
- "95% CI  ",
61733
+ "likely between ",
61565
61734
  signed(a2.lo),
61566
- " ",
61735
+ " and ",
61567
61736
  signed(a2.hi)
61568
61737
  ] }) })
61569
61738
  ] })
@@ -61573,6 +61742,42 @@ function AnswerNumeric({ a: a2, verdict, higherBetter, badge, barList }) {
61573
61742
  /* @__PURE__ */ jsxRuntime.jsx(Help, { id: `verdict_${(verdict == null ? void 0 : verdict.tag) ?? "modest"}`, children: badgeText })
61574
61743
  ] })
61575
61744
  ] }),
61745
+ checks && checks.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
61746
+ react.Box,
61747
+ {
61748
+ as: "button",
61749
+ onClick: onGoTrust,
61750
+ display: "inline-flex",
61751
+ alignItems: "center",
61752
+ gap: "2",
61753
+ flexWrap: "wrap",
61754
+ mt: "2.5",
61755
+ bg: "transparent",
61756
+ border: "0",
61757
+ p: "0",
61758
+ cursor: "pointer",
61759
+ title: "See the full stress-test checklist",
61760
+ children: [
61761
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption", color: "fg.muted", children: "Stress tests:" }),
61762
+ checks.map((c2, i) => /* @__PURE__ */ jsxRuntime.jsxs(
61763
+ react.Box,
61764
+ {
61765
+ as: "span",
61766
+ display: "inline-flex",
61767
+ alignItems: "center",
61768
+ gap: "1.5",
61769
+ color: c2.passed ? "fg.success" : "fg.warning",
61770
+ children: [
61771
+ /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: c2.passed ? faCheck : faTriangleExclamation, style: { fontSize: "10px" } }),
61772
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", textStyle: "caption", color: "fg.default", children: c2.short }),
61773
+ i < checks.length - 1 && /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", textStyle: "caption", color: "fg.subtle", children: "·" })
61774
+ ]
61775
+ },
61776
+ i
61777
+ ))
61778
+ ]
61779
+ }
61780
+ ),
61576
61781
  a2.cautious && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "banner.stale", display: "flex", alignItems: "flex-start", gap: "2", mt: "3", children: [
61577
61782
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", color: "fg.warning", flexShrink: "0", mt: "0.5", fontSize: "12px", children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation }) }),
61578
61783
  /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "body.sm", color: "fg.default", children: [
@@ -61586,15 +61791,15 @@ function AnswerNumeric({ a: a2, verdict, higherBetter, badge, barList }) {
61586
61791
  /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "body.sm", color: "fg.default", children: [
61587
61792
  /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "answer_flip", children: /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "bold", children: "Raw and like-for-like disagree." }) }),
61588
61793
  " In the plain average, the ",
61589
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", children: a2.treatment }),
61794
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "semibold", children: a2.treatment }),
61590
61795
  " group sits ",
61591
61796
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontStyle: "italic", children: lowerWord }),
61592
61797
  " on ",
61593
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", children: a2.outcome }),
61798
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "semibold", children: a2.outcome }),
61594
61799
  " (",
61595
61800
  signed(a2.naive),
61596
61801
  ") — but they also differ most on ",
61597
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", children: top.col }),
61802
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "semibold", children: top.label }),
61598
61803
  " (",
61599
61804
  top.display,
61600
61805
  "). Adjusting for it reverses the result."
@@ -61617,11 +61822,40 @@ function AnswerNumeric({ a: a2, verdict, higherBetter, badge, barList }) {
61617
61822
  }
61618
61823
  )
61619
61824
  ] }),
61620
- /* @__PURE__ */ jsxRuntime.jsxs(Card, { children: [
61621
- /* @__PURE__ */ jsxRuntime.jsx(Cap, { help: "balance", children: "How unbalanced each one was — before adjusting" }),
61622
- /* @__PURE__ */ jsxRuntime.jsx(react.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 }))) })
61825
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { as: "details", layerStyle: "card", borderRadius: "lg", mt: "3", children: [
61826
+ /* @__PURE__ */ jsxRuntime.jsxs(
61827
+ react.Box,
61828
+ {
61829
+ as: "summary",
61830
+ textStyle: "caption.eyebrow",
61831
+ fontSize: "9px",
61832
+ cursor: "pointer",
61833
+ p: "3.5",
61834
+ display: "flex",
61835
+ alignItems: "center",
61836
+ gap: "1.5",
61837
+ css: { listStyle: "none", "&::-webkit-details-marker": { display: "none" } },
61838
+ children: [
61839
+ /* @__PURE__ */ jsxRuntime.jsx(
61840
+ react.Box,
61841
+ {
61842
+ as: "span",
61843
+ display: "inline-flex",
61844
+ color: "fg.subtle",
61845
+ fontSize: "10px",
61846
+ transition: "transform 180ms ease",
61847
+ transform: "rotate(-90deg)",
61848
+ css: { "details[open] > summary > &": { transform: "rotate(0deg)" } },
61849
+ children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faChevronDown })
61850
+ }
61851
+ ),
61852
+ /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "balance", children: "How we levelled the playing field" })
61853
+ ]
61854
+ }
61855
+ ),
61856
+ /* @__PURE__ */ jsxRuntime.jsx(react.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 }))) })
61623
61857
  ] }),
61624
- /* @__PURE__ */ jsxRuntime.jsx(react.Box, { mt: "3.5", children: /* @__PURE__ */ jsxRuntime.jsx(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__ */ jsxRuntime.jsxs(react.Text, { textStyle: "mono.sm", color: "fg.muted", children: [
61858
+ /* @__PURE__ */ jsxRuntime.jsx(react.Box, { mt: "3.5", children: /* @__PURE__ */ jsxRuntime.jsx(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__ */ jsxRuntime.jsxs(react.Text, { textStyle: "mono.sm", color: "fg.muted", children: [
61625
61859
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: Number(n2) }),
61626
61860
  " ",
61627
61861
  label
@@ -61725,7 +61959,7 @@ function ValidatePanel({ vm, barList }) {
61725
61959
  ] }, i)) })
61726
61960
  ] });
61727
61961
  }
61728
- function FilterRail({ fields, population, onChange: onChange2, chip, button, readonly }) {
61962
+ function FilterRail({ fields, population, onChange: onChange2, chip, button, readonly, subjectMany }) {
61729
61963
  const [open, setOpen] = React.useState(null);
61730
61964
  const replaceAt = (i, p2) => onChange2(population.map((f2, j2) => j2 === i ? p2 : f2));
61731
61965
  const removeAt = (i) => onChange2(population.filter((_2, j2) => j2 !== i));
@@ -61733,7 +61967,7 @@ function FilterRail({ fields, population, onChange: onChange2, chip, button, rea
61733
61967
  if (readonly) {
61734
61968
  return /* @__PURE__ */ jsxRuntime.jsx(react.Box, { display: "flex", flexWrap: "wrap", gap: "1.5", alignItems: "center", children: population.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: chip({ tone: "plain", numeric: true, shape: "rounded" }), children: [
61735
61969
  "all ",
61736
- SUBJECT_MANY
61970
+ subjectMany
61737
61971
  ] }) : population.map((pred, i) => /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: chip({ tone: "brand", numeric: true, shape: "rounded" }), flexShrink: 0, children: /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", whiteSpace: "nowrap", children: eastUiComponents.formatPredicate(pred) }) }, i)) });
61738
61972
  }
61739
61973
  return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", flexWrap: "wrap", gap: "1.5", alignItems: "center", children: [