@validation-os/dashboard 0.16.1 → 0.16.3

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.d.ts CHANGED
@@ -586,6 +586,9 @@ interface PipelineRow {
586
586
  nextMove: string;
587
587
  /** The assumption's Question Type (DEV-5890) — kind of claim. */
588
588
  questionType: string | null;
589
+ /** The max ceiling for this question type (highest anchor across all
590
+ * rungs) — the Known meter fills relative to this, not the absolute 100. */
591
+ questionTypeCeiling: number | null;
589
592
  /** The assumption's Stage (DEV-5890) — kind of response / threshold. */
590
593
  stage: string | null;
591
594
  /** The stage's Risk threshold (DEV-5890) — the stopping bar. */
@@ -1461,11 +1464,11 @@ declare function NextMoveSurface({ basePath, onNavigate }: NextMoveSurfaceProps)
1461
1464
 
1462
1465
  /** The four discovery stages, in their canonical ordinal order (1→4). The
1463
1466
  * stored value is the name; the ordinal is for sort/display only. */
1464
- declare const STAGE_ORDER: readonly ["Discovery", "Validation", "Scale", "Maturity"];
1465
- type StageValue = (typeof STAGE_ORDER)[number];
1467
+ declare const STAGE_ORDER$1: readonly ["Discovery", "Validation", "Scale", "Maturity"];
1468
+ type StageValue$1 = (typeof STAGE_ORDER$1)[number];
1466
1469
  /** A short, plain-language gloss for each stage — the column header's
1467
1470
  * subtitle and the cell tooltip. Drawn from docs/stage-policy.md. */
1468
- declare const STAGE_GLOSS: Record<StageValue, string>;
1471
+ declare const STAGE_GLOSS: Record<StageValue$1, string>;
1469
1472
  /** One cell of the Lens × Stage grid. */
1470
1473
  interface StageGridCell {
1471
1474
  /** The Lens (row) this cell sits under. */
@@ -1473,7 +1476,7 @@ interface StageGridCell {
1473
1476
  /** The Stage (column) this cell sits under — one of the four canonical
1474
1477
  * stages, or the `NO_STAGE` ("—") sentinel for records with no/invalid
1475
1478
  * Stage (the gate-leakage bucket, only emitted when needed). */
1476
- stage: StageValue | typeof NO_STAGE;
1479
+ stage: StageValue$1 | typeof NO_STAGE$1;
1477
1480
  /** How many assumptions hold this Lens × Stage pair. */
1478
1481
  count: number;
1479
1482
  /** The assumptions in this cell, ranked by Risk (highest first). Empty
@@ -1491,7 +1494,7 @@ interface StageGridView {
1491
1494
  * a trailing "—" row for assumptions with no Lens set. */
1492
1495
  lenses: string[];
1493
1496
  /** The four stages in their canonical ordinal order. */
1494
- stages: StageValue[];
1497
+ stages: StageValue$1[];
1495
1498
  /** One cell per (lens, stage) pair, in row-major order: lens-first,
1496
1499
  * stage-within-lens. */
1497
1500
  cells: StageGridCell[];
@@ -1504,11 +1507,11 @@ interface StageGridView {
1504
1507
  total: number;
1505
1508
  }
1506
1509
  declare const NO_LENS = "\u2014";
1507
- declare const NO_STAGE = "\u2014";
1510
+ declare const NO_STAGE$1 = "\u2014";
1508
1511
  /** A stage value from a record, validated against the fixed vocabulary.
1509
1512
  * Returns null for an empty or unrecognised value — the caller decides
1510
1513
  * whether to bucket those or drop them. */
1511
- declare function stageOf(record: AnyRecord): StageValue | null;
1514
+ declare function stageOf(record: AnyRecord): StageValue$1 | null;
1512
1515
  /** Sort a list of assumptions by Risk, highest first. Stable on tie by id —
1513
1516
  * matches the pipeline board's "riskiest first" convention. Pure: returns a
1514
1517
  * new array, leaves the input alone. */
@@ -1540,7 +1543,7 @@ declare function buildStageGrid(assumptions: AnyRecord[]): StageGridView;
1540
1543
  /** The cell at a given (lens, stage) — null if no such cell (e.g. a stage
1541
1544
  * not in the canonical order). The "—" row / "—" column are addressable
1542
1545
  * with the `NO_LENS` / `NO_STAGE` sentinels. */
1543
- declare function cellAt(view: StageGridView, lens: string, stage: StageValue | typeof NO_STAGE): StageGridCell | null;
1546
+ declare function cellAt(view: StageGridView, lens: string, stage: StageValue$1 | typeof NO_STAGE$1): StageGridCell | null;
1544
1547
 
1545
1548
  /**
1546
1549
  * The Lens × Stage heatmap surface (docs/stage-policy.md §The dashboard
@@ -1570,6 +1573,60 @@ interface StageGridSurfaceProps {
1570
1573
  }
1571
1574
  declare function StageGridSurface({ basePath, onNavigate }: StageGridSurfaceProps): react.JSX.Element;
1572
1575
 
1576
+ /**
1577
+ * The Lens × Stage × Question Type heatmap view-model (DEV-5890) — pure, no
1578
+ * React, no I/O. Extends the 2D Lens × Stage grid with a Question Type
1579
+ * filter axis and Risk-weighted heat.
1580
+ *
1581
+ * The grid is the **filter**, Risk is the **heat**. Each cell's colour
1582
+ * intensity reflects the aggregate Risk (sum of derived.risk) of the
1583
+ * assumptions in that cell, not just the count. A cell with 3 high-Risk
1584
+ * assumptions is hotter than a cell with 10 low-Risk ones.
1585
+ *
1586
+ * The Question Type axis is a filter: selecting a type (e.g. "Existence")
1587
+ * shows only assumptions of that type in the grid. "All" shows everything.
1588
+ */
1589
+
1590
+ declare const STAGE_ORDER: readonly ["Discovery", "Validation", "Scale", "Maturity"];
1591
+ type StageValue = (typeof STAGE_ORDER)[number];
1592
+ /** The 7 question types + "All" for the filter. */
1593
+ declare const QUESTION_TYPE_FILTERS: readonly ["All", "Existence", "Prevalence", "CausalEffect", "WillingnessToPay", "ValueUtility", "Regulatory", "Feasibility"];
1594
+ type QuestionTypeFilter = (typeof QUESTION_TYPE_FILTERS)[number];
1595
+ interface HeatCell {
1596
+ lens: string;
1597
+ stage: StageValue | typeof NO_STAGE;
1598
+ questionType: string | null;
1599
+ count: number;
1600
+ /** The sum of Risk across all assumptions in this cell — the heat value. */
1601
+ totalRisk: number;
1602
+ /** The average Risk per assumption — used for the heat scale. */
1603
+ avgRisk: number;
1604
+ /** The assumptions in this cell, ranked by Risk (highest first). */
1605
+ assumptions: AnyRecord[];
1606
+ /** Heat intensity in [0, 1] — avgRisk / maxAvgRisk across the grid. */
1607
+ heat: number;
1608
+ }
1609
+ interface HeatGridView {
1610
+ lenses: string[];
1611
+ stages: StageValue[];
1612
+ /** The question types present in the data (for the filter tabs). */
1613
+ questionTypes: string[];
1614
+ cells: HeatCell[];
1615
+ maxAvgRisk: number;
1616
+ maxCellCount: number;
1617
+ total: number;
1618
+ }
1619
+ declare const NO_STAGE = "\u2014";
1620
+ /**
1621
+ * Build the Lens × Stage × Question Type grid. When `questionTypeFilter` is
1622
+ * "All" or null, all assumptions are included. When a specific type is
1623
+ * selected, only assumptions of that type are shown. Heat = avg Risk per cell.
1624
+ */
1625
+ declare function buildHeatGrid(assumptions: AnyRecord[], questionTypeFilter?: QuestionTypeFilter): HeatGridView;
1626
+ declare function heatCellAt(view: HeatGridView, lens: string, stage: StageValue | typeof NO_STAGE): HeatCell | null;
1627
+ /** Map a heat value [0, 1] to a 5-step colour scale. */
1628
+ declare function heatLevel(heat: number): 0 | 1 | 2 | 3 | 4;
1629
+
1573
1630
  interface ScoreImpactFormProps {
1574
1631
  /** The assumption being weighted (its version guards the write). */
1575
1632
  assumption: AnyRecord;
@@ -1992,4 +2049,4 @@ declare const REGISTER_GROUPS: {
1992
2049
  registers: Collection[];
1993
2050
  }[];
1994
2051
 
1995
- export { type BacklinkItem, BeliefJourney, type BeliefJourneyProps, type BeliefVerdict, BeliefVerdicts, CONFLICT_MESSAGE, type CellKind, type ColdStart, type ColumnDef, ConfidenceCell, ConnectClaudeCode, type ConnectClaudeCodeProps, type ConnectCommandInput, type Counts, type CycleReadingView, type CycleView, DEFAULT_TOKEN_ENV, DIRECT_CYCLE_KEY, type DashboardConfig, type DetailRelationItem, type DetailRow, type DetailRowKind, type Draft, EditBeliefForm, type EditBeliefFormProps, EditFields, type ExperimentView, FIRST_RUN_LINE, type FieldEditor, FieldInput, type FieldKind, type FormField, type GlossaryTerm, GlossaryText, type GlossaryTextProps, type GroupBucket, type GroupByAxis, type HumanText, type JourneyColdState, type JourneyEventView, type JourneyView, type LinkArgs, type LinkChoice, type LinkifyNode, type LinkifyOptions, Markdown, type Meter, type MeterKind, type MovePresentation, NO_LENS, NO_STAGE, type NeedsHumanByRegister, type NeedsHumanCounts, type NeighbourChip, type NestedGroup, type NextColdStart, type NextMoveRecords, NextMoveSurface, type NextMoveSurfaceProps, type OtherMover, type Pill, type PipelineColdStart, type PipelineRow, PipelineSurface, type PipelineView, REGISTER_GROUPS, REGISTER_ICON, REGISTER_LABEL, REGISTER_ORDER, REGISTER_SINGULAR, REGISTER_SUBTITLE, RISK_CRIT, RISK_WARN, RecordDrawer, type RecordDrawerProps, RecordForm, type RecordFormProps, RecordPage, type RecordPageModel, type RecordPageProps, type RecordTabId, RegisterBrowser, type RegisterBrowserProps, type RegisterContext, RegisterCounts, type RegisterCountsProps, RegisterTable, type RegisterTableProps, type RelatedSet, RelationEditor, type RelationEditorProps, type RelationPanel, type ResolvedBarLine, type ResolvedRow, type RiskBand, RiskBar, type Route, STAGE_GLOSS, STAGE_ORDER, type SaveResult, type SavedView, type ScoreChip, ScoreImpactForm, type ScoreImpactFormProps, type ShapedRegister, SidebarNav, type SidebarNavProps, type SortSpec, Sparkline, type StageGridCell, StageGridSurface, type StageGridSurfaceProps, type StageGridView, type StageMeterInput, type StageMeterView, type StageValue, StatTile, StatusPill, type StepInForm, type StoryStepIn, SurfacePlaceholder, type SurfacePlaceholderProps, type TabDef, type TermPreview, type Tone, type Understanding, UnderstandingPanel, type UseCountsResult, type UseCreateResult, type UseLinkResult, type UseListResult, type UseNeedsHumanResult, type UseRecordResult, type UseSavedViewsResult, type UseUpdateResult, ValidationOSDashboard, type ValidationOSDashboardProps, type ViewDescriptor, WriteDecisionForm, type WriteDecisionFormProps, backlinkPanels, bodyPreview, buildCycles, buildJourney, buildPatch, buildPipeline, buildRecordPage, buildStageGrid, buildUnderstanding, cellAt, cellValue, coldStartFor, columnsFor, composeConnectCommand, confidenceTone, configureRiskBands, defaultTabId, detailRows, dontConfuseWith, draftFrom, editableFields, emptyDraft, eventStepIn, eventTone, filterRecords, formFieldsFor, formatCount, formatRoute, formatSigned, formatValue, groupByAxesFor, groupRecords, hasEdits, headerPills, humanInputFields, interpretSave, journeyColdState, leadingMeters, linkChoicesFrom, linkify, missingRequired, movePresentation, needsHumanCounts, nestReadingsByPlan, ownerNames, parseRoute, primaryLabel, rankByRisk, readingAssumptionChips, readingBeliefVerdicts, resolveBarLines, riskBand, riskFraction, riskLevel, scoreChip, shapeRegister, sortRecords, sparklinePath, stageMeters, stageOf, statusTone, tabsFor, toCreatePayload, toGlossaryTerms, toNextMoveInput, toStageExperimentInput, useCounts, useCreate, useLink, useList, useNeedsHuman, useRecord, useSavedViews, useUpdate, weekOverWeekDelta };
2052
+ export { type BacklinkItem, BeliefJourney, type BeliefJourneyProps, type BeliefVerdict, BeliefVerdicts, CONFLICT_MESSAGE, type CellKind, type ColdStart, type ColumnDef, ConfidenceCell, ConnectClaudeCode, type ConnectClaudeCodeProps, type ConnectCommandInput, type Counts, type CycleReadingView, type CycleView, DEFAULT_TOKEN_ENV, DIRECT_CYCLE_KEY, type DashboardConfig, type DetailRelationItem, type DetailRow, type DetailRowKind, type Draft, EditBeliefForm, type EditBeliefFormProps, EditFields, type ExperimentView, FIRST_RUN_LINE, type FieldEditor, FieldInput, type FieldKind, type FormField, type GlossaryTerm, GlossaryText, type GlossaryTextProps, type GroupBucket, type GroupByAxis, type HeatCell, type HeatGridView, type HumanText, type JourneyColdState, type JourneyEventView, type JourneyView, type LinkArgs, type LinkChoice, type LinkifyNode, type LinkifyOptions, Markdown, type Meter, type MeterKind, type MovePresentation, NO_LENS, NO_STAGE$1 as NO_STAGE, type NeedsHumanByRegister, type NeedsHumanCounts, type NeighbourChip, type NestedGroup, type NextColdStart, type NextMoveRecords, NextMoveSurface, type NextMoveSurfaceProps, type OtherMover, type Pill, type PipelineColdStart, type PipelineRow, PipelineSurface, type PipelineView, QUESTION_TYPE_FILTERS, type QuestionTypeFilter, REGISTER_GROUPS, REGISTER_ICON, REGISTER_LABEL, REGISTER_ORDER, REGISTER_SINGULAR, REGISTER_SUBTITLE, RISK_CRIT, RISK_WARN, RecordDrawer, type RecordDrawerProps, RecordForm, type RecordFormProps, RecordPage, type RecordPageModel, type RecordPageProps, type RecordTabId, RegisterBrowser, type RegisterBrowserProps, type RegisterContext, RegisterCounts, type RegisterCountsProps, RegisterTable, type RegisterTableProps, type RelatedSet, RelationEditor, type RelationEditorProps, type RelationPanel, type ResolvedBarLine, type ResolvedRow, type RiskBand, RiskBar, type Route, STAGE_GLOSS, STAGE_ORDER$1 as STAGE_ORDER, type SaveResult, type SavedView, type ScoreChip, ScoreImpactForm, type ScoreImpactFormProps, type ShapedRegister, SidebarNav, type SidebarNavProps, type SortSpec, Sparkline, type StageGridCell, StageGridSurface, type StageGridSurfaceProps, type StageGridView, type StageMeterInput, type StageMeterView, type StageValue$1 as StageValue, StatTile, StatusPill, type StepInForm, type StoryStepIn, SurfacePlaceholder, type SurfacePlaceholderProps, type TabDef, type TermPreview, type Tone, type Understanding, UnderstandingPanel, type UseCountsResult, type UseCreateResult, type UseLinkResult, type UseListResult, type UseNeedsHumanResult, type UseRecordResult, type UseSavedViewsResult, type UseUpdateResult, ValidationOSDashboard, type ValidationOSDashboardProps, type ViewDescriptor, WriteDecisionForm, type WriteDecisionFormProps, backlinkPanels, bodyPreview, buildCycles, buildHeatGrid, buildJourney, buildPatch, buildPipeline, buildRecordPage, buildStageGrid, buildUnderstanding, cellAt, cellValue, coldStartFor, columnsFor, composeConnectCommand, confidenceTone, configureRiskBands, defaultTabId, detailRows, dontConfuseWith, draftFrom, editableFields, emptyDraft, eventStepIn, eventTone, filterRecords, formFieldsFor, formatCount, formatRoute, formatSigned, formatValue, groupByAxesFor, groupRecords, hasEdits, headerPills, heatCellAt, heatLevel, humanInputFields, interpretSave, journeyColdState, leadingMeters, linkChoicesFrom, linkify, missingRequired, movePresentation, needsHumanCounts, nestReadingsByPlan, ownerNames, parseRoute, primaryLabel, rankByRisk, readingAssumptionChips, readingBeliefVerdicts, resolveBarLines, riskBand, riskFraction, riskLevel, scoreChip, shapeRegister, sortRecords, sparklinePath, stageMeters, stageOf, statusTone, tabsFor, toCreatePayload, toGlossaryTerms, toNextMoveInput, toStageExperimentInput, useCounts, useCreate, useLink, useList, useNeedsHuman, useRecord, useSavedViews, useUpdate, weekOverWeekDelta };
package/dist/index.js CHANGED
@@ -1802,10 +1802,7 @@ function ThresholdBar({
1802
1802
  stage,
1803
1803
  cleared
1804
1804
  }) {
1805
- const riskTrackMax = Math.max(risk, threshold) * 1.25;
1806
- const riskPct = Math.min(100, risk / riskTrackMax * 100);
1807
- const thresholdPct = Math.min(100, threshold / riskTrackMax * 100);
1808
- const confPct = Math.min(100, Math.abs(confidence2));
1805
+ const confPct = Math.min(100, Math.max(0, Math.abs(confidence2)));
1809
1806
  const confFloorPct = Math.min(100, confFloor);
1810
1807
  const riskBelow = risk <= threshold;
1811
1808
  const confAbove = confidence2 >= confFloor;
@@ -1813,49 +1810,30 @@ function ThresholdBar({
1813
1810
  return /* @__PURE__ */ jsxs4("div", { className: "vos-card vos-threshold-bar-card", children: [
1814
1811
  /* @__PURE__ */ jsxs4("div", { className: "vos-threshold-bar-header", children: [
1815
1812
  /* @__PURE__ */ jsxs4("span", { className: "vos-threshold-bar-title", children: [
1816
- stage,
1817
- " evidence bar",
1813
+ "Evidence needed",
1818
1814
  /* @__PURE__ */ jsx4("span", { className: "vos-threshold-bar-stage-tag", children: stage })
1819
1815
  ] }),
1820
1816
  /* @__PURE__ */ jsx4("span", { className: `vos-threshold-bar-status vos-threshold-bar-${tone}`, children: cleared ? "Cleared" : "Needs evidence" })
1821
1817
  ] }),
1822
1818
  /* @__PURE__ */ jsxs4("div", { className: "vos-threshold-bar-section", children: [
1823
1819
  /* @__PURE__ */ jsxs4("div", { className: "vos-threshold-bar-section-label", children: [
1824
- /* @__PURE__ */ jsx4("span", { children: "Risk" }),
1825
- /* @__PURE__ */ jsx4("span", { className: "vos-num vos-threshold-bar-section-val", children: Math.round(risk) }),
1820
+ /* @__PURE__ */ jsx4("span", { children: "Confidence" }),
1821
+ /* @__PURE__ */ jsx4("span", { className: "vos-num vos-threshold-bar-section-val", children: formatSigned(confidence2) }),
1826
1822
  /* @__PURE__ */ jsxs4("span", { className: "vos-threshold-bar-section-target", children: [
1827
- "bar \u2264 ",
1828
- threshold
1823
+ "need \u2265 ",
1824
+ confFloor,
1825
+ " ",
1826
+ cleared ? "\u2713" : ""
1829
1827
  ] })
1830
1828
  ] }),
1831
- /* @__PURE__ */ jsxs4("div", { className: "vos-threshold-bar-track", children: [
1829
+ /* @__PURE__ */ jsxs4("div", { className: "vos-threshold-bar-track vos-threshold-bar-track-tall", children: [
1832
1830
  /* @__PURE__ */ jsx4(
1833
1831
  "div",
1834
1832
  {
1835
- className: `vos-threshold-bar-fill vos-fill-${riskBelow ? "good" : tone}`,
1836
- style: { width: `${riskPct}%` }
1833
+ className: "vos-threshold-bar-target-zone",
1834
+ style: { left: `${confFloorPct}%` }
1837
1835
  }
1838
1836
  ),
1839
- /* @__PURE__ */ jsx4(
1840
- "div",
1841
- {
1842
- className: "vos-threshold-bar-marker",
1843
- style: { left: `${thresholdPct}%` },
1844
- title: `Risk threshold: ${threshold}`
1845
- }
1846
- )
1847
- ] })
1848
- ] }),
1849
- /* @__PURE__ */ jsxs4("div", { className: "vos-threshold-bar-section", children: [
1850
- /* @__PURE__ */ jsxs4("div", { className: "vos-threshold-bar-section-label", children: [
1851
- /* @__PURE__ */ jsx4("span", { children: "Confidence" }),
1852
- /* @__PURE__ */ jsx4("span", { className: "vos-num vos-threshold-bar-section-val", children: formatSigned(confidence2) }),
1853
- /* @__PURE__ */ jsxs4("span", { className: "vos-threshold-bar-section-target", children: [
1854
- "floor \u2265 ",
1855
- confFloor
1856
- ] })
1857
- ] }),
1858
- /* @__PURE__ */ jsxs4("div", { className: "vos-threshold-bar-track vos-threshold-bar-track-conf", children: [
1859
1837
  /* @__PURE__ */ jsx4(
1860
1838
  "div",
1861
1839
  {
@@ -1866,14 +1844,32 @@ function ThresholdBar({
1866
1844
  /* @__PURE__ */ jsx4(
1867
1845
  "div",
1868
1846
  {
1869
- className: "vos-threshold-bar-marker vos-threshold-bar-marker-conf",
1847
+ className: "vos-threshold-bar-marker",
1870
1848
  style: { left: `${confFloorPct}%` },
1871
- title: `Confidence floor: ${confFloor}`
1849
+ title: `Need Confidence \u2265 ${confFloor} for ${stage}`,
1850
+ children: /* @__PURE__ */ jsx4("span", { className: "vos-threshold-bar-marker-label vos-num", children: confFloor })
1872
1851
  }
1873
1852
  )
1853
+ ] }),
1854
+ /* @__PURE__ */ jsxs4("div", { className: "vos-threshold-bar-scale", children: [
1855
+ /* @__PURE__ */ jsx4("span", { className: "vos-num", children: "0" }),
1856
+ /* @__PURE__ */ jsx4("span", { className: "vos-num vos-threshold-bar-scale-mid", children: "50" }),
1857
+ /* @__PURE__ */ jsx4("span", { className: "vos-num", children: "100" })
1858
+ ] })
1859
+ ] }),
1860
+ /* @__PURE__ */ jsxs4("div", { className: "vos-threshold-bar-risk-summary", children: [
1861
+ /* @__PURE__ */ jsxs4("span", { className: `vos-threshold-bar-risk-val vos-text-${riskBelow ? "good" : "crit"}`, children: [
1862
+ "Risk ",
1863
+ Math.round(risk)
1864
+ ] }),
1865
+ /* @__PURE__ */ jsxs4("span", { className: "vos-threshold-bar-risk-cap", children: [
1866
+ "max ",
1867
+ threshold,
1868
+ " for ",
1869
+ stage
1874
1870
  ] })
1875
1871
  ] }),
1876
- /* @__PURE__ */ jsx4("div", { className: "vos-threshold-bar-hint", children: cleared ? `Risk ${Math.round(risk)} \u2264 ${threshold} and Confidence ${formatSigned(confidence2)} \u2265 ${confFloor} \u2014 cleared for ${stage}.` : `Risk ${Math.round(risk)} ${riskBelow ? "\u2264" : ">"} ${threshold}, Confidence ${formatSigned(confidence2)} ${confAbove ? "\u2265" : "<"} ${confFloor} \u2014 needs ${!riskBelow ? "lower Risk" : ""}${!riskBelow && !confAbove ? " + " : ""}${!confAbove ? "higher Confidence" : ""}.` })
1872
+ /* @__PURE__ */ jsx4("div", { className: "vos-threshold-bar-hint", children: cleared ? `Confidence ${formatSigned(confidence2)} \u2265 ${confFloor} and Risk ${Math.round(risk)} \u2264 ${threshold} \u2014 cleared for ${stage}.` : `Need Confidence \u2265 ${confFloor}${!confAbove ? ` (now ${formatSigned(confidence2)})` : " \u2713"} and Risk \u2264 ${threshold}${!riskBelow ? ` (now ${Math.round(risk)})` : " \u2713"}.` })
1877
1873
  ] });
1878
1874
  }
1879
1875
  function riskTone(risk) {
@@ -2210,7 +2206,11 @@ import {
2210
2206
  assumptionCompleteness,
2211
2207
  readingBeliefInputs
2212
2208
  } from "@validation-os/core";
2213
- import { confidenceFloorForStage as confidenceFloorForStage2, riskThresholdForStage as riskThresholdForStage2 } from "@validation-os/core/derivation";
2209
+ import {
2210
+ confidenceFloorForStage as confidenceFloorForStage2,
2211
+ riskThresholdForStage as riskThresholdForStage2,
2212
+ RUNG_ANCHOR as RUNG_ANCHOR3
2213
+ } from "@validation-os/core/derivation";
2214
2214
 
2215
2215
  // src/stage-grid-model.ts
2216
2216
  var STAGE_ORDER = [
@@ -2407,6 +2407,13 @@ function buildPipeline(assumptions, experiments) {
2407
2407
  const framed = assumptionCompleteness(a);
2408
2408
  const stage = deriveBeliefStage({ framed, confidence: d.confidence, test });
2409
2409
  const questionType = str3(a["Question Type"]);
2410
+ let questionTypeCeiling = null;
2411
+ if (questionType && questionType in RUNG_ANCHOR3) {
2412
+ const subLadder = RUNG_ANCHOR3[questionType];
2413
+ questionTypeCeiling = Math.max(
2414
+ ...Object.values(subLadder).map((r) => r.High)
2415
+ );
2416
+ }
2410
2417
  const stageName = str3(a.Stage);
2411
2418
  const stageKey = stageName && STAGE_ORDER.includes(stageName) ? stageName : null;
2412
2419
  const riskThreshold = stageKey ? riskThresholdForStage2(stageKey) : null;
@@ -2425,6 +2432,7 @@ function buildPipeline(assumptions, experiments) {
2425
2432
  tested: stage.tested,
2426
2433
  nextMove: nextMove(stage.stage, stage.killZone),
2427
2434
  questionType,
2435
+ questionTypeCeiling,
2428
2436
  stage: stageName,
2429
2437
  riskThreshold,
2430
2438
  clearedThreshold: riskThreshold != null ? d.risk <= riskThreshold && (confFloor == null || stage.confidence >= confFloor) : null
@@ -2672,6 +2680,148 @@ function stageMeters(input) {
2672
2680
  ];
2673
2681
  }
2674
2682
 
2683
+ // src/heat-grid-model.ts
2684
+ var STAGE_ORDER2 = [
2685
+ "Discovery",
2686
+ "Validation",
2687
+ "Scale",
2688
+ "Maturity"
2689
+ ];
2690
+ var STAGE_GLOSS2 = {
2691
+ Discovery: "Problem-solution fit \u2014 will they engage, care, disclose?",
2692
+ Validation: "Product-market fit \u2014 will they pay, sign, stay?",
2693
+ Scale: "Growth \u2014 can we acquire efficiently, does CAC<LTV hold at volume?",
2694
+ Maturity: "Defense \u2014 will incumbents respond, will regulators accept?"
2695
+ };
2696
+ var QUESTION_TYPE_FILTERS = [
2697
+ "All",
2698
+ "Existence",
2699
+ "Prevalence",
2700
+ "CausalEffect",
2701
+ "WillingnessToPay",
2702
+ "ValueUtility",
2703
+ "Regulatory",
2704
+ "Feasibility"
2705
+ ];
2706
+ var NO_LENS2 = "\u2014";
2707
+ var NO_STAGE2 = "\u2014";
2708
+ function stageOf2(record) {
2709
+ const v = str(record.Stage);
2710
+ if (!v) return null;
2711
+ return STAGE_ORDER2.includes(v) ? v : null;
2712
+ }
2713
+ function questionTypeOf(record) {
2714
+ return str(record["Question Type"]);
2715
+ }
2716
+ function rankByRisk2(records) {
2717
+ return [...records].map((r) => ({ r, risk: derivedNum(r, "risk") ?? 0 })).sort(
2718
+ (a, b) => a.risk !== b.risk ? b.risk - a.risk : a.r.id.localeCompare(b.r.id)
2719
+ ).map((x) => x.r);
2720
+ }
2721
+ function buildHeatGrid(assumptions, questionTypeFilter = "All") {
2722
+ const filtered = questionTypeFilter === "All" || !questionTypeFilter ? assumptions : assumptions.filter((a) => questionTypeOf(a) === questionTypeFilter);
2723
+ const lensOrder = [];
2724
+ const seenLens = /* @__PURE__ */ new Set();
2725
+ const pushLens = (lens) => {
2726
+ if (!seenLens.has(lens)) {
2727
+ seenLens.add(lens);
2728
+ lensOrder.push(lens);
2729
+ }
2730
+ };
2731
+ const buckets = /* @__PURE__ */ new Map();
2732
+ const ensureLens = (lens) => {
2733
+ let m = buckets.get(lens);
2734
+ if (!m) {
2735
+ m = /* @__PURE__ */ new Map();
2736
+ buckets.set(lens, m);
2737
+ }
2738
+ return m;
2739
+ };
2740
+ let hasNoLens = false;
2741
+ let hasNoStage = false;
2742
+ for (const a of filtered) {
2743
+ const lens = str(a.Lens) ?? NO_LENS2;
2744
+ const stage = stageOf2(a) ?? NO_STAGE2;
2745
+ if (lens === NO_LENS2) hasNoLens = true;
2746
+ if (stage === NO_STAGE2) hasNoStage = true;
2747
+ pushLens(lens);
2748
+ const byStage = ensureLens(lens);
2749
+ let list = byStage.get(stage);
2750
+ if (!list) {
2751
+ list = [];
2752
+ byStage.set(stage, list);
2753
+ }
2754
+ list.push(a);
2755
+ }
2756
+ if (hasNoLens) {
2757
+ lensOrder.splice(lensOrder.indexOf(NO_LENS2), 1);
2758
+ lensOrder.push(NO_LENS2);
2759
+ } else {
2760
+ const idx = lensOrder.indexOf(NO_LENS2);
2761
+ if (idx >= 0) lensOrder.splice(idx, 1);
2762
+ }
2763
+ const stageOrder = [...STAGE_ORDER2];
2764
+ if (hasNoStage) stageOrder.push(NO_STAGE2);
2765
+ const cells = [];
2766
+ let maxAvgRisk = 0;
2767
+ let maxCellCount = 0;
2768
+ let total = 0;
2769
+ for (const lens of lensOrder) {
2770
+ const byStage = buckets.get(lens) ?? /* @__PURE__ */ new Map();
2771
+ for (const stage of stageOrder) {
2772
+ const records = byStage.get(stage) ?? [];
2773
+ const ranked = rankByRisk2(records);
2774
+ const count = ranked.length;
2775
+ const totalRisk = ranked.reduce(
2776
+ (sum, r) => sum + (derivedNum(r, "risk") ?? 0),
2777
+ 0
2778
+ );
2779
+ const avgRisk = count > 0 ? totalRisk / count : 0;
2780
+ maxAvgRisk = Math.max(maxAvgRisk, avgRisk);
2781
+ maxCellCount = Math.max(maxCellCount, count);
2782
+ total += count;
2783
+ cells.push({
2784
+ lens,
2785
+ stage,
2786
+ questionType: questionTypeFilter === "All" ? null : questionTypeFilter,
2787
+ count,
2788
+ totalRisk,
2789
+ avgRisk,
2790
+ assumptions: ranked,
2791
+ heat: 0
2792
+ });
2793
+ }
2794
+ }
2795
+ const norm2 = maxAvgRisk > 0 ? 1 / maxAvgRisk : 0;
2796
+ for (const cell of cells) {
2797
+ cell.heat = cell.avgRisk * norm2;
2798
+ }
2799
+ const qtSet = /* @__PURE__ */ new Set();
2800
+ for (const a of assumptions) {
2801
+ const qt = questionTypeOf(a);
2802
+ if (qt) qtSet.add(qt);
2803
+ }
2804
+ return {
2805
+ lenses: lensOrder,
2806
+ stages: [...STAGE_ORDER2],
2807
+ questionTypes: [...qtSet].sort(),
2808
+ cells,
2809
+ maxAvgRisk,
2810
+ maxCellCount,
2811
+ total
2812
+ };
2813
+ }
2814
+ function heatCellAt(view, lens, stage) {
2815
+ return view.cells.find((c) => c.lens === lens && c.stage === stage) ?? null;
2816
+ }
2817
+ function heatLevel(heat) {
2818
+ if (heat <= 0) return 0;
2819
+ if (heat < 0.2) return 1;
2820
+ if (heat < 0.4) return 2;
2821
+ if (heat < 0.7) return 3;
2822
+ return 4;
2823
+ }
2824
+
2675
2825
  // src/assumptions-surface.tsx
2676
2826
  import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2677
2827
  function AssumptionsSurface({
@@ -2746,7 +2896,11 @@ function GridPane({
2746
2896
  readings,
2747
2897
  onNavigate
2748
2898
  }) {
2749
- const view = useMemo2(() => buildStageGrid(assumptions), [assumptions]);
2899
+ const [qtFilter, setQtFilter] = useState3("All");
2900
+ const view = useMemo2(
2901
+ () => buildHeatGrid(assumptions, qtFilter),
2902
+ [assumptions, qtFilter]
2903
+ );
2750
2904
  const recs = useMemo2(
2751
2905
  () => buildRecommendedExperiments(assumptions, experiments),
2752
2906
  [assumptions, experiments]
@@ -2787,40 +2941,67 @@ function GridPane({
2787
2941
  } else {
2788
2942
  onNavigate({
2789
2943
  name: "assumptions",
2790
- lens: cell.lens === NO_LENS ? void 0 : cell.lens,
2791
- stage: cell.stage === NO_STAGE ? void 0 : cell.stage
2944
+ lens: cell.lens === NO_LENS2 ? void 0 : cell.lens,
2945
+ stage: cell.stage === NO_STAGE2 ? void 0 : cell.stage
2792
2946
  });
2793
2947
  }
2794
2948
  }
2949
+ const qtTabs = QUESTION_TYPE_FILTERS.filter(
2950
+ (qt) => qt === "All" || view.questionTypes.includes(qt)
2951
+ );
2795
2952
  return /* @__PURE__ */ jsxs5(Fragment4, { children: [
2796
2953
  /* @__PURE__ */ jsxs5("div", { className: "vos-card vos-stage-grid-card", children: [
2797
- /* @__PURE__ */ jsx5("div", { className: "vos-stage-grid-scroll", children: /* @__PURE__ */ jsxs5("table", { className: "vos-stage-grid", role: "grid", "aria-label": "Lens \xD7 Stage heatmap", children: [
2954
+ /* @__PURE__ */ jsx5("div", { className: "vos-qt-filter-bar", children: qtTabs.map((qt) => /* @__PURE__ */ jsx5(
2955
+ "button",
2956
+ {
2957
+ type: "button",
2958
+ className: `vos-qt-tab ${qtFilter === qt ? "vos-qt-tab-active" : ""}`,
2959
+ onClick: () => setQtFilter(qt),
2960
+ children: qt
2961
+ },
2962
+ qt
2963
+ )) }),
2964
+ /* @__PURE__ */ jsx5("div", { className: "vos-stage-grid-scroll", children: /* @__PURE__ */ jsxs5("table", { className: "vos-stage-grid", role: "grid", "aria-label": "Lens \xD7 Stage heatmap (heat = Risk)", children: [
2798
2965
  /* @__PURE__ */ jsx5("thead", { children: /* @__PURE__ */ jsxs5("tr", { children: [
2799
2966
  /* @__PURE__ */ jsx5("th", { scope: "col", className: "vos-stage-grid-corner", children: "Lens \u2193 / Stage \u2192" }),
2800
2967
  view.stages.map((s) => /* @__PURE__ */ jsxs5("th", { scope: "col", className: "vos-stage-grid-col", children: [
2801
2968
  /* @__PURE__ */ jsx5("span", { className: "vos-stage-grid-stagename", children: s }),
2802
- /* @__PURE__ */ jsx5("span", { className: "vos-stage-grid-stagegloss", children: STAGE_GLOSS[s] })
2969
+ /* @__PURE__ */ jsx5("span", { className: "vos-stage-grid-stagegloss", children: STAGE_GLOSS2[s] })
2803
2970
  ] }, s))
2804
2971
  ] }) }),
2805
2972
  /* @__PURE__ */ jsx5("tbody", { children: view.lenses.map((lens) => /* @__PURE__ */ jsxs5("tr", { children: [
2806
2973
  /* @__PURE__ */ jsx5("th", { scope: "row", className: "vos-stage-grid-rowhead", children: lens }),
2807
2974
  view.stages.map((s) => {
2808
- const cell = cellAt(view, lens, s);
2975
+ const cell = heatCellAt(view, lens, s);
2809
2976
  return /* @__PURE__ */ jsx5("td", { className: "vos-stage-grid-cell", children: /* @__PURE__ */ jsx5(
2810
2977
  "button",
2811
2978
  {
2812
2979
  type: "button",
2813
- className: `vos-stage-grid-btn vos-heat-${heatLevel(cell.density)}`,
2980
+ className: `vos-stage-grid-btn vos-heat-${heatLevel(cell.heat)}`,
2814
2981
  disabled: cell.count === 0,
2815
2982
  onClick: () => cellClick(cell),
2816
- "aria-label": `${cell.count} assumptions in ${lens} \xD7 ${s}`,
2983
+ title: `${cell.count} assumptions \xB7 avg Risk ${Math.round(cell.avgRisk)} \xB7 total Risk ${Math.round(cell.totalRisk)}`,
2984
+ "aria-label": `${cell.count} assumptions in ${lens} \xD7 ${s}, average risk ${Math.round(cell.avgRisk)}`,
2817
2985
  children: cell.count === 0 ? "\xB7" : cell.count === 1 ? "1" : String(cell.count)
2818
2986
  }
2819
2987
  ) }, s);
2820
2988
  })
2821
2989
  ] }, lens)) })
2822
2990
  ] }) }),
2823
- /* @__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." })
2991
+ /* @__PURE__ */ jsxs5("div", { className: "vos-heat-legend", children: [
2992
+ /* @__PURE__ */ jsx5("span", { className: "vos-heat-legend-label", children: "Risk heat:" }),
2993
+ /* @__PURE__ */ jsx5("span", { className: "vos-heat-swatch vos-heat-0" }),
2994
+ " low",
2995
+ /* @__PURE__ */ jsx5("span", { className: "vos-heat-swatch vos-heat-1" }),
2996
+ /* @__PURE__ */ jsx5("span", { className: "vos-heat-swatch vos-heat-2" }),
2997
+ /* @__PURE__ */ jsx5("span", { className: "vos-heat-swatch vos-heat-3" }),
2998
+ /* @__PURE__ */ jsx5("span", { className: "vos-heat-swatch vos-heat-4" }),
2999
+ " high"
3000
+ ] }),
3001
+ /* @__PURE__ */ jsxs5("p", { className: "vos-hint vos-stage-grid-foot", children: [
3002
+ "Heat = average Risk per cell. Click a cell to drill into its assumptions, ranked by Risk. Filter by Question Type above.",
3003
+ qtFilter !== "All" ? ` Showing ${qtFilter} claims only.` : ""
3004
+ ] })
2824
3005
  ] }),
2825
3006
  needsFraming.length > 0 || recs.length > 0 ? /* @__PURE__ */ jsx5(
2826
3007
  NextMovesSection,
@@ -2832,13 +3013,6 @@ function GridPane({
2832
3013
  ) : null
2833
3014
  ] });
2834
3015
  }
2835
- function heatLevel(density) {
2836
- if (density <= 0) return 0;
2837
- if (density < 0.25) return 1;
2838
- if (density < 0.5) return 2;
2839
- if (density < 0.75) return 3;
2840
- return 4;
2841
- }
2842
3016
  function PipelineBoard({
2843
3017
  assumptions,
2844
3018
  experiments,
@@ -2850,8 +3024,8 @@ function PipelineBoard({
2850
3024
  const filtered = useMemo2(() => {
2851
3025
  if (filterLens === void 0 && filterStage === void 0) return assumptions;
2852
3026
  return assumptions.filter((a) => {
2853
- const l = String(a.Lens ?? NO_LENS);
2854
- const s = String(a.Stage ?? NO_STAGE);
3027
+ const l = String(a.Lens ?? NO_LENS2);
3028
+ const s = String(a.Stage ?? NO_STAGE2);
2855
3029
  return (filterLens === void 0 || l === filterLens) && (filterStage === void 0 || s === filterStage);
2856
3030
  });
2857
3031
  }, [assumptions, filterLens, filterStage]);
@@ -2919,7 +3093,8 @@ function PipelineBoard({
2919
3093
  }
2920
3094
  function PipelineRowView({ row, onOpen }) {
2921
3095
  const stripeTone = row.riskTone;
2922
- const knownPct = Math.max(0, Math.min(100, Math.abs(row.confidence)));
3096
+ const ceiling = row.questionTypeCeiling ?? 99;
3097
+ const knownPct = Math.max(0, Math.min(100, Math.abs(row.confidence) / ceiling * 100));
2923
3098
  const knownSign = row.confSign;
2924
3099
  return /* @__PURE__ */ jsxs5("div", { className: "vos-pipe-row vos-pipe-row-2seg", children: [
2925
3100
  /* @__PURE__ */ jsx5("div", { className: `vos-pipe-stripe vos-fill-${stripeTone}` }),
@@ -8498,6 +8673,7 @@ export {
8498
8673
  NO_STAGE,
8499
8674
  NextMoveSurface,
8500
8675
  PipelineSurface,
8676
+ QUESTION_TYPE_FILTERS,
8501
8677
  REGISTER_GROUPS,
8502
8678
  REGISTER_ICON,
8503
8679
  REGISTER_LABEL,
@@ -8529,6 +8705,7 @@ export {
8529
8705
  backlinkPanels,
8530
8706
  bodyPreview,
8531
8707
  buildCycles,
8708
+ buildHeatGrid,
8532
8709
  buildJourney,
8533
8710
  buildPatch,
8534
8711
  buildPipeline,
@@ -8560,6 +8737,8 @@ export {
8560
8737
  groupRecords,
8561
8738
  hasEdits,
8562
8739
  headerPills,
8740
+ heatCellAt,
8741
+ heatLevel,
8563
8742
  humanInputFields,
8564
8743
  interpretSave,
8565
8744
  journeyColdState,