@validation-os/dashboard 0.16.2 → 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
@@ -1464,11 +1464,11 @@ declare function NextMoveSurface({ basePath, onNavigate }: NextMoveSurfaceProps)
1464
1464
 
1465
1465
  /** The four discovery stages, in their canonical ordinal order (1→4). The
1466
1466
  * stored value is the name; the ordinal is for sort/display only. */
1467
- declare const STAGE_ORDER: readonly ["Discovery", "Validation", "Scale", "Maturity"];
1468
- 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];
1469
1469
  /** A short, plain-language gloss for each stage — the column header's
1470
1470
  * subtitle and the cell tooltip. Drawn from docs/stage-policy.md. */
1471
- declare const STAGE_GLOSS: Record<StageValue, string>;
1471
+ declare const STAGE_GLOSS: Record<StageValue$1, string>;
1472
1472
  /** One cell of the Lens × Stage grid. */
1473
1473
  interface StageGridCell {
1474
1474
  /** The Lens (row) this cell sits under. */
@@ -1476,7 +1476,7 @@ interface StageGridCell {
1476
1476
  /** The Stage (column) this cell sits under — one of the four canonical
1477
1477
  * stages, or the `NO_STAGE` ("—") sentinel for records with no/invalid
1478
1478
  * Stage (the gate-leakage bucket, only emitted when needed). */
1479
- stage: StageValue | typeof NO_STAGE;
1479
+ stage: StageValue$1 | typeof NO_STAGE$1;
1480
1480
  /** How many assumptions hold this Lens × Stage pair. */
1481
1481
  count: number;
1482
1482
  /** The assumptions in this cell, ranked by Risk (highest first). Empty
@@ -1494,7 +1494,7 @@ interface StageGridView {
1494
1494
  * a trailing "—" row for assumptions with no Lens set. */
1495
1495
  lenses: string[];
1496
1496
  /** The four stages in their canonical ordinal order. */
1497
- stages: StageValue[];
1497
+ stages: StageValue$1[];
1498
1498
  /** One cell per (lens, stage) pair, in row-major order: lens-first,
1499
1499
  * stage-within-lens. */
1500
1500
  cells: StageGridCell[];
@@ -1507,11 +1507,11 @@ interface StageGridView {
1507
1507
  total: number;
1508
1508
  }
1509
1509
  declare const NO_LENS = "\u2014";
1510
- declare const NO_STAGE = "\u2014";
1510
+ declare const NO_STAGE$1 = "\u2014";
1511
1511
  /** A stage value from a record, validated against the fixed vocabulary.
1512
1512
  * Returns null for an empty or unrecognised value — the caller decides
1513
1513
  * whether to bucket those or drop them. */
1514
- declare function stageOf(record: AnyRecord): StageValue | null;
1514
+ declare function stageOf(record: AnyRecord): StageValue$1 | null;
1515
1515
  /** Sort a list of assumptions by Risk, highest first. Stable on tie by id —
1516
1516
  * matches the pipeline board's "riskiest first" convention. Pure: returns a
1517
1517
  * new array, leaves the input alone. */
@@ -1543,7 +1543,7 @@ declare function buildStageGrid(assumptions: AnyRecord[]): StageGridView;
1543
1543
  /** The cell at a given (lens, stage) — null if no such cell (e.g. a stage
1544
1544
  * not in the canonical order). The "—" row / "—" column are addressable
1545
1545
  * with the `NO_LENS` / `NO_STAGE` sentinels. */
1546
- 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;
1547
1547
 
1548
1548
  /**
1549
1549
  * The Lens × Stage heatmap surface (docs/stage-policy.md §The dashboard
@@ -1573,6 +1573,60 @@ interface StageGridSurfaceProps {
1573
1573
  }
1574
1574
  declare function StageGridSurface({ basePath, onNavigate }: StageGridSurfaceProps): react.JSX.Element;
1575
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
+
1576
1630
  interface ScoreImpactFormProps {
1577
1631
  /** The assumption being weighted (its version guards the write). */
1578
1632
  assumption: AnyRecord;
@@ -1995,4 +2049,4 @@ declare const REGISTER_GROUPS: {
1995
2049
  registers: Collection[];
1996
2050
  }[];
1997
2051
 
1998
- 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
@@ -2680,6 +2680,148 @@ function stageMeters(input) {
2680
2680
  ];
2681
2681
  }
2682
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
+
2683
2825
  // src/assumptions-surface.tsx
2684
2826
  import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2685
2827
  function AssumptionsSurface({
@@ -2754,7 +2896,11 @@ function GridPane({
2754
2896
  readings,
2755
2897
  onNavigate
2756
2898
  }) {
2757
- const view = useMemo2(() => buildStageGrid(assumptions), [assumptions]);
2899
+ const [qtFilter, setQtFilter] = useState3("All");
2900
+ const view = useMemo2(
2901
+ () => buildHeatGrid(assumptions, qtFilter),
2902
+ [assumptions, qtFilter]
2903
+ );
2758
2904
  const recs = useMemo2(
2759
2905
  () => buildRecommendedExperiments(assumptions, experiments),
2760
2906
  [assumptions, experiments]
@@ -2795,40 +2941,67 @@ function GridPane({
2795
2941
  } else {
2796
2942
  onNavigate({
2797
2943
  name: "assumptions",
2798
- lens: cell.lens === NO_LENS ? void 0 : cell.lens,
2799
- 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
2800
2946
  });
2801
2947
  }
2802
2948
  }
2949
+ const qtTabs = QUESTION_TYPE_FILTERS.filter(
2950
+ (qt) => qt === "All" || view.questionTypes.includes(qt)
2951
+ );
2803
2952
  return /* @__PURE__ */ jsxs5(Fragment4, { children: [
2804
2953
  /* @__PURE__ */ jsxs5("div", { className: "vos-card vos-stage-grid-card", children: [
2805
- /* @__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: [
2806
2965
  /* @__PURE__ */ jsx5("thead", { children: /* @__PURE__ */ jsxs5("tr", { children: [
2807
2966
  /* @__PURE__ */ jsx5("th", { scope: "col", className: "vos-stage-grid-corner", children: "Lens \u2193 / Stage \u2192" }),
2808
2967
  view.stages.map((s) => /* @__PURE__ */ jsxs5("th", { scope: "col", className: "vos-stage-grid-col", children: [
2809
2968
  /* @__PURE__ */ jsx5("span", { className: "vos-stage-grid-stagename", children: s }),
2810
- /* @__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] })
2811
2970
  ] }, s))
2812
2971
  ] }) }),
2813
2972
  /* @__PURE__ */ jsx5("tbody", { children: view.lenses.map((lens) => /* @__PURE__ */ jsxs5("tr", { children: [
2814
2973
  /* @__PURE__ */ jsx5("th", { scope: "row", className: "vos-stage-grid-rowhead", children: lens }),
2815
2974
  view.stages.map((s) => {
2816
- const cell = cellAt(view, lens, s);
2975
+ const cell = heatCellAt(view, lens, s);
2817
2976
  return /* @__PURE__ */ jsx5("td", { className: "vos-stage-grid-cell", children: /* @__PURE__ */ jsx5(
2818
2977
  "button",
2819
2978
  {
2820
2979
  type: "button",
2821
- className: `vos-stage-grid-btn vos-heat-${heatLevel(cell.density)}`,
2980
+ className: `vos-stage-grid-btn vos-heat-${heatLevel(cell.heat)}`,
2822
2981
  disabled: cell.count === 0,
2823
2982
  onClick: () => cellClick(cell),
2824
- "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)}`,
2825
2985
  children: cell.count === 0 ? "\xB7" : cell.count === 1 ? "1" : String(cell.count)
2826
2986
  }
2827
2987
  ) }, s);
2828
2988
  })
2829
2989
  ] }, lens)) })
2830
2990
  ] }) }),
2831
- /* @__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
+ ] })
2832
3005
  ] }),
2833
3006
  needsFraming.length > 0 || recs.length > 0 ? /* @__PURE__ */ jsx5(
2834
3007
  NextMovesSection,
@@ -2840,13 +3013,6 @@ function GridPane({
2840
3013
  ) : null
2841
3014
  ] });
2842
3015
  }
2843
- function heatLevel(density) {
2844
- if (density <= 0) return 0;
2845
- if (density < 0.25) return 1;
2846
- if (density < 0.5) return 2;
2847
- if (density < 0.75) return 3;
2848
- return 4;
2849
- }
2850
3016
  function PipelineBoard({
2851
3017
  assumptions,
2852
3018
  experiments,
@@ -2858,8 +3024,8 @@ function PipelineBoard({
2858
3024
  const filtered = useMemo2(() => {
2859
3025
  if (filterLens === void 0 && filterStage === void 0) return assumptions;
2860
3026
  return assumptions.filter((a) => {
2861
- const l = String(a.Lens ?? NO_LENS);
2862
- const s = String(a.Stage ?? NO_STAGE);
3027
+ const l = String(a.Lens ?? NO_LENS2);
3028
+ const s = String(a.Stage ?? NO_STAGE2);
2863
3029
  return (filterLens === void 0 || l === filterLens) && (filterStage === void 0 || s === filterStage);
2864
3030
  });
2865
3031
  }, [assumptions, filterLens, filterStage]);
@@ -8507,6 +8673,7 @@ export {
8507
8673
  NO_STAGE,
8508
8674
  NextMoveSurface,
8509
8675
  PipelineSurface,
8676
+ QUESTION_TYPE_FILTERS,
8510
8677
  REGISTER_GROUPS,
8511
8678
  REGISTER_ICON,
8512
8679
  REGISTER_LABEL,
@@ -8538,6 +8705,7 @@ export {
8538
8705
  backlinkPanels,
8539
8706
  bodyPreview,
8540
8707
  buildCycles,
8708
+ buildHeatGrid,
8541
8709
  buildJourney,
8542
8710
  buildPatch,
8543
8711
  buildPipeline,
@@ -8569,6 +8737,8 @@ export {
8569
8737
  groupRecords,
8570
8738
  hasEdits,
8571
8739
  headerPills,
8740
+ heatCellAt,
8741
+ heatLevel,
8572
8742
  humanInputFields,
8573
8743
  interpretSave,
8574
8744
  journeyColdState,