@validation-os/dashboard 0.15.2 → 0.15.4
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 +326 -100
- package/dist/index.js.map +1 -1
- package/dist/styles.css +224 -32
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -219,6 +219,75 @@ function buildEvidenceComposition(assumption, readings) {
|
|
|
219
219
|
};
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
// src/confidence-explainer.ts
|
|
223
|
+
import { RUNG_ANCHOR, W0_BY_RUNG } from "@validation-os/core/derivation";
|
|
224
|
+
var RUNG_INFO = {
|
|
225
|
+
Talk: {
|
|
226
|
+
label: "Talk",
|
|
227
|
+
description: "Opinions, pitch reactions, anecdotes. Quick to get but slow to move the needle \u2014 needs ~10 distinct sources to approach the cap."
|
|
228
|
+
},
|
|
229
|
+
"Desk research": {
|
|
230
|
+
label: "Desk research",
|
|
231
|
+
description: "Published sources, competitor analysis, market reports. Authoritative \u2014 2 strong sources nearly saturate this rung."
|
|
232
|
+
},
|
|
233
|
+
"Signed up": {
|
|
234
|
+
label: "Signed up (Consumer)",
|
|
235
|
+
description: "Consumers signing up for the product. A do-rung \u2014 20 signups bring this to ~75% of its cap."
|
|
236
|
+
},
|
|
237
|
+
"Observed usage": {
|
|
238
|
+
label: "Observed usage (Consumer)",
|
|
239
|
+
description: "Prototype usage, analytics, telemetry. A do-rung \u2014 20 observed users bring this to ~75% of its cap."
|
|
240
|
+
},
|
|
241
|
+
"Signed intent": {
|
|
242
|
+
label: "Signed intent (Commercial)",
|
|
243
|
+
description: "LOIs, signed letters of intent from businesses. A do-rung \u2014 20 signed intents bring this to ~75% of its cap."
|
|
244
|
+
},
|
|
245
|
+
"Paying users": {
|
|
246
|
+
label: "Paying users (Commercial)",
|
|
247
|
+
description: "Closed commitments \u2014 revenue. The strongest do-rung \u2014 20 paying users bring this to ~75% of its cap."
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
function buildConfidenceExplainer(assumption, readings) {
|
|
251
|
+
const comp = buildEvidenceComposition(assumption, readings);
|
|
252
|
+
const lens = str(assumption.Lens) ?? "";
|
|
253
|
+
const lensRungs = new Set(comp.rungs.map((r) => r.rung));
|
|
254
|
+
const allRungs = [
|
|
255
|
+
"Talk",
|
|
256
|
+
"Desk research",
|
|
257
|
+
"Signed up",
|
|
258
|
+
"Observed usage",
|
|
259
|
+
"Signed intent",
|
|
260
|
+
"Paying users"
|
|
261
|
+
];
|
|
262
|
+
const rungs = allRungs.map((rung) => {
|
|
263
|
+
const e = comp.rungs.find((r) => r.rung === rung);
|
|
264
|
+
const info = RUNG_INFO[rung] ?? { label: rung, description: "" };
|
|
265
|
+
return {
|
|
266
|
+
rung,
|
|
267
|
+
w0: W0_BY_RUNG[rung] ?? 100,
|
|
268
|
+
anchors: RUNG_ANCHOR[rung] ?? { Low: 0, Typical: 0, High: 0 },
|
|
269
|
+
contribution: e?.contribution ?? 0,
|
|
270
|
+
count: e?.count ?? 0,
|
|
271
|
+
inLens: lensRungs.has(rung),
|
|
272
|
+
label: info.label,
|
|
273
|
+
description: info.description
|
|
274
|
+
};
|
|
275
|
+
});
|
|
276
|
+
const confidence2 = assumption.derived?.confidence ?? 0;
|
|
277
|
+
const totalContribution = comp.totalContribution;
|
|
278
|
+
const rungsWithEvidence = rungs.filter((r) => r.count > 0);
|
|
279
|
+
const w0Sum = rungsWithEvidence.reduce((s, r) => s + r.w0, 0);
|
|
280
|
+
const summary = rungsWithEvidence.length === 0 ? "No concluded evidence yet. Confidence is 0 \u2014 the bet is open. Any Validated or Invalidated evidence at any rung will start moving this number." : rungsWithEvidence.length === 1 ? `All evidence is at the ${rungsWithEvidence[0].rung} rung (${rungsWithEvidence[0].count} source${rungsWithEvidence[0].count === 1 ? "" : "s"}). ${rungsWithEvidence[0].description}` : `Evidence spans ${rungsWithEvidence.length} rungs. The strongest push comes from ${rungs.reduce((a, b) => Math.abs(b.contribution) > Math.abs(a.contribution) ? b : a).rung}.`;
|
|
281
|
+
return {
|
|
282
|
+
confidence: confidence2,
|
|
283
|
+
formula: "Confidence = \u03A3(weight \xD7 strength) / (\u03A3 W0[rungs with evidence] + \u03A3 weights)",
|
|
284
|
+
rungs,
|
|
285
|
+
totalContribution,
|
|
286
|
+
denominator: w0Sum,
|
|
287
|
+
summary
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
222
291
|
// src/markdown.tsx
|
|
223
292
|
import { useState } from "react";
|
|
224
293
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
@@ -1426,6 +1495,7 @@ function AssumptionDetail({
|
|
|
1426
1495
|
/* @__PURE__ */ jsx4(ScoreCard, { label: "Framed", value: `${Math.round(framed)}%` })
|
|
1427
1496
|
] }),
|
|
1428
1497
|
/* @__PURE__ */ jsx4(EvidenceCompositionView, { assumption: record, readings: readings.records ?? [] }),
|
|
1498
|
+
/* @__PURE__ */ jsx4(ConfidenceExplainerView, { assumption: record, readings: readings.records ?? [] }),
|
|
1429
1499
|
statement ? /* @__PURE__ */ jsxs4("div", { className: "vos-card vos-detail-section", children: [
|
|
1430
1500
|
/* @__PURE__ */ jsx4("div", { className: "vos-detail-section-label", children: "Body" }),
|
|
1431
1501
|
/* @__PURE__ */ jsx4(GlossaryText, { text: statement, terms: glossaryTerms, selfId: assumptionId })
|
|
@@ -1513,11 +1583,10 @@ function AssumptionDetail({
|
|
|
1513
1583
|
/* @__PURE__ */ jsx4("span", { className: `vos-pill vos-pill-${verdictTone(result)}`, children: result }),
|
|
1514
1584
|
/* @__PURE__ */ jsx4("span", { className: "vos-rung-tag", children: rung })
|
|
1515
1585
|
] }),
|
|
1516
|
-
/* @__PURE__ */ jsxs4("div", { className: `vos-
|
|
1517
|
-
|
|
1518
|
-
justification
|
|
1519
|
-
|
|
1520
|
-
] }),
|
|
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,
|
|
1521
1590
|
/* @__PURE__ */ jsxs4("div", { className: "vos-reading-source", children: [
|
|
1522
1591
|
source,
|
|
1523
1592
|
expId ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
@@ -1631,6 +1700,72 @@ function nextMoveFor(assumption, experiments) {
|
|
|
1631
1700
|
if (framed) return "Design an experiment";
|
|
1632
1701
|
return "Frame the belief";
|
|
1633
1702
|
}
|
|
1703
|
+
function ConfidenceExplainerView({
|
|
1704
|
+
assumption,
|
|
1705
|
+
readings
|
|
1706
|
+
}) {
|
|
1707
|
+
const view = useMemo(
|
|
1708
|
+
() => buildConfidenceExplainer(assumption, readings),
|
|
1709
|
+
[assumption, readings]
|
|
1710
|
+
);
|
|
1711
|
+
return /* @__PURE__ */ jsxs4("details", { className: "vos-card vos-detail-section vos-explainer", children: [
|
|
1712
|
+
/* @__PURE__ */ jsxs4("summary", { className: "vos-explainer-summary", children: [
|
|
1713
|
+
/* @__PURE__ */ jsx4("span", { className: "vos-detail-section-label", children: "How Confidence is calculated" }),
|
|
1714
|
+
/* @__PURE__ */ jsx4("span", { className: "vos-explainer-conf vos-num", children: formatSigned(view.confidence) })
|
|
1715
|
+
] }),
|
|
1716
|
+
/* @__PURE__ */ jsxs4("div", { className: "vos-explainer-body", children: [
|
|
1717
|
+
/* @__PURE__ */ jsx4("p", { className: "vos-explainer-formula", children: view.formula }),
|
|
1718
|
+
/* @__PURE__ */ jsx4("p", { className: "vos-explainer-summary-text", children: view.summary }),
|
|
1719
|
+
/* @__PURE__ */ jsxs4("div", { className: "vos-explainer-rungs", children: [
|
|
1720
|
+
/* @__PURE__ */ jsxs4("div", { className: "vos-explainer-rungs-head", children: [
|
|
1721
|
+
/* @__PURE__ */ jsx4("span", { children: "Rung" }),
|
|
1722
|
+
/* @__PURE__ */ jsx4("span", { children: "W0 (prior)" }),
|
|
1723
|
+
/* @__PURE__ */ jsx4("span", { children: "Anchors (L/T/H)" }),
|
|
1724
|
+
/* @__PURE__ */ jsx4("span", { children: "Evidence" }),
|
|
1725
|
+
/* @__PURE__ */ jsx4("span", { children: "Contribution" })
|
|
1726
|
+
] }),
|
|
1727
|
+
view.rungs.map((r) => /* @__PURE__ */ jsxs4(
|
|
1728
|
+
"div",
|
|
1729
|
+
{
|
|
1730
|
+
className: `vos-explainer-rung ${!r.inLens ? "is-not-lens" : ""} ${r.count > 0 ? "has-evidence" : ""}`,
|
|
1731
|
+
children: [
|
|
1732
|
+
/* @__PURE__ */ jsx4("span", { className: "vos-explainer-rung-name", title: r.description, children: r.label }),
|
|
1733
|
+
/* @__PURE__ */ jsx4("span", { className: "vos-explainer-rung-w0 vos-num", children: r.w0 }),
|
|
1734
|
+
/* @__PURE__ */ jsxs4("span", { className: "vos-explainer-rung-anchors vos-num", children: [
|
|
1735
|
+
r.anchors.Low,
|
|
1736
|
+
"/",
|
|
1737
|
+
r.anchors.Typical,
|
|
1738
|
+
"/",
|
|
1739
|
+
r.anchors.High
|
|
1740
|
+
] }),
|
|
1741
|
+
/* @__PURE__ */ jsx4("span", { className: "vos-explainer-rung-count vos-num", children: r.count > 0 ? `${r.count} source${r.count === 1 ? "" : "s"}` : "\u2014" }),
|
|
1742
|
+
/* @__PURE__ */ jsx4("span", { className: `vos-explainer-rung-contrib vos-num ${r.contribution > 0 ? "vos-text-good" : r.contribution < 0 ? "vos-text-crit" : ""}`, children: r.count > 0 ? formatSigned(r.contribution) : "\u2014" })
|
|
1743
|
+
]
|
|
1744
|
+
},
|
|
1745
|
+
r.rung
|
|
1746
|
+
))
|
|
1747
|
+
] }),
|
|
1748
|
+
/* @__PURE__ */ jsxs4("div", { className: "vos-explainer-foot", children: [
|
|
1749
|
+
/* @__PURE__ */ jsxs4("p", { children: [
|
|
1750
|
+
/* @__PURE__ */ jsx4("strong", { children: "W0" }),
|
|
1751
|
+
" = the prior weight for each rung \u2014 how many distinct sources it takes to approach that rung's cap. Desk research has a low W0 (2 \u2014 one authoritative source nearly saturates it); Talk has a higher W0 (6.5 \u2014 needs ~10 sources); do-rungs have high W0s (327 \u2014 needs ~20 sources to reach 75% of the cap)."
|
|
1752
|
+
] }),
|
|
1753
|
+
/* @__PURE__ */ jsxs4("p", { children: [
|
|
1754
|
+
/* @__PURE__ */ jsx4("strong", { children: "Strength" }),
|
|
1755
|
+
" = the rung's anchor \xD7 the sign of the result (Validated = +, Invalidated = \u2212, Inconclusive = 0). The anchor is the band (Low/Typical/High) the evidence lands at."
|
|
1756
|
+
] }),
|
|
1757
|
+
/* @__PURE__ */ jsxs4("p", { children: [
|
|
1758
|
+
/* @__PURE__ */ jsx4("strong", { children: "Weight" }),
|
|
1759
|
+
" = |Strength| \xD7 source quality \xD7 commitment factor (1.0 for evidence linked to an experiment, 0.85 for found evidence)."
|
|
1760
|
+
] }),
|
|
1761
|
+
/* @__PURE__ */ jsxs4("p", { children: [
|
|
1762
|
+
/* @__PURE__ */ jsx4("strong", { children: "Contribution" }),
|
|
1763
|
+
" = (Weight \xD7 Strength) / denominator. The sum of all contributions equals Confidence. Evidence at a higher rung always outweighs evidence at a lower rung \u2014 the rung ladder dominates."
|
|
1764
|
+
] })
|
|
1765
|
+
] })
|
|
1766
|
+
] })
|
|
1767
|
+
] });
|
|
1768
|
+
}
|
|
1634
1769
|
function EvidenceCompositionView({
|
|
1635
1770
|
assumption,
|
|
1636
1771
|
readings
|
|
@@ -1964,6 +2099,45 @@ function weekOverWeekDelta(assumptions, readings, now) {
|
|
|
1964
2099
|
}
|
|
1965
2100
|
|
|
1966
2101
|
// src/recommended-experiments.ts
|
|
2102
|
+
var MAX_RECOMMENDED = 2;
|
|
2103
|
+
var MAX_CLUSTER_SIZE = 3;
|
|
2104
|
+
var MAX_NEEDS_FRAMING = 3;
|
|
2105
|
+
function buildNeedsFraming(assumptions) {
|
|
2106
|
+
const live = assumptions.filter((a) => {
|
|
2107
|
+
const status = str(a.Status);
|
|
2108
|
+
const moot = a.moot === true;
|
|
2109
|
+
return !moot && (status === "Live" || status === "Draft");
|
|
2110
|
+
});
|
|
2111
|
+
const items = live.map((a) => {
|
|
2112
|
+
const id = str(a.id) ?? "";
|
|
2113
|
+
const completeness = derivedNum(a, "completeness") ?? 0;
|
|
2114
|
+
const risk = derivedNum(a, "risk") ?? 0;
|
|
2115
|
+
const lens = str(a.Lens) ?? "\u2014";
|
|
2116
|
+
const stage = str(a.Stage) ?? "\u2014";
|
|
2117
|
+
const title = str(a.Title) ?? id;
|
|
2118
|
+
const hint = framingHint(a, completeness);
|
|
2119
|
+
return { id, title, risk, completeness, lens, stage, hint };
|
|
2120
|
+
}).filter((a) => a.completeness < 100);
|
|
2121
|
+
const byLens = /* @__PURE__ */ new Map();
|
|
2122
|
+
for (const item of items) {
|
|
2123
|
+
const existing = byLens.get(item.lens);
|
|
2124
|
+
if (!existing || item.risk > existing.risk) {
|
|
2125
|
+
byLens.set(item.lens, item);
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
return [...byLens.values()].sort((a, b) => b.risk - a.risk).slice(0, MAX_NEEDS_FRAMING);
|
|
2129
|
+
}
|
|
2130
|
+
function framingHint(a, completeness) {
|
|
2131
|
+
const hasScoring = Boolean(str(a["Scoring justification"]));
|
|
2132
|
+
const hasDescription = Boolean(str(a.Description));
|
|
2133
|
+
if (completeness < 50) {
|
|
2134
|
+
return hasDescription ? "The belief is written but incomplete \u2014 fill in the scoring justification and any missing framing fields." : "The belief needs a description \u2014 what exactly is being claimed, in falsifiable terms.";
|
|
2135
|
+
}
|
|
2136
|
+
if (!hasScoring) {
|
|
2137
|
+
return "Add a scoring justification \u2014 why is the Impact seed scored as it is?";
|
|
2138
|
+
}
|
|
2139
|
+
return "Nearly framed \u2014 check the 5 Whys and the metric for truth are complete.";
|
|
2140
|
+
}
|
|
1967
2141
|
var LENS_TO_TYPE = {
|
|
1968
2142
|
Consumer: "Observation",
|
|
1969
2143
|
Commercial: "Test"
|
|
@@ -1992,16 +2166,22 @@ function buildRecommendedExperiments(assumptions, experiments) {
|
|
|
1992
2166
|
if (bucket) bucket.push(a);
|
|
1993
2167
|
else clusters.set(key, [a]);
|
|
1994
2168
|
}
|
|
2169
|
+
const clusterEntries = [...clusters.entries()].map(([key, cluster]) => {
|
|
2170
|
+
const ranked = cluster.sort((a, b) => (derivedNum(b, "risk") ?? 0) - (derivedNum(a, "risk") ?? 0)).slice(0, MAX_CLUSTER_SIZE);
|
|
2171
|
+
const maxRisk = Math.max(...ranked.map((a) => derivedNum(a, "risk") ?? 0), 0);
|
|
2172
|
+
return { key, cluster: ranked, maxRisk };
|
|
2173
|
+
}).sort((a, b) => b.maxRisk - a.maxRisk).slice(0, MAX_RECOMMENDED);
|
|
1995
2174
|
const recs = [];
|
|
1996
|
-
for (const
|
|
1997
|
-
const
|
|
1998
|
-
const
|
|
1999
|
-
const maxRisk = Math.max(...risks, 0);
|
|
2175
|
+
for (const { key, cluster, maxRisk } of clusterEntries) {
|
|
2176
|
+
const lens = key.split("\xD7")[0] ?? "\u2014";
|
|
2177
|
+
const stage = key.split("\xD7")[1] ?? "\u2014";
|
|
2000
2178
|
const assumptionIds = cluster.map((a) => str(a.id) ?? "").filter(Boolean).sort();
|
|
2001
2179
|
const type = LENS_TO_TYPE[lens ?? ""] ?? "Desk research";
|
|
2180
|
+
const titles = cluster.map((a) => str(a.Title) ?? str(a.id) ?? "");
|
|
2002
2181
|
const title = cluster.length === 1 ? `Test ${assumptionIds[0]}` : `Test ${cluster.length} ${lens} \xB7 ${stage} beliefs`;
|
|
2003
2182
|
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.`;
|
|
2004
2183
|
const barPreview = `The riskiest belief moves out of the kill zone, or stays in it.`;
|
|
2184
|
+
const body = generateExperimentBody(type, lens, stage, cluster, maxRisk);
|
|
2005
2185
|
recs.push({
|
|
2006
2186
|
id: assumptionIds.join("+"),
|
|
2007
2187
|
type,
|
|
@@ -2009,7 +2189,8 @@ function buildRecommendedExperiments(assumptions, experiments) {
|
|
|
2009
2189
|
assumptionIds,
|
|
2010
2190
|
maxRisk,
|
|
2011
2191
|
rationale,
|
|
2012
|
-
barPreview
|
|
2192
|
+
barPreview,
|
|
2193
|
+
body
|
|
2013
2194
|
});
|
|
2014
2195
|
}
|
|
2015
2196
|
recs.sort(
|
|
@@ -2017,6 +2198,29 @@ function buildRecommendedExperiments(assumptions, experiments) {
|
|
|
2017
2198
|
);
|
|
2018
2199
|
return recs;
|
|
2019
2200
|
}
|
|
2201
|
+
function generateExperimentBody(type, lens, stage, cluster, maxRisk) {
|
|
2202
|
+
const beliefs = cluster.map((a) => `- **${str(a.id)}**: ${str(a.Title) ?? str(a.id) ?? ""}`).join("\n");
|
|
2203
|
+
const rung = type === "Observation" ? "Observed usage" : type === "Test" ? "Signed intent" : "Desk research";
|
|
2204
|
+
return `## What this tests
|
|
2205
|
+
|
|
2206
|
+
This ${type.toLowerCase()} addresses ${cluster.length} ${lens} \xB7 ${stage} belief${cluster.length === 1 ? "" : "s"} at ${Math.round(maxRisk)} risk \u2014 the riskiest untested cluster on the board.
|
|
2207
|
+
|
|
2208
|
+
${beliefs}
|
|
2209
|
+
|
|
2210
|
+
## How to run it
|
|
2211
|
+
|
|
2212
|
+
${type === "Observation" ? `Set up a prototype or analytics instrument that captures real user behaviour. Watch how ${cluster.length === 1 ? "this belief plays out" : "these beliefs play out"} in actual usage \u2014 not what people say they'd do, but what they actually do. Log each observation as evidence at the **${rung}** rung.` : type === "Test" ? `Reach out to potential ${lens === "Consumer" ? "customers" : "businesses"} and seek a signed commitment \u2014 a letter of intent, a pre-order, a pilot agreement. The commitment is the evidence; it lands at the **${rung}** rung.` : `Research published sources, competitor behaviour, and market data. Find external evidence that bears on ${cluster.length === 1 ? "this belief" : "these beliefs"}. Log each source as evidence at the **Desk research** rung.`}
|
|
2213
|
+
|
|
2214
|
+
## Questions to answer
|
|
2215
|
+
|
|
2216
|
+
${cluster.map((a, i) => `${i + 1}. Does ${str(a.Title) ?? str(a.id) ?? ""} hold \u2014 or does the evidence break it?`).join("\n")}
|
|
2217
|
+
|
|
2218
|
+
## Pre-registered bars
|
|
2219
|
+
|
|
2220
|
+
- **Right if:** the riskiest belief moves out of the kill zone (Confidence > 0).
|
|
2221
|
+
- **Wrong if:** the evidence invalidates the riskiest belief (Confidence enters the kill zone).
|
|
2222
|
+
`;
|
|
2223
|
+
}
|
|
2020
2224
|
|
|
2021
2225
|
// src/stage-meters.ts
|
|
2022
2226
|
function clamp(pct2) {
|
|
@@ -2255,6 +2459,10 @@ function GridPane({
|
|
|
2255
2459
|
() => buildRecommendedExperiments(assumptions, experiments),
|
|
2256
2460
|
[assumptions, experiments]
|
|
2257
2461
|
);
|
|
2462
|
+
const needsFraming = useMemo2(
|
|
2463
|
+
() => buildNeedsFraming(assumptions),
|
|
2464
|
+
[assumptions]
|
|
2465
|
+
);
|
|
2258
2466
|
const cold = coldStartFor({
|
|
2259
2467
|
assumptions,
|
|
2260
2468
|
experiments,
|
|
@@ -2322,9 +2530,10 @@ function GridPane({
|
|
|
2322
2530
|
] }) }),
|
|
2323
2531
|
/* @__PURE__ */ jsx5("p", { className: "vos-hint vos-stage-grid-foot", children: "The densest cell per row is where that part of the business is. Click a cell to drill into its assumptions, ranked by Risk; a single-assumption cell opens the detail directly." })
|
|
2324
2532
|
] }),
|
|
2325
|
-
recs.length > 0 ? /* @__PURE__ */ jsx5(
|
|
2326
|
-
|
|
2533
|
+
needsFraming.length > 0 || recs.length > 0 ? /* @__PURE__ */ jsx5(
|
|
2534
|
+
NextMovesSection,
|
|
2327
2535
|
{
|
|
2536
|
+
needsFraming,
|
|
2328
2537
|
recs,
|
|
2329
2538
|
onOpenAssumption: (id) => onNavigate({ name: "assumption", id })
|
|
2330
2539
|
}
|
|
@@ -2461,37 +2670,84 @@ function Meter2Seg({
|
|
|
2461
2670
|
] })
|
|
2462
2671
|
] });
|
|
2463
2672
|
}
|
|
2464
|
-
function
|
|
2673
|
+
function NextMovesSection({
|
|
2674
|
+
needsFraming,
|
|
2465
2675
|
recs,
|
|
2466
2676
|
onOpenAssumption
|
|
2467
2677
|
}) {
|
|
2468
|
-
return /* @__PURE__ */ jsxs5("div", { className: "vos-
|
|
2469
|
-
/* @__PURE__ */ jsx5("div", { className: "vos-
|
|
2470
|
-
/* @__PURE__ */
|
|
2471
|
-
/* @__PURE__ */ jsxs5("div", { className: "vos-
|
|
2472
|
-
/* @__PURE__ */
|
|
2473
|
-
|
|
2474
|
-
|
|
2678
|
+
return /* @__PURE__ */ jsxs5("div", { className: "vos-next-moves", children: [
|
|
2679
|
+
/* @__PURE__ */ jsx5("div", { className: "vos-next-moves-head", children: "Next moves" }),
|
|
2680
|
+
/* @__PURE__ */ jsxs5("div", { className: "vos-next-moves-cols", children: [
|
|
2681
|
+
/* @__PURE__ */ jsxs5("div", { className: "vos-card vos-next-moves-col", children: [
|
|
2682
|
+
/* @__PURE__ */ jsxs5("div", { className: "vos-next-moves-col-label", children: [
|
|
2683
|
+
"Needs framing \xB7 top ",
|
|
2684
|
+
needsFraming.length
|
|
2685
|
+
] }),
|
|
2686
|
+
needsFraming.length === 0 ? /* @__PURE__ */ jsx5("div", { className: "vos-muted vos-next-moves-empty", children: "Every belief is fully framed." }) : needsFraming.map((a) => /* @__PURE__ */ jsxs5(
|
|
2687
|
+
"button",
|
|
2688
|
+
{
|
|
2689
|
+
type: "button",
|
|
2690
|
+
className: "vos-next-moves-item",
|
|
2691
|
+
onClick: () => onOpenAssumption(a.id),
|
|
2692
|
+
children: [
|
|
2693
|
+
/* @__PURE__ */ jsxs5("div", { className: "vos-next-moves-item-head", children: [
|
|
2694
|
+
/* @__PURE__ */ jsx5("span", { className: "vos-next-moves-item-id vos-num", children: a.id }),
|
|
2695
|
+
/* @__PURE__ */ jsxs5("span", { className: `vos-next-moves-item-risk vos-num vos-text-${riskToneClass(a.risk)}`, children: [
|
|
2696
|
+
Math.round(a.risk),
|
|
2697
|
+
" risk"
|
|
2698
|
+
] })
|
|
2699
|
+
] }),
|
|
2700
|
+
/* @__PURE__ */ jsx5("div", { className: "vos-next-moves-item-title", children: a.title }),
|
|
2701
|
+
/* @__PURE__ */ jsx5("div", { className: "vos-next-moves-item-hint", children: a.hint })
|
|
2702
|
+
]
|
|
2703
|
+
},
|
|
2704
|
+
a.id
|
|
2705
|
+
))
|
|
2475
2706
|
] }),
|
|
2476
|
-
/* @__PURE__ */
|
|
2477
|
-
"
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
children:
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2707
|
+
/* @__PURE__ */ jsxs5("div", { className: "vos-card vos-next-moves-col", children: [
|
|
2708
|
+
/* @__PURE__ */ jsxs5("div", { className: "vos-next-moves-col-label", children: [
|
|
2709
|
+
"Proposed experiments \xB7 top ",
|
|
2710
|
+
recs.length
|
|
2711
|
+
] }),
|
|
2712
|
+
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: [
|
|
2713
|
+
/* @__PURE__ */ jsxs5("summary", { className: "vos-pipe-rec-head", children: [
|
|
2714
|
+
/* @__PURE__ */ jsx5("span", { className: "vos-pipe-rec-type", children: rec.type }),
|
|
2715
|
+
/* @__PURE__ */ jsx5("span", { className: "vos-pipe-rec-title", children: rec.title }),
|
|
2716
|
+
/* @__PURE__ */ jsxs5("span", { className: "vos-pipe-rec-risk vos-num", children: [
|
|
2717
|
+
Math.round(rec.maxRisk),
|
|
2718
|
+
" risk"
|
|
2719
|
+
] })
|
|
2720
|
+
] }),
|
|
2721
|
+
/* @__PURE__ */ jsxs5("div", { className: "vos-pipe-rec-body-inner", children: [
|
|
2722
|
+
/* @__PURE__ */ jsx5("div", { className: "vos-pipe-rec-chips", children: rec.assumptionIds.map((id) => /* @__PURE__ */ jsx5(
|
|
2723
|
+
"button",
|
|
2724
|
+
{
|
|
2725
|
+
type: "button",
|
|
2726
|
+
className: "vos-pipe-rec-chip",
|
|
2727
|
+
onClick: () => onOpenAssumption(id),
|
|
2728
|
+
children: id
|
|
2729
|
+
},
|
|
2730
|
+
id
|
|
2731
|
+
)) }),
|
|
2732
|
+
/* @__PURE__ */ jsx5("div", { className: "vos-pipe-rec-rationale", children: rec.rationale }),
|
|
2733
|
+
/* @__PURE__ */ jsxs5("div", { className: "vos-pipe-rec-bar", children: [
|
|
2734
|
+
/* @__PURE__ */ jsx5("strong", { children: "Right if:" }),
|
|
2735
|
+
" ",
|
|
2736
|
+
rec.barPreview
|
|
2737
|
+
] }),
|
|
2738
|
+
/* @__PURE__ */ jsx5("div", { className: "vos-pipe-rec-body", children: /* @__PURE__ */ jsx5(EvidenceBody, { text: rec.body }) }),
|
|
2739
|
+
/* @__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" }) })
|
|
2740
|
+
] })
|
|
2741
|
+
] }, rec.id))
|
|
2491
2742
|
] })
|
|
2492
|
-
] }
|
|
2743
|
+
] })
|
|
2493
2744
|
] });
|
|
2494
2745
|
}
|
|
2746
|
+
function riskToneClass(risk) {
|
|
2747
|
+
if (risk >= 70) return "crit";
|
|
2748
|
+
if (risk >= 40) return "warn";
|
|
2749
|
+
return "good";
|
|
2750
|
+
}
|
|
2495
2751
|
function Breadcrumb2({
|
|
2496
2752
|
trail
|
|
2497
2753
|
}) {
|
|
@@ -2510,15 +2766,14 @@ function ConfidenceDonut({
|
|
|
2510
2766
|
value,
|
|
2511
2767
|
size = 56
|
|
2512
2768
|
}) {
|
|
2513
|
-
const
|
|
2769
|
+
const stroke = size > 60 ? 6 : 4;
|
|
2770
|
+
const r = (size - stroke) / 2;
|
|
2514
2771
|
const cx = size / 2;
|
|
2515
2772
|
const cy = size / 2;
|
|
2773
|
+
const circumference = 2 * Math.PI * r;
|
|
2516
2774
|
const pct2 = Math.max(0, Math.min(100, value)) / 100;
|
|
2775
|
+
const dash = pct2 * circumference;
|
|
2517
2776
|
const color = value >= 67 ? "var(--vos-good)" : value >= 33 ? "var(--vos-warn)" : "var(--vos-crit)";
|
|
2518
|
-
const endAngle = -Math.PI / 2 + pct2 * 2 * Math.PI;
|
|
2519
|
-
const x2 = cx + r * Math.cos(endAngle);
|
|
2520
|
-
const y2 = cy + r * Math.sin(endAngle);
|
|
2521
|
-
const largeArc = pct2 > 0.5 ? 1 : 0;
|
|
2522
2777
|
return /* @__PURE__ */ jsxs6("div", { className: "vos-donut", style: { width: size, height: size }, children: [
|
|
2523
2778
|
/* @__PURE__ */ jsxs6("svg", { width: size, height: size, "aria-hidden": "true", children: [
|
|
2524
2779
|
/* @__PURE__ */ jsx6(
|
|
@@ -2529,21 +2784,33 @@ function ConfidenceDonut({
|
|
|
2529
2784
|
r,
|
|
2530
2785
|
fill: "none",
|
|
2531
2786
|
stroke: "var(--vos-surface-2)",
|
|
2532
|
-
strokeWidth:
|
|
2787
|
+
strokeWidth: stroke
|
|
2533
2788
|
}
|
|
2534
2789
|
),
|
|
2535
2790
|
value > 0 ? /* @__PURE__ */ jsx6(
|
|
2536
|
-
"
|
|
2791
|
+
"circle",
|
|
2537
2792
|
{
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2793
|
+
cx,
|
|
2794
|
+
cy,
|
|
2795
|
+
r,
|
|
2796
|
+
fill: "none",
|
|
2541
2797
|
stroke: color,
|
|
2542
|
-
strokeWidth:
|
|
2798
|
+
strokeWidth: stroke,
|
|
2799
|
+
strokeDasharray: `${dash} ${circumference - dash}`,
|
|
2800
|
+
strokeDashoffset: 0,
|
|
2801
|
+
strokeLinecap: "round",
|
|
2802
|
+
transform: `rotate(-90 ${cx} ${cy})`
|
|
2543
2803
|
}
|
|
2544
2804
|
) : null
|
|
2545
2805
|
] }),
|
|
2546
|
-
/* @__PURE__ */ jsx6(
|
|
2806
|
+
/* @__PURE__ */ jsx6(
|
|
2807
|
+
"span",
|
|
2808
|
+
{
|
|
2809
|
+
className: "vos-donut-num vos-num",
|
|
2810
|
+
style: { fontSize: size > 60 ? 20 : 14 },
|
|
2811
|
+
children: Math.round(value)
|
|
2812
|
+
}
|
|
2813
|
+
)
|
|
2547
2814
|
] });
|
|
2548
2815
|
}
|
|
2549
2816
|
|
|
@@ -2713,37 +2980,10 @@ function ExperimentDetail({
|
|
|
2713
2980
|
}
|
|
2714
2981
|
)
|
|
2715
2982
|
] }),
|
|
2716
|
-
beliefs.length > 0 ? /* @__PURE__ */ jsx7("div", { className: "vos-reading-quotes", children: beliefs.map((b) => {
|
|
2717
|
-
const justification = String(b["Grading justification"] ?? "");
|
|
2718
|
-
const excerpt = justification || truncate(String(r.body ?? ""), 120);
|
|
2719
|
-
if (!excerpt) return null;
|
|
2720
|
-
return /* @__PURE__ */ jsxs7(
|
|
2721
|
-
"div",
|
|
2722
|
-
{
|
|
2723
|
-
className: `vos-reading-quote vos-verdict-border-${verdictTone2(b.Result)}`,
|
|
2724
|
-
children: [
|
|
2725
|
-
/* @__PURE__ */ jsxs7("span", { className: "vos-reading-quote-id vos-num", children: [
|
|
2726
|
-
b.assumptionId,
|
|
2727
|
-
" \xB7"
|
|
2728
|
-
] }),
|
|
2729
|
-
'"',
|
|
2730
|
-
excerpt,
|
|
2731
|
-
'"'
|
|
2732
|
-
]
|
|
2733
|
-
},
|
|
2734
|
-
b.assumptionId
|
|
2735
|
-
);
|
|
2736
|
-
}) }) : null,
|
|
2737
|
-
/* @__PURE__ */ jsxs7("div", { className: "vos-reading-beliefs-label", children: [
|
|
2738
|
-
"ADDRESSES ",
|
|
2739
|
-
beliefs.length,
|
|
2740
|
-
" BELIEF",
|
|
2741
|
-
beliefs.length === 1 ? "" : "S",
|
|
2742
|
-
":"
|
|
2743
|
-
] }),
|
|
2744
2983
|
beliefs.map((b) => {
|
|
2745
2984
|
const bl = barLines.find((x) => x.assumptionId === b.assumptionId);
|
|
2746
2985
|
const a = (assumptions.records ?? []).find((x) => String(x.id) === b.assumptionId);
|
|
2986
|
+
const justification = String(b["Grading justification"] ?? "");
|
|
2747
2987
|
return /* @__PURE__ */ jsxs7("div", { className: `vos-belief-card vos-verdict-border-${verdictTone2(b.Result)}`, children: [
|
|
2748
2988
|
/* @__PURE__ */ jsxs7("div", { className: "vos-belief-head", children: [
|
|
2749
2989
|
/* @__PURE__ */ jsx7(
|
|
@@ -2759,7 +2999,12 @@ function ExperimentDetail({
|
|
|
2759
2999
|
/* @__PURE__ */ jsx7("span", { className: `vos-pill vos-pill-${verdictTone2(b.Result)}`, children: b.Result }),
|
|
2760
3000
|
/* @__PURE__ */ jsx7("span", { className: "vos-rung-tag", children: String(r.Rung ?? "") })
|
|
2761
3001
|
] }),
|
|
3002
|
+
justification ? /* @__PURE__ */ jsxs7("div", { className: `vos-belief-rationale vos-verdict-border-${verdictTone2(b.Result)}`, children: [
|
|
3003
|
+
/* @__PURE__ */ jsx7("span", { className: "vos-belief-rationale-label", children: "grading rationale:" }),
|
|
3004
|
+
justification
|
|
3005
|
+
] }) : null,
|
|
2762
3006
|
bl ? /* @__PURE__ */ jsxs7("div", { className: "vos-belief-bar", children: [
|
|
3007
|
+
/* @__PURE__ */ jsx7("div", { className: "vos-belief-bar-label", children: "Pre-registered bar" }),
|
|
2763
3008
|
/* @__PURE__ */ jsxs7("div", { children: [
|
|
2764
3009
|
/* @__PURE__ */ jsx7("strong", { children: "Right if:" }),
|
|
2765
3010
|
" ",
|
|
@@ -2774,8 +3019,7 @@ function ExperimentDetail({
|
|
|
2774
3019
|
"Bar verdict: ",
|
|
2775
3020
|
/* @__PURE__ */ jsx7("strong", { children: bl.barVerdict })
|
|
2776
3021
|
] }) : null
|
|
2777
|
-
] }) : null
|
|
2778
|
-
/* @__PURE__ */ jsx7("div", { className: "vos-belief-why", children: String(b["Grading justification"] ?? "") })
|
|
3022
|
+
] }) : null
|
|
2779
3023
|
] }, b.assumptionId);
|
|
2780
3024
|
})
|
|
2781
3025
|
] })
|
|
@@ -2827,10 +3071,6 @@ function ExperimentDetail({
|
|
|
2827
3071
|
function pct(n, total) {
|
|
2828
3072
|
return total === 0 ? 0 : n / total * 100;
|
|
2829
3073
|
}
|
|
2830
|
-
function truncate(s, max) {
|
|
2831
|
-
if (s.length <= max) return s;
|
|
2832
|
-
return s.slice(0, max - 1) + "\u2026";
|
|
2833
|
-
}
|
|
2834
3074
|
function verdictTone2(verdict) {
|
|
2835
3075
|
if (verdict === "Validated") return "good";
|
|
2836
3076
|
if (verdict === "Invalidated") return "crit";
|
|
@@ -2982,7 +3222,6 @@ function ReadingDetail({
|
|
|
2982
3222
|
const bl = experiment && Array.isArray(experiment.barLines) ? experiment.barLines.find((x) => x?.assumptionId === b.assumptionId) : null;
|
|
2983
3223
|
const result = String(b.Result ?? "Inconclusive");
|
|
2984
3224
|
const justification = String(b["Grading justification"] ?? "");
|
|
2985
|
-
const excerpt = justification || truncate2(body, 120);
|
|
2986
3225
|
return /* @__PURE__ */ jsxs9("div", { className: `vos-belief-card vos-verdict-border-${verdictTone3(result)}`, children: [
|
|
2987
3226
|
/* @__PURE__ */ jsxs9("div", { className: "vos-belief-head", children: [
|
|
2988
3227
|
/* @__PURE__ */ jsx9(
|
|
@@ -2998,11 +3237,11 @@ function ReadingDetail({
|
|
|
2998
3237
|
/* @__PURE__ */ jsx9("span", { className: `vos-pill vos-pill-${verdictTone3(result)}`, children: result }),
|
|
2999
3238
|
/* @__PURE__ */ jsx9("span", { className: "vos-rung-tag", children: rung })
|
|
3000
3239
|
] }),
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
'"'
|
|
3240
|
+
justification ? /* @__PURE__ */ jsxs9("div", { className: `vos-belief-rationale vos-verdict-border-${verdictTone3(result)}`, children: [
|
|
3241
|
+
/* @__PURE__ */ jsx9("span", { className: "vos-belief-rationale-label", children: "grading rationale:" }),
|
|
3242
|
+
justification
|
|
3005
3243
|
] }) : null,
|
|
3244
|
+
body ? /* @__PURE__ */ jsx9("div", { className: "vos-belief-context-link vos-muted", children: "see context above for the full quote" }) : null,
|
|
3006
3245
|
bl ? /* @__PURE__ */ jsxs9("div", { className: "vos-belief-bar", children: [
|
|
3007
3246
|
/* @__PURE__ */ jsx9("div", { className: "vos-belief-bar-label", children: "Pre-registered bar" }),
|
|
3008
3247
|
/* @__PURE__ */ jsxs9("div", { children: [
|
|
@@ -3049,10 +3288,6 @@ function verdictTone3(verdict) {
|
|
|
3049
3288
|
if (verdict === "Invalidated") return "crit";
|
|
3050
3289
|
return "neutral";
|
|
3051
3290
|
}
|
|
3052
|
-
function truncate2(s, max) {
|
|
3053
|
-
if (s.length <= max) return s;
|
|
3054
|
-
return s.slice(0, max - 1) + "\u2026";
|
|
3055
|
-
}
|
|
3056
3291
|
|
|
3057
3292
|
// src/readings-surface.tsx
|
|
3058
3293
|
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
@@ -6800,15 +7035,6 @@ function ValidationOSDashboard({ config = {} }) {
|
|
|
6800
7035
|
brandName
|
|
6801
7036
|
] }),
|
|
6802
7037
|
/* @__PURE__ */ jsxs25("div", { className: "vos-topbar", children: [
|
|
6803
|
-
backendLabel ? /* @__PURE__ */ jsxs25("div", { className: "vos-backend", children: [
|
|
6804
|
-
/* @__PURE__ */ jsx25("span", { className: "vos-live-dot" }),
|
|
6805
|
-
" Backend: ",
|
|
6806
|
-
/* @__PURE__ */ jsx25("b", { children: backendLabel })
|
|
6807
|
-
] }) : null,
|
|
6808
|
-
agentLabel ? /* @__PURE__ */ jsxs25("span", { className: "vos-hint", children: [
|
|
6809
|
-
"Agent: ",
|
|
6810
|
-
/* @__PURE__ */ jsx25("b", { style: { color: "var(--vos-text)" }, children: agentLabel })
|
|
6811
|
-
] }) : null,
|
|
6812
7038
|
/* @__PURE__ */ jsx25("div", { className: "vos-spacer" }),
|
|
6813
7039
|
/* @__PURE__ */ jsx25("button", { type: "button", className: "vos-iconbtn", onClick: toggleTheme, children: "\u25D0 Theme" }),
|
|
6814
7040
|
user?.name ? /* @__PURE__ */ jsxs25("div", { className: "vos-user", children: [
|