@validation-os/dashboard 0.15.3 → 0.15.5

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
@@ -9,14 +9,14 @@ var REGISTER_LABEL = {
9
9
  // One container-agnostic lens now Goal is unified into Experiment (OPS-1299 /
10
10
  // OPS-1287 story 22): all plans browse in one place under this label.
11
11
  experiments: "Evidence plans",
12
- readings: "Readings",
12
+ readings: "Evidence",
13
13
  decisions: "Decisions",
14
14
  glossary: "Glossary"
15
15
  };
16
16
  var REGISTER_SINGULAR = {
17
17
  assumptions: "Assumption",
18
18
  experiments: "Evidence plan",
19
- readings: "Reading",
19
+ readings: "Evidence",
20
20
  decisions: "Decision",
21
21
  glossary: "Glossary term"
22
22
  };
@@ -37,7 +37,7 @@ var REGISTER_SUBTITLE = {
37
37
  var REGISTER_ICON = {
38
38
  assumptions: "\u25CE",
39
39
  experiments: "\u2697",
40
- readings: "\u25A4",
40
+ readings: "\u2726",
41
41
  decisions: "\xA7",
42
42
  glossary: "A"
43
43
  };
@@ -50,7 +50,7 @@ var REGISTER_GROUPS = [
50
50
  var WORKFLOW_NAV = [
51
51
  { route: "assumptions", label: "Assumptions", icon: "\u25CE", isDefault: true },
52
52
  { route: "experiments", label: "Experiments", icon: "\u2697" },
53
- { route: "readings", label: "Readings", icon: "\u25A4" }
53
+ { route: "readings", label: "Evidence", icon: "\u2726" }
54
54
  ];
55
55
 
56
56
  // src/assumption-detail.tsx
@@ -218,6 +218,47 @@ function buildEvidenceComposition(assumption, readings) {
218
218
  totalContribution: Math.round((total + Number.EPSILON) * 100) / 100
219
219
  };
220
220
  }
221
+ function readingContributions(assumption, readings) {
222
+ const id = str(assumption.id) ?? "";
223
+ const inputs = [];
224
+ for (const r of readings) {
225
+ const belief2 = readingBeliefFor(r, id);
226
+ if (!belief2) continue;
227
+ const result = str(belief2.Result) ?? "Inconclusive";
228
+ if (result === "Inconclusive") continue;
229
+ const rung = str(r.Rung) ?? "Talk";
230
+ inputs.push({
231
+ id: str(r.id) ?? "",
232
+ source: str(r.Source) ?? null,
233
+ rung,
234
+ result,
235
+ representativeness: Number(r.Representativeness) || 1,
236
+ credibility: Number(r.Credibility) || 1,
237
+ date: str(r.Date),
238
+ magnitudeBand: r.magnitudeBand,
239
+ experimentId: str(r.experimentId)
240
+ });
241
+ }
242
+ const winners = scoreAndDedupe(inputs);
243
+ const winnerIds = new Set(winners.map((w) => w.input.id));
244
+ const rungsPresent = new Set(winners.map((x) => x.input.rung));
245
+ let den = 0;
246
+ for (const rung of rungsPresent) den += w0ForRung(rung);
247
+ for (const x of winners) den += x.weight;
248
+ return inputs.map((input) => {
249
+ const w = winners.find((x) => x.input.id === input.id);
250
+ const used = winnerIds.has(input.id);
251
+ const strength = w ? w.strength : (input.result === "Validated" ? 1 : input.result === "Invalidated" ? -1 : 0) * (RUNG_CAPS[input.rung] ?? 0);
252
+ const weight = w ? w.weight : 0;
253
+ return {
254
+ id: input.id,
255
+ contribution: w && den > 0 ? Math.round(w.weight * w.strength / den * 100) / 100 : 0,
256
+ weight,
257
+ strength,
258
+ used
259
+ };
260
+ });
261
+ }
221
262
 
222
263
  // src/confidence-explainer.ts
223
264
  import { RUNG_ANCHOR, W0_BY_RUNG } from "@validation-os/core/derivation";
@@ -1004,10 +1045,26 @@ function readingBeliefVerdicts(reading, assumptions = []) {
1004
1045
  linked: hit != null,
1005
1046
  result: b.Result ?? null,
1006
1047
  strength,
1007
- justification: typeof b["Grading justification"] === "string" ? b["Grading justification"] : ""
1048
+ justification: typeof b["Grading justification"] === "string" ? b["Grading justification"] : "",
1049
+ excerpt: typeof b.excerpt === "string" && b.excerpt !== "" ? b.excerpt : snippetFromBody(String(reading.body ?? ""), b.assumptionId)
1008
1050
  };
1009
1051
  });
1010
1052
  }
1053
+ function snippetFromBody(body, cue) {
1054
+ if (!body) return "";
1055
+ const quoteMatch = body.match(/## Quote\n+([\s\S]*?)(?=\n## |\n##$|$)/i);
1056
+ if (quoteMatch) {
1057
+ const q = quoteMatch[1].trim();
1058
+ return q.length > 220 ? q.slice(0, 217).trim() + "\u2026" : q;
1059
+ }
1060
+ const sentences = body.split(/(?<=[.!?])\s+/);
1061
+ const cueLower = cue.toLowerCase();
1062
+ for (const s of sentences) {
1063
+ if (s.toLowerCase().includes(cueLower)) return s.trim();
1064
+ }
1065
+ const first = sentences[0]?.trim() ?? "";
1066
+ return first.length > 220 ? first.slice(0, 217).trim() + "\u2026" : first;
1067
+ }
1011
1068
  function readingBeliefSummary(reading, assumptions = []) {
1012
1069
  const verdicts = readingBeliefVerdicts(reading, assumptions);
1013
1070
  let validated = 0;
@@ -1554,64 +1611,118 @@ function AssumptionDetail({
1554
1611
  );
1555
1612
  })
1556
1613
  ] }) : null,
1557
- linkedReadings.length > 0 ? /* @__PURE__ */ jsxs4("div", { className: "vos-card vos-detail-section", children: [
1558
- /* @__PURE__ */ jsxs4("div", { className: "vos-detail-section-label", children: [
1559
- "Evidence \xB7 ",
1560
- linkedReadings.length,
1561
- " reading",
1562
- linkedReadings.length === 1 ? "" : "s"
1563
- ] }),
1564
- linkedReadings.map((r) => {
1565
- const id = String(r.id ?? "");
1566
- const belief2 = readingBeliefFor(r, assumptionId);
1567
- if (!belief2) return null;
1568
- const result = String(belief2.Result ?? "Inconclusive");
1569
- const justification = String(belief2["Grading justification"] ?? "");
1570
- const rung = String(r.Rung ?? "");
1571
- const source = String(r.Source ?? "");
1572
- const expId = r.experimentId ? String(r.experimentId) : null;
1573
- return /* @__PURE__ */ jsxs4(
1574
- "button",
1575
- {
1576
- type: "button",
1577
- className: `vos-linked-row vos-linked-reading vos-verdict-${verdictTone(result)}`,
1578
- onClick: () => onNavigate({ name: "reading", id }),
1579
- children: [
1580
- /* @__PURE__ */ jsxs4("div", { className: "vos-reading-head", children: [
1581
- /* @__PURE__ */ jsx4("span", { className: "vos-reading-date vos-num", children: String(r.Date ?? "") }),
1582
- /* @__PURE__ */ jsx4("span", { className: "vos-reading-title", children: String(r.Title ?? id) }),
1583
- /* @__PURE__ */ jsx4("span", { className: `vos-pill vos-pill-${verdictTone(result)}`, children: result }),
1584
- /* @__PURE__ */ jsx4("span", { className: "vos-rung-tag", children: rung })
1585
- ] }),
1586
- justification ? /* @__PURE__ */ jsxs4("div", { className: `vos-belief-rationale vos-verdict-border-${verdictTone(result)}`, children: [
1587
- /* @__PURE__ */ jsx4("span", { className: "vos-belief-rationale-label", children: "grading rationale:" }),
1588
- justification
1589
- ] }) : null,
1590
- /* @__PURE__ */ jsxs4("div", { className: "vos-reading-source", children: [
1591
- source,
1592
- expId ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
1593
- " \xB7 from ",
1594
- /* @__PURE__ */ jsx4(
1595
- "span",
1596
- {
1597
- className: "vos-link",
1598
- onClick: (ev) => {
1599
- ev.stopPropagation();
1600
- onNavigate({ name: "experiment", id: expId });
1601
- },
1602
- children: expId
1603
- }
1604
- )
1605
- ] }) : null
1606
- ] })
1607
- ]
1608
- },
1609
- id
1610
- );
1611
- })
1612
- ] }) : null
1614
+ /* @__PURE__ */ jsx4(
1615
+ EvidenceList,
1616
+ {
1617
+ assumptionId,
1618
+ readings: readings.records ?? [],
1619
+ onNavigate
1620
+ }
1621
+ )
1622
+ ] });
1623
+ }
1624
+ function EvidenceList({
1625
+ assumptionId,
1626
+ readings,
1627
+ onNavigate
1628
+ }) {
1629
+ const linkedReadings = readings.filter((r) => readingBeliefs(r).some((b) => b.assumptionId === assumptionId)).sort((a, b) => String(b.Date ?? "").localeCompare(String(a.Date ?? "")));
1630
+ const contribById = useMemo(() => {
1631
+ const rec = readings.find((a) => String(a.id) === assumptionId);
1632
+ if (!rec) return /* @__PURE__ */ new Map();
1633
+ const rows = readingContributions(rec, readings);
1634
+ return new Map(rows.map((r) => [r.id, r]));
1635
+ }, [readings, assumptionId]);
1636
+ if (linkedReadings.length === 0) return null;
1637
+ return /* @__PURE__ */ jsxs4("div", { className: "vos-card vos-detail-section", children: [
1638
+ /* @__PURE__ */ jsxs4("div", { className: "vos-detail-section-label", children: [
1639
+ "Evidence \xB7 ",
1640
+ linkedReadings.length,
1641
+ " piece",
1642
+ linkedReadings.length === 1 ? "" : "s",
1643
+ " of evidence"
1644
+ ] }),
1645
+ linkedReadings.map((r) => {
1646
+ const id = String(r.id ?? "");
1647
+ const belief2 = readingBeliefFor(r, assumptionId);
1648
+ if (!belief2) return null;
1649
+ const result = String(belief2.Result ?? "Inconclusive");
1650
+ const justification = String(belief2["Grading justification"] ?? "");
1651
+ const excerpt = typeof belief2.excerpt === "string" && belief2.excerpt !== "" ? belief2.excerpt : snippetFromBody2(String(r.body ?? ""), assumptionId);
1652
+ const rung = String(r.Rung ?? "");
1653
+ const source = String(r.Source ?? "");
1654
+ const expId = r.experimentId ? String(r.experimentId) : null;
1655
+ const c = contribById.get(id);
1656
+ return /* @__PURE__ */ jsxs4(
1657
+ "button",
1658
+ {
1659
+ type: "button",
1660
+ className: `vos-evidence-row vos-verdict-${verdictTone(result)}`,
1661
+ onClick: () => onNavigate({ name: "reading", id }),
1662
+ children: [
1663
+ /* @__PURE__ */ jsxs4("div", { className: "vos-evidence-row-head", children: [
1664
+ /* @__PURE__ */ jsx4("span", { className: "vos-evidence-date vos-num", children: String(r.Date ?? "") }),
1665
+ /* @__PURE__ */ jsx4("span", { className: "vos-evidence-title", children: String(r.Title ?? id) }),
1666
+ /* @__PURE__ */ jsx4("span", { className: `vos-pill vos-pill-${verdictTone(result)}`, children: result }),
1667
+ /* @__PURE__ */ jsx4("span", { className: "vos-rung-tag", children: rung }),
1668
+ c && c.used ? /* @__PURE__ */ jsxs4("span", { className: `vos-evidence-score vos-num vos-text-${contributionTone(c.contribution)}`, children: [
1669
+ formatSigned(c.contribution),
1670
+ " confidence"
1671
+ ] }) : null
1672
+ ] }),
1673
+ excerpt ? /* @__PURE__ */ jsxs4("div", { className: "vos-evidence-excerpt", children: [
1674
+ "\u201C",
1675
+ excerpt,
1676
+ "\u201D"
1677
+ ] }) : null,
1678
+ justification ? /* @__PURE__ */ jsxs4("div", { className: `vos-belief-rationale vos-verdict-border-${verdictTone(result)}`, children: [
1679
+ /* @__PURE__ */ jsx4("span", { className: "vos-belief-rationale-label", children: "grading rationale:" }),
1680
+ justification
1681
+ ] }) : null,
1682
+ /* @__PURE__ */ jsxs4("div", { className: "vos-evidence-source", children: [
1683
+ /* @__PURE__ */ jsx4("span", { className: "vos-evidence-source-link", title: source, children: source }),
1684
+ expId ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
1685
+ " \xB7 from experiment ",
1686
+ /* @__PURE__ */ jsx4(
1687
+ "span",
1688
+ {
1689
+ className: "vos-link",
1690
+ onClick: (ev) => {
1691
+ ev.stopPropagation();
1692
+ onNavigate({ name: "experiment", id: expId });
1693
+ },
1694
+ children: expId
1695
+ }
1696
+ )
1697
+ ] }) : null
1698
+ ] })
1699
+ ]
1700
+ },
1701
+ id
1702
+ );
1703
+ })
1613
1704
  ] });
1614
1705
  }
1706
+ function contributionTone(v) {
1707
+ if (v > 0) return "good";
1708
+ if (v < 0) return "crit";
1709
+ return "neutral";
1710
+ }
1711
+ function snippetFromBody2(body, cue) {
1712
+ if (!body) return "";
1713
+ const quoteMatch = body.match(/## Quote\n+([\s\S]*?)(?=\n## |\n##$|$)/i);
1714
+ if (quoteMatch) {
1715
+ const q = quoteMatch[1].trim();
1716
+ return q.length > 220 ? q.slice(0, 217).trim() + "\u2026" : q;
1717
+ }
1718
+ const sentences = body.split(/(?<=[.!?])\s+/);
1719
+ const cueLower = cue.toLowerCase();
1720
+ for (const s of sentences) {
1721
+ if (s.toLowerCase().includes(cueLower)) return s.trim();
1722
+ }
1723
+ const first = sentences[0]?.trim() ?? "";
1724
+ return first.length > 220 ? first.slice(0, 217).trim() + "\u2026" : first;
1725
+ }
1615
1726
  function ScoreCard({
1616
1727
  label,
1617
1728
  value,
@@ -1781,16 +1892,24 @@ function EvidenceCompositionView({
1781
1892
  " (adds to Confidence)"
1782
1893
  ] }),
1783
1894
  comp.rungs.length === 0 ? /* @__PURE__ */ jsx4("div", { className: "vos-muted", children: "No lens set \u2014 evidence composition needs a lens." }) : comp.rungs.map((r) => {
1784
- const pct2 = r.cap > 0 ? Math.min(100, Math.abs(r.contribution) / r.cap * 100) : 0;
1895
+ const abs = Math.abs(r.contribution);
1896
+ const pct2 = r.cap > 0 ? Math.min(100, abs / r.cap * 100) : 0;
1785
1897
  const isEmpty = r.count === 0;
1898
+ const tone = r.contribution > 0 ? "good" : r.contribution < 0 ? "crit" : "accent";
1786
1899
  return /* @__PURE__ */ jsxs4("div", { className: "vos-comp-row", children: [
1787
1900
  /* @__PURE__ */ jsx4("span", { className: `vos-comp-rung ${isEmpty ? "is-empty" : ""}`, children: r.rung }),
1788
- /* @__PURE__ */ jsx4("div", { className: "vos-comp-bar", children: /* @__PURE__ */ jsx4("i", { className: "vos-comp-fill", style: { width: `${pct2}%`, opacity: isEmpty ? 0.15 : 1 } }) }),
1789
- /* @__PURE__ */ jsx4("span", { className: "vos-comp-val vos-num", children: isEmpty ? "\u2014" : `${formatSigned(r.contribution)}/${r.cap}` }),
1901
+ /* @__PURE__ */ jsx4("div", { className: "vos-comp-bar", children: /* @__PURE__ */ jsx4("i", { className: `vos-comp-fill vos-fill-${tone}`, style: { width: `${isEmpty ? 0 : Math.max(4, pct2)}%` } }) }),
1902
+ /* @__PURE__ */ jsx4("span", { className: "vos-comp-val vos-num", children: isEmpty ? "\u2014" : /* @__PURE__ */ jsxs4(Fragment3, { children: [
1903
+ /* @__PURE__ */ jsx4("span", { className: `vos-text-${tone}`, children: formatSigned(r.contribution) }),
1904
+ /* @__PURE__ */ jsxs4("span", { style: { color: "var(--vos-faint)" }, children: [
1905
+ " \xB7 cap ",
1906
+ r.cap
1907
+ ] })
1908
+ ] }) }),
1790
1909
  /* @__PURE__ */ jsxs4("span", { className: "vos-comp-count vos-num", children: [
1791
1910
  r.count,
1792
- " src",
1793
- r.count === 1 ? "" : "s"
1911
+ " ",
1912
+ r.count === 1 ? "source" : "sources"
1794
1913
  ] })
1795
1914
  ] }, r.rung);
1796
1915
  })
@@ -2099,9 +2218,14 @@ function weekOverWeekDelta(assumptions, readings, now) {
2099
2218
  }
2100
2219
 
2101
2220
  // src/recommended-experiments.ts
2102
- var MAX_RECOMMENDED = 2;
2221
+ var MAX_RECOMMENDED = 3;
2103
2222
  var MAX_CLUSTER_SIZE = 3;
2104
- var MAX_NEEDS_FRAMING = 2;
2223
+ var MAX_NEEDS_FRAMING = 3;
2224
+ var LENS_COLOUR = {
2225
+ Consumer: "#8b83f5",
2226
+ Commercial: "#38c793",
2227
+ Investor: "#e6a23c"
2228
+ };
2105
2229
  function buildNeedsFraming(assumptions) {
2106
2230
  const live = assumptions.filter((a) => {
2107
2231
  const status = str(a.Status);
@@ -2116,9 +2240,17 @@ function buildNeedsFraming(assumptions) {
2116
2240
  const stage = str(a.Stage) ?? "\u2014";
2117
2241
  const title = str(a.Title) ?? id;
2118
2242
  const hint = framingHint(a, completeness);
2119
- return { id, title, risk, completeness, lens, stage, hint };
2120
- }).filter((a) => a.completeness < 100).sort((a, b) => b.risk - a.risk).slice(0, MAX_NEEDS_FRAMING);
2121
- return items;
2243
+ const lensColour = LENS_COLOUR[lens] ?? LENS_COLOUR["Investor"] ?? "#6b7484";
2244
+ return { id, title, risk, completeness, lens, stage, hint, lensColour };
2245
+ }).filter((a) => a.completeness < 100);
2246
+ const byLens = /* @__PURE__ */ new Map();
2247
+ for (const item of items) {
2248
+ const existing = byLens.get(item.lens);
2249
+ if (!existing || item.risk > existing.risk) {
2250
+ byLens.set(item.lens, item);
2251
+ }
2252
+ }
2253
+ return [...byLens.values()].sort((a, b) => b.risk - a.risk).slice(0, MAX_NEEDS_FRAMING);
2122
2254
  }
2123
2255
  function framingHint(a, completeness) {
2124
2256
  const hasScoring = Boolean(str(a["Scoring justification"]));
@@ -2159,22 +2291,28 @@ function buildRecommendedExperiments(assumptions, experiments) {
2159
2291
  if (bucket) bucket.push(a);
2160
2292
  else clusters.set(key, [a]);
2161
2293
  }
2162
- const clusterEntries = [...clusters.entries()].map(([key, cluster]) => {
2294
+ const rankedClusters = [...clusters.entries()].map(([key, cluster]) => {
2163
2295
  const ranked = cluster.sort((a, b) => (derivedNum(b, "risk") ?? 0) - (derivedNum(a, "risk") ?? 0)).slice(0, MAX_CLUSTER_SIZE);
2164
2296
  const maxRisk = Math.max(...ranked.map((a) => derivedNum(a, "risk") ?? 0), 0);
2165
2297
  return { key, cluster: ranked, maxRisk };
2166
- }).sort((a, b) => b.maxRisk - a.maxRisk).slice(0, MAX_RECOMMENDED);
2298
+ }).sort((a, b) => b.maxRisk - a.maxRisk);
2299
+ const byLens = /* @__PURE__ */ new Map();
2300
+ for (const entry of rankedClusters) {
2301
+ const lens = entry.key.split("\xD7")[0] ?? "\u2014";
2302
+ if (!byLens.has(lens)) byLens.set(lens, entry);
2303
+ }
2304
+ const clusterEntries = [...byLens.values()].sort((a, b) => b.maxRisk - a.maxRisk).slice(0, MAX_RECOMMENDED);
2167
2305
  const recs = [];
2168
2306
  for (const { key, cluster, maxRisk } of clusterEntries) {
2169
2307
  const lens = key.split("\xD7")[0] ?? "\u2014";
2170
2308
  const stage = key.split("\xD7")[1] ?? "\u2014";
2171
2309
  const assumptionIds = cluster.map((a) => str(a.id) ?? "").filter(Boolean).sort();
2172
2310
  const type = LENS_TO_TYPE[lens ?? ""] ?? "Desk research";
2173
- const titles = cluster.map((a) => str(a.Title) ?? str(a.id) ?? "");
2174
2311
  const title = cluster.length === 1 ? `Test ${assumptionIds[0]}` : `Test ${cluster.length} ${lens} \xB7 ${stage} beliefs`;
2175
2312
  const rationale = cluster.length === 1 ? `One belief (${assumptionIds[0]}) at ${Math.round(maxRisk)} risk with no live test. Designing an experiment here would buy down the most risk.` : `${cluster.length} beliefs share ${lens} \xB7 ${stage}, the riskiest at ${Math.round(maxRisk)} risk. One experiment can address them all.`;
2176
2313
  const barPreview = `The riskiest belief moves out of the kill zone, or stays in it.`;
2177
2314
  const body = generateExperimentBody(type, lens, stage, cluster, maxRisk);
2315
+ const lensColour = LENS_COLOUR[lens] ?? LENS_COLOUR["Investor"] ?? "#6b7484";
2178
2316
  recs.push({
2179
2317
  id: assumptionIds.join("+"),
2180
2318
  type,
@@ -2183,7 +2321,9 @@ function buildRecommendedExperiments(assumptions, experiments) {
2183
2321
  maxRisk,
2184
2322
  rationale,
2185
2323
  barPreview,
2186
- body
2324
+ body,
2325
+ lens,
2326
+ lensColour
2187
2327
  });
2188
2328
  }
2189
2329
  recs.sort(
@@ -2672,10 +2812,7 @@ function NextMovesSection({
2672
2812
  /* @__PURE__ */ jsx5("div", { className: "vos-next-moves-head", children: "Next moves" }),
2673
2813
  /* @__PURE__ */ jsxs5("div", { className: "vos-next-moves-cols", children: [
2674
2814
  /* @__PURE__ */ jsxs5("div", { className: "vos-card vos-next-moves-col", children: [
2675
- /* @__PURE__ */ jsxs5("div", { className: "vos-next-moves-col-label", children: [
2676
- "Needs framing \xB7 top ",
2677
- needsFraming.length
2678
- ] }),
2815
+ /* @__PURE__ */ jsx5("div", { className: "vos-next-moves-col-label", children: "Needs framing \xB7 one per lens" }),
2679
2816
  needsFraming.length === 0 ? /* @__PURE__ */ jsx5("div", { className: "vos-muted vos-next-moves-empty", children: "Every belief is fully framed." }) : needsFraming.map((a) => /* @__PURE__ */ jsxs5(
2680
2817
  "button",
2681
2818
  {
@@ -2684,13 +2821,24 @@ function NextMovesSection({
2684
2821
  onClick: () => onOpenAssumption(a.id),
2685
2822
  children: [
2686
2823
  /* @__PURE__ */ jsxs5("div", { className: "vos-next-moves-item-head", children: [
2687
- /* @__PURE__ */ jsx5("span", { className: "vos-next-moves-item-id vos-num", children: a.id }),
2824
+ /* @__PURE__ */ jsx5(
2825
+ "span",
2826
+ {
2827
+ className: "vos-next-moves-lens",
2828
+ style: { background: `${a.lensColour}20`, color: a.lensColour, borderColor: `${a.lensColour}40` },
2829
+ children: a.lens
2830
+ }
2831
+ ),
2688
2832
  /* @__PURE__ */ jsxs5("span", { className: `vos-next-moves-item-risk vos-num vos-text-${riskToneClass(a.risk)}`, children: [
2689
2833
  Math.round(a.risk),
2690
2834
  " risk"
2691
2835
  ] })
2692
2836
  ] }),
2693
- /* @__PURE__ */ jsx5("div", { className: "vos-next-moves-item-title", children: a.title }),
2837
+ /* @__PURE__ */ jsxs5("div", { className: "vos-next-moves-item-title", children: [
2838
+ /* @__PURE__ */ jsx5("span", { className: "vos-next-moves-item-id vos-num", children: a.id }),
2839
+ " \xB7 ",
2840
+ a.title
2841
+ ] }),
2694
2842
  /* @__PURE__ */ jsx5("div", { className: "vos-next-moves-item-hint", children: a.hint })
2695
2843
  ]
2696
2844
  },
@@ -2698,39 +2846,36 @@ function NextMovesSection({
2698
2846
  ))
2699
2847
  ] }),
2700
2848
  /* @__PURE__ */ jsxs5("div", { className: "vos-card vos-next-moves-col", children: [
2701
- /* @__PURE__ */ jsxs5("div", { className: "vos-next-moves-col-label", children: [
2702
- "Proposed experiments \xB7 top ",
2703
- recs.length
2704
- ] }),
2705
- recs.length === 0 ? /* @__PURE__ */ jsx5("div", { className: "vos-muted vos-next-moves-empty", children: "Every risk has a live test." }) : recs.map((rec) => /* @__PURE__ */ jsxs5("details", { className: "vos-pipe-rec vos-next-moves-rec", children: [
2706
- /* @__PURE__ */ jsxs5("summary", { className: "vos-pipe-rec-head", children: [
2707
- /* @__PURE__ */ jsx5("span", { className: "vos-pipe-rec-type", children: rec.type }),
2708
- /* @__PURE__ */ jsx5("span", { className: "vos-pipe-rec-title", children: rec.title }),
2709
- /* @__PURE__ */ jsxs5("span", { className: "vos-pipe-rec-risk vos-num", children: [
2849
+ /* @__PURE__ */ jsx5("div", { className: "vos-next-moves-col-label", children: "Proposed experiments \xB7 one per lens" }),
2850
+ recs.length === 0 ? /* @__PURE__ */ jsx5("div", { className: "vos-muted vos-next-moves-empty", children: "Every risk has a live test." }) : recs.map((rec) => /* @__PURE__ */ jsxs5("div", { className: "vos-next-moves-rec", children: [
2851
+ /* @__PURE__ */ jsxs5("div", { className: "vos-next-moves-rec-head", children: [
2852
+ /* @__PURE__ */ jsx5(
2853
+ "span",
2854
+ {
2855
+ className: "vos-next-moves-lens",
2856
+ style: { background: `${rec.lensColour}20`, color: rec.lensColour, borderColor: `${rec.lensColour}40` },
2857
+ children: rec.lens
2858
+ }
2859
+ ),
2860
+ /* @__PURE__ */ jsx5("span", { className: "vos-next-moves-rec-type", children: rec.type }),
2861
+ /* @__PURE__ */ jsxs5("span", { className: "vos-next-moves-rec-risk vos-num", children: [
2710
2862
  Math.round(rec.maxRisk),
2711
2863
  " risk"
2712
2864
  ] })
2713
2865
  ] }),
2714
- /* @__PURE__ */ jsxs5("div", { className: "vos-pipe-rec-body-inner", children: [
2715
- /* @__PURE__ */ jsx5("div", { className: "vos-pipe-rec-chips", children: rec.assumptionIds.map((id) => /* @__PURE__ */ jsx5(
2716
- "button",
2717
- {
2718
- type: "button",
2719
- className: "vos-pipe-rec-chip",
2720
- onClick: () => onOpenAssumption(id),
2721
- children: id
2722
- },
2723
- id
2724
- )) }),
2725
- /* @__PURE__ */ jsx5("div", { className: "vos-pipe-rec-rationale", children: rec.rationale }),
2726
- /* @__PURE__ */ jsxs5("div", { className: "vos-pipe-rec-bar", children: [
2727
- /* @__PURE__ */ jsx5("strong", { children: "Right if:" }),
2728
- " ",
2729
- rec.barPreview
2730
- ] }),
2731
- /* @__PURE__ */ jsx5("div", { className: "vos-pipe-rec-body", children: /* @__PURE__ */ jsx5(EvidenceBody, { text: rec.body }) }),
2732
- /* @__PURE__ */ jsx5("div", { className: "vos-pipe-rec-actions", children: /* @__PURE__ */ jsx5("button", { type: "button", className: "vos-btn vos-btn-sm vos-btn-accent", children: "Accept & create experiment" }) })
2733
- ] })
2866
+ /* @__PURE__ */ jsx5("div", { className: "vos-next-moves-rec-title", children: rec.title }),
2867
+ /* @__PURE__ */ jsx5("div", { className: "vos-next-moves-rec-rationale", children: rec.rationale }),
2868
+ /* @__PURE__ */ jsx5("div", { className: "vos-next-moves-rec-chips", children: rec.assumptionIds.map((id) => /* @__PURE__ */ jsx5(
2869
+ "button",
2870
+ {
2871
+ type: "button",
2872
+ className: "vos-next-moves-rec-chip",
2873
+ onClick: () => onOpenAssumption(id),
2874
+ children: id
2875
+ },
2876
+ id
2877
+ )) }),
2878
+ /* @__PURE__ */ jsx5("div", { className: "vos-next-moves-rec-actions", children: /* @__PURE__ */ jsx5("button", { type: "button", className: "vos-btn vos-btn-sm", children: "Accept & create experiment" }) })
2734
2879
  ] }, rec.id))
2735
2880
  ] })
2736
2881
  ] })
@@ -2759,15 +2904,14 @@ function ConfidenceDonut({
2759
2904
  value,
2760
2905
  size = 56
2761
2906
  }) {
2762
- const r = size / 2 - 5;
2907
+ const stroke = size > 60 ? 4 : 3;
2908
+ const r = (size - stroke * 3) / 2;
2763
2909
  const cx = size / 2;
2764
2910
  const cy = size / 2;
2911
+ const circumference = 2 * Math.PI * r;
2765
2912
  const pct2 = Math.max(0, Math.min(100, value)) / 100;
2913
+ const dash = pct2 * circumference;
2766
2914
  const color = value >= 67 ? "var(--vos-good)" : value >= 33 ? "var(--vos-warn)" : "var(--vos-crit)";
2767
- const endAngle = -Math.PI / 2 + pct2 * 2 * Math.PI;
2768
- const x2 = cx + r * Math.cos(endAngle);
2769
- const y2 = cy + r * Math.sin(endAngle);
2770
- const largeArc = pct2 > 0.5 ? 1 : 0;
2771
2915
  return /* @__PURE__ */ jsxs6("div", { className: "vos-donut", style: { width: size, height: size }, children: [
2772
2916
  /* @__PURE__ */ jsxs6("svg", { width: size, height: size, "aria-hidden": "true", children: [
2773
2917
  /* @__PURE__ */ jsx6(
@@ -2777,22 +2921,34 @@ function ConfidenceDonut({
2777
2921
  cy,
2778
2922
  r,
2779
2923
  fill: "none",
2780
- stroke: "var(--vos-surface-2)",
2781
- strokeWidth: 4
2924
+ stroke: "var(--vos-border)",
2925
+ strokeWidth: stroke
2782
2926
  }
2783
2927
  ),
2784
2928
  value > 0 ? /* @__PURE__ */ jsx6(
2785
- "path",
2929
+ "circle",
2786
2930
  {
2787
- d: `M ${cx} ${cy} L ${cx} ${cy - r} A ${r} ${r} 0 ${largeArc} 1 ${x2} ${y2} Z`,
2788
- fill: color,
2789
- opacity: 0.25,
2931
+ cx,
2932
+ cy,
2933
+ r,
2934
+ fill: "none",
2790
2935
  stroke: color,
2791
- strokeWidth: 1.5
2936
+ strokeWidth: stroke,
2937
+ strokeDasharray: `${dash} ${circumference - dash}`,
2938
+ strokeDashoffset: 0,
2939
+ strokeLinecap: "round",
2940
+ transform: `rotate(-90 ${cx} ${cy})`
2792
2941
  }
2793
2942
  ) : null
2794
2943
  ] }),
2795
- /* @__PURE__ */ jsx6("span", { className: "vos-donut-num vos-num", style: { fontSize: size > 60 ? 18 : 14 }, children: Math.round(value) })
2944
+ /* @__PURE__ */ jsx6(
2945
+ "span",
2946
+ {
2947
+ className: "vos-donut-num vos-num",
2948
+ style: { fontSize: size > 60 ? 22 : 15 },
2949
+ children: Math.round(value)
2950
+ }
2951
+ )
2796
2952
  ] });
2797
2953
  }
2798
2954
 
@@ -2930,11 +3086,11 @@ function ExperimentDetail({
2930
3086
  /* @__PURE__ */ jsxs7("div", { className: "vos-detail-section-label", children: [
2931
3087
  "Evidence \xB7 ",
2932
3088
  expReadings.length,
2933
- " reading",
3089
+ " piece",
2934
3090
  expReadings.length === 1 ? "" : "s",
2935
3091
  " from this experiment"
2936
3092
  ] }),
2937
- expReadings.length === 0 ? /* @__PURE__ */ jsx7("div", { className: "vos-muted vos-empty", children: "No readings yet \u2014 experiment is running." }) : expReadings.map((r) => {
3093
+ expReadings.length === 0 ? /* @__PURE__ */ jsx7("div", { className: "vos-muted vos-empty", children: "No evidence yet \u2014 experiment is running." }) : expReadings.map((r) => {
2938
3094
  const beliefs = readingBeliefs(r);
2939
3095
  const rDate = String(r.Date ?? "");
2940
3096
  const rTitle = String(r.Title ?? r.id);
@@ -2962,35 +3118,10 @@ function ExperimentDetail({
2962
3118
  }
2963
3119
  )
2964
3120
  ] }),
2965
- beliefs.length > 0 ? /* @__PURE__ */ jsx7("div", { className: "vos-reading-quotes", children: beliefs.map((b) => {
2966
- const justification = String(b["Grading justification"] ?? "");
2967
- if (!justification) return null;
2968
- return /* @__PURE__ */ jsxs7(
2969
- "div",
2970
- {
2971
- className: `vos-reading-rationale vos-verdict-border-${verdictTone2(b.Result)}`,
2972
- children: [
2973
- /* @__PURE__ */ jsxs7("span", { className: "vos-reading-quote-id vos-num", children: [
2974
- b.assumptionId,
2975
- " \xB7"
2976
- ] }),
2977
- /* @__PURE__ */ jsx7("span", { className: "vos-reading-rationale-label", children: "grading rationale:" }),
2978
- justification
2979
- ]
2980
- },
2981
- b.assumptionId
2982
- );
2983
- }) }) : null,
2984
- /* @__PURE__ */ jsxs7("div", { className: "vos-reading-beliefs-label", children: [
2985
- "ADDRESSES ",
2986
- beliefs.length,
2987
- " BELIEF",
2988
- beliefs.length === 1 ? "" : "S",
2989
- ":"
2990
- ] }),
2991
3121
  beliefs.map((b) => {
2992
3122
  const bl = barLines.find((x) => x.assumptionId === b.assumptionId);
2993
3123
  const a = (assumptions.records ?? []).find((x) => String(x.id) === b.assumptionId);
3124
+ const justification = String(b["Grading justification"] ?? "");
2994
3125
  return /* @__PURE__ */ jsxs7("div", { className: `vos-belief-card vos-verdict-border-${verdictTone2(b.Result)}`, children: [
2995
3126
  /* @__PURE__ */ jsxs7("div", { className: "vos-belief-head", children: [
2996
3127
  /* @__PURE__ */ jsx7(
@@ -3006,7 +3137,22 @@ function ExperimentDetail({
3006
3137
  /* @__PURE__ */ jsx7("span", { className: `vos-pill vos-pill-${verdictTone2(b.Result)}`, children: b.Result }),
3007
3138
  /* @__PURE__ */ jsx7("span", { className: "vos-rung-tag", children: String(r.Rung ?? "") })
3008
3139
  ] }),
3140
+ typeof b.excerpt === "string" && b.excerpt !== "" ? /* @__PURE__ */ jsxs7("div", { className: `vos-belief-excerpt vos-verdict-border-${verdictTone2(b.Result)}`, children: [
3141
+ "\u201C",
3142
+ b.excerpt,
3143
+ "\u201D"
3144
+ ] }) : justification ? /* @__PURE__ */ jsxs7(
3145
+ "div",
3146
+ {
3147
+ className: `vos-belief-rationale vos-verdict-border-${verdictTone2(b.Result)}`,
3148
+ children: [
3149
+ /* @__PURE__ */ jsx7("span", { className: "vos-belief-rationale-label", children: "grading rationale:" }),
3150
+ justification
3151
+ ]
3152
+ }
3153
+ ) : null,
3009
3154
  bl ? /* @__PURE__ */ jsxs7("div", { className: "vos-belief-bar", children: [
3155
+ /* @__PURE__ */ jsx7("div", { className: "vos-belief-bar-label", children: "Pre-registered bar" }),
3010
3156
  /* @__PURE__ */ jsxs7("div", { children: [
3011
3157
  /* @__PURE__ */ jsx7("strong", { children: "Right if:" }),
3012
3158
  " ",
@@ -3021,8 +3167,7 @@ function ExperimentDetail({
3021
3167
  "Bar verdict: ",
3022
3168
  /* @__PURE__ */ jsx7("strong", { children: bl.barVerdict })
3023
3169
  ] }) : null
3024
- ] }) : null,
3025
- /* @__PURE__ */ jsx7("div", { className: "vos-belief-why", children: String(b["Grading justification"] ?? "") })
3170
+ ] }) : null
3026
3171
  ] }, b.assumptionId);
3027
3172
  })
3028
3173
  ] })
@@ -3035,7 +3180,7 @@ function ExperimentDetail({
3035
3180
  unstartedBars.length,
3036
3181
  " bar",
3037
3182
  unstartedBars.length === 1 ? "" : "s",
3038
- " with no readings"
3183
+ " with no evidence"
3039
3184
  ] }),
3040
3185
  unstartedBars.map((bl) => {
3041
3186
  const a = (assumptions.records ?? []).find((x) => String(x.id) === bl.assumptionId);
@@ -3052,7 +3197,7 @@ function ExperimentDetail({
3052
3197
  ),
3053
3198
  /* @__PURE__ */ jsx7("span", { className: "vos-belief-title", children: String(a?.Title ?? bl.assumptionId) }),
3054
3199
  /* @__PURE__ */ jsx7("span", { className: "vos-rung-tag", children: String(bl.plannedRung ?? "") }),
3055
- /* @__PURE__ */ jsx7("span", { className: "vos-muted", children: "\u25CC no reading" })
3200
+ /* @__PURE__ */ jsx7("span", { className: "vos-muted", children: "\u25CC no evidence" })
3056
3201
  ] }),
3057
3202
  /* @__PURE__ */ jsxs7("div", { className: "vos-belief-bar", children: [
3058
3203
  /* @__PURE__ */ jsxs7("div", { children: [
@@ -3171,15 +3316,15 @@ function ReadingDetail({
3171
3316
  );
3172
3317
  if (loading) {
3173
3318
  return /* @__PURE__ */ jsxs9("div", { children: [
3174
- /* @__PURE__ */ jsx9(Breadcrumb, { trail: [{ label: "Readings", route: { name: "readings" } }], onNavigate }),
3175
- /* @__PURE__ */ jsx9("p", { className: "vos-muted", children: "Loading reading\u2026" })
3319
+ /* @__PURE__ */ jsx9(Breadcrumb, { trail: [{ label: "Evidence", route: { name: "readings" } }], onNavigate }),
3320
+ /* @__PURE__ */ jsx9("p", { className: "vos-muted", children: "Loading evidence\u2026" })
3176
3321
  ] });
3177
3322
  }
3178
3323
  if (!reading) {
3179
3324
  return /* @__PURE__ */ jsxs9("div", { children: [
3180
- /* @__PURE__ */ jsx9(Breadcrumb, { trail: [{ label: "Readings", route: { name: "readings" } }], onNavigate }),
3325
+ /* @__PURE__ */ jsx9(Breadcrumb, { trail: [{ label: "Evidence", route: { name: "readings" } }], onNavigate }),
3181
3326
  /* @__PURE__ */ jsxs9("p", { className: "vos-error", children: [
3182
- "Reading not found: ",
3327
+ "Evidence not found: ",
3183
3328
  readingId
3184
3329
  ] })
3185
3330
  ] });
@@ -3196,7 +3341,7 @@ function ReadingDetail({
3196
3341
  Breadcrumb,
3197
3342
  {
3198
3343
  trail: [
3199
- { label: "Readings", route: { name: "readings" } },
3344
+ { label: "Evidence", route: { name: "readings" } },
3200
3345
  { label: readingId, route: { name: "reading", id: readingId } }
3201
3346
  ],
3202
3347
  onNavigate
@@ -3220,7 +3365,7 @@ function ReadingDetail({
3220
3365
  beliefs.length === 1 ? "" : "s",
3221
3366
  " \xB7 each with its own quote"
3222
3367
  ] }),
3223
- beliefs.length === 0 ? /* @__PURE__ */ jsx9("div", { className: "vos-muted", children: "No beliefs scored in this reading." }) : beliefs.map((b) => {
3368
+ beliefs.length === 0 ? /* @__PURE__ */ jsx9("div", { className: "vos-muted", children: "No beliefs scored in this evidence." }) : beliefs.map((b) => {
3224
3369
  const a = (assumptions.records ?? []).find((x) => String(x.id) === b.assumptionId);
3225
3370
  const bl = experiment && Array.isArray(experiment.barLines) ? experiment.barLines.find((x) => x?.assumptionId === b.assumptionId) : null;
3226
3371
  const result = String(b.Result ?? "Inconclusive");
@@ -3240,6 +3385,15 @@ function ReadingDetail({
3240
3385
  /* @__PURE__ */ jsx9("span", { className: `vos-pill vos-pill-${verdictTone3(result)}`, children: result }),
3241
3386
  /* @__PURE__ */ jsx9("span", { className: "vos-rung-tag", children: rung })
3242
3387
  ] }),
3388
+ typeof b.excerpt === "string" && b.excerpt !== "" ? /* @__PURE__ */ jsxs9("div", { className: `vos-belief-excerpt vos-verdict-border-${verdictTone3(result)}`, children: [
3389
+ "\u201C",
3390
+ b.excerpt,
3391
+ "\u201D"
3392
+ ] }) : body ? /* @__PURE__ */ jsxs9("div", { className: `vos-belief-excerpt vos-verdict-border-${verdictTone3(result)}`, children: [
3393
+ "\u201C",
3394
+ snippetFromReadingBody(body, b.assumptionId),
3395
+ "\u201D"
3396
+ ] }) : null,
3243
3397
  justification ? /* @__PURE__ */ jsxs9("div", { className: `vos-belief-rationale vos-verdict-border-${verdictTone3(result)}`, children: [
3244
3398
  /* @__PURE__ */ jsx9("span", { className: "vos-belief-rationale-label", children: "grading rationale:" }),
3245
3399
  justification
@@ -3290,6 +3444,21 @@ function verdictTone3(verdict) {
3290
3444
  if (verdict === "Invalidated") return "crit";
3291
3445
  return "neutral";
3292
3446
  }
3447
+ function snippetFromReadingBody(body, cue) {
3448
+ if (!body) return "";
3449
+ const quoteMatch = body.match(/## Quote\n+([\s\S]*?)(?=\n## |\n##$|$)/i);
3450
+ if (quoteMatch) {
3451
+ const q = quoteMatch[1].trim();
3452
+ return q.length > 220 ? q.slice(0, 217).trim() + "\u2026" : q;
3453
+ }
3454
+ const sentences = body.split(/(?<=[.!?])\s+/);
3455
+ const cueLower = cue.toLowerCase();
3456
+ for (const s of sentences) {
3457
+ if (s.toLowerCase().includes(cueLower)) return s.trim();
3458
+ }
3459
+ const first = sentences[0]?.trim() ?? "";
3460
+ return first.length > 220 ? first.slice(0, 217).trim() + "\u2026" : first;
3461
+ }
3293
3462
 
3294
3463
  // src/readings-surface.tsx
3295
3464
  import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
@@ -5121,6 +5290,11 @@ function BeliefVerdicts({
5121
5290
  "Strength ",
5122
5291
  formatSigned(v.strength)
5123
5292
  ] }) }) : null,
5293
+ v.excerpt ? /* @__PURE__ */ jsxs17("p", { className: "vos-verdict-quote", children: [
5294
+ "\u201C",
5295
+ v.excerpt,
5296
+ "\u201D"
5297
+ ] }) : null,
5124
5298
  v.justification ? /* @__PURE__ */ jsx17("p", { className: "vos-verdict-why", children: v.justification }) : null
5125
5299
  ]
5126
5300
  },
@@ -7037,15 +7211,6 @@ function ValidationOSDashboard({ config = {} }) {
7037
7211
  brandName
7038
7212
  ] }),
7039
7213
  /* @__PURE__ */ jsxs25("div", { className: "vos-topbar", children: [
7040
- backendLabel ? /* @__PURE__ */ jsxs25("div", { className: "vos-backend", children: [
7041
- /* @__PURE__ */ jsx25("span", { className: "vos-live-dot" }),
7042
- " Backend: ",
7043
- /* @__PURE__ */ jsx25("b", { children: backendLabel })
7044
- ] }) : null,
7045
- agentLabel ? /* @__PURE__ */ jsxs25("span", { className: "vos-hint", children: [
7046
- "Agent: ",
7047
- /* @__PURE__ */ jsx25("b", { style: { color: "var(--vos-text)" }, children: agentLabel })
7048
- ] }) : null,
7049
7214
  /* @__PURE__ */ jsx25("div", { className: "vos-spacer" }),
7050
7215
  /* @__PURE__ */ jsx25("button", { type: "button", className: "vos-iconbtn", onClick: toggleTheme, children: "\u25D0 Theme" }),
7051
7216
  user?.name ? /* @__PURE__ */ jsxs25("div", { className: "vos-user", children: [