@ai-matrx/design-system 0.61.17 → 0.61.18

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/data-table/copy-helpers.d.cts +2 -2
  3. package/dist/data-table/copy-helpers.d.ts +2 -2
  4. package/dist/data-table/drill-chart.cjs.map +1 -1
  5. package/dist/data-table/drill-chart.d.cts +2 -2
  6. package/dist/data-table/drill-chart.d.ts +2 -2
  7. package/dist/data-table/drill-chart.js.map +1 -1
  8. package/dist/data-table/filter-engine.d.cts +2 -2
  9. package/dist/data-table/filter-engine.d.ts +2 -2
  10. package/dist/data-table/host.d.cts +2 -2
  11. package/dist/data-table/host.d.ts +2 -2
  12. package/dist/data-table/index.cjs.map +1 -1
  13. package/dist/data-table/index.d.cts +4 -4
  14. package/dist/data-table/index.d.ts +4 -4
  15. package/dist/data-table/index.js.map +1 -1
  16. package/dist/data-table/infer-filter.d.cts +2 -2
  17. package/dist/data-table/infer-filter.d.ts +2 -2
  18. package/dist/data-table/layered-filters.d.cts +2 -2
  19. package/dist/data-table/layered-filters.d.ts +2 -2
  20. package/dist/data-table/query-control.d.cts +2 -2
  21. package/dist/data-table/query-control.d.ts +2 -2
  22. package/dist/data-table/types.d.cts +2 -2
  23. package/dist/data-table/types.d.ts +2 -2
  24. package/dist/data-table/url-state.d.cts +2 -2
  25. package/dist/data-table/url-state.d.ts +2 -2
  26. package/dist/data-table/xlsx.d.cts +2 -2
  27. package/dist/data-table/xlsx.d.ts +2 -2
  28. package/dist/{drill-BjC7CLHX.d.cts → drill-CqAuF9dK.d.cts} +1 -1
  29. package/dist/{drill-BjC7CLHX.d.ts → drill-CqAuF9dK.d.ts} +1 -1
  30. package/dist/{layered-filters-P0gCSV77.d.ts → layered-filters-Cd_lUKZf.d.ts} +1 -1
  31. package/dist/{layered-filters-yUjWTbsv.d.cts → layered-filters-DA-viRyX.d.cts} +1 -1
  32. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/data-table/DrillChart.tsx","../../src/cn.ts","../../src/error-box.tsx","../../src/motion.ts","../../src/skeleton.tsx","../../src/data-table/drill.ts"],"sourcesContent":["\"use client\";\n\n/**\n * THE DRILL CHART — `@ai-matrx/design-system/data-table/drill-chart` (lane DRILL-CHART,\n * DRILL-FINISH decision 5). ONE shared chart, fed by drill answers (`platform.drill_ask`):\n * stacked bars over time; the stack is the top N (`seriesLimit`, the host's knob\n * `drill.chart.top_n`, default 10) of the question's split Dimension ranked by the Measure over the\n * WHOLE window, everything else one Other series — every bar sums to its period's total and the\n * bars to the window total. No split = one series.\n *\n * What it asks is decided by the pure model in `drill.ts` (`drillChartQuestions` → round one,\n * `drillChartCellsQuestion` → round two, `drillChartData` → the bars); this file only draws.\n *\n * click a segment → drill: the value joins the trail, exactly as the answer table's drill\n * click a period → the window narrows to that period (`from..to`)\n * click Other → the period is broken out: the window narrows to it, where the answer table\n * lists every value one by one (said in the tooltip — never a dead click)\n * legend → buttons (keyboard reachable) that hide and show a series\n *\n * Its own entry, so `./data-table` never imports recharts — an OPTIONAL peer only a host that\n * draws this chart installs. Colours are the package's `--matrx-chart-*` tokens (light and dark).\n */\n\nimport { useMemo, useRef, useState, type ReactNode } from \"react\";\nimport { Bar, BarChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from \"recharts\";\n\nimport { formatCount } from \"@ai-matrx/kit/format\";\n\nimport { cn } from \"../cn\";\nimport { ErrorBox } from \"../error-box\";\nimport { Skeleton } from \"../skeleton\";\nimport {\n MATRX_DRILL_CHART_SERIES_LIMIT,\n dimensionRefLabel,\n drillChartData,\n drillChartDrill,\n drillChartNarrowToPeriod,\n drillChartQuestions,\n type MatrxDrillChartAnswers,\n type MatrxDrillChartData,\n type MatrxDrillChartPeriod,\n type MatrxDrillChartSeries,\n type MatrxDrillDimension,\n type MatrxDrillMeasure,\n type MatrxDrillQuestion,\n} from \"./drill\";\n\nexport {\n MATRX_DRILL_CHART_CEILING,\n MATRX_DRILL_CHART_OTHER,\n MATRX_DRILL_CHART_SERIES_LIMIT,\n drillChartCellsQuestion,\n drillChartData,\n drillChartDrill,\n drillChartNarrowToPeriod,\n drillChartQuestions,\n drillChartSeriesKey,\n type MatrxDrillChartAnswers,\n type MatrxDrillChartContext,\n type MatrxDrillChartData,\n type MatrxDrillChartPeriod,\n type MatrxDrillChartPlan,\n type MatrxDrillChartRequest,\n type MatrxDrillChartRequestKey,\n type MatrxDrillChartRow,\n type MatrxDrillChartSeries,\n} from \"./drill\";\n\nexport interface MatrxDrillChartProps {\n dimensions: readonly MatrxDrillDimension[];\n measures: readonly MatrxDrillMeasure[];\n /** The question the answer table answers; the chart asks its own questions from it. */\n question: MatrxDrillQuestion;\n onQuestionChange: (next: MatrxDrillQuestion) => void;\n /** The door's rows (with `kind`) per chart request key: `series`, `periods`, `cells`. */\n answers: MatrxDrillChartAnswers;\n /** Series before Other — the host feeds the knob `drill.chart.top_n`. Default 10. */\n seriesLimit?: number | undefined;\n /** The Measure stacked. Default: the question's first shown Measure. */\n measure?: string | null | undefined;\n /**\n * STACK THESE MEASURES instead (two or more that add up, e.g. input and output tokens): one series\n * each, no split; a segment click narrows to its period (lane DRILL-FLIP-FIXES L3).\n */\n stack?: readonly string[] | null | undefined;\n /** The time reference when the question does not group by time. */\n time?: string | null | undefined;\n /** The store's page ceiling, when the host knows it is not 1000. */\n ceiling?: number | undefined;\n /** Declared drill paths; a segment click follows them like the answer table. */\n paths?: readonly (readonly string[])[] | undefined;\n /** A read that failed, in words. */\n error?: string | null | undefined;\n emptyLabel?: string | undefined;\n /** Plot height in px (default 220). */\n height?: number | undefined;\n /** A fixed plot width in px; default fills its container. */\n width?: number | undefined;\n /** Words above the chart (a title, the freshness). */\n note?: ReactNode | undefined;\n className?: string | undefined;\n}\n\nconst NO_PATHS: readonly (readonly string[])[] = [];\nconst PLOT_MARGIN = { top: 4, right: 4, bottom: 0, left: 0 };\nconst Y_AXIS_WIDTH = 56;\n\nfunction colorOf(series: MatrxDrillChartSeries, index: number): string {\n if (series.color) return series.color;\n return series.other ? \"hsl(var(--matrx-chart-other))\" : `hsl(var(--matrx-chart-${(index % 10) + 1}))`;\n}\n\nfunction formatWith(measure: MatrxDrillMeasure | undefined, value: number | null | undefined): string {\n if (value === null || value === undefined) return \"not measured\";\n if (measure?.format) return measure.format(value);\n return value.toLocaleString(undefined, { maximumFractionDigits: 2 });\n}\n\n/** A short axis number: the Measure's own format when it has one, else 1.2k / 3.4M. */\n/**\n * A tick on the value axis: the measure's own words while they fit the axis (\"$12\", \"40 ms\"), else the\n * number in compact form (\"28M\") — the chart's title already names the unit (\"Cost (points) by day\").\n * A long formatted value (\"28,000,000 points\") was clipped to \",000,000 points\" (lane DRILL-ADOPT walk).\n */\nexport function drillChartAxisNumber(measure: MatrxDrillMeasure | undefined, value: number): string {\n // A fractional tick under a thousand keeps one decimal (0.5); everything else is kit's compact count.\n const compact =\n Math.abs(value) < 1000 && !Number.isInteger(value)\n ? String(Math.round(value * 10) / 10)\n : formatCount(value, { style: \"compact\" });\n if (!measure?.format) return compact;\n const said = measure.format(value);\n if (said.length <= 7) return said;\n // THE TICK SAYS THE NUMBER THE FORMAT SAID, NOT THE RAW VALUE. A Measure's format may convert its unit\n // (the usage explorer stores dollars and prints points at the points rate), so compacting the raw value\n // put \"450\" under \"Cost (points) by day\" for a 9,000,000-point day (localhost walk, 2026-09-30). The\n // number the format printed is compacted, keeping a leading currency sign (\"$1,234.56\" → \"$1.2k\").\n const printed = said.match(/^([^\\d-]*)(-?\\d[\\d,]*(?:\\.\\d+)?)/);\n if (!printed) return compact;\n const numeric = printed[2];\n if (!numeric) return compact;\n const n = Number(numeric.replace(/,/g, \"\"));\n if (!Number.isFinite(n)) return compact;\n const prefix = printed[1]?.trim() ?? \"\";\n const shown = Math.abs(n) < 1000 && !Number.isInteger(n) ? String(Math.round(n * 10) / 10) : formatCount(n, { style: \"compact\" });\n return prefix.length <= 1 ? `${prefix}${shown}` : shown;\n}\nconst axisNumber = drillChartAxisNumber;\n\nexport function MatrxDrillChart({\n dimensions,\n measures,\n question,\n onQuestionChange,\n answers,\n seriesLimit = MATRX_DRILL_CHART_SERIES_LIMIT,\n measure: measureKey,\n stack,\n time,\n ceiling,\n paths = NO_PATHS,\n error,\n emptyLabel = \"No value\",\n height = 220,\n width,\n note,\n className,\n}: MatrxDrillChartProps) {\n const plan = useMemo(\n () => drillChartQuestions(question, seriesLimit, { dimensions, measures, measure: measureKey, stack, time, ceiling }),\n [question, seriesLimit, dimensions, measures, measureKey, stack, time, ceiling],\n );\n const data = useMemo(() => drillChartData(plan, answers, dimensions, measures, emptyLabel), [plan, answers, dimensions, measures, emptyLabel]);\n const measure = measures.find((m) => m.key === plan.measure);\n const [hidden, setHidden] = useState<ReadonlySet<string>>(() => new Set());\n const [hover, setHover] = useState<string | null>(null);\n // A segment's click reaches the chart's own click too (it bubbles): the segment wins.\n const segmentClicked = useRef(false);\n const plotRef = useRef<HTMLDivElement | null>(null);\n\n const measureWords = plan.stack ? plan.stack.map((k) => measures.find((m) => m.key === k)?.label ?? k).join(\" and \") : measure?.label ?? plan.measure;\n const title = plan.time\n ? `${measureWords} by ${dimensionRefLabel(dimensions, plan.time).replace(/^.* by /, \"\")}${\n plan.split ? `, split by ${dimensionRefLabel(dimensions, plan.split)}` : \"\"\n }`\n : measureWords;\n\n const frame = (body: ReactNode) => (\n <section data-matrx-drill-chart aria-label={title} className={cn(\"flex min-w-0 flex-col gap-1.5 px-2 py-2\", className)}>\n {note ? <div data-matrx-drill-chart-note className=\"text-xs text-muted-foreground\">{note}</div> : null}\n {body}\n </section>\n );\n\n if (error) {\n return frame(<ErrorBox className=\"text-sm text-destructive\" message={error} operation=\"Draw the chart\" />);\n }\n if (plan.refused) {\n return frame(\n <p data-matrx-drill-chart-refused className=\"text-xs text-muted-foreground\">\n {plan.refused}\n </p>,\n );\n }\n if (!data) {\n return frame(<Skeleton data-matrx-drill-chart-loading className=\"w-full\" style={{ height }} />);\n }\n if (data.periods.length === 0) {\n return frame(<p data-matrx-drill-chart-empty className=\"text-xs text-muted-foreground\">Nothing was counted in this window.</p>);\n }\n\n const toggle = (key: string) =>\n setHidden((was) => {\n const next = new Set(was);\n if (next.has(key)) next.delete(key);\n else next.add(key);\n return next;\n });\n const drawn = data.series.map((s, i) => ({ s, i, dataKey: `s${i}` })).filter(({ s }) => !hidden.has(s.key));\n const rows = data.periods.map((p) => {\n const row: Record<string, string | number | null> = { __key: p.key, __label: p.label };\n data.series.forEach((s, i) => {\n row[`s${i}`] = p.values[s.key] ?? null;\n });\n return row;\n });\n const splitLabel = data.split ? dimensionRefLabel(dimensions, data.split) : \"\";\n const onPeriod = (period: MatrxDrillChartPeriod | undefined) => {\n if (period) onQuestionChange(drillChartNarrowToPeriod(question, plan, period.value, dimensions));\n };\n const onSegment = (series: MatrxDrillChartSeries, period: MatrxDrillChartPeriod | undefined) => {\n segmentClicked.current = true;\n if (series.other || !data.split) {\n onPeriod(period);\n return;\n }\n onQuestionChange(drillChartDrill(question, plan, series.value, dimensions, paths));\n };\n const hintFor = (key: string | null): string | null => {\n const series = data.series.find((s) => s.key === key);\n if (!series) return \"Click a bar to narrow the window to its period.\";\n if (series.other) return `Other is everything past the top ${plan.seriesLimit}: click to break this period out, one by one, in the table.`;\n if (!data.split) return \"Click to narrow the window to this period.\";\n return `Click to drill into ${series.label}.`;\n };\n\n const plot = (\n <BarChart\n data={rows}\n margin={PLOT_MARGIN}\n barCategoryGap=\"12%\"\n onClick={(_state, event) => {\n if (segmentClicked.current) {\n segmentClicked.current = false;\n return;\n }\n // The period is read from WHERE the person clicked, never from the tooltip's last index:\n // a click that arrives before the chart has seen the pointer move would otherwise read a\n // stale index (walked: a quick click landed on the first day). Bands are equal-width over\n // the plot, which starts after the value axis and ends at the right margin.\n const box = (plotRef.current?.querySelector(\"svg.recharts-surface\") ?? plotRef.current)?.getBoundingClientRect();\n if (!box || data.periods.length === 0) return;\n const left = box.left + PLOT_MARGIN.left + Y_AXIS_WIDTH;\n const right = box.right - PLOT_MARGIN.right;\n const index = Math.floor(((event.clientX - left) / (right - left)) * data.periods.length);\n if (index >= 0 && index < data.periods.length) onPeriod(data.periods[index]);\n }}\n {...(width ? { width, height } : {})}\n >\n <CartesianGrid vertical={false} stroke=\"hsl(var(--border))\" strokeDasharray=\"0\" />\n <XAxis\n dataKey=\"__label\"\n tickLine={false}\n axisLine={{ stroke: \"hsl(var(--border))\" }}\n tick={{ fill: \"hsl(var(--muted-foreground))\", fontSize: 11 }}\n minTickGap={12}\n interval=\"preserveStartEnd\"\n />\n <YAxis\n tickLine={false}\n axisLine={false}\n width={Y_AXIS_WIDTH}\n tick={{ fill: \"hsl(var(--muted-foreground))\", fontSize: 11 }}\n tickFormatter={(v: number) => axisNumber(measure, v)}\n />\n <Tooltip\n cursor={{ fill: \"hsl(var(--muted))\", opacity: 0.6 }}\n isAnimationActive={false}\n content={({ active, label }) => {\n if (!active) return null;\n const period = data.periods.find((p) => p.label === label);\n if (!period) return null;\n return (\n <DrillChartTooltip\n period={period}\n data={data}\n hidden={hidden}\n measure={measure}\n splitLabel={splitLabel}\n hint={hintFor(hover)}\n />\n );\n }}\n />\n {drawn.map(({ s, i, dataKey }) => (\n <Bar\n key={s.key}\n dataKey={dataKey}\n name={s.label}\n stackId=\"drill\"\n fill={colorOf(s, i)}\n stroke=\"hsl(var(--background))\"\n strokeWidth={1}\n isAnimationActive={false}\n cursor=\"pointer\"\n onMouseEnter={() => setHover(s.key)}\n onMouseLeave={() => setHover(null)}\n onClick={(_item, index) => onSegment(s, data.periods[index])}\n />\n ))}\n </BarChart>\n );\n\n return frame(\n <>\n <div className=\"flex flex-wrap items-baseline justify-between gap-x-3 gap-y-0.5\">\n <h3 className=\"text-xs font-medium text-foreground\">{title}</h3>\n <span data-matrx-drill-chart-total className=\"text-xs text-muted-foreground\">\n Total {formatWith(measure, data.total)}\n </span>\n </div>\n <div ref={plotRef} data-matrx-drill-chart-plot className=\"w-full min-w-0\" style={{ height }}>\n {width ? plot : <ResponsiveContainer width=\"100%\" height=\"100%\">{plot}</ResponsiveContainer>}\n </div>\n {data.series.length > 1 || data.split ? (\n <ul data-matrx-drill-chart-legend aria-label=\"Series — press to hide or show\" className=\"flex flex-wrap gap-x-1 gap-y-0.5\">\n {data.series.map((s, i) => {\n const off = hidden.has(s.key);\n return (\n <li key={s.key} className=\"min-w-0\">\n <button\n type=\"button\"\n data-matrx-drill-chart-series={s.key}\n aria-pressed={!off}\n title={`${off ? \"Show\" : \"Hide\"} ${s.label} (${formatWith(measure, s.total)})`}\n onClick={() => toggle(s.key)}\n className={cn(\n \"inline-flex max-w-[16rem] items-center gap-1.5 rounded px-1.5 py-0.5 text-xs text-foreground hover:bg-muted focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring\",\n off && \"text-muted-foreground line-through\",\n )}\n >\n <span aria-hidden=\"true\" className=\"h-2.5 w-2.5 shrink-0 rounded-sm\" style={{ background: off ? \"transparent\" : colorOf(s, i), boxShadow: `inset 0 0 0 1.5px ${colorOf(s, i)}` }} />\n <span className=\"truncate\">{s.label}</span>\n <span className=\"shrink-0 tabular-nums text-muted-foreground\">{formatWith(measure, s.total)}</span>\n </button>\n </li>\n );\n })}\n </ul>\n ) : null}\n {data.says.length > 0 ? (\n <p data-matrx-drill-chart-says className=\"text-xs text-muted-foreground\">\n {data.says.join(\" \")}\n </p>\n ) : null}\n </>,\n );\n}\n\n/** Every series' value in the period, each in the Measure's own format, and the period's total. */\nfunction DrillChartTooltip({\n period,\n data,\n hidden,\n measure,\n splitLabel,\n hint,\n}: {\n period: MatrxDrillChartPeriod;\n data: MatrxDrillChartData;\n hidden: ReadonlySet<string>;\n measure: MatrxDrillMeasure | undefined;\n splitLabel: string;\n hint: string | null;\n}) {\n return (\n <div\n data-matrx-drill-chart-tooltip\n className=\"min-w-[12rem] max-w-[18rem] rounded-md border border-border bg-popover px-2.5 py-2 text-xs text-popover-foreground shadow-md\"\n >\n <div className=\"mb-1 font-medium\">{period.label}</div>\n {data.split ? <div className=\"mb-1 text-muted-foreground\">{splitLabel}</div> : null}\n <ul className=\"flex flex-col gap-0.5\">\n {data.series.map((s, i) => (\n <li key={s.key} className={cn(\"flex items-center gap-1.5\", hidden.has(s.key) && \"opacity-50\")}>\n <span aria-hidden=\"true\" className=\"h-2 w-2 shrink-0 rounded-sm\" style={{ background: colorOf(s, i) }} />\n <span className=\"min-w-0 flex-1 truncate\">{s.label}</span>\n <span className=\"tabular-nums\">{formatWith(measure, period.values[s.key] ?? null)}</span>\n </li>\n ))}\n </ul>\n <div className=\"mt-1 flex justify-between border-t border-border pt-1 font-medium\">\n <span>Total</span>\n <span className=\"tabular-nums\">{formatWith(measure, period.total)}</span>\n </div>\n {hint ? <div className=\"mt-1 text-muted-foreground\">{hint}</div> : null}\n </div>\n );\n}\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","/**\n * ErrorBox — THE one way a package draws an error on screen, and the seam a\n * host uses to put its own actions on every one of them.\n *\n * Every error sentence this package (and every sibling package that depends\n * on it) shows is an `ErrorBox`. The box is a plain `role=\"alert\"` element —\n * its tag and classes are the caller's, so nothing about the look changes — and\n * after its children it renders whatever the host's `ErrorActionsProvider`\n * returns for that error. With no provider it renders nothing extra.\n *\n * Why a slot and not a menu: the package cannot know the host's AI copy, bug\n * report, or support flow (Matrx fills it with its Alchemy copy-for-AI menu at\n * the app root). The package supplies the error's facts — the sentence, the\n * raw error, the operation, the records — and the host decides the actions.\n *\n * Guard: `error-box.census.test.ts` fails when a package file renders\n * `role=\"alert\"` for an error without going through `ErrorBox` (or, for the\n * `Alert` family, `ErrorActions`).\n */\nimport * as React from \"react\";\n\nexport interface ErrorActionsRecord {\n type: string;\n id: string;\n label?: string;\n}\n\n/** The facts a package knows about an error it is showing. All optional. */\nexport interface ErrorActionsInput {\n /** The sentence shown. Omit when the words are only in the rendered box. */\n message?: string;\n title?: string;\n /** The raw error (code/status/stack stay available to the host). */\n error?: unknown;\n /** What was being attempted (\"Load saved views\"). */\n operation?: string;\n records?: readonly ErrorActionsRecord[];\n /** The person's input that did not land. */\n unsavedInput?: unknown;\n /** The package + component that drew it (\"design-system/SavedViewsControl\"). */\n origin?: string;\n}\n\nexport type ErrorActionsRenderer = (input: ErrorActionsInput) => React.ReactNode;\n\n/**\n * ONE context for every copy of this module. tsup builds each entry point\n * (`.`, `./data-table`, …) without code splitting, so this file is inlined into\n * several bundles; a plain `createContext` would give each bundle its own\n * context and the host's provider (imported from `.`) would never reach an\n * error drawn by the data table. The registry key makes them one.\n */\nconst CONTEXT_KEY = Symbol.for(\"@ai-matrx/design-system/error-actions\");\ntype ContextRegistry = Record<symbol, React.Context<ErrorActionsRenderer | null> | undefined>;\nconst registry = globalThis as unknown as ContextRegistry;\nconst ErrorActionsContext: React.Context<ErrorActionsRenderer | null> =\n registry[CONTEXT_KEY] ??\n (registry[CONTEXT_KEY] = React.createContext<ErrorActionsRenderer | null>(null));\n\n/** Mount once at the host's app root; every ErrorBox below renders `render(...)`. */\nexport function ErrorActionsProvider({\n render,\n children,\n}: {\n render: ErrorActionsRenderer;\n children: React.ReactNode;\n}) {\n return (\n <ErrorActionsContext.Provider value={render}>{children}</ErrorActionsContext.Provider>\n );\n}\n\nexport function useErrorActions(): ErrorActionsRenderer | null {\n return React.useContext(ErrorActionsContext);\n}\n\n/**\n * The host's actions for one error, placed wherever the caller puts this\n * element. Renders nothing without a provider. Use inside a box you do not\n * render yourself (the `Alert` family); everything else uses `ErrorBox`.\n */\nexport function ErrorActions(props: ErrorActionsInput) {\n const render = useErrorActions();\n if (!render) return null;\n return <>{render(props)}</>;\n}\n\ntype ErrorBoxTag = \"p\" | \"div\" | \"span\" | \"section\" | \"li\";\n\nexport interface ErrorBoxProps\n extends Omit<React.HTMLAttributes<HTMLElement>, \"role\" | \"title\">,\n ErrorActionsInput {\n /** The element drawn — the caller's existing tag, so layout never shifts. */\n as?: ErrorBoxTag;\n}\n\nexport const ErrorBox = React.forwardRef<HTMLElement, ErrorBoxProps>(function ErrorBox(\n {\n as = \"p\",\n message,\n title,\n error,\n operation,\n records,\n unsavedInput,\n origin,\n children,\n ...rest\n },\n ref,\n) {\n const Tag = as as React.ElementType;\n const facts: ErrorActionsInput = {\n ...(message !== undefined ? { message } : {}),\n ...(title !== undefined ? { title } : {}),\n ...(error !== undefined ? { error } : {}),\n ...(operation !== undefined ? { operation } : {}),\n ...(records !== undefined ? { records } : {}),\n ...(unsavedInput !== undefined ? { unsavedInput } : {}),\n ...(origin !== undefined ? { origin } : {}),\n };\n return (\n <Tag ref={ref} role=\"alert\" data-error-box=\"\" {...rest}>\n {children ?? message}\n <ErrorActions {...facts} />\n </Tag>\n );\n});\n","/**\n * THE MOTION CONTRACT (0.10.0) — the class names every animated surface in\n * this package puts on the DOM, in ONE place.\n *\n * Why a module and not ten inline strings: the sweep that produced this file\n * found the same defect ten times over, and it was invisible each time\n * because each primitive spelled its animation out by hand. Ten hand-written\n * copies of `data-[state=open]:animate-in data-[state=closed]:animate-out\n * data-[state=closed]:fade-out-0 …` meant ten chances to reach for a utility\n * nothing in the package defines — which is exactly what happened. A single\n * exported constant per motion SHAPE means the next primitive cannot invent\n * an eleventh spelling, and `styles.test.ts` can prove every one of them\n * resolves to a rule in the shipped stylesheet.\n *\n * Every rule these name lives in `styles.css` under the package's own\n * keyframes. NONE of them is a Tailwind utility, and none requires a host\n * plugin — see that file's header for the measurement that made this\n * necessary.\n */\n\n/** Modal scrims: Dialog, AlertDialog, Sheet, CommandDialog. Fade only. */\nexport const MOTION_OVERLAY = \"matrx-motion-overlay\";\n\n/** The centred dialog card. Fades and scales; centring is not its business. */\nexport const MOTION_DIALOG = \"matrx-motion-dialog\";\n\n/**\n * THE CENTRING OF A CENTRED DIALOG CARD — package-owned, in the `translate`\n * property, and NEVER a Tailwind `translate-x-[-50%] translate-y-[-50%]` pair.\n *\n * Those utilities do not compile to the same thing in every host: Tailwind v3\n * writes them into `transform`, Tailwind v4 writes them into the independent\n * `translate` property, which COMPOSES with `transform` instead of being\n * replaced by it. 0.10.0's keyframes restated the centring inside `transform`,\n * which was harmless on v3 and doubled the centring on v4 — the card flew in\n * from off the top-left of the viewport for the animation's whole duration and\n * snapped into place at the end (fixed in 0.10.1).\n *\n * Owning it here means one spelling that reads the same on both, and it frees\n * `transform` so the keyframes carry ONLY the delta. Put this on every centred\n * surface UNCONDITIONALLY — it is geometry, so unlike {@link MOTION_DIALOG} it\n * must survive `animated={false}`. Its rule ships in `styles.css`.\n */\nexport const CENTERED_DIALOG = \"matrx-dialog-centered\";\n\n/**\n * The resting gap between a `popper`-positioned surface and its trigger, for\n * the primitives that want one (today: `Select`). Same law and same reason as\n * {@link CENTERED_DIALOG} — a position an animated surface holds AT REST is\n * the `translate` property's job, never a Tailwind translate utility, because\n * those land in `transform` on a v3 host and in `translate` on a v4 one while\n * the keyframes animate `transform` either way. Sizes from\n * `--matrx-popper-offset`; keys off Radix's `data-side`.\n */\nexport const POPPER_OFFSET = \"matrx-popper-offset\";\n\n/** The mobile Dialog / CommandDialog bottom sheet. Slides up and fades. */\nexport const MOTION_BOTTOM_SHEET = \"matrx-motion-bottom-sheet\";\n\n/**\n * Radix-popper surfaces: Popover, DropdownMenu, ContextMenu, HoverCard,\n * Select, Tooltip. Reads `--radix-popper-transform-origin` so the surface\n * grows out of its trigger, and `data-side` so it travels from it.\n */\nexport const MOTION_POPPER = \"matrx-motion-popper\";\n\n/** Edge sheets, one class per `sheetVariants` side. */\nexport const MOTION_SHEET = {\n top: \"matrx-motion-sheet-top\",\n bottom: \"matrx-motion-sheet-bottom\",\n left: \"matrx-motion-sheet-left\",\n right: \"matrx-motion-sheet-right\",\n center: \"matrx-motion-sheet-center\",\n} as const;\n\n/** Skeleton's breathing loop. */\nexport const MOTION_PULSE = \"matrx-pulse\";\n\n/** The spinner loop, for every inline busy indicator in the package. */\nexport const MOTION_SPIN = \"matrx-spin\";\n\n/**\n * Every motion class the package ships, for the guards. If a rule for one of\n * these is missing from `styles.css`, a component animates in no consumer and\n * says nothing about it — the C26 failure this list exists to make loud.\n */\nexport const ALL_MOTION_CLASSES = [\n MOTION_OVERLAY,\n MOTION_DIALOG,\n MOTION_BOTTOM_SHEET,\n MOTION_POPPER,\n ...Object.values(MOTION_SHEET),\n MOTION_PULSE,\n MOTION_SPIN,\n] as const;\n\n/**\n * The host-plugin utilities this package is forbidden to emit. They are\n * supplied by `tailwindcss-animate` / `tw-animate-css`, which is a HOST CSS\n * entry concern a package cannot declare a dependency on: three of the four\n * AI Matrx consumers never loaded it, so every surface wearing these appeared\n * with no animation and no error. `styles.test.ts` fails the build if one\n * comes back.\n */\nexport const FORBIDDEN_HOST_MOTION_UTILITIES = [\n \"animate-in\",\n \"animate-out\",\n \"fade-in\",\n \"fade-out\",\n \"zoom-in\",\n \"zoom-out\",\n \"slide-in-from\",\n \"slide-out-to\",\n \"animate-accordion\",\n \"animate-slide-down\",\n \"animate-slide-up\",\n \"animate-pulse\",\n \"animate-spin\",\n] as const;\n","/**\n * Skeleton — the \"content is coming\" placeholder.\n *\n * Ported from matrx-frontend `components/ui/skeleton.tsx`; no seams, pure\n * markup over semantic tokens. ONE change from the original (0.10.0): the\n * breathing loop is `.matrx-pulse`, a package-owned keyframe, not Tailwind's\n * `animate-pulse`. A Skeleton that does not pulse is indistinguishable from an\n * empty grey box — it stops saying \"loading\" and starts saying \"nothing here\"\n * — so the motion is part of the component's meaning and ships with it (see\n * `styles.css` § THE MOTION LAYER).\n */\n\nimport * as React from \"react\";\n\nimport { cn } from \"./cn\";\nimport { MOTION_PULSE } from \"./motion\";\n\nexport interface SkeletonProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Package-owned pulse. Default `true`. Pass `false` for a static\n * placeholder — a print/PDF render, or a screenshot fixture where the\n * animation makes the output non-deterministic. `prefers-reduced-motion`\n * already SLOWS the pulse rather than stopping it (a still skeleton would\n * lose the information), so this is not the accessibility switch.\n */\n animated?: boolean;\n}\n\nexport function Skeleton({ className, animated = true, ...props }: SkeletonProps) {\n return (\n <div\n data-slot=\"skeleton\"\n className={cn(\n \"rounded-md bg-primary/10\",\n animated && MOTION_PULSE,\n className,\n )}\n {...props}\n />\n );\n}\n","/**\n * DRILL — the table primitive's four analytic abilities, as pure data (lane D3 of\n * common-docs/systems/platform/drill-down/STATE.md).\n *\n * 1. GROUP BY A DIMENSION, answered by the SERVER — every group's count and measure subtotals\n * are worked out over the whole table, never over the page the browser happens to hold.\n * 2. PIVOT a second Dimension across the top (`across`).\n * 3. WHAT TO DISPLAY — the Measures shown (`show`).\n * 4. DRILL — a click on a group adds its value as a filter and regroups by the next Dimension\n * of the drill path; the trail of filters is the breadcrumb, and every state is a URL.\n *\n * Vocabulary (Arman, 2026-09-29): \"Dimension\" and \"Measure\" are platform words; a saved\n * exploration is a Saved view.\n *\n * THE TABLE ASKS, THE HOST ANSWERS. This file never reads a database. `drillRequests(question)`\n * names every grouping the screen needs (one per level, one per level × the pivot, the pivot's\n * own totals and the grand total); the host runs each through its door — today\n * `custom.record_aggregate` for a custom Table, tomorrow the shared read door lanes D1/D2 build —\n * and hands the rows back keyed by `request.key`. Because every number on the screen comes from\n * one of those answers, a subtotal and the total can never disagree, and the rows a group holds\n * beyond the door's group limit are said as ONE honest \"N more\" row (`otherOf`), never dropped.\n */\n\nimport { formatCount } from \"@ai-matrx/kit/format\";\n\n/** A Dimension's kind — what it is made of, which decides what a click on one of its values means. */\nexport type MatrxDrillDimensionKind = \"choice\" | \"boolean\" | \"text\" | \"time\" | \"relation\";\n\n/** The periods a time Dimension is cut into. The store's own list (`custom.agg_buckets()`). */\nexport type MatrxDrillGrain = \"hour\" | \"day\" | \"week\" | \"month\" | \"quarter\" | \"year\";\n\n/** Every grain, finest first (`at:hour` … `at:year`). */\nexport const MATRX_DRILL_GRAINS: readonly MatrxDrillGrain[] = [\"hour\", \"day\", \"week\", \"month\", \"quarter\", \"year\"];\n\n/**\n * The grains a time Dimension offers when it declares none: a day and coarser (the door's own\n * default). Hour is offered only where a Dimension declares it — a date column cut by hour is nonsense.\n */\nexport const MATRX_DRILL_DEFAULT_GRAINS: readonly MatrxDrillGrain[] = [\"day\", \"week\", \"month\", \"quarter\", \"year\"];\n\n/** One axis a table can be cut by. */\nexport interface MatrxDrillDimension {\n /** The column or Field key. A time Dimension is referenced as `<key>:<grain>`. */\n key: string;\n label: string;\n kind: MatrxDrillDimensionKind;\n /** Time only: the grains offered. Default a day and coarser (`MATRX_DRILL_DEFAULT_GRAINS`); hour only when declared. */\n grains?: readonly MatrxDrillGrain[] | undefined;\n /** `high` → never offered as a pivot, because its columns would not fit a screen. */\n cardinality?: \"low\" | \"medium\" | \"high\" | undefined;\n /** How one of its values reads to a person (a choice's words, a person's name). Default: the value. */\n labelFor?: ((value: string | null) => string) | undefined;\n /**\n * THE COLOUR A VALUE KEEPS (adopted from the old usage page's per-origin colours): a chart token\n * (`--matrx-chart-3`) the value is drawn in on every chart and share bar, light and dark. Absent\n * or empty = the chart's own order.\n */\n colorFor?: ((value: string | null) => string | null | undefined) | undefined;\n /**\n * A RELATION'S RECORD KIND (a registry token: `organization`, `agent`, `user`): a group row of this\n * Dimension offers that record's own doors — open, preview and its kind's menu — through the\n * table host's `resolveEntityDoors`, in the row's ⋯ menu and on a right-click.\n */\n entity?: string | undefined;\n /** What it groups by, at most 140 characters: the column header's tooltip (lane DRILL-FLIP-FIXES). */\n description?: string | undefined;\n}\n\n/** A chart token as the colour a style reads (`--matrx-chart-3` → `hsl(var(--matrx-chart-3))`); null when it is not one. */\nexport function drillTokenColor(token: string | null | undefined): string | null {\n return token && /^--matrx-chart-([1-9]|10|other)$/.test(token) ? `hsl(var(${token}))` : null;\n}\n\n/** The colour a value of this Dimension keeps, as a style reads it — null when it keeps none. */\nexport function drillValueColor(dimensions: readonly MatrxDrillDimension[], ref: string, value: string | null): string | null {\n return drillTokenColor(dimensionFor(dimensions, ref)?.colorFor?.(value));\n}\n\n/** One number a table can show. */\nexport interface MatrxDrillMeasure {\n /** The key the host's answer carries it under (`count`, `sum_quantity`). */\n key: string;\n label: string;\n /**\n * May it be added up across groups? `count` and `sum` may; an average, a median, a min and a\n * max may not — the \"N more\" row leaves those blank rather than inventing them.\n */\n additive: boolean;\n /** How the cell prints. Default: a plain number. */\n format?: ((value: number | null) => string) | undefined;\n /** A smaller number is the better one (cost, position, time taken): a fall reads as good news. */\n lowerIsBetter?: boolean | undefined;\n /**\n * A MOMENT, not an amount (\"Last active\", carried as epoch ms): never a share, never a change\n * against the prior window, never a Pareto line — only the moment itself.\n */\n moment?: boolean | undefined;\n /** What the number counts, at most 140 characters: the column header's tooltip (lane DRILL-FLIP-FIXES). */\n description?: string | undefined;\n}\n\n/** One step of the breadcrumb: a Dimension reference and the value it was narrowed to. */\nexport interface MatrxDrillFilter {\n /** A Dimension reference (`status`, `last_serviced:month`). */\n dim: string;\n /** The value, as the answer carried it. `null` is \"no value\". */\n value: string | null;\n}\n\n/** THE QUESTION — what a Saved view stores and the address encodes. */\nexport interface MatrxDrillQuestion {\n /** Row groups, outermost first (at most three, the store's own limit). Empty = the records. */\n by: string[];\n /** One pivot Dimension drawn across the top. */\n across?: string | null | undefined;\n /** The Measures shown, in column order. */\n show: string[];\n /** The drill trail, in order. Each crumb removes the filters after it. */\n where: MatrxDrillFilter[];\n /** The drill path a click follows (Dimension references). Default: the Dimensions in order. */\n path?: string[] | null | undefined;\n /** A Measure key (or `label`) and a direction; default the answer's own order (largest first). */\n sort?: { key: string; direction: \"asc\" | \"desc\" } | null | undefined;\n /**\n * SHARE OF TOTAL (adopted from the Spend Explorer's share bar, the per-user origin mix and the\n * backlink anchor profile): each additive Measure also says what part of its parent it is — the\n * whole answer for the outer level, the group above for a nested one.\n */\n share?: boolean | undefined;\n /**\n * COMPARE (adopted from Search Console's delta columns, the marketing reports' \"+N vs previous\"\n * and the dashboards' prior-period overlay): every Measure beside its change against the prior\n * window, sortable by that change (`sort.key = \"delta:<measure>\"`).\n */\n compare?: MatrxDrillCompare | null | undefined;\n /**\n * THE WINDOW on the source's time Dimension (adopted from the preset pickers Spend, Search\n * Console, Codex usage and the user usage page each hand-rolled): `7d`, `30d`, `90d`, `365d`,\n * `24h`, `today`, `yesterday` (the viewer's local midnight), or `from..to` (days, or moments with\n * a time: `2026-09-28T14:00Z..2026-09-28T18:00Z`). Absent = all time.\n */\n window?: string | null | undefined;\n}\n\nexport type MatrxDrillCompare = \"previous_period\" | \"same_period_last_year\";\n\n/** The presets a window control offers, in order (the store's own set: `Nh` / `Nd`). */\nexport const MATRX_DRILL_WINDOWS: ReadonlyArray<{ key: string; label: string }> = [\n { key: \"24h\", label: \"Last 24 hours\" },\n { key: \"7d\", label: \"Last 7 days\" },\n { key: \"30d\", label: \"Last 30 days\" },\n { key: \"90d\", label: \"Last 90 days\" },\n { key: \"365d\", label: \"Last 12 months\" },\n];\n\n/** One choice a window control offers: a key `drillWindowRange` resolves and the words it reads as. */\nexport interface MatrxDrillWindowPreset {\n key: string;\n label: string;\n}\n\n/**\n * THE CALENDAR WINDOWS (adopted from the Spend Explorer, whose \"Yesterday\" exists because \"a\n * dashboard that only shows today is worthless one minute past midnight\" — Arman, 2026-09-12):\n * today so far and all of yesterday, cut at the VIEWER'S local midnight.\n */\nexport const MATRX_DRILL_CALENDAR_WINDOWS: ReadonlyArray<MatrxDrillWindowPreset> = [\n { key: \"today\", label: \"Today\" },\n { key: \"yesterday\", label: \"Yesterday\" },\n];\n\n/**\n * What a window control offers by default, in order: Today, Yesterday, then the rolling windows.\n * \"All time\" and \"Custom range…\" are the control's own (see `MatrxDrillWindowMenu`); a host\n * declares its own list through the menu's `presets` prop.\n */\nexport const MATRX_DRILL_WINDOW_PRESETS: ReadonlyArray<MatrxDrillWindowPreset> = [...MATRX_DRILL_CALENDAR_WINDOWS, ...MATRX_DRILL_WINDOWS];\n\nfunction localMidnight(now: Date, daysBack: number): Date {\n return new Date(now.getFullYear(), now.getMonth(), now.getDate() - daysBack);\n}\n\n/** A window as the half-open moments it covers; `now` is an argument so a test is deterministic. */\nexport function drillWindowRange(window: string | null | undefined, now: Date = new Date()): { from: string; to: string } | null {\n if (!window) return null;\n if (window === \"today\") return { from: localMidnight(now, 0).toISOString(), to: now.toISOString() };\n if (window === \"yesterday\") return { from: localMidnight(now, 1).toISOString(), to: localMidnight(now, 0).toISOString() };\n const custom = window.match(WINDOW_RANGE);\n if (custom) return { from: momentOf(custom[1]!), to: momentOf(custom[2]!) };\n const preset = window.match(/^(\\d{1,4})(h|d)$/);\n if (!preset) return null;\n const ms = Number(preset[1]) * (preset[2] === \"h\" ? 3_600_000 : 86_400_000);\n return { from: new Date(now.getTime() - ms).toISOString(), to: now.toISOString() };\n}\n\n/**\n * A custom window's two ends: a date (`2026-09-28`) or a moment (`2026-09-28T14:00Z`, or with an\n * offset — which an address may have decoded from `+00:00` to ` 00:00`; it is read back as `+`).\n */\nconst MOMENT = String.raw`\\d{4}-\\d{2}-\\d{2}(?:T\\d{2}:\\d{2}(?::\\d{2})?(?:Z|[+\\- ]\\d{2}:\\d{2})?)?`;\nconst WINDOW_RANGE = new RegExp(`^(${MOMENT})\\\\.\\\\.(${MOMENT})$`);\n\nfunction momentOf(end: string): string {\n return end.replace(/(T\\d{2}:\\d{2}(?::\\d{2})?) (\\d{2}:\\d{2})$/, \"$1+$2\");\n}\n\n/** A moment as a window end in the address: UTC, to the minute (`2026-09-28T14:00Z`). */\nexport function drillWindowMoment(value: string | Date): string {\n const d = typeof value === \"string\" ? new Date(value) : value;\n return `${d.toISOString().slice(0, 16)}Z`;\n}\n\nfunction momentWords(end: string): { day: string; clock: string | null; zone: string } {\n const [y, m, d] = end.slice(0, 10).split(\"-\").map(Number) as [number, number, number];\n const month = new Date(Date.UTC(y, m - 1, d)).toLocaleString(\"en-US\", { month: \"short\", timeZone: \"UTC\" });\n const clock = end.length > 10 ? end.slice(11, 16) : null;\n const zone = end.endsWith(\"Z\") ? \"UTC\" : end.length > 16 ? `UTC${end.slice(-6)}`.replace(\"UTC+00:00\", \"UTC\") : \"\";\n return { day: `${month} ${d}, ${y}`, clock, zone };\n}\n\n/** How a window reads: its preset's words, or the dates — and, for a window of moments, the clock. */\nexport function drillWindowLabel(window: string | null | undefined): string {\n if (!window) return \"All time\";\n const preset = MATRX_DRILL_WINDOW_PRESETS.find((w) => w.key === window);\n if (preset) return preset.label;\n const rolling = window.match(/^(\\d{1,4})(h|d)$/);\n if (rolling) {\n const n = Number(rolling[1]);\n return `Last ${n.toLocaleString()} ${rolling[2] === \"h\" ? (n === 1 ? \"hour\" : \"hours\") : n === 1 ? \"day\" : \"days\"}`;\n }\n const custom = window.match(WINDOW_RANGE);\n if (custom && (custom[1]!.length > 10 || custom[2]!.length > 10)) {\n const a = momentWords(momentOf(custom[1]!));\n const b = momentWords(momentOf(custom[2]!));\n const left = `${a.day} ${a.clock ?? \"00:00\"}`;\n const right = a.day === b.day ? (b.clock ?? \"00:00\") : `${b.day} ${b.clock ?? \"00:00\"}`;\n return `${left} – ${right}${b.zone || a.zone ? ` ${b.zone || a.zone}` : \"\"}`;\n }\n const range = window.split(\"..\");\n return range.length === 2 ? `${range[0]} – ${range[1]}` : window;\n}\n\n/** One Measure now and before, and the change as a share of before (`null` when before was nothing). */\nexport function drillDelta(now: number | null | undefined, before: number | null | undefined): { change: number | null; pct: number | null } {\n if (now === null || now === undefined || before === null || before === undefined) return { change: null, pct: null };\n const change = now - before;\n return { change, pct: before === 0 ? null : change / Math.abs(before) };\n}\n\n/** What part of its parent a value is (`null` when the parent is nothing, or the Measure cannot be added up). */\nexport function drillShare(value: number | null | undefined, whole: number | null | undefined): number | null {\n if (value === null || value === undefined || !whole) return null;\n return value / whole;\n}\n\n/** One grouped row of an answer, exactly as the store's aggregate door returns it. */\nexport interface MatrxDrillAnswerRow {\n /** Keyed by Dimension reference. */\n groups: Record<string, string | null>;\n measures: Record<string, number | null>;\n row_count: number;\n /** The same group in the prior window, when the question compares. */\n prior_measures?: Record<string, number | null> | null | undefined;\n /** How many groups there were before the door's cap (Top N of M). */\n distinct_groups?: number | null | undefined;\n}\n\n/** One grouping the table needs answered. */\nexport interface MatrxDrillRequest {\n /** Stable key the host answers under. */\n key: string;\n /** Dimension references to group by, outermost first; empty = the grand total. */\n by: string[];\n}\n\n/** What the host hands back: rows per request key; a key not yet present is still loading. */\nexport type MatrxDrillAnswers = Readonly<Record<string, readonly MatrxDrillAnswerRow[] | undefined>>;\n\n/** The store's own ceiling on levels (`custom.view_declare`). */\nexport const MATRX_DRILL_MAX_LEVELS = 3;\n/**\n * Pivot columns before the one rest column: the DEFAULT of the live knob `drill.pivot_columns`,\n * which a host reads and passes as `MatrxDrillAnswerTable`'s `pivotColumnCap`.\n */\nexport const MATRX_DRILL_PIVOT_COLUMNS = 24;\n\n// ── references ─────────────────────────────────────────────────────────────\n\n/** `last_serviced:month` → `{ key: \"last_serviced\", grain: \"month\" }`. */\nexport function parseDimensionRef(ref: string): { key: string; grain: MatrxDrillGrain | null } {\n const at = ref.lastIndexOf(\":\");\n if (at <= 0) return { key: ref, grain: null };\n const grain = ref.slice(at + 1) as MatrxDrillGrain;\n if (!MATRX_DRILL_GRAINS.includes(grain)) return { key: ref, grain: null };\n return { key: ref.slice(0, at), grain };\n}\n\nexport function dimensionFor(\n dimensions: readonly MatrxDrillDimension[],\n ref: string,\n): MatrxDrillDimension | undefined {\n const { key } = parseDimensionRef(ref);\n return dimensions.find((d) => d.key === key);\n}\n\n/** \"Last serviced by month\", \"Condition\". */\nexport function dimensionRefLabel(dimensions: readonly MatrxDrillDimension[], ref: string): string {\n const dim = dimensionFor(dimensions, ref);\n const { grain } = parseDimensionRef(ref);\n const base = dim?.label ?? ref;\n return grain ? `${base} by ${grain}` : base;\n}\n\n/** The references a person may group by: each Dimension, and each time Dimension at each grain. */\nexport function dimensionRefs(dimensions: readonly MatrxDrillDimension[]): string[] {\n const out: string[] = [];\n for (const dim of dimensions) {\n if (dim.kind === \"time\") {\n for (const grain of dim.grains ?? MATRX_DRILL_DEFAULT_GRAINS) out.push(`${dim.key}:${grain}`);\n } else out.push(dim.key);\n }\n return out;\n}\n\n/** How one group value reads. A blank is said, never shown as an empty cell. */\nexport function drillValueLabel(\n dimensions: readonly MatrxDrillDimension[],\n ref: string,\n value: string | null,\n emptyLabel = \"No value\",\n): string {\n const dim = dimensionFor(dimensions, ref);\n if (dim?.labelFor) return dim.labelFor(value);\n if (value === null || value === \"\") return emptyLabel;\n const { grain } = parseDimensionRef(ref);\n if (grain) return bucketLabel(value, grain);\n if (dim?.kind === \"boolean\") return value === \"true\" ? \"Yes\" : value === \"false\" ? \"No\" : value;\n return value;\n}\n\n/** A bucket start printed for its grain: \"Sep 28, 2026 14:00\", \"Sep 2026\", \"Q3 2026\", \"2026\", \"Week of Sep 7, 2026\". */\nexport function bucketLabel(value: string, grain: MatrxDrillGrain): string {\n const day = value.slice(0, 10);\n const [y, m, d] = day.split(\"-\").map((part) => Number(part));\n if (!y || !m || !d) return value;\n const date = new Date(Date.UTC(y, m - 1, d));\n const month = date.toLocaleString(\"en-US\", { month: \"short\", timeZone: \"UTC\" });\n if (grain === \"year\") return String(y);\n if (grain === \"quarter\") return `Q${Math.floor((m - 1) / 3) + 1} ${y}`;\n if (grain === \"month\") return `${month} ${y}`;\n if (grain === \"week\") return `Week of ${month} ${d}, ${y}`;\n // An hour reads on the store's own clock (the label's offset), never the browser's.\n if (grain === \"hour\") return `${month} ${d}, ${y} ${/^\\d{4}-\\d{2}-\\d{2}[T ](\\d{2}:\\d{2})/.exec(value)?.[1] ?? \"00:00\"}`;\n return `${month} ${d}, ${y}`;\n}\n\n/**\n * THE HALF-OPEN WINDOW a bucket value stands for — what a click on \"Sep 2026\" filters to. The\n * start keeps whatever offset the store cut it with; the end is the next bucket's start in the\n * same offset, so the window and the bucket are one period.\n */\nexport function bucketWindow(value: string, grain: MatrxDrillGrain): { from: string; to: string } {\n if (grain === \"hour\") {\n // The wall-clock hour after, in the same offset the store cut it with.\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})[T ](\\d{2}):(\\d{2})(?::(\\d{2}))?(.*)$/.exec(value);\n if (m) {\n const next = new Date(Date.UTC(+m[1]!, +m[2]! - 1, +m[3]!, +m[4]! + 1, +m[5]!, +(m[6] ?? 0)));\n const iso = next.toISOString();\n return { from: value, to: `${iso.slice(0, 10)}T${iso.slice(11, 16)}${m[6] !== undefined ? iso.slice(16, 19) : \"\"}${m[7] ?? \"\"}` };\n }\n }\n const day = value.slice(0, 10);\n const rest = value.slice(10);\n const [y, m, d] = day.split(\"-\").map((part) => Number(part));\n const start = new Date(Date.UTC(y ?? 1970, (m ?? 1) - 1, d ?? 1));\n const end = new Date(start);\n if (grain === \"day\") end.setUTCDate(end.getUTCDate() + 1);\n else if (grain === \"week\") end.setUTCDate(end.getUTCDate() + 7);\n else if (grain === \"month\") end.setUTCMonth(end.getUTCMonth() + 1);\n else if (grain === \"quarter\") end.setUTCMonth(end.getUTCMonth() + 3);\n else end.setUTCFullYear(end.getUTCFullYear() + 1);\n return { from: value, to: `${end.toISOString().slice(0, 10)}${rest}` };\n}\n\n// ── the question ───────────────────────────────────────────────────────────\n\nexport function emptyDrillQuestion(show: string[] = [\"count\"]): MatrxDrillQuestion {\n return { by: [], across: null, show, where: [] };\n}\n\n/** Is the table answering a grouped question (summary rows) rather than listing records? */\nexport function drillIsGrouped(question: MatrxDrillQuestion): boolean {\n return question.by.length > 0;\n}\n\n/** The drill path: the question's own, else every Dimension reference in the order declared (time at month). */\nexport function drillPath(question: MatrxDrillQuestion, dimensions: readonly MatrxDrillDimension[]): string[] {\n if (question.path && question.path.length > 0) return question.path;\n return dimensions.map((d) => (d.kind === \"time\" ? `${d.key}:month` : d.key));\n}\n\n/**\n * The Dimension a drill on the deepest level regroups by: the next step of the path after the\n * deepest level, skipping anything already filtered or grouped. `null` = the path is spent, so\n * the drill shows the records themselves.\n */\nexport function nextDrillLevel(\n question: MatrxDrillQuestion,\n dimensions: readonly MatrxDrillDimension[],\n paths: readonly (readonly string[])[] = [],\n): string | null {\n const deepest = question.by[question.by.length - 1];\n const usedRefs = new Set<string>([...question.where.map((w) => w.dim), ...question.by]);\n // A DECLARED PATH wins: the level after the deepest one (a year drills into its quarters, the\n // same date column one period finer — the one place a Dimension follows itself).\n if (deepest) {\n for (const levels of paths) {\n const at = levels.indexOf(deepest);\n if (at < 0) continue;\n const next = levels.slice(at + 1).find((ref) => !usedRefs.has(ref) && dimensionFor(dimensions, ref));\n if (next) return next;\n }\n }\n const path = drillPath(question, dimensions);\n const used = new Set<string>([\n ...question.where.map((w) => parseDimensionRef(w.dim).key),\n ...question.by.map((b) => parseDimensionRef(b).key),\n ...(question.across ? [parseDimensionRef(question.across).key] : []),\n ]);\n const from = deepest ? path.indexOf(deepest) : -1;\n const ordered = from >= 0 ? [...path.slice(from + 1), ...path.slice(0, from)] : path;\n for (const ref of ordered) {\n if (!used.has(parseDimensionRef(ref).key) && dimensionFor(dimensions, ref)) return ref;\n }\n return null;\n}\n\n/**\n * DRILL INTO A GROUP (Metabase's default click). Every level of the clicked row becomes a\n * filter, in order, and the table regroups by `into` — the next Dimension of the path when not\n * named (\"Drill into\"), any Dimension when named (\"Break out by\"). `into === null` with nothing\n * left in the path lists the group's records.\n */\nexport function drillInto(\n question: MatrxDrillQuestion,\n groups: Record<string, string | null>,\n dimensions: readonly MatrxDrillDimension[],\n into?: string | null,\n paths: readonly (readonly string[])[] = [],\n): MatrxDrillQuestion {\n const added: MatrxDrillFilter[] = question.by\n .filter((ref) => ref in groups)\n .map((ref) => ({ dim: ref, value: groups[ref] ?? null }));\n const narrowed: MatrxDrillQuestion = { ...question, where: [...question.where, ...added] };\n const next = into === undefined ? nextDrillLevel(question, dimensions, paths) : into;\n return { ...narrowed, by: next ? [next] : [], across: next && question.across === next ? null : question.across ?? null };\n}\n\n/** \"See these records\": the group's filters, and no grouping — the records themselves. */\nexport function drillToRecords(question: MatrxDrillQuestion, groups: Record<string, string | null>): MatrxDrillQuestion {\n const added = question.by.filter((ref) => ref in groups).map((ref) => ({ dim: ref, value: groups[ref] ?? null }));\n return { ...question, by: [], across: null, where: [...question.where, ...added] };\n}\n\n/** One crumb of the trail. `index` -1 is the root (\"All records\"). */\nexport interface MatrxDrillCrumb {\n index: number;\n label: string;\n /** The Dimension that was filtered at this step, and its value's words. */\n dimLabel?: string;\n valueLabel?: string;\n}\n\nexport function drillCrumbs(\n question: MatrxDrillQuestion,\n dimensions: readonly MatrxDrillDimension[],\n rootLabel: string,\n emptyLabel?: string,\n): MatrxDrillCrumb[] {\n return [\n { index: -1, label: rootLabel },\n ...question.where.map((w, index) => {\n const dimLabel = dimensionRefLabel(dimensions, w.dim);\n const valueLabel = drillValueLabel(dimensions, w.dim, w.value, emptyLabel);\n return { index, label: valueLabel, dimLabel, valueLabel };\n }),\n ];\n}\n\n/**\n * ZOOM BACK OUT to a crumb: the filters up to and including it stay; the table regroups by the\n * Dimension that was filtered NEXT, so the person sees the level they drilled out of — Power BI's\n * drill-up, one crumb at a time or straight to any crumb.\n */\nexport function zoomOutTo(question: MatrxDrillQuestion, index: number): MatrxDrillQuestion {\n const kept = question.where.slice(0, index + 1);\n const leaving = question.where[index + 1];\n if (!leaving) return question;\n return { ...question, where: kept, by: [leaving.dim] };\n}\n\n// ── what the host is asked ─────────────────────────────────────────────────\n\nexport function drillRequestKey(by: readonly string[]): string {\n return by.length === 0 ? \"∅\" : by.join(\"|\");\n}\n\n/**\n * EVERY GROUPING THE SCREEN NEEDS, and nothing it does not: the grand total; each level's\n * prefix of `by` (so an outer group's average is the store's average, never an average of\n * averages); and, with a pivot, each of those crossed with `across` plus `across` alone for the\n * column totals. At most eight calls for the deepest pivot.\n */\nexport function drillRequests(question: MatrxDrillQuestion): MatrxDrillRequest[] {\n const out: MatrxDrillRequest[] = [];\n const add = (by: string[]) => {\n const key = drillRequestKey(by);\n if (!out.some((r) => r.key === key)) out.push({ key, by });\n };\n add([]);\n const by = question.by.slice(0, MATRX_DRILL_MAX_LEVELS);\n for (let i = 1; i <= by.length; i++) add(by.slice(0, i));\n const across = question.across ?? null;\n if (across && by.length > 0) {\n add([across]);\n for (let i = 1; i <= by.length; i++) add([...by.slice(0, i), across]);\n }\n return out;\n}\n\n// ── the answer, as a tree ──────────────────────────────────────────────────\n\nexport interface MatrxDrillNode {\n /** Stable across re-reads: the path of values from the outermost level. */\n key: string;\n level: number;\n /** Dimension reference → value, for this level and every level above it. */\n groups: Record<string, string | null>;\n row: MatrxDrillAnswerRow;\n children: MatrxDrillNode[];\n /** What the store's group limit left out under this node (or at the top), said as one row. */\n other: MatrxDrillOther | null;\n}\n\n/** THE REST — what a group limit left out, so the page always adds up to its total. */\nexport interface MatrxDrillOther {\n row_count: number;\n /** Only additive measures; a non-additive one is `null` (left blank, not invented). */\n measures: Record<string, number | null>;\n}\n\nfunction valueKey(value: string | null): string {\n return value === null ? \"∅\" : `v:${value}`;\n}\n\nfunction matches(row: MatrxDrillAnswerRow, groups: Record<string, string | null>): boolean {\n for (const [ref, value] of Object.entries(groups)) {\n if ((row.groups[ref] ?? null) !== value) return false;\n }\n return true;\n}\n\n/**\n * THE REST, worked out from the parent's own total: the parent's rows minus the rows its shown\n * children hold. Non-additive measures cannot be subtracted and stay blank. `null` when nothing\n * is left over — the common case, and then no row is drawn.\n */\nexport function otherOf(\n parent: MatrxDrillAnswerRow | undefined,\n children: readonly MatrxDrillAnswerRow[],\n measures: readonly MatrxDrillMeasure[],\n): MatrxDrillOther | null {\n if (!parent) return null;\n const shown = children.reduce((sum, row) => sum + (row.row_count ?? 0), 0);\n const rest = (parent.row_count ?? 0) - shown;\n if (rest <= 0) return null;\n const out: Record<string, number | null> = {};\n for (const measure of measures) {\n if (!measure.additive) {\n out[measure.key] = null;\n continue;\n }\n const whole = parent.measures[measure.key];\n if (whole === null || whole === undefined) {\n out[measure.key] = null;\n continue;\n }\n const part = children.reduce((sum, row) => sum + (row.measures[measure.key] ?? 0), 0);\n out[measure.key] = whole - part;\n }\n return { row_count: rest, measures: out };\n}\n\nfunction sortRows(\n rows: MatrxDrillAnswerRow[],\n ref: string,\n sort: MatrxDrillQuestion[\"sort\"],\n /** A pivot column's cell for a row (`across:<measure>:<value>` sort keys), when the answer pivots. */\n cellOf?: (row: MatrxDrillAnswerRow, measure: string, value: string | null) => number | null | undefined,\n): MatrxDrillAnswerRow[] {\n if (!sort) return rows;\n const dir = sort.direction === \"asc\" ? 1 : -1;\n return [...rows].sort((a, b) => {\n if (sort.key === \"label\") {\n const av = a.groups[ref];\n const bv = b.groups[ref];\n if (av === bv) return 0;\n if (av === null || av === undefined) return 1;\n if (bv === null || bv === undefined) return -1;\n return av.localeCompare(bv, undefined, { numeric: true }) * dir;\n }\n const read = (row: MatrxDrillAnswerRow): number | null | undefined => {\n // ONE PIVOT COLUMN'S CELL (L5): `across:<measure>:<value>`, \"(none)\" the no-value column\n if (sort.key.startsWith(\"across:\")) {\n const rest = sort.key.slice(7);\n const at = rest.indexOf(\":\");\n if (at < 0 || !cellOf) return null;\n const value = rest.slice(at + 1);\n return cellOf(row, rest.slice(0, at), value === \"(none)\" ? null : value);\n }\n if (sort.key.startsWith(\"delta:\")) {\n const key = sort.key.slice(6);\n return drillDelta(row.measures[key], row.prior_measures?.[key]).change;\n }\n return sort.key === \"count\" && !(sort.key in row.measures) ? row.row_count : row.measures[sort.key];\n };\n const av = read(a);\n const bv = read(b);\n if (av === bv) return 0;\n if (av === null || av === undefined) return 1;\n if (bv === null || bv === undefined) return -1;\n return (av - bv) * dir;\n });\n}\n\n/**\n * THE TREE a grouped answer draws: level 0 from the request for `by[0]`, each deeper level from\n * the request for its prefix, children matched to their parent by the parent's values. `null`\n * while any level is still unanswered.\n */\nexport function buildDrillTree(\n question: MatrxDrillQuestion,\n answers: MatrxDrillAnswers,\n measures: readonly MatrxDrillMeasure[],\n): { nodes: MatrxDrillNode[]; total: MatrxDrillAnswerRow | null; other: MatrxDrillOther | null } | null {\n const by = question.by.slice(0, MATRX_DRILL_MAX_LEVELS);\n const levels: (readonly MatrxDrillAnswerRow[])[] = [];\n for (let i = 1; i <= by.length; i++) {\n const rows = answers[drillRequestKey(by.slice(0, i))];\n if (!rows) return null;\n levels.push(rows);\n }\n const totalRows = answers[drillRequestKey([])];\n if (!totalRows) return null;\n const total = totalRows[0] ?? { groups: {}, measures: {}, row_count: 0 };\n const shown = measures.filter((m) => question.show.includes(m.key));\n\n const build = (level: number, parentGroups: Record<string, string | null>, parentKey: string): MatrxDrillNode[] => {\n const ref = by[level]!;\n // A PERIOD READS IN TIME ORDER unless the person sorted: months ranked by their count\n // (the door's order) read Aug, Jan, Feb, Nov — a calendar nobody keeps.\n const sort = question.sort ?? (parseDimensionRef(ref).grain ? { key: \"label\", direction: \"asc\" as const } : null);\n const across = question.across ?? null;\n const crossed = across ? answers[drillRequestKey([...by.slice(0, level + 1), across])] : undefined;\n const rows = sortRows(\n (levels[level] ?? []).filter((row) => matches(row, parentGroups)),\n ref,\n sort,\n across && crossed\n ? (row, measure, value) => {\n const groups = { ...parentGroups, [ref]: row.groups[ref] ?? null };\n const hit = crossed.find((r) => matches(r, groups) && (r.groups[across] ?? null) === value);\n return hit ? (hit.measures[measure] ?? null) : null;\n }\n : undefined,\n );\n return rows.map((row) => {\n const groups = { ...parentGroups, [ref]: row.groups[ref] ?? null };\n const key = `${parentKey}/${valueKey(row.groups[ref] ?? null)}`;\n const children = level + 1 < by.length ? build(level + 1, groups, key) : [];\n const childRows = children.map((c) => c.row);\n return {\n key,\n level,\n groups,\n row,\n children,\n other: level + 1 < by.length ? otherOf(row, childRows, shown) : null,\n };\n });\n };\n const nodes = build(0, {}, \"\");\n return { nodes, total, other: otherOf(total, nodes.map((n) => n.row), shown) };\n}\n\n// ── the pivot ──────────────────────────────────────────────────────────────\n\nexport interface MatrxDrillPivotColumn {\n /** `null` is the \"no value\" column; `OTHER` is everything past the cap. */\n value: string | null;\n other?: boolean;\n label: string;\n total: MatrxDrillAnswerRow;\n /** The rest column only: what it holds, in one sentence (\"Showing the latest 24 of 30 periods; …\"). */\n says?: string;\n}\n\nexport const MATRX_DRILL_OTHER_COLUMN = \"\\u0000other\";\n\n/** A period's start as a number to order by (an address may carry a space for the `T`). */\nfunction periodOrder(value: string): number {\n const ms = Date.parse(value.replace(\" \", \"T\"));\n return Number.isNaN(ms) ? Number.NaN : ms;\n}\n\nfunction byPeriod(a: string, b: string): number {\n const x = periodOrder(a);\n const y = periodOrder(b);\n if (Number.isNaN(x) || Number.isNaN(y)) return a < b ? -1 : a > b ? 1 : 0;\n return x - y;\n}\n\n/**\n * THE COLUMNS a pivot draws, capped at `cap` (the host's knob `drill.pivot_columns`), with ONE rest\n * column holding what the cap left out, so the columns always add up to the row total. `null`\n * while the pivot's own totals are unanswered.\n *\n * - Across a CATEGORY: the values largest first (the store's order); the rest column comes last\n * (\"6 more\").\n * - Across TIME: the periods in CALENDAR order, and when capped the LATEST periods are kept and\n * the earlier ones fold into the rest column, drawn first (\"6 earlier\") — the same rule the\n * door uses for a time-first grouping. A column of no date comes after the periods.\n *\n * The rest column says what it holds in `says`; the answer table prints it above the table.\n */\nexport function pivotColumns(\n question: MatrxDrillQuestion,\n answers: MatrxDrillAnswers,\n dimensions: readonly MatrxDrillDimension[],\n measures: readonly MatrxDrillMeasure[],\n cap = MATRX_DRILL_PIVOT_COLUMNS,\n): MatrxDrillPivotColumn[] | null {\n const across = question.across ?? null;\n if (!across || question.by.length === 0) return [];\n const rows = answers[drillRequestKey([across])];\n if (!rows) return null;\n const limit = Math.max(1, Math.floor(cap));\n const column = (row: MatrxDrillAnswerRow): MatrxDrillPivotColumn => ({\n value: row.groups[across] ?? null,\n label: drillValueLabel(dimensions, across, row.groups[across] ?? null),\n total: row,\n });\n const restOf = (rest: readonly MatrxDrillAnswerRow[], label: string, says: string): MatrxDrillPivotColumn => {\n const shown = measures.filter((m) => question.show.includes(m.key));\n const measuresOut: Record<string, number | null> = {};\n for (const m of shown) {\n measuresOut[m.key] = m.additive ? rest.reduce((sum, r) => sum + (r.measures[m.key] ?? 0), 0) : null;\n }\n return {\n value: MATRX_DRILL_OTHER_COLUMN,\n other: true,\n label,\n says,\n total: { groups: {}, measures: measuresOut, row_count: rest.reduce((sum, r) => sum + r.row_count, 0) },\n };\n };\n\n const isTime = parseDimensionRef(across).grain !== null || dimensionFor(dimensions, across)?.kind === \"time\";\n if (isTime) {\n const dated = rows.filter((r) => r.groups[across] !== null && r.groups[across] !== undefined && r.groups[across] !== \"\");\n const undated = rows.filter((r) => !dated.includes(r));\n const inOrder = [...dated].sort((a, b) => byPeriod(a.groups[across]!, b.groups[across]!));\n const room = Math.max(0, limit - undated.length);\n const earlier = inOrder.length > room ? inOrder.slice(0, inOrder.length - room) : [];\n const kept = inOrder.slice(earlier.length);\n const cols = kept.map(column);\n if (earlier.length > 0) {\n cols.unshift(\n restOf(\n earlier,\n `${earlier.length.toLocaleString()} earlier`,\n `Showing the latest ${kept.length.toLocaleString()} of ${inOrder.length.toLocaleString()} periods; the ${earlier.length.toLocaleString()} earlier ${earlier.length === 1 ? \"one is\" : \"ones are\"} added together in the first column. Ask by a coarser period or a shorter window to see them one by one.`,\n ),\n );\n }\n return [...cols, ...undated.map(column)];\n }\n\n const cols = rows.slice(0, limit).map(column);\n if (rows.length > limit) {\n const rest = rows.slice(limit);\n cols.push(\n restOf(\n rest,\n `${formatCount(rest.length)} more`,\n `Showing ${formatCount(limit)} of ${formatCount(rows.length)} columns; the other ${formatCount(rest.length)} are added together in the last column.`,\n ),\n );\n }\n return cols;\n}\n\n/**\n * One pivot cell: the row's values crossed with one column. The \"Other\" column is the rest of the\n * row — the row's own total minus the capped columns — for additive measures only.\n */\nexport function pivotCell(\n question: MatrxDrillQuestion,\n answers: MatrxDrillAnswers,\n node: Pick<MatrxDrillNode, \"level\" | \"groups\" | \"row\">,\n column: MatrxDrillPivotColumn,\n columns: readonly MatrxDrillPivotColumn[],\n measure: MatrxDrillMeasure,\n): number | null {\n const across = question.across;\n if (!across) return null;\n const prefix = question.by.slice(0, node.level + 1);\n const rows = answers[drillRequestKey([...prefix, across])] ?? [];\n const mine = rows.filter((row) => matches(row, node.groups));\n if (column.other) {\n if (!measure.additive) return null;\n const whole = node.row.measures[measure.key];\n if (whole === null || whole === undefined) return null;\n const named = columns.filter((c) => !c.other);\n const part = mine\n .filter((row) => named.some((c) => c.value === (row.groups[across] ?? null)))\n .reduce((sum, row) => sum + (row.measures[measure.key] ?? 0), 0);\n const rest = whole - part;\n return rest === 0 ? null : rest;\n }\n const hit = mine.find((row) => (row.groups[across] ?? null) === column.value);\n return hit ? (hit.measures[measure.key] ?? null) : null;\n}\n\n// ── the address ────────────────────────────────────────────────────────────\n\n/** \"(none)\" is the address's word for a blank value (the design's URL grammar, f.4). */\nexport const MATRX_DRILL_NONE = \"(none)\";\n\nconst QUESTION_KEYS = [\"by\", \"across\", \"show\", \"w\"] as const;\n\nfunction withPrefix(prefix: string | undefined, key: string): string {\n return prefix ? `${prefix}.${key}` : key;\n}\n\n/**\n * Does the address carry a drill QUESTION? `sort` and `path` only refine one — and other screens\n * write a bare `sort` of their own (`?sort=title.asc`) — so they never, alone, override a Saved\n * view's question.\n */\nexport function addressHasDrill(params: URLSearchParams, prefix?: string): boolean {\n for (const key of QUESTION_KEYS) if (params.has(withPrefix(prefix, key))) return true;\n const f = withPrefix(prefix, \"f.\");\n for (const key of params.keys()) if (key.startsWith(f)) return true;\n return false;\n}\n\n/**\n * THE ADDRESS → THE QUESTION. `?by=condition,room&across=last_serviced:month&show=count,sum_quantity\n * &f.condition=Needs%20repair&sort=-sum_quantity&path=condition,room`. The `f.` filters are read\n * in the order the address holds them — that order IS the breadcrumb.\n */\nexport function drillQuestionFromParams(\n params: URLSearchParams,\n fallback: MatrxDrillQuestion,\n prefix?: string,\n): MatrxDrillQuestion {\n if (!addressHasDrill(params, prefix)) return fallback;\n const list = (key: string): string[] | null => {\n const raw = params.get(withPrefix(prefix, key));\n if (raw === null) return null;\n return raw.split(\",\").map((s) => s.trim()).filter(Boolean);\n };\n const where: MatrxDrillFilter[] = [];\n const f = withPrefix(prefix, \"f.\");\n for (const [key, value] of params.entries()) {\n if (!key.startsWith(f)) continue;\n where.push({ dim: key.slice(f.length), value: value === MATRX_DRILL_NONE ? null : value });\n }\n const sortRaw = params.get(withPrefix(prefix, \"sort\"));\n const sort = sortRaw\n ? { key: sortRaw.replace(/^-/, \"\"), direction: sortRaw.startsWith(\"-\") ? (\"desc\" as const) : (\"asc\" as const) }\n : null;\n const by = list(\"by\") ?? [];\n const show = list(\"show\");\n const path = list(\"path\");\n const cmp = params.get(withPrefix(prefix, \"cmp\"));\n const compare: MatrxDrillCompare | null = cmp === \"prev\" ? \"previous_period\" : cmp === \"yoy\" ? \"same_period_last_year\" : null;\n const w = params.get(withPrefix(prefix, \"w\"));\n return {\n by: by.slice(0, MATRX_DRILL_MAX_LEVELS),\n across: params.get(withPrefix(prefix, \"across\")) || null,\n show: show && show.length > 0 ? show : fallback.show,\n where,\n ...(path && path.length > 0 ? { path } : {}),\n ...(sort ? { sort } : {}),\n ...(params.get(withPrefix(prefix, \"share\")) === \"1\" ? { share: true } : {}),\n ...(compare ? { compare } : {}),\n ...(w ? { window: w } : {}),\n };\n}\n\n/**\n * THE QUESTION → AN ADDRESS PATCH for `commitUrlParams`: every drill key set or cleared, and every\n * `f.` filter the address holds today cleared unless the question keeps it. `f.` keys are written\n * in the trail's order.\n */\nexport function drillQuestionToParams(\n question: MatrxDrillQuestion,\n current: URLSearchParams,\n prefix?: string,\n): Record<string, string | null> {\n const patch: Record<string, string | null> = {};\n const f = withPrefix(prefix, \"f.\");\n for (const key of current.keys()) if (key.startsWith(f)) patch[key] = null;\n patch[withPrefix(prefix, \"by\")] = question.by.length > 0 ? question.by.join(\",\") : null;\n patch[withPrefix(prefix, \"across\")] = question.across || null;\n patch[withPrefix(prefix, \"show\")] = question.show.length > 0 ? question.show.join(\",\") : null;\n patch[withPrefix(prefix, \"path\")] = question.path && question.path.length > 0 ? question.path.join(\",\") : null;\n patch[withPrefix(prefix, \"sort\")] = question.sort\n ? `${question.sort.direction === \"desc\" ? \"-\" : \"\"}${question.sort.key}`\n : null;\n patch[withPrefix(prefix, \"share\")] = question.share ? \"1\" : null;\n patch[withPrefix(prefix, \"cmp\")] =\n question.compare === \"previous_period\" ? \"prev\" : question.compare === \"same_period_last_year\" ? \"yoy\" : null;\n patch[withPrefix(prefix, \"w\")] = question.window || null;\n for (const w of question.where) patch[`${f}${w.dim}`] = w.value === null ? MATRX_DRILL_NONE : w.value;\n return patch;\n}\n\n/** Two questions ask the same thing (the address and a Saved view agree). */\nexport function sameDrillQuestion(a: MatrxDrillQuestion, b: MatrxDrillQuestion): boolean {\n return JSON.stringify(normalize(a)) === JSON.stringify(normalize(b));\n}\n\nfunction normalize(q: MatrxDrillQuestion) {\n return {\n by: q.by,\n across: q.across ?? null,\n show: q.show,\n where: q.where.map((w) => [w.dim, w.value]),\n path: q.path && q.path.length > 0 ? q.path : null,\n sort: q.sort ?? null,\n share: q.share ?? false,\n compare: q.compare ?? null,\n window: q.window ?? null,\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────────────────────\n// ADOPTED FROM THE USAGE SCREENS (lane DRILL-USAGE-PAGE, 2026-09-29) — what the Spend Explorer,\n// cx-dashboard usage and the per-user usage page each hand-rolled, once, over the same answer:\n// the grouped answer exported (CSV / text for a person or an AI), the coverage line, the time\n// grain chosen from the window, and the series an inline chart draws.\n// ─────────────────────────────────────────────────────────────────────────────────────────────\n\n/** One line of the grouped answer as a person reads it: the group's words per level, then each shown Measure. */\nexport interface MatrxDrillGridLine {\n kind: \"group\" | \"other\" | \"total\";\n level: number;\n /** One cell per asked Dimension (outer first); a deeper level repeats its parents' words. */\n labels: string[];\n values: Array<number | null>;\n row_count: number;\n}\n\n/**\n * THE GROUPED ANSWER, FLATTENED IN THE ORDER THE TABLE DRAWS IT (adopted from cx-dashboard's CSV/\n * JSON export and the Spend Explorer's Copy): every group, each nested level under its parent,\n * the rest a limit left out as its own \"N more\" line, and the total last — so an export adds up\n * exactly like the screen. `null` while an answer is still being read.\n */\nexport function drillAnswerGrid(\n question: MatrxDrillQuestion,\n answers: MatrxDrillAnswers,\n dimensions: readonly MatrxDrillDimension[],\n measures: readonly MatrxDrillMeasure[],\n emptyLabel = \"No value\",\n): { header: string[]; lines: MatrxDrillGridLine[] } | null {\n const tree = buildDrillTree(question, answers, measures);\n if (!tree) return null;\n const by = question.by.slice(0, MATRX_DRILL_MAX_LEVELS);\n const shown = question.show\n .map((key) => measures.find((m) => m.key === key))\n .filter((m): m is MatrxDrillMeasure => m !== undefined);\n const header = [...by.map((ref) => dimensionRefLabel(dimensions, ref)), ...shown.map((m) => m.label), \"Rows\"];\n const lines: MatrxDrillGridLine[] = [];\n const valuesOf = (source: Record<string, number | null>) => shown.map((m) => source[m.key] ?? null);\n const walk = (nodes: MatrxDrillNode[], parentLabels: string[], other: MatrxDrillOther | null, level: number) => {\n for (const node of nodes) {\n const ref = by[node.level]!;\n const labels = [...parentLabels, drillValueLabel(dimensions, ref, node.groups[ref] ?? null, emptyLabel)];\n lines.push({ kind: \"group\", level: node.level, labels: padLabels(labels, by.length), values: valuesOf(node.row.measures), row_count: node.row.row_count });\n if (node.children.length > 0) walk(node.children, labels, node.other, node.level + 1);\n }\n if (other) {\n const labels = [...parentLabels, \"Everything else\"];\n lines.push({ kind: \"other\", level, labels: padLabels(labels, by.length), values: valuesOf(other.measures), row_count: other.row_count });\n }\n };\n walk(tree.nodes, [], tree.other, 0);\n if (tree.total) {\n lines.push({ kind: \"total\", level: 0, labels: padLabels([\"Total\"], by.length), values: valuesOf(tree.total.measures), row_count: tree.total.row_count });\n }\n return { header, lines };\n}\n\nfunction padLabels(labels: string[], width: number): string[] {\n const out = labels.slice(0, Math.max(width, 1));\n while (out.length < Math.max(width, 1)) out.push(\"\");\n return out;\n}\n\nfunction csvCell(value: string): string {\n return /[\",\\n\\r]/.test(value) ? `\"${value.replace(/\"/g, '\"\"')}\"` : value;\n}\n\n/** The grouped answer as CSV: raw numbers (a spreadsheet does its own formatting), one header row. */\nexport function drillAnswerCsv(grid: { header: string[]; lines: MatrxDrillGridLine[] }): string {\n const rows = [grid.header, ...grid.lines.map((l) => [...l.labels, ...l.values.map((v) => (v === null ? \"\" : String(v))), String(l.row_count)])];\n return rows.map((r) => r.map(csvCell).join(\",\")).join(\"\\n\");\n}\n\n/**\n * The grouped answer as text a person pastes or an AI reads: what was asked (the trail, the\n * window), then one tab-separated line per group with each Measure in its own format.\n */\nexport function drillAnswerText(\n grid: { header: string[]; lines: MatrxDrillGridLine[] },\n measures: readonly MatrxDrillMeasure[],\n question: MatrxDrillQuestion,\n context: { title: string; trail?: string[] | undefined; window?: string | null | undefined },\n): string {\n const shown = question.show\n .map((key) => measures.find((m) => m.key === key))\n .filter((m): m is MatrxDrillMeasure => m !== undefined);\n const levels = Math.max(question.by.slice(0, MATRX_DRILL_MAX_LEVELS).length, 1);\n const head = [\n context.title,\n `Window: ${drillWindowLabel(context.window ?? question.window ?? null)}`,\n ...(context.trail && context.trail.length > 0 ? [`Narrowed to: ${context.trail.join(\" › \")}`] : []),\n \"\",\n grid.header.join(\"\\t\"),\n ];\n const body = grid.lines.map((l) => {\n const indent = l.kind === \"group\" ? \" \".repeat(l.level) : \"\";\n const labels = l.labels.slice(0, levels).map((x, i) => (i === l.level ? indent + x : x));\n const values = l.values.map((v, i) => {\n if (v === null) return \"not measured\";\n const m = shown[i];\n return m?.format ? m.format(v) : v.toLocaleString(undefined, { maximumFractionDigits: 2 });\n });\n return [...labels, ...values, l.row_count.toLocaleString()].join(\"\\t\");\n });\n return [...head, ...body].join(\"\\n\");\n}\n\n/**\n * THE GRAIN A WINDOW READS BEST AT (adopted from the Spend Explorer, which draws hours for short\n * windows and days otherwise; DRILL-DOWN-DESIGN (g) default): two days or less by hour, up to 90\n * days by day, up to a year by week, longer by month. `grains` = what the Dimension offers: a\n * grain it does not offer steps to the nearest coarser one it does (a date column never reads by hour).\n */\nexport function drillAutoGrain(\n window: string | null | undefined,\n now: Date = new Date(),\n grains: readonly MatrxDrillGrain[] = MATRX_DRILL_GRAINS,\n): MatrxDrillGrain {\n const range = drillWindowRange(window, now);\n const days = range ? (new Date(range.to).getTime() - new Date(range.from).getTime()) / 86_400_000 : NaN;\n const wanted: MatrxDrillGrain = !range ? \"month\" : !Number.isFinite(days) || days <= 2 ? \"hour\" : days <= 90 ? \"day\" : days <= 366 ? \"week\" : \"month\";\n return offeredGrain(wanted, grains);\n}\n\n/** The grain itself when offered, else the nearest coarser offered one (else the coarsest offered). */\nfunction offeredGrain(wanted: MatrxDrillGrain, grains: readonly MatrxDrillGrain[]): MatrxDrillGrain {\n if (grains.length === 0 || grains.includes(wanted)) return wanted;\n const at = MATRX_DRILL_GRAINS.indexOf(wanted);\n return MATRX_DRILL_GRAINS.slice(at).find((g) => grains.includes(g)) ?? grains[grains.length - 1]!;\n}\n\n/**\n * THE COVERAGE LINE (adopted from the Spend Explorer's filter chips: \"This slice is $X of the $Y\n * the whole window cost\"): what part of the unfiltered whole the narrowed answer is. `null` when\n * nothing is narrowed or the whole is not known yet.\n */\nexport function drillCoverage(\n slice: number | null | undefined,\n whole: number | null | undefined,\n format: (value: number) => string,\n): { share: number; sentence: string } | null {\n if (slice === null || slice === undefined || whole === null || whole === undefined || whole <= 0) return null;\n const share = slice / whole;\n const pctText = `${share >= 0.995 ? Math.round(share * 100) : (share * 100).toFixed(share * 100 < 10 ? 1 : 0)}%`;\n // a label, not a sentence (interface text is layout, 2026-09-30): the bar beside it and its tooltip\n // say it is this slice's share of the whole window\n return { share, sentence: `${format(slice)} of ${format(whole)} (${pctText})` };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────────────────────\n// THE CHART (lane DRILL-CHART, DRILL-FINISH decision 5) — stacked bars over time, as pure data.\n// The component (`./data-table/drill-chart`, recharts) only draws what these functions decide.\n//\n// WHAT THE DOOR CAN AND CANNOT SAY (read from platform._drill_compile on the nightly clone,\n// 2026-09-30): the group cap ranks the WHOLE group key. `by: [model, \"at:day\"]` with a limit of 10\n// keeps the ten largest (model, day) PAIRS and returns everything else as ONE \"other\" row with no\n// period — so the rest cannot be read per period from a two-level question. A pivot\n// (`across: model`) folds by COUNT, not by the chosen Measure, under its own knob, and is refused\n// for a high-cardinality Dimension. So the chart asks, in two rounds:\n// 1. `series` — by [split], sorted by the Measure, limit = seriesLimit: the top N over the WHOLE\n// window, their totals, and the window total;\n// `periods` — by [time]: every period's total (asked beside `series`);\n// 2. `cells` — by [time, split], where split ∈ the top N: each shown value per period.\n// Other per period = the period's total − its shown values, so every bar's stack sums to its\n// period's total and all bars sum to the window total. With no split, `periods` alone is one series.\n// ─────────────────────────────────────────────────────────────────────────────────────────────\n\n/** Series shown before Other (the host feeds the knob `drill.chart.top_n`; default 10). */\nexport const MATRX_DRILL_CHART_SERIES_LIMIT = 10;\n/** The store's page ceiling by default (`custom.page_ceiling`); a host with a lower one passes it. */\nexport const MATRX_DRILL_CHART_CEILING = 1000;\n/** The series key of Other — everything past the top N. */\nexport const MATRX_DRILL_CHART_OTHER = \"\\u0000other\";\n\n/** One row exactly as the door returned it: `kind` tells a group from the rest and the total. */\nexport interface MatrxDrillChartRow extends MatrxDrillAnswerRow {\n kind?: \"group\" | \"other\" | \"total\" | undefined;\n}\n\nexport type MatrxDrillChartRequestKey = \"series\" | \"periods\" | \"cells\";\n\n/** One question the host asks its door (`platform.drill_ask`), in the contract's own words. */\nexport interface MatrxDrillChartRequest {\n key: MatrxDrillChartRequestKey;\n /** Dimension references, outermost first. */\n by: string[];\n /** The trail as equalities (`null` = not set), plus — for `cells` — the split as a list of values. */\n where: Record<string, string | null | Array<string | null>>;\n show: string[];\n sort: { key: string; direction: \"asc\" | \"desc\" } | null;\n /** Groups before Other; `null` = the door's own default. */\n limit: number | null;\n /** The question's window, unchanged (the host maps it as it maps the table's). */\n window: string | null;\n}\n\nexport interface MatrxDrillChartPlan {\n /** The time reference the bars run along (`at:day`). */\n time: string | null;\n /** The Dimension the stack splits by; `null` = one series. */\n split: string | null;\n measure: string;\n /**\n * STACKED MEASURES (lane DRILL-FLIP-FIXES L3, the CX tab's input vs output tokens): two or more\n * Measures that add up, one series each, stacked per period; no split. Absent = one Measure.\n */\n stack?: string[] | undefined;\n seriesLimit: number;\n /** Why the chart cannot be drawn, in words — or `null`. */\n refused: string | null;\n /** Round one, asked together. Empty when refused. */\n first: MatrxDrillChartRequest[];\n /** @internal carried for round two */\n ceiling: number;\n periodsHint: number | null;\n}\n\nexport interface MatrxDrillChartContext {\n dimensions: readonly MatrxDrillDimension[];\n measures: readonly MatrxDrillMeasure[];\n /** The Measure stacked. Default: the question's first shown Measure. */\n measure?: string | null | undefined;\n /** Stack these Measures (two or more that add up) instead of splitting one — see `MatrxDrillChartPlan.stack`. */\n stack?: readonly string[] | null | undefined;\n /**\n * The time reference when the question does not group by time (`at:day`). Default: the first\n * time Dimension at the grain the window reads best at (`drillAutoGrain`).\n */\n time?: string | null | undefined;\n /** The store's page ceiling (default 1000). */\n ceiling?: number | undefined;\n now?: Date | undefined;\n}\n\nconst GRAIN_DAYS: Record<MatrxDrillGrain, number> = { hour: 1 / 24, day: 1, week: 7, month: 30.44, quarter: 91.31, year: 365.25 };\n\nfunction trailWhere(question: MatrxDrillQuestion): Record<string, string | null> {\n const where: Record<string, string | null> = {};\n for (const w of question.where) where[w.dim] = w.value;\n return where;\n}\n\nfunction clampLimit(n: number, ceiling: number): number {\n return Math.max(1, Math.min(ceiling, Math.ceil(n)));\n}\n\n/**\n * WHAT THE CHART MUST ASK, from the question the table answers. The bars run along the question's\n * time grouping (or `context.time`, or the first time Dimension at the window's grain); the stack\n * splits by the outermost non-time Dimension it groups by. A Measure that does not add up\n * (an average, a ratio, a distinct count) cannot be stacked across a split: the plan refuses it in\n * words, and the same Measure over time alone (no split) is drawn as one series.\n */\nexport function drillChartQuestions(\n question: MatrxDrillQuestion,\n seriesLimit: number = MATRX_DRILL_CHART_SERIES_LIMIT,\n context: MatrxDrillChartContext,\n): MatrxDrillChartPlan {\n const ceiling = Math.max(1, context.ceiling ?? MATRX_DRILL_CHART_CEILING);\n const limit = clampLimit(Number.isFinite(seriesLimit) ? seriesLimit : MATRX_DRILL_CHART_SERIES_LIMIT, ceiling);\n const measureKey = context.measure ?? question.show[0] ?? \"count\";\n const measure = context.measures.find((m) => m.key === measureKey);\n const base = { measure: measureKey, seriesLimit: limit, ceiling };\n\n const timeInBy = question.by.find((ref) => parseDimensionRef(ref).grain !== null);\n const firstTime = context.dimensions.find((d) => d.kind === \"time\");\n const time =\n timeInBy ??\n context.time ??\n (firstTime ? `${firstTime.key}:${drillAutoGrain(question.window ?? null, context.now, firstTime.grains ?? MATRX_DRILL_DEFAULT_GRAINS)}` : null);\n const stack = context.stack && context.stack.length >= 2 ? [...context.stack] : null;\n const split = stack\n ? null\n : question.by.find((ref) => parseDimensionRef(ref).grain === null && dimensionFor(context.dimensions, ref)?.kind !== \"time\") ?? null;\n\n const refuse = (sentence: string): MatrxDrillChartPlan => ({ ...base, time, split, refused: sentence, first: [], periodsHint: null });\n if (!time) return refuse(\"This answer has no time to chart it over.\");\n if (stack) {\n const missing = stack.find((k) => !context.measures.some((m) => m.key === k));\n if (missing) return refuse(`There is no Measure \"${missing}\" to chart.`);\n const loose = stack.map((k) => context.measures.find((m) => m.key === k)!).find((m) => !m.additive || m.moment);\n if (loose) return refuse(`${loose.label} does not add up, so it cannot be stacked.`);\n }\n if (!measure) return refuse(`There is no Measure \"${measureKey}\" to chart.`);\n if (split && !measure.additive) {\n return refuse(\n `${measure.label} does not add up across ${dimensionRefLabel(context.dimensions, split)}, so it cannot be stacked. ` +\n `Group by time alone to chart it, or show a Measure that adds up.`,\n );\n }\n\n const grain = parseDimensionRef(time).grain ?? \"day\";\n const range = drillWindowRange(question.window ?? null, context.now);\n const days = range ? (new Date(range.to).getTime() - new Date(range.from).getTime()) / 86_400_000 : NaN;\n const periodsHint = Number.isFinite(days) && days > 0 ? Math.ceil(days / GRAIN_DAYS[grain]) + 1 : null;\n const where = trailWhere(question);\n const window = question.window ?? null;\n const show = stack ?? [measureKey];\n const first: MatrxDrillChartRequest[] = [];\n if (split) {\n first.push({ key: \"series\", by: [split], where, show, sort: { key: measureKey, direction: \"desc\" }, limit, window });\n }\n first.push({ key: \"periods\", by: [time], where, show, sort: null, limit: periodsHint ? clampLimit(periodsHint, ceiling) : null, window });\n return { ...base, ...(stack ? { measure: stack[0]!, stack } : {}), time, split, refused: null, first, periodsHint };\n}\n\n/**\n * ROUND TWO: each shown value per period — `by [time, split]` restricted to the top N the `series`\n * answer named. `null` without a split, or when the window holds no group at all.\n */\nexport function drillChartCellsQuestion(\n plan: MatrxDrillChartPlan,\n question: MatrxDrillQuestion,\n series: readonly MatrxDrillChartRow[],\n): MatrxDrillChartRequest | null {\n if (plan.refused || !plan.split || !plan.time) return null;\n const split = plan.split;\n const values = series.filter((r) => rowKind(r) === \"group\").slice(0, plan.seriesLimit).map((r) => r.groups[split] ?? null);\n if (values.length === 0) return null;\n const cells = plan.periodsHint ? plan.periodsHint * values.length : plan.ceiling;\n return {\n key: \"cells\",\n by: [plan.time, split],\n where: { ...trailWhere(question), [split]: values },\n show: [plan.measure],\n sort: null,\n limit: clampLimit(cells, plan.ceiling),\n window: question.window ?? null,\n };\n}\n\nfunction rowKind(row: MatrxDrillChartRow): \"group\" | \"other\" | \"total\" {\n if (row.kind) return row.kind;\n return Object.keys(row.groups ?? {}).length > 0 ? \"group\" : \"total\";\n}\n\nexport interface MatrxDrillChartSeries {\n /** `v:<value>`, `∅` for \"no value\", or MATRX_DRILL_CHART_OTHER. */\n key: string;\n value: string | null;\n label: string;\n other: boolean;\n /** Its total over the whole window. */\n total: number | null;\n /** The colour its value keeps (the split Dimension's `colorFor`), as a style reads it. */\n color?: string | undefined;\n}\n\nexport interface MatrxDrillChartPeriod {\n key: string;\n /** The period as the door labelled it (its start). */\n value: string;\n label: string;\n total: number | null;\n /** Series key → its value in this period (Other = the period's total − the shown values). */\n values: Record<string, number | null>;\n}\n\nexport interface MatrxDrillChartData {\n time: string;\n split: string | null;\n measure: string;\n series: MatrxDrillChartSeries[];\n periods: MatrxDrillChartPeriod[];\n /** The window total (the `series` total with a split, else the `periods` total). */\n total: number | null;\n /** Honest sentences: periods or cells past a limit, Other's size. */\n says: string[];\n}\n\nexport type MatrxDrillChartAnswers = Readonly<Partial<Record<MatrxDrillChartRequestKey, readonly MatrxDrillChartRow[] | undefined>>>;\n\nexport function drillChartSeriesKey(value: string | null): string {\n return value === null ? \"∅\" : `v:${value}`;\n}\n\nconst EPSILON = 1e-9;\nfunction tidy(n: number, scale: number | null | undefined): number {\n return Math.abs(n) <= EPSILON * Math.max(1, Math.abs(scale ?? 0)) ? 0 : n;\n}\n\nfunction periodTime(value: string): number {\n const t = Date.parse(value);\n return Number.isFinite(t) ? t : NaN;\n}\n\n/**\n * THE CHART'S DATA from the three answers: periods in time order, the top N series in the answer's\n * rank (largest first) and Other last, each period's Other worked out from its own total — so a\n * bar's stack sums to its period's total and the bars sum to the window total. `null` while any\n * answer the plan needs is still being read (or the plan refused).\n */\nexport function drillChartData(\n plan: MatrxDrillChartPlan,\n answers: MatrxDrillChartAnswers,\n dimensions: readonly MatrxDrillDimension[],\n measures: readonly MatrxDrillMeasure[],\n emptyLabel = \"No value\",\n): MatrxDrillChartData | null {\n if (plan.refused || !plan.time) return null;\n const time = plan.time;\n const split = plan.split;\n const m = plan.measure;\n const measure = measures.find((x) => x.key === m);\n const periodRows = answers.periods;\n if (!periodRows) return null;\n const says: string[] = [];\n const read = (row: MatrxDrillChartRow | undefined): number | null => {\n if (!row) return null;\n const v = row.measures[m];\n return v === null || v === undefined ? (m === \"count\" ? row.row_count : null) : v;\n };\n\n const groups = periodRows\n .filter((r) => rowKind(r) === \"group\")\n .slice()\n .sort((a, b) => {\n const av = a.groups[time] ?? \"\";\n const bv = b.groups[time] ?? \"\";\n const at = periodTime(av);\n const bt = periodTime(bv);\n return Number.isFinite(at) && Number.isFinite(bt) ? at - bt : av.localeCompare(bv);\n });\n const periodsTotal = read(periodRows.find((r) => rowKind(r) === \"total\"));\n const earlier = periodRows.find((r) => rowKind(r) === \"other\");\n if (earlier) {\n const n = Math.max(0, (earlier.distinct_groups ?? groups.length) - groups.length);\n const fmt = measure?.format ?? ((v: number | null) => (v === null ? \"—\" : v.toLocaleString()));\n says.push(\n `${n > 0 ? `The ${n.toLocaleString()} earliest periods` : \"The earliest periods\"} (${fmt(read(earlier))}) are past the limit and not drawn; choose a coarser period or a shorter window to see them.`,\n );\n }\n const grain = parseDimensionRef(time).grain;\n const periodBase = groups.map((r) => {\n const value = r.groups[time] ?? \"\";\n return {\n key: value,\n value,\n label: grain ? bucketLabel(value, grain) : drillValueLabel(dimensions, time, value, emptyLabel),\n total: read(r),\n values: {} as Record<string, number | null>,\n };\n });\n\n if (plan.stack) {\n // ONE SERIES PER MEASURE, stacked: each period's bar sums the Measures (their units agree)\n const keys = plan.stack;\n const readOf = (row: MatrxDrillChartRow | undefined, k: string): number | null => {\n const v = row?.measures[k];\n return v === null || v === undefined ? null : v;\n };\n const totalRow = periodRows.find((r) => rowKind(r) === \"total\");\n for (const [i, p] of periodBase.entries()) {\n const row = groups[i];\n let sum: number | null = null;\n for (const k of keys) {\n const v = readOf(row, k);\n p.values[`m:${k}`] = v;\n if (v !== null) sum = (sum ?? 0) + v;\n }\n p.total = sum;\n }\n const totals = keys.map((k) => readOf(totalRow, k));\n const total = totals.every((t) => t === null) ? null : totals.reduce<number>((a, t) => a + (t ?? 0), 0);\n return {\n time,\n split: null,\n measure: m,\n series: keys.map((k, i) => ({ key: `m:${k}`, value: null, label: measures.find((x) => x.key === k)?.label ?? k, other: false, total: totals[i] ?? null })),\n periods: periodBase,\n total,\n says,\n };\n }\n\n if (!split) {\n const key = \"all\";\n for (const p of periodBase) p.values[key] = p.total;\n return {\n time,\n split: null,\n measure: m,\n series: [{ key, value: null, label: measure?.label ?? m, other: false, total: periodsTotal }],\n periods: periodBase,\n total: periodsTotal,\n says,\n };\n }\n\n const seriesRows = answers.series;\n if (!seriesRows) return null;\n const top = seriesRows.filter((r) => rowKind(r) === \"group\").slice(0, plan.seriesLimit);\n const windowTotal = read(seriesRows.find((r) => rowKind(r) === \"total\")) ?? periodsTotal;\n const cellRows = top.length > 0 ? answers.cells : [];\n if (!cellRows) return null;\n\n const series: MatrxDrillChartSeries[] = top.map((r) => {\n const value = r.groups[split] ?? null;\n const color = drillValueColor(dimensions, split, value);\n return { key: drillChartSeriesKey(value), value, label: drillValueLabel(dimensions, split, value, emptyLabel), other: false, total: read(r), ...(color ? { color } : {}) };\n });\n const shownKeys = new Set(series.map((s) => s.key));\n const cellsPast = cellRows.find((r) => rowKind(r) === \"other\");\n if (cellsPast) {\n says.push(\"Some period-by-series values were past the limit, so they are counted in Other.\");\n }\n const byPeriod = new Map<string, Map<string, number | null>>();\n for (const r of cellRows) {\n if (rowKind(r) !== \"group\") continue;\n const period = r.groups[time] ?? \"\";\n const key = drillChartSeriesKey(r.groups[split] ?? null);\n if (!shownKeys.has(key)) continue;\n let row = byPeriod.get(period);\n if (!row) byPeriod.set(period, (row = new Map()));\n row.set(key, read(r));\n }\n let anyOther = false;\n for (const p of periodBase) {\n const cells = byPeriod.get(p.value);\n let shown = 0;\n for (const s of series) {\n // No row for this value in this period is nothing counted (0); a row whose Measure is blank stays blank.\n const v = cells?.has(s.key) ? (cells.get(s.key) ?? null) : 0;\n p.values[s.key] = v;\n shown += v ?? 0;\n }\n const other = p.total === null ? null : tidy(p.total - shown, p.total);\n p.values[MATRX_DRILL_CHART_OTHER] = other;\n if (other !== null && other !== 0) anyOther = true;\n }\n const shownTotal = series.reduce((sum, s) => sum + (s.total ?? 0), 0);\n const otherTotal = windowTotal === null ? null : tidy(windowTotal - shownTotal, windowTotal);\n const restCount = Math.max(0, (seriesRows.find((r) => r.distinct_groups != null)?.distinct_groups ?? series.length) - series.length);\n if (anyOther || (otherTotal !== null && otherTotal !== 0)) {\n series.push({\n key: MATRX_DRILL_CHART_OTHER,\n value: null,\n label: restCount > 0 ? `Other (${restCount.toLocaleString()} more)` : \"Other\",\n other: true,\n total: otherTotal,\n });\n } else {\n for (const p of periodBase) delete p.values[MATRX_DRILL_CHART_OTHER];\n }\n return { time, split, measure: m, series, periods: periodBase, total: windowTotal, says };\n}\n\n/**\n * A CLICK ON A PERIOD narrows the window to that period (`from..to`, the grammar the address\n * already carries). When the question groups by that time, the grain moves to the one the new\n * window reads best at if that is finer — a month clicked reads by day.\n */\nexport function drillChartNarrowToPeriod(\n question: MatrxDrillQuestion,\n plan: Pick<MatrxDrillChartPlan, \"time\">,\n periodValue: string,\n dimensions: readonly MatrxDrillDimension[] = [],\n): MatrxDrillQuestion {\n const time = plan.time;\n const grain = time ? parseDimensionRef(time).grain : null;\n if (!time || !grain) return question;\n const span = bucketWindow(periodValue, grain);\n // An hour narrows to that hour (moments in UTC); a day and coarser to their dates.\n const window = grain === \"hour\" ? `${drillWindowMoment(span.from)}..${drillWindowMoment(span.to)}` : `${span.from.slice(0, 10)}..${span.to.slice(0, 10)}`;\n const offered = dimensionFor(dimensions, time)?.grains ?? MATRX_DRILL_DEFAULT_GRAINS;\n const finer = drillAutoGrain(window, new Date(span.to), offered);\n const { key } = parseDimensionRef(time);\n const by =\n MATRX_DRILL_GRAINS.indexOf(finer) < MATRX_DRILL_GRAINS.indexOf(grain)\n ? question.by.map((ref) => (ref === time ? `${key}:${finer}` : ref))\n : question.by;\n return { ...question, by, window };\n}\n\n/**\n * A CLICK ON A SEGMENT drills exactly as the answer table's drill does: the split's value becomes\n * a filter of the trail and the answer regroups by the next Dimension of the path (the records,\n * when the path is spent).\n */\nexport function drillChartDrill(\n question: MatrxDrillQuestion,\n plan: Pick<MatrxDrillChartPlan, \"split\">,\n value: string | null,\n dimensions: readonly MatrxDrillDimension[],\n paths: readonly (readonly string[])[] = [],\n): MatrxDrillQuestion {\n const split = plan.split;\n if (!split) return question;\n const scope: MatrxDrillQuestion = { ...question, by: [split] };\n const into = nextDrillLevel(scope, dimensions, paths);\n return drillInto(scope, { [split]: value }, dimensions, into, paths);\n}\n"],"mappings":"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","names":["formatCount","ErrorBox","jsx","m","Fragment","jsx","jsxs","formatCount"]}
1
+ {"version":3,"sources":["../../src/data-table/DrillChart.tsx","../../src/cn.ts","../../src/error-box.tsx","../../src/motion.ts","../../src/skeleton.tsx","../../src/data-table/drill.ts"],"sourcesContent":["\"use client\";\n\n/**\n * THE DRILL CHART — `@ai-matrx/design-system/data-table/drill-chart` (lane DRILL-CHART,\n * DRILL-FINISH decision 5). ONE shared chart, fed by drill answers (`platform.drill_ask`):\n * stacked bars over time; the stack is the top N (`seriesLimit`, the host's knob\n * `drill.chart.top_n`, default 10) of the question's split Dimension ranked by the Measure over the\n * WHOLE window, everything else one Other series — every bar sums to its period's total and the\n * bars to the window total. No split = one series.\n *\n * What it asks is decided by the pure model in `drill.ts` (`drillChartQuestions` → round one,\n * `drillChartCellsQuestion` → round two, `drillChartData` → the bars); this file only draws.\n *\n * click a segment → drill: the value joins the trail, exactly as the answer table's drill\n * click a period → the window narrows to that period (`from..to`)\n * click Other → the period is broken out: the window narrows to it, where the answer table\n * lists every value one by one (said in the tooltip — never a dead click)\n * legend → buttons (keyboard reachable) that hide and show a series\n *\n * Its own entry, so `./data-table` never imports recharts — an OPTIONAL peer only a host that\n * draws this chart installs. Colours are the package's `--matrx-chart-*` tokens (light and dark).\n */\n\nimport { useMemo, useRef, useState, type ReactNode } from \"react\";\nimport { Bar, BarChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from \"recharts\";\n\nimport { formatCount } from \"@ai-matrx/kit/format\";\n\nimport { cn } from \"../cn\";\nimport { ErrorBox } from \"../error-box\";\nimport { Skeleton } from \"../skeleton\";\nimport {\n MATRX_DRILL_CHART_SERIES_LIMIT,\n dimensionRefLabel,\n drillChartData,\n drillChartDrill,\n drillChartNarrowToPeriod,\n drillChartQuestions,\n type MatrxDrillChartAnswers,\n type MatrxDrillChartData,\n type MatrxDrillChartPeriod,\n type MatrxDrillChartSeries,\n type MatrxDrillDimension,\n type MatrxDrillMeasure,\n type MatrxDrillQuestion,\n} from \"./drill\";\n\nexport {\n MATRX_DRILL_CHART_CEILING,\n MATRX_DRILL_CHART_OTHER,\n MATRX_DRILL_CHART_SERIES_LIMIT,\n drillChartCellsQuestion,\n drillChartData,\n drillChartDrill,\n drillChartNarrowToPeriod,\n drillChartQuestions,\n drillChartSeriesKey,\n type MatrxDrillChartAnswers,\n type MatrxDrillChartContext,\n type MatrxDrillChartData,\n type MatrxDrillChartPeriod,\n type MatrxDrillChartPlan,\n type MatrxDrillChartRequest,\n type MatrxDrillChartRequestKey,\n type MatrxDrillChartRow,\n type MatrxDrillChartSeries,\n} from \"./drill\";\n\nexport interface MatrxDrillChartProps {\n dimensions: readonly MatrxDrillDimension[];\n measures: readonly MatrxDrillMeasure[];\n /** The question the answer table answers; the chart asks its own questions from it. */\n question: MatrxDrillQuestion;\n onQuestionChange: (next: MatrxDrillQuestion) => void;\n /** The door's rows (with `kind`) per chart request key: `series`, `periods`, `cells`. */\n answers: MatrxDrillChartAnswers;\n /** Series before Other — the host feeds the knob `drill.chart.top_n`. Default 10. */\n seriesLimit?: number | undefined;\n /** The Measure stacked. Default: the question's first shown Measure. */\n measure?: string | null | undefined;\n /**\n * STACK THESE MEASURES instead (two or more that add up, e.g. input and output tokens): one series\n * each, no split; a segment click narrows to its period (lane DRILL-FLIP-FIXES L3).\n */\n stack?: readonly string[] | null | undefined;\n /** The time reference when the question does not group by time. */\n time?: string | null | undefined;\n /** The store's page ceiling, when the host knows it is not 1000. */\n ceiling?: number | undefined;\n /** Declared drill paths; a segment click follows them like the answer table. */\n paths?: readonly (readonly string[])[] | undefined;\n /** A read that failed, in words. */\n error?: string | null | undefined;\n emptyLabel?: string | undefined;\n /** Plot height in px (default 220). */\n height?: number | undefined;\n /** A fixed plot width in px; default fills its container. */\n width?: number | undefined;\n /** Words above the chart (a title, the freshness). */\n note?: ReactNode | undefined;\n className?: string | undefined;\n}\n\nconst NO_PATHS: readonly (readonly string[])[] = [];\nconst PLOT_MARGIN = { top: 4, right: 4, bottom: 0, left: 0 };\nconst Y_AXIS_WIDTH = 56;\n\nfunction colorOf(series: MatrxDrillChartSeries, index: number): string {\n if (series.color) return series.color;\n return series.other ? \"hsl(var(--matrx-chart-other))\" : `hsl(var(--matrx-chart-${(index % 10) + 1}))`;\n}\n\nfunction formatWith(measure: MatrxDrillMeasure | undefined, value: number | null | undefined): string {\n if (value === null || value === undefined) return \"not measured\";\n if (measure?.format) return measure.format(value);\n return value.toLocaleString(undefined, { maximumFractionDigits: 2 });\n}\n\n/** A short axis number: the Measure's own format when it has one, else 1.2k / 3.4M. */\n/**\n * A tick on the value axis: the measure's own words while they fit the axis (\"$12\", \"40 ms\"), else the\n * number in compact form (\"28M\") — the chart's title already names the unit (\"Cost (points) by day\").\n * A long formatted value (\"28,000,000 points\") was clipped to \",000,000 points\" (lane DRILL-ADOPT walk).\n */\nexport function drillChartAxisNumber(measure: MatrxDrillMeasure | undefined, value: number): string {\n // A fractional tick under a thousand keeps one decimal (0.5); everything else is kit's compact count.\n const compact =\n Math.abs(value) < 1000 && !Number.isInteger(value)\n ? String(Math.round(value * 10) / 10)\n : formatCount(value, { style: \"compact\" });\n if (!measure?.format) return compact;\n const said = measure.format(value);\n if (said.length <= 7) return said;\n // THE TICK SAYS THE NUMBER THE FORMAT SAID, NOT THE RAW VALUE. A Measure's format may convert its unit\n // (the usage explorer stores dollars and prints points at the points rate), so compacting the raw value\n // put \"450\" under \"Cost (points) by day\" for a 9,000,000-point day (localhost walk, 2026-09-30). The\n // number the format printed is compacted, keeping a leading currency sign (\"$1,234.56\" → \"$1.2k\").\n const printed = said.match(/^([^\\d-]*)(-?\\d[\\d,]*(?:\\.\\d+)?)/);\n if (!printed) return compact;\n const numeric = printed[2];\n if (!numeric) return compact;\n const n = Number(numeric.replace(/,/g, \"\"));\n if (!Number.isFinite(n)) return compact;\n const prefix = printed[1]?.trim() ?? \"\";\n const shown = Math.abs(n) < 1000 && !Number.isInteger(n) ? String(Math.round(n * 10) / 10) : formatCount(n, { style: \"compact\" });\n return prefix.length <= 1 ? `${prefix}${shown}` : shown;\n}\nconst axisNumber = drillChartAxisNumber;\n\nexport function MatrxDrillChart({\n dimensions,\n measures,\n question,\n onQuestionChange,\n answers,\n seriesLimit = MATRX_DRILL_CHART_SERIES_LIMIT,\n measure: measureKey,\n stack,\n time,\n ceiling,\n paths = NO_PATHS,\n error,\n emptyLabel = \"No value\",\n height = 220,\n width,\n note,\n className,\n}: MatrxDrillChartProps) {\n const plan = useMemo(\n () => drillChartQuestions(question, seriesLimit, { dimensions, measures, measure: measureKey, stack, time, ceiling }),\n [question, seriesLimit, dimensions, measures, measureKey, stack, time, ceiling],\n );\n const data = useMemo(() => drillChartData(plan, answers, dimensions, measures, emptyLabel), [plan, answers, dimensions, measures, emptyLabel]);\n const measure = measures.find((m) => m.key === plan.measure);\n const [hidden, setHidden] = useState<ReadonlySet<string>>(() => new Set());\n const [hover, setHover] = useState<string | null>(null);\n // A segment's click reaches the chart's own click too (it bubbles): the segment wins.\n const segmentClicked = useRef(false);\n const plotRef = useRef<HTMLDivElement | null>(null);\n\n const measureWords = plan.stack ? plan.stack.map((k) => measures.find((m) => m.key === k)?.label ?? k).join(\" and \") : measure?.label ?? plan.measure;\n const title = plan.time\n ? `${measureWords} by ${dimensionRefLabel(dimensions, plan.time).replace(/^.* by /, \"\")}${\n plan.split ? `, split by ${dimensionRefLabel(dimensions, plan.split)}` : \"\"\n }`\n : measureWords;\n\n const frame = (body: ReactNode) => (\n <section data-matrx-drill-chart aria-label={title} className={cn(\"flex min-w-0 flex-col gap-1.5 px-2 py-2\", className)}>\n {note ? <div data-matrx-drill-chart-note className=\"text-xs text-muted-foreground\">{note}</div> : null}\n {body}\n </section>\n );\n\n if (error) {\n return frame(<ErrorBox className=\"text-sm text-destructive\" message={error} operation=\"Draw the chart\" />);\n }\n if (plan.refused) {\n return frame(\n <p data-matrx-drill-chart-refused className=\"text-xs text-muted-foreground\">\n {plan.refused}\n </p>,\n );\n }\n if (!data) {\n return frame(<Skeleton data-matrx-drill-chart-loading className=\"w-full\" style={{ height }} />);\n }\n if (data.periods.length === 0) {\n return frame(<p data-matrx-drill-chart-empty className=\"text-xs text-muted-foreground\">Nothing was counted in this window.</p>);\n }\n\n const toggle = (key: string) =>\n setHidden((was) => {\n const next = new Set(was);\n if (next.has(key)) next.delete(key);\n else next.add(key);\n return next;\n });\n const drawn = data.series.map((s, i) => ({ s, i, dataKey: `s${i}` })).filter(({ s }) => !hidden.has(s.key));\n const rows = data.periods.map((p) => {\n const row: Record<string, string | number | null> = { __key: p.key, __label: p.label };\n data.series.forEach((s, i) => {\n row[`s${i}`] = p.values[s.key] ?? null;\n });\n return row;\n });\n const splitLabel = data.split ? dimensionRefLabel(dimensions, data.split) : \"\";\n const onPeriod = (period: MatrxDrillChartPeriod | undefined) => {\n if (period) onQuestionChange(drillChartNarrowToPeriod(question, plan, period.value, dimensions));\n };\n const onSegment = (series: MatrxDrillChartSeries, period: MatrxDrillChartPeriod | undefined) => {\n segmentClicked.current = true;\n if (series.other || !data.split) {\n onPeriod(period);\n return;\n }\n onQuestionChange(drillChartDrill(question, plan, series.value, dimensions, paths));\n };\n const hintFor = (key: string | null): string | null => {\n const series = data.series.find((s) => s.key === key);\n if (!series) return \"Click a bar to narrow the window to its period.\";\n if (series.other) return `Other is everything past the top ${plan.seriesLimit}: click to break this period out, one by one, in the table.`;\n if (!data.split) return \"Click to narrow the window to this period.\";\n return `Click to drill into ${series.label}.`;\n };\n\n const plot = (\n <BarChart\n data={rows}\n margin={PLOT_MARGIN}\n barCategoryGap=\"12%\"\n onClick={(_state, event) => {\n if (segmentClicked.current) {\n segmentClicked.current = false;\n return;\n }\n // The period is read from WHERE the person clicked, never from the tooltip's last index:\n // a click that arrives before the chart has seen the pointer move would otherwise read a\n // stale index (walked: a quick click landed on the first day). Bands are equal-width over\n // the plot, which starts after the value axis and ends at the right margin.\n const box = (plotRef.current?.querySelector(\"svg.recharts-surface\") ?? plotRef.current)?.getBoundingClientRect();\n if (!box || data.periods.length === 0) return;\n const left = box.left + PLOT_MARGIN.left + Y_AXIS_WIDTH;\n const right = box.right - PLOT_MARGIN.right;\n const index = Math.floor(((event.clientX - left) / (right - left)) * data.periods.length);\n if (index >= 0 && index < data.periods.length) onPeriod(data.periods[index]);\n }}\n {...(width ? { width, height } : {})}\n >\n <CartesianGrid vertical={false} stroke=\"hsl(var(--border))\" strokeDasharray=\"0\" />\n <XAxis\n dataKey=\"__label\"\n tickLine={false}\n axisLine={{ stroke: \"hsl(var(--border))\" }}\n tick={{ fill: \"hsl(var(--muted-foreground))\", fontSize: 11 }}\n minTickGap={12}\n interval=\"preserveStartEnd\"\n />\n <YAxis\n tickLine={false}\n axisLine={false}\n width={Y_AXIS_WIDTH}\n tick={{ fill: \"hsl(var(--muted-foreground))\", fontSize: 11 }}\n tickFormatter={(v: number) => axisNumber(measure, v)}\n />\n <Tooltip\n cursor={{ fill: \"hsl(var(--muted))\", opacity: 0.6 }}\n isAnimationActive={false}\n content={({ active, label }) => {\n if (!active) return null;\n const period = data.periods.find((p) => p.label === label);\n if (!period) return null;\n return (\n <DrillChartTooltip\n period={period}\n data={data}\n hidden={hidden}\n measure={measure}\n splitLabel={splitLabel}\n hint={hintFor(hover)}\n />\n );\n }}\n />\n {drawn.map(({ s, i, dataKey }) => (\n <Bar\n key={s.key}\n dataKey={dataKey}\n name={s.label}\n stackId=\"drill\"\n fill={colorOf(s, i)}\n stroke=\"hsl(var(--background))\"\n strokeWidth={1}\n isAnimationActive={false}\n cursor=\"pointer\"\n onMouseEnter={() => setHover(s.key)}\n onMouseLeave={() => setHover(null)}\n onClick={(_item, index) => onSegment(s, data.periods[index])}\n />\n ))}\n </BarChart>\n );\n\n return frame(\n <>\n <div className=\"flex flex-wrap items-baseline justify-between gap-x-3 gap-y-0.5\">\n <h3 className=\"text-xs font-medium text-foreground\">{title}</h3>\n <span data-matrx-drill-chart-total className=\"text-xs text-muted-foreground\">\n Total {formatWith(measure, data.total)}\n </span>\n </div>\n <div ref={plotRef} data-matrx-drill-chart-plot className=\"w-full min-w-0\" style={{ height }}>\n {width ? plot : <ResponsiveContainer width=\"100%\" height=\"100%\">{plot}</ResponsiveContainer>}\n </div>\n {data.series.length > 1 || data.split ? (\n <ul data-matrx-drill-chart-legend aria-label=\"Series — press to hide or show\" className=\"flex flex-wrap gap-x-1 gap-y-0.5\">\n {data.series.map((s, i) => {\n const off = hidden.has(s.key);\n return (\n <li key={s.key} className=\"min-w-0\">\n <button\n type=\"button\"\n data-matrx-drill-chart-series={s.key}\n aria-pressed={!off}\n title={`${off ? \"Show\" : \"Hide\"} ${s.label} (${formatWith(measure, s.total)})`}\n onClick={() => toggle(s.key)}\n className={cn(\n \"inline-flex max-w-[16rem] items-center gap-1.5 rounded px-1.5 py-0.5 text-xs text-foreground hover:bg-muted focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring\",\n off && \"text-muted-foreground line-through\",\n )}\n >\n <span aria-hidden=\"true\" className=\"h-2.5 w-2.5 shrink-0 rounded-sm\" style={{ background: off ? \"transparent\" : colorOf(s, i), boxShadow: `inset 0 0 0 1.5px ${colorOf(s, i)}` }} />\n <span className=\"truncate\">{s.label}</span>\n <span className=\"shrink-0 tabular-nums text-muted-foreground\">{formatWith(measure, s.total)}</span>\n </button>\n </li>\n );\n })}\n </ul>\n ) : null}\n {data.says.length > 0 ? (\n <p data-matrx-drill-chart-says className=\"text-xs text-muted-foreground\">\n {data.says.join(\" \")}\n </p>\n ) : null}\n </>,\n );\n}\n\n/** Every series' value in the period, each in the Measure's own format, and the period's total. */\nfunction DrillChartTooltip({\n period,\n data,\n hidden,\n measure,\n splitLabel,\n hint,\n}: {\n period: MatrxDrillChartPeriod;\n data: MatrxDrillChartData;\n hidden: ReadonlySet<string>;\n measure: MatrxDrillMeasure | undefined;\n splitLabel: string;\n hint: string | null;\n}) {\n return (\n <div\n data-matrx-drill-chart-tooltip\n className=\"min-w-[12rem] max-w-[18rem] rounded-md border border-border bg-popover px-2.5 py-2 text-xs text-popover-foreground shadow-md\"\n >\n <div className=\"mb-1 font-medium\">{period.label}</div>\n {data.split ? <div className=\"mb-1 text-muted-foreground\">{splitLabel}</div> : null}\n <ul className=\"flex flex-col gap-0.5\">\n {data.series.map((s, i) => (\n <li key={s.key} className={cn(\"flex items-center gap-1.5\", hidden.has(s.key) && \"opacity-50\")}>\n <span aria-hidden=\"true\" className=\"h-2 w-2 shrink-0 rounded-sm\" style={{ background: colorOf(s, i) }} />\n <span className=\"min-w-0 flex-1 truncate\">{s.label}</span>\n <span className=\"tabular-nums\">{formatWith(measure, period.values[s.key] ?? null)}</span>\n </li>\n ))}\n </ul>\n <div className=\"mt-1 flex justify-between border-t border-border pt-1 font-medium\">\n <span>Total</span>\n <span className=\"tabular-nums\">{formatWith(measure, period.total)}</span>\n </div>\n {hint ? <div className=\"mt-1 text-muted-foreground\">{hint}</div> : null}\n </div>\n );\n}\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","/**\n * ErrorBox — THE one way a package draws an error on screen, and the seam a\n * host uses to put its own actions on every one of them.\n *\n * Every error sentence this package (and every sibling package that depends\n * on it) shows is an `ErrorBox`. The box is a plain `role=\"alert\"` element —\n * its tag and classes are the caller's, so nothing about the look changes — and\n * after its children it renders whatever the host's `ErrorActionsProvider`\n * returns for that error. With no provider it renders nothing extra.\n *\n * Why a slot and not a menu: the package cannot know the host's AI copy, bug\n * report, or support flow (Matrx fills it with its Alchemy copy-for-AI menu at\n * the app root). The package supplies the error's facts — the sentence, the\n * raw error, the operation, the records — and the host decides the actions.\n *\n * Guard: `error-box.census.test.ts` fails when a package file renders\n * `role=\"alert\"` for an error without going through `ErrorBox` (or, for the\n * `Alert` family, `ErrorActions`).\n */\nimport * as React from \"react\";\n\nexport interface ErrorActionsRecord {\n type: string;\n id: string;\n label?: string;\n}\n\n/** The facts a package knows about an error it is showing. All optional. */\nexport interface ErrorActionsInput {\n /** The sentence shown. Omit when the words are only in the rendered box. */\n message?: string;\n title?: string;\n /** The raw error (code/status/stack stay available to the host). */\n error?: unknown;\n /** What was being attempted (\"Load saved views\"). */\n operation?: string;\n records?: readonly ErrorActionsRecord[];\n /** The person's input that did not land. */\n unsavedInput?: unknown;\n /** The package + component that drew it (\"design-system/SavedViewsControl\"). */\n origin?: string;\n}\n\nexport type ErrorActionsRenderer = (input: ErrorActionsInput) => React.ReactNode;\n\n/**\n * ONE context for every copy of this module. tsup builds each entry point\n * (`.`, `./data-table`, …) without code splitting, so this file is inlined into\n * several bundles; a plain `createContext` would give each bundle its own\n * context and the host's provider (imported from `.`) would never reach an\n * error drawn by the data table. The registry key makes them one.\n */\nconst CONTEXT_KEY = Symbol.for(\"@ai-matrx/design-system/error-actions\");\ntype ContextRegistry = Record<symbol, React.Context<ErrorActionsRenderer | null> | undefined>;\nconst registry = globalThis as unknown as ContextRegistry;\nconst ErrorActionsContext: React.Context<ErrorActionsRenderer | null> =\n registry[CONTEXT_KEY] ??\n (registry[CONTEXT_KEY] = React.createContext<ErrorActionsRenderer | null>(null));\n\n/** Mount once at the host's app root; every ErrorBox below renders `render(...)`. */\nexport function ErrorActionsProvider({\n render,\n children,\n}: {\n render: ErrorActionsRenderer;\n children: React.ReactNode;\n}) {\n return (\n <ErrorActionsContext.Provider value={render}>{children}</ErrorActionsContext.Provider>\n );\n}\n\nexport function useErrorActions(): ErrorActionsRenderer | null {\n return React.useContext(ErrorActionsContext);\n}\n\n/**\n * The host's actions for one error, placed wherever the caller puts this\n * element. Renders nothing without a provider. Use inside a box you do not\n * render yourself (the `Alert` family); everything else uses `ErrorBox`.\n */\nexport function ErrorActions(props: ErrorActionsInput) {\n const render = useErrorActions();\n if (!render) return null;\n return <>{render(props)}</>;\n}\n\ntype ErrorBoxTag = \"p\" | \"div\" | \"span\" | \"section\" | \"li\";\n\nexport interface ErrorBoxProps\n extends Omit<React.HTMLAttributes<HTMLElement>, \"role\" | \"title\">,\n ErrorActionsInput {\n /** The element drawn — the caller's existing tag, so layout never shifts. */\n as?: ErrorBoxTag;\n}\n\nexport const ErrorBox = React.forwardRef<HTMLElement, ErrorBoxProps>(function ErrorBox(\n {\n as = \"p\",\n message,\n title,\n error,\n operation,\n records,\n unsavedInput,\n origin,\n children,\n ...rest\n },\n ref,\n) {\n const Tag = as as React.ElementType;\n const facts: ErrorActionsInput = {\n ...(message !== undefined ? { message } : {}),\n ...(title !== undefined ? { title } : {}),\n ...(error !== undefined ? { error } : {}),\n ...(operation !== undefined ? { operation } : {}),\n ...(records !== undefined ? { records } : {}),\n ...(unsavedInput !== undefined ? { unsavedInput } : {}),\n ...(origin !== undefined ? { origin } : {}),\n };\n return (\n <Tag ref={ref} role=\"alert\" data-error-box=\"\" {...rest}>\n {children ?? message}\n <ErrorActions {...facts} />\n </Tag>\n );\n});\n","/**\n * THE MOTION CONTRACT (0.10.0) — the class names every animated surface in\n * this package puts on the DOM, in ONE place.\n *\n * Why a module and not ten inline strings: the sweep that produced this file\n * found the same defect ten times over, and it was invisible each time\n * because each primitive spelled its animation out by hand. Ten hand-written\n * copies of `data-[state=open]:animate-in data-[state=closed]:animate-out\n * data-[state=closed]:fade-out-0 …` meant ten chances to reach for a utility\n * nothing in the package defines — which is exactly what happened. A single\n * exported constant per motion SHAPE means the next primitive cannot invent\n * an eleventh spelling, and `styles.test.ts` can prove every one of them\n * resolves to a rule in the shipped stylesheet.\n *\n * Every rule these name lives in `styles.css` under the package's own\n * keyframes. NONE of them is a Tailwind utility, and none requires a host\n * plugin — see that file's header for the measurement that made this\n * necessary.\n */\n\n/** Modal scrims: Dialog, AlertDialog, Sheet, CommandDialog. Fade only. */\nexport const MOTION_OVERLAY = \"matrx-motion-overlay\";\n\n/** The centred dialog card. Fades and scales; centring is not its business. */\nexport const MOTION_DIALOG = \"matrx-motion-dialog\";\n\n/**\n * THE CENTRING OF A CENTRED DIALOG CARD — package-owned, in the `translate`\n * property, and NEVER a Tailwind `translate-x-[-50%] translate-y-[-50%]` pair.\n *\n * Those utilities do not compile to the same thing in every host: Tailwind v3\n * writes them into `transform`, Tailwind v4 writes them into the independent\n * `translate` property, which COMPOSES with `transform` instead of being\n * replaced by it. 0.10.0's keyframes restated the centring inside `transform`,\n * which was harmless on v3 and doubled the centring on v4 — the card flew in\n * from off the top-left of the viewport for the animation's whole duration and\n * snapped into place at the end (fixed in 0.10.1).\n *\n * Owning it here means one spelling that reads the same on both, and it frees\n * `transform` so the keyframes carry ONLY the delta. Put this on every centred\n * surface UNCONDITIONALLY — it is geometry, so unlike {@link MOTION_DIALOG} it\n * must survive `animated={false}`. Its rule ships in `styles.css`.\n */\nexport const CENTERED_DIALOG = \"matrx-dialog-centered\";\n\n/**\n * The resting gap between a `popper`-positioned surface and its trigger, for\n * the primitives that want one (today: `Select`). Same law and same reason as\n * {@link CENTERED_DIALOG} — a position an animated surface holds AT REST is\n * the `translate` property's job, never a Tailwind translate utility, because\n * those land in `transform` on a v3 host and in `translate` on a v4 one while\n * the keyframes animate `transform` either way. Sizes from\n * `--matrx-popper-offset`; keys off Radix's `data-side`.\n */\nexport const POPPER_OFFSET = \"matrx-popper-offset\";\n\n/** The mobile Dialog / CommandDialog bottom sheet. Slides up and fades. */\nexport const MOTION_BOTTOM_SHEET = \"matrx-motion-bottom-sheet\";\n\n/**\n * Radix-popper surfaces: Popover, DropdownMenu, ContextMenu, HoverCard,\n * Select, Tooltip. Reads `--radix-popper-transform-origin` so the surface\n * grows out of its trigger, and `data-side` so it travels from it.\n */\nexport const MOTION_POPPER = \"matrx-motion-popper\";\n\n/** Edge sheets, one class per `sheetVariants` side. */\nexport const MOTION_SHEET = {\n top: \"matrx-motion-sheet-top\",\n bottom: \"matrx-motion-sheet-bottom\",\n left: \"matrx-motion-sheet-left\",\n right: \"matrx-motion-sheet-right\",\n center: \"matrx-motion-sheet-center\",\n} as const;\n\n/** Skeleton's breathing loop. */\nexport const MOTION_PULSE = \"matrx-pulse\";\n\n/** The spinner loop, for every inline busy indicator in the package. */\nexport const MOTION_SPIN = \"matrx-spin\";\n\n/**\n * Every motion class the package ships, for the guards. If a rule for one of\n * these is missing from `styles.css`, a component animates in no consumer and\n * says nothing about it — the C26 failure this list exists to make loud.\n */\nexport const ALL_MOTION_CLASSES = [\n MOTION_OVERLAY,\n MOTION_DIALOG,\n MOTION_BOTTOM_SHEET,\n MOTION_POPPER,\n ...Object.values(MOTION_SHEET),\n MOTION_PULSE,\n MOTION_SPIN,\n] as const;\n\n/**\n * The host-plugin utilities this package is forbidden to emit. They are\n * supplied by `tailwindcss-animate` / `tw-animate-css`, which is a HOST CSS\n * entry concern a package cannot declare a dependency on: three of the four\n * AI Matrx consumers never loaded it, so every surface wearing these appeared\n * with no animation and no error. `styles.test.ts` fails the build if one\n * comes back.\n */\nexport const FORBIDDEN_HOST_MOTION_UTILITIES = [\n \"animate-in\",\n \"animate-out\",\n \"fade-in\",\n \"fade-out\",\n \"zoom-in\",\n \"zoom-out\",\n \"slide-in-from\",\n \"slide-out-to\",\n \"animate-accordion\",\n \"animate-slide-down\",\n \"animate-slide-up\",\n \"animate-pulse\",\n \"animate-spin\",\n] as const;\n","/**\n * Skeleton — the \"content is coming\" placeholder.\n *\n * Ported from matrx-frontend `components/ui/skeleton.tsx`; no seams, pure\n * markup over semantic tokens. ONE change from the original (0.10.0): the\n * breathing loop is `.matrx-pulse`, a package-owned keyframe, not Tailwind's\n * `animate-pulse`. A Skeleton that does not pulse is indistinguishable from an\n * empty grey box — it stops saying \"loading\" and starts saying \"nothing here\"\n * — so the motion is part of the component's meaning and ships with it (see\n * `styles.css` § THE MOTION LAYER).\n */\n\nimport * as React from \"react\";\n\nimport { cn } from \"./cn\";\nimport { MOTION_PULSE } from \"./motion\";\n\nexport interface SkeletonProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Package-owned pulse. Default `true`. Pass `false` for a static\n * placeholder — a print/PDF render, or a screenshot fixture where the\n * animation makes the output non-deterministic. `prefers-reduced-motion`\n * already SLOWS the pulse rather than stopping it (a still skeleton would\n * lose the information), so this is not the accessibility switch.\n */\n animated?: boolean;\n}\n\nexport function Skeleton({ className, animated = true, ...props }: SkeletonProps) {\n return (\n <div\n data-slot=\"skeleton\"\n className={cn(\n \"rounded-md bg-primary/10\",\n animated && MOTION_PULSE,\n className,\n )}\n {...props}\n />\n );\n}\n","/**\n * DRILL — the table primitive's four analytic abilities, as pure data (lane D3 of\n * common-docs/systems/data/drill-down/STATE.md).\n *\n * 1. GROUP BY A DIMENSION, answered by the SERVER — every group's count and measure subtotals\n * are worked out over the whole table, never over the page the browser happens to hold.\n * 2. PIVOT a second Dimension across the top (`across`).\n * 3. WHAT TO DISPLAY — the Measures shown (`show`).\n * 4. DRILL — a click on a group adds its value as a filter and regroups by the next Dimension\n * of the drill path; the trail of filters is the breadcrumb, and every state is a URL.\n *\n * Vocabulary (Arman, 2026-09-29): \"Dimension\" and \"Measure\" are platform words; a saved\n * exploration is a Saved view.\n *\n * THE TABLE ASKS, THE HOST ANSWERS. This file never reads a database. `drillRequests(question)`\n * names every grouping the screen needs (one per level, one per level × the pivot, the pivot's\n * own totals and the grand total); the host runs each through its door — today\n * `custom.record_aggregate` for a custom Table, tomorrow the shared read door lanes D1/D2 build —\n * and hands the rows back keyed by `request.key`. Because every number on the screen comes from\n * one of those answers, a subtotal and the total can never disagree, and the rows a group holds\n * beyond the door's group limit are said as ONE honest \"N more\" row (`otherOf`), never dropped.\n */\n\nimport { formatCount } from \"@ai-matrx/kit/format\";\n\n/** A Dimension's kind — what it is made of, which decides what a click on one of its values means. */\nexport type MatrxDrillDimensionKind = \"choice\" | \"boolean\" | \"text\" | \"time\" | \"relation\";\n\n/** The periods a time Dimension is cut into. The store's own list (`custom.agg_buckets()`). */\nexport type MatrxDrillGrain = \"hour\" | \"day\" | \"week\" | \"month\" | \"quarter\" | \"year\";\n\n/** Every grain, finest first (`at:hour` … `at:year`). */\nexport const MATRX_DRILL_GRAINS: readonly MatrxDrillGrain[] = [\"hour\", \"day\", \"week\", \"month\", \"quarter\", \"year\"];\n\n/**\n * The grains a time Dimension offers when it declares none: a day and coarser (the door's own\n * default). Hour is offered only where a Dimension declares it — a date column cut by hour is nonsense.\n */\nexport const MATRX_DRILL_DEFAULT_GRAINS: readonly MatrxDrillGrain[] = [\"day\", \"week\", \"month\", \"quarter\", \"year\"];\n\n/** One axis a table can be cut by. */\nexport interface MatrxDrillDimension {\n /** The column or Field key. A time Dimension is referenced as `<key>:<grain>`. */\n key: string;\n label: string;\n kind: MatrxDrillDimensionKind;\n /** Time only: the grains offered. Default a day and coarser (`MATRX_DRILL_DEFAULT_GRAINS`); hour only when declared. */\n grains?: readonly MatrxDrillGrain[] | undefined;\n /** `high` → never offered as a pivot, because its columns would not fit a screen. */\n cardinality?: \"low\" | \"medium\" | \"high\" | undefined;\n /** How one of its values reads to a person (a choice's words, a person's name). Default: the value. */\n labelFor?: ((value: string | null) => string) | undefined;\n /**\n * THE COLOUR A VALUE KEEPS (adopted from the old usage page's per-origin colours): a chart token\n * (`--matrx-chart-3`) the value is drawn in on every chart and share bar, light and dark. Absent\n * or empty = the chart's own order.\n */\n colorFor?: ((value: string | null) => string | null | undefined) | undefined;\n /**\n * A RELATION'S RECORD KIND (a registry token: `organization`, `agent`, `user`): a group row of this\n * Dimension offers that record's own doors — open, preview and its kind's menu — through the\n * table host's `resolveEntityDoors`, in the row's ⋯ menu and on a right-click.\n */\n entity?: string | undefined;\n /** What it groups by, at most 140 characters: the column header's tooltip (lane DRILL-FLIP-FIXES). */\n description?: string | undefined;\n}\n\n/** A chart token as the colour a style reads (`--matrx-chart-3` → `hsl(var(--matrx-chart-3))`); null when it is not one. */\nexport function drillTokenColor(token: string | null | undefined): string | null {\n return token && /^--matrx-chart-([1-9]|10|other)$/.test(token) ? `hsl(var(${token}))` : null;\n}\n\n/** The colour a value of this Dimension keeps, as a style reads it — null when it keeps none. */\nexport function drillValueColor(dimensions: readonly MatrxDrillDimension[], ref: string, value: string | null): string | null {\n return drillTokenColor(dimensionFor(dimensions, ref)?.colorFor?.(value));\n}\n\n/** One number a table can show. */\nexport interface MatrxDrillMeasure {\n /** The key the host's answer carries it under (`count`, `sum_quantity`). */\n key: string;\n label: string;\n /**\n * May it be added up across groups? `count` and `sum` may; an average, a median, a min and a\n * max may not — the \"N more\" row leaves those blank rather than inventing them.\n */\n additive: boolean;\n /** How the cell prints. Default: a plain number. */\n format?: ((value: number | null) => string) | undefined;\n /** A smaller number is the better one (cost, position, time taken): a fall reads as good news. */\n lowerIsBetter?: boolean | undefined;\n /**\n * A MOMENT, not an amount (\"Last active\", carried as epoch ms): never a share, never a change\n * against the prior window, never a Pareto line — only the moment itself.\n */\n moment?: boolean | undefined;\n /** What the number counts, at most 140 characters: the column header's tooltip (lane DRILL-FLIP-FIXES). */\n description?: string | undefined;\n}\n\n/** One step of the breadcrumb: a Dimension reference and the value it was narrowed to. */\nexport interface MatrxDrillFilter {\n /** A Dimension reference (`status`, `last_serviced:month`). */\n dim: string;\n /** The value, as the answer carried it. `null` is \"no value\". */\n value: string | null;\n}\n\n/** THE QUESTION — what a Saved view stores and the address encodes. */\nexport interface MatrxDrillQuestion {\n /** Row groups, outermost first (at most three, the store's own limit). Empty = the records. */\n by: string[];\n /** One pivot Dimension drawn across the top. */\n across?: string | null | undefined;\n /** The Measures shown, in column order. */\n show: string[];\n /** The drill trail, in order. Each crumb removes the filters after it. */\n where: MatrxDrillFilter[];\n /** The drill path a click follows (Dimension references). Default: the Dimensions in order. */\n path?: string[] | null | undefined;\n /** A Measure key (or `label`) and a direction; default the answer's own order (largest first). */\n sort?: { key: string; direction: \"asc\" | \"desc\" } | null | undefined;\n /**\n * SHARE OF TOTAL (adopted from the Spend Explorer's share bar, the per-user origin mix and the\n * backlink anchor profile): each additive Measure also says what part of its parent it is — the\n * whole answer for the outer level, the group above for a nested one.\n */\n share?: boolean | undefined;\n /**\n * COMPARE (adopted from Search Console's delta columns, the marketing reports' \"+N vs previous\"\n * and the dashboards' prior-period overlay): every Measure beside its change against the prior\n * window, sortable by that change (`sort.key = \"delta:<measure>\"`).\n */\n compare?: MatrxDrillCompare | null | undefined;\n /**\n * THE WINDOW on the source's time Dimension (adopted from the preset pickers Spend, Search\n * Console, Codex usage and the user usage page each hand-rolled): `7d`, `30d`, `90d`, `365d`,\n * `24h`, `today`, `yesterday` (the viewer's local midnight), or `from..to` (days, or moments with\n * a time: `2026-09-28T14:00Z..2026-09-28T18:00Z`). Absent = all time.\n */\n window?: string | null | undefined;\n}\n\nexport type MatrxDrillCompare = \"previous_period\" | \"same_period_last_year\";\n\n/** The presets a window control offers, in order (the store's own set: `Nh` / `Nd`). */\nexport const MATRX_DRILL_WINDOWS: ReadonlyArray<{ key: string; label: string }> = [\n { key: \"24h\", label: \"Last 24 hours\" },\n { key: \"7d\", label: \"Last 7 days\" },\n { key: \"30d\", label: \"Last 30 days\" },\n { key: \"90d\", label: \"Last 90 days\" },\n { key: \"365d\", label: \"Last 12 months\" },\n];\n\n/** One choice a window control offers: a key `drillWindowRange` resolves and the words it reads as. */\nexport interface MatrxDrillWindowPreset {\n key: string;\n label: string;\n}\n\n/**\n * THE CALENDAR WINDOWS (adopted from the Spend Explorer, whose \"Yesterday\" exists because \"a\n * dashboard that only shows today is worthless one minute past midnight\" — Arman, 2026-09-12):\n * today so far and all of yesterday, cut at the VIEWER'S local midnight.\n */\nexport const MATRX_DRILL_CALENDAR_WINDOWS: ReadonlyArray<MatrxDrillWindowPreset> = [\n { key: \"today\", label: \"Today\" },\n { key: \"yesterday\", label: \"Yesterday\" },\n];\n\n/**\n * What a window control offers by default, in order: Today, Yesterday, then the rolling windows.\n * \"All time\" and \"Custom range…\" are the control's own (see `MatrxDrillWindowMenu`); a host\n * declares its own list through the menu's `presets` prop.\n */\nexport const MATRX_DRILL_WINDOW_PRESETS: ReadonlyArray<MatrxDrillWindowPreset> = [...MATRX_DRILL_CALENDAR_WINDOWS, ...MATRX_DRILL_WINDOWS];\n\nfunction localMidnight(now: Date, daysBack: number): Date {\n return new Date(now.getFullYear(), now.getMonth(), now.getDate() - daysBack);\n}\n\n/** A window as the half-open moments it covers; `now` is an argument so a test is deterministic. */\nexport function drillWindowRange(window: string | null | undefined, now: Date = new Date()): { from: string; to: string } | null {\n if (!window) return null;\n if (window === \"today\") return { from: localMidnight(now, 0).toISOString(), to: now.toISOString() };\n if (window === \"yesterday\") return { from: localMidnight(now, 1).toISOString(), to: localMidnight(now, 0).toISOString() };\n const custom = window.match(WINDOW_RANGE);\n if (custom) return { from: momentOf(custom[1]!), to: momentOf(custom[2]!) };\n const preset = window.match(/^(\\d{1,4})(h|d)$/);\n if (!preset) return null;\n const ms = Number(preset[1]) * (preset[2] === \"h\" ? 3_600_000 : 86_400_000);\n return { from: new Date(now.getTime() - ms).toISOString(), to: now.toISOString() };\n}\n\n/**\n * A custom window's two ends: a date (`2026-09-28`) or a moment (`2026-09-28T14:00Z`, or with an\n * offset — which an address may have decoded from `+00:00` to ` 00:00`; it is read back as `+`).\n */\nconst MOMENT = String.raw`\\d{4}-\\d{2}-\\d{2}(?:T\\d{2}:\\d{2}(?::\\d{2})?(?:Z|[+\\- ]\\d{2}:\\d{2})?)?`;\nconst WINDOW_RANGE = new RegExp(`^(${MOMENT})\\\\.\\\\.(${MOMENT})$`);\n\nfunction momentOf(end: string): string {\n return end.replace(/(T\\d{2}:\\d{2}(?::\\d{2})?) (\\d{2}:\\d{2})$/, \"$1+$2\");\n}\n\n/** A moment as a window end in the address: UTC, to the minute (`2026-09-28T14:00Z`). */\nexport function drillWindowMoment(value: string | Date): string {\n const d = typeof value === \"string\" ? new Date(value) : value;\n return `${d.toISOString().slice(0, 16)}Z`;\n}\n\nfunction momentWords(end: string): { day: string; clock: string | null; zone: string } {\n const [y, m, d] = end.slice(0, 10).split(\"-\").map(Number) as [number, number, number];\n const month = new Date(Date.UTC(y, m - 1, d)).toLocaleString(\"en-US\", { month: \"short\", timeZone: \"UTC\" });\n const clock = end.length > 10 ? end.slice(11, 16) : null;\n const zone = end.endsWith(\"Z\") ? \"UTC\" : end.length > 16 ? `UTC${end.slice(-6)}`.replace(\"UTC+00:00\", \"UTC\") : \"\";\n return { day: `${month} ${d}, ${y}`, clock, zone };\n}\n\n/** How a window reads: its preset's words, or the dates — and, for a window of moments, the clock. */\nexport function drillWindowLabel(window: string | null | undefined): string {\n if (!window) return \"All time\";\n const preset = MATRX_DRILL_WINDOW_PRESETS.find((w) => w.key === window);\n if (preset) return preset.label;\n const rolling = window.match(/^(\\d{1,4})(h|d)$/);\n if (rolling) {\n const n = Number(rolling[1]);\n return `Last ${n.toLocaleString()} ${rolling[2] === \"h\" ? (n === 1 ? \"hour\" : \"hours\") : n === 1 ? \"day\" : \"days\"}`;\n }\n const custom = window.match(WINDOW_RANGE);\n if (custom && (custom[1]!.length > 10 || custom[2]!.length > 10)) {\n const a = momentWords(momentOf(custom[1]!));\n const b = momentWords(momentOf(custom[2]!));\n const left = `${a.day} ${a.clock ?? \"00:00\"}`;\n const right = a.day === b.day ? (b.clock ?? \"00:00\") : `${b.day} ${b.clock ?? \"00:00\"}`;\n return `${left} – ${right}${b.zone || a.zone ? ` ${b.zone || a.zone}` : \"\"}`;\n }\n const range = window.split(\"..\");\n return range.length === 2 ? `${range[0]} – ${range[1]}` : window;\n}\n\n/** One Measure now and before, and the change as a share of before (`null` when before was nothing). */\nexport function drillDelta(now: number | null | undefined, before: number | null | undefined): { change: number | null; pct: number | null } {\n if (now === null || now === undefined || before === null || before === undefined) return { change: null, pct: null };\n const change = now - before;\n return { change, pct: before === 0 ? null : change / Math.abs(before) };\n}\n\n/** What part of its parent a value is (`null` when the parent is nothing, or the Measure cannot be added up). */\nexport function drillShare(value: number | null | undefined, whole: number | null | undefined): number | null {\n if (value === null || value === undefined || !whole) return null;\n return value / whole;\n}\n\n/** One grouped row of an answer, exactly as the store's aggregate door returns it. */\nexport interface MatrxDrillAnswerRow {\n /** Keyed by Dimension reference. */\n groups: Record<string, string | null>;\n measures: Record<string, number | null>;\n row_count: number;\n /** The same group in the prior window, when the question compares. */\n prior_measures?: Record<string, number | null> | null | undefined;\n /** How many groups there were before the door's cap (Top N of M). */\n distinct_groups?: number | null | undefined;\n}\n\n/** One grouping the table needs answered. */\nexport interface MatrxDrillRequest {\n /** Stable key the host answers under. */\n key: string;\n /** Dimension references to group by, outermost first; empty = the grand total. */\n by: string[];\n}\n\n/** What the host hands back: rows per request key; a key not yet present is still loading. */\nexport type MatrxDrillAnswers = Readonly<Record<string, readonly MatrxDrillAnswerRow[] | undefined>>;\n\n/** The store's own ceiling on levels (`custom.view_declare`). */\nexport const MATRX_DRILL_MAX_LEVELS = 3;\n/**\n * Pivot columns before the one rest column: the DEFAULT of the live knob `drill.pivot_columns`,\n * which a host reads and passes as `MatrxDrillAnswerTable`'s `pivotColumnCap`.\n */\nexport const MATRX_DRILL_PIVOT_COLUMNS = 24;\n\n// ── references ─────────────────────────────────────────────────────────────\n\n/** `last_serviced:month` → `{ key: \"last_serviced\", grain: \"month\" }`. */\nexport function parseDimensionRef(ref: string): { key: string; grain: MatrxDrillGrain | null } {\n const at = ref.lastIndexOf(\":\");\n if (at <= 0) return { key: ref, grain: null };\n const grain = ref.slice(at + 1) as MatrxDrillGrain;\n if (!MATRX_DRILL_GRAINS.includes(grain)) return { key: ref, grain: null };\n return { key: ref.slice(0, at), grain };\n}\n\nexport function dimensionFor(\n dimensions: readonly MatrxDrillDimension[],\n ref: string,\n): MatrxDrillDimension | undefined {\n const { key } = parseDimensionRef(ref);\n return dimensions.find((d) => d.key === key);\n}\n\n/** \"Last serviced by month\", \"Condition\". */\nexport function dimensionRefLabel(dimensions: readonly MatrxDrillDimension[], ref: string): string {\n const dim = dimensionFor(dimensions, ref);\n const { grain } = parseDimensionRef(ref);\n const base = dim?.label ?? ref;\n return grain ? `${base} by ${grain}` : base;\n}\n\n/** The references a person may group by: each Dimension, and each time Dimension at each grain. */\nexport function dimensionRefs(dimensions: readonly MatrxDrillDimension[]): string[] {\n const out: string[] = [];\n for (const dim of dimensions) {\n if (dim.kind === \"time\") {\n for (const grain of dim.grains ?? MATRX_DRILL_DEFAULT_GRAINS) out.push(`${dim.key}:${grain}`);\n } else out.push(dim.key);\n }\n return out;\n}\n\n/** How one group value reads. A blank is said, never shown as an empty cell. */\nexport function drillValueLabel(\n dimensions: readonly MatrxDrillDimension[],\n ref: string,\n value: string | null,\n emptyLabel = \"No value\",\n): string {\n const dim = dimensionFor(dimensions, ref);\n if (dim?.labelFor) return dim.labelFor(value);\n if (value === null || value === \"\") return emptyLabel;\n const { grain } = parseDimensionRef(ref);\n if (grain) return bucketLabel(value, grain);\n if (dim?.kind === \"boolean\") return value === \"true\" ? \"Yes\" : value === \"false\" ? \"No\" : value;\n return value;\n}\n\n/** A bucket start printed for its grain: \"Sep 28, 2026 14:00\", \"Sep 2026\", \"Q3 2026\", \"2026\", \"Week of Sep 7, 2026\". */\nexport function bucketLabel(value: string, grain: MatrxDrillGrain): string {\n const day = value.slice(0, 10);\n const [y, m, d] = day.split(\"-\").map((part) => Number(part));\n if (!y || !m || !d) return value;\n const date = new Date(Date.UTC(y, m - 1, d));\n const month = date.toLocaleString(\"en-US\", { month: \"short\", timeZone: \"UTC\" });\n if (grain === \"year\") return String(y);\n if (grain === \"quarter\") return `Q${Math.floor((m - 1) / 3) + 1} ${y}`;\n if (grain === \"month\") return `${month} ${y}`;\n if (grain === \"week\") return `Week of ${month} ${d}, ${y}`;\n // An hour reads on the store's own clock (the label's offset), never the browser's.\n if (grain === \"hour\") return `${month} ${d}, ${y} ${/^\\d{4}-\\d{2}-\\d{2}[T ](\\d{2}:\\d{2})/.exec(value)?.[1] ?? \"00:00\"}`;\n return `${month} ${d}, ${y}`;\n}\n\n/**\n * THE HALF-OPEN WINDOW a bucket value stands for — what a click on \"Sep 2026\" filters to. The\n * start keeps whatever offset the store cut it with; the end is the next bucket's start in the\n * same offset, so the window and the bucket are one period.\n */\nexport function bucketWindow(value: string, grain: MatrxDrillGrain): { from: string; to: string } {\n if (grain === \"hour\") {\n // The wall-clock hour after, in the same offset the store cut it with.\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})[T ](\\d{2}):(\\d{2})(?::(\\d{2}))?(.*)$/.exec(value);\n if (m) {\n const next = new Date(Date.UTC(+m[1]!, +m[2]! - 1, +m[3]!, +m[4]! + 1, +m[5]!, +(m[6] ?? 0)));\n const iso = next.toISOString();\n return { from: value, to: `${iso.slice(0, 10)}T${iso.slice(11, 16)}${m[6] !== undefined ? iso.slice(16, 19) : \"\"}${m[7] ?? \"\"}` };\n }\n }\n const day = value.slice(0, 10);\n const rest = value.slice(10);\n const [y, m, d] = day.split(\"-\").map((part) => Number(part));\n const start = new Date(Date.UTC(y ?? 1970, (m ?? 1) - 1, d ?? 1));\n const end = new Date(start);\n if (grain === \"day\") end.setUTCDate(end.getUTCDate() + 1);\n else if (grain === \"week\") end.setUTCDate(end.getUTCDate() + 7);\n else if (grain === \"month\") end.setUTCMonth(end.getUTCMonth() + 1);\n else if (grain === \"quarter\") end.setUTCMonth(end.getUTCMonth() + 3);\n else end.setUTCFullYear(end.getUTCFullYear() + 1);\n return { from: value, to: `${end.toISOString().slice(0, 10)}${rest}` };\n}\n\n// ── the question ───────────────────────────────────────────────────────────\n\nexport function emptyDrillQuestion(show: string[] = [\"count\"]): MatrxDrillQuestion {\n return { by: [], across: null, show, where: [] };\n}\n\n/** Is the table answering a grouped question (summary rows) rather than listing records? */\nexport function drillIsGrouped(question: MatrxDrillQuestion): boolean {\n return question.by.length > 0;\n}\n\n/** The drill path: the question's own, else every Dimension reference in the order declared (time at month). */\nexport function drillPath(question: MatrxDrillQuestion, dimensions: readonly MatrxDrillDimension[]): string[] {\n if (question.path && question.path.length > 0) return question.path;\n return dimensions.map((d) => (d.kind === \"time\" ? `${d.key}:month` : d.key));\n}\n\n/**\n * The Dimension a drill on the deepest level regroups by: the next step of the path after the\n * deepest level, skipping anything already filtered or grouped. `null` = the path is spent, so\n * the drill shows the records themselves.\n */\nexport function nextDrillLevel(\n question: MatrxDrillQuestion,\n dimensions: readonly MatrxDrillDimension[],\n paths: readonly (readonly string[])[] = [],\n): string | null {\n const deepest = question.by[question.by.length - 1];\n const usedRefs = new Set<string>([...question.where.map((w) => w.dim), ...question.by]);\n // A DECLARED PATH wins: the level after the deepest one (a year drills into its quarters, the\n // same date column one period finer — the one place a Dimension follows itself).\n if (deepest) {\n for (const levels of paths) {\n const at = levels.indexOf(deepest);\n if (at < 0) continue;\n const next = levels.slice(at + 1).find((ref) => !usedRefs.has(ref) && dimensionFor(dimensions, ref));\n if (next) return next;\n }\n }\n const path = drillPath(question, dimensions);\n const used = new Set<string>([\n ...question.where.map((w) => parseDimensionRef(w.dim).key),\n ...question.by.map((b) => parseDimensionRef(b).key),\n ...(question.across ? [parseDimensionRef(question.across).key] : []),\n ]);\n const from = deepest ? path.indexOf(deepest) : -1;\n const ordered = from >= 0 ? [...path.slice(from + 1), ...path.slice(0, from)] : path;\n for (const ref of ordered) {\n if (!used.has(parseDimensionRef(ref).key) && dimensionFor(dimensions, ref)) return ref;\n }\n return null;\n}\n\n/**\n * DRILL INTO A GROUP (Metabase's default click). Every level of the clicked row becomes a\n * filter, in order, and the table regroups by `into` — the next Dimension of the path when not\n * named (\"Drill into\"), any Dimension when named (\"Break out by\"). `into === null` with nothing\n * left in the path lists the group's records.\n */\nexport function drillInto(\n question: MatrxDrillQuestion,\n groups: Record<string, string | null>,\n dimensions: readonly MatrxDrillDimension[],\n into?: string | null,\n paths: readonly (readonly string[])[] = [],\n): MatrxDrillQuestion {\n const added: MatrxDrillFilter[] = question.by\n .filter((ref) => ref in groups)\n .map((ref) => ({ dim: ref, value: groups[ref] ?? null }));\n const narrowed: MatrxDrillQuestion = { ...question, where: [...question.where, ...added] };\n const next = into === undefined ? nextDrillLevel(question, dimensions, paths) : into;\n return { ...narrowed, by: next ? [next] : [], across: next && question.across === next ? null : question.across ?? null };\n}\n\n/** \"See these records\": the group's filters, and no grouping — the records themselves. */\nexport function drillToRecords(question: MatrxDrillQuestion, groups: Record<string, string | null>): MatrxDrillQuestion {\n const added = question.by.filter((ref) => ref in groups).map((ref) => ({ dim: ref, value: groups[ref] ?? null }));\n return { ...question, by: [], across: null, where: [...question.where, ...added] };\n}\n\n/** One crumb of the trail. `index` -1 is the root (\"All records\"). */\nexport interface MatrxDrillCrumb {\n index: number;\n label: string;\n /** The Dimension that was filtered at this step, and its value's words. */\n dimLabel?: string;\n valueLabel?: string;\n}\n\nexport function drillCrumbs(\n question: MatrxDrillQuestion,\n dimensions: readonly MatrxDrillDimension[],\n rootLabel: string,\n emptyLabel?: string,\n): MatrxDrillCrumb[] {\n return [\n { index: -1, label: rootLabel },\n ...question.where.map((w, index) => {\n const dimLabel = dimensionRefLabel(dimensions, w.dim);\n const valueLabel = drillValueLabel(dimensions, w.dim, w.value, emptyLabel);\n return { index, label: valueLabel, dimLabel, valueLabel };\n }),\n ];\n}\n\n/**\n * ZOOM BACK OUT to a crumb: the filters up to and including it stay; the table regroups by the\n * Dimension that was filtered NEXT, so the person sees the level they drilled out of — Power BI's\n * drill-up, one crumb at a time or straight to any crumb.\n */\nexport function zoomOutTo(question: MatrxDrillQuestion, index: number): MatrxDrillQuestion {\n const kept = question.where.slice(0, index + 1);\n const leaving = question.where[index + 1];\n if (!leaving) return question;\n return { ...question, where: kept, by: [leaving.dim] };\n}\n\n// ── what the host is asked ─────────────────────────────────────────────────\n\nexport function drillRequestKey(by: readonly string[]): string {\n return by.length === 0 ? \"∅\" : by.join(\"|\");\n}\n\n/**\n * EVERY GROUPING THE SCREEN NEEDS, and nothing it does not: the grand total; each level's\n * prefix of `by` (so an outer group's average is the store's average, never an average of\n * averages); and, with a pivot, each of those crossed with `across` plus `across` alone for the\n * column totals. At most eight calls for the deepest pivot.\n */\nexport function drillRequests(question: MatrxDrillQuestion): MatrxDrillRequest[] {\n const out: MatrxDrillRequest[] = [];\n const add = (by: string[]) => {\n const key = drillRequestKey(by);\n if (!out.some((r) => r.key === key)) out.push({ key, by });\n };\n add([]);\n const by = question.by.slice(0, MATRX_DRILL_MAX_LEVELS);\n for (let i = 1; i <= by.length; i++) add(by.slice(0, i));\n const across = question.across ?? null;\n if (across && by.length > 0) {\n add([across]);\n for (let i = 1; i <= by.length; i++) add([...by.slice(0, i), across]);\n }\n return out;\n}\n\n// ── the answer, as a tree ──────────────────────────────────────────────────\n\nexport interface MatrxDrillNode {\n /** Stable across re-reads: the path of values from the outermost level. */\n key: string;\n level: number;\n /** Dimension reference → value, for this level and every level above it. */\n groups: Record<string, string | null>;\n row: MatrxDrillAnswerRow;\n children: MatrxDrillNode[];\n /** What the store's group limit left out under this node (or at the top), said as one row. */\n other: MatrxDrillOther | null;\n}\n\n/** THE REST — what a group limit left out, so the page always adds up to its total. */\nexport interface MatrxDrillOther {\n row_count: number;\n /** Only additive measures; a non-additive one is `null` (left blank, not invented). */\n measures: Record<string, number | null>;\n}\n\nfunction valueKey(value: string | null): string {\n return value === null ? \"∅\" : `v:${value}`;\n}\n\nfunction matches(row: MatrxDrillAnswerRow, groups: Record<string, string | null>): boolean {\n for (const [ref, value] of Object.entries(groups)) {\n if ((row.groups[ref] ?? null) !== value) return false;\n }\n return true;\n}\n\n/**\n * THE REST, worked out from the parent's own total: the parent's rows minus the rows its shown\n * children hold. Non-additive measures cannot be subtracted and stay blank. `null` when nothing\n * is left over — the common case, and then no row is drawn.\n */\nexport function otherOf(\n parent: MatrxDrillAnswerRow | undefined,\n children: readonly MatrxDrillAnswerRow[],\n measures: readonly MatrxDrillMeasure[],\n): MatrxDrillOther | null {\n if (!parent) return null;\n const shown = children.reduce((sum, row) => sum + (row.row_count ?? 0), 0);\n const rest = (parent.row_count ?? 0) - shown;\n if (rest <= 0) return null;\n const out: Record<string, number | null> = {};\n for (const measure of measures) {\n if (!measure.additive) {\n out[measure.key] = null;\n continue;\n }\n const whole = parent.measures[measure.key];\n if (whole === null || whole === undefined) {\n out[measure.key] = null;\n continue;\n }\n const part = children.reduce((sum, row) => sum + (row.measures[measure.key] ?? 0), 0);\n out[measure.key] = whole - part;\n }\n return { row_count: rest, measures: out };\n}\n\nfunction sortRows(\n rows: MatrxDrillAnswerRow[],\n ref: string,\n sort: MatrxDrillQuestion[\"sort\"],\n /** A pivot column's cell for a row (`across:<measure>:<value>` sort keys), when the answer pivots. */\n cellOf?: (row: MatrxDrillAnswerRow, measure: string, value: string | null) => number | null | undefined,\n): MatrxDrillAnswerRow[] {\n if (!sort) return rows;\n const dir = sort.direction === \"asc\" ? 1 : -1;\n return [...rows].sort((a, b) => {\n if (sort.key === \"label\") {\n const av = a.groups[ref];\n const bv = b.groups[ref];\n if (av === bv) return 0;\n if (av === null || av === undefined) return 1;\n if (bv === null || bv === undefined) return -1;\n return av.localeCompare(bv, undefined, { numeric: true }) * dir;\n }\n const read = (row: MatrxDrillAnswerRow): number | null | undefined => {\n // ONE PIVOT COLUMN'S CELL (L5): `across:<measure>:<value>`, \"(none)\" the no-value column\n if (sort.key.startsWith(\"across:\")) {\n const rest = sort.key.slice(7);\n const at = rest.indexOf(\":\");\n if (at < 0 || !cellOf) return null;\n const value = rest.slice(at + 1);\n return cellOf(row, rest.slice(0, at), value === \"(none)\" ? null : value);\n }\n if (sort.key.startsWith(\"delta:\")) {\n const key = sort.key.slice(6);\n return drillDelta(row.measures[key], row.prior_measures?.[key]).change;\n }\n return sort.key === \"count\" && !(sort.key in row.measures) ? row.row_count : row.measures[sort.key];\n };\n const av = read(a);\n const bv = read(b);\n if (av === bv) return 0;\n if (av === null || av === undefined) return 1;\n if (bv === null || bv === undefined) return -1;\n return (av - bv) * dir;\n });\n}\n\n/**\n * THE TREE a grouped answer draws: level 0 from the request for `by[0]`, each deeper level from\n * the request for its prefix, children matched to their parent by the parent's values. `null`\n * while any level is still unanswered.\n */\nexport function buildDrillTree(\n question: MatrxDrillQuestion,\n answers: MatrxDrillAnswers,\n measures: readonly MatrxDrillMeasure[],\n): { nodes: MatrxDrillNode[]; total: MatrxDrillAnswerRow | null; other: MatrxDrillOther | null } | null {\n const by = question.by.slice(0, MATRX_DRILL_MAX_LEVELS);\n const levels: (readonly MatrxDrillAnswerRow[])[] = [];\n for (let i = 1; i <= by.length; i++) {\n const rows = answers[drillRequestKey(by.slice(0, i))];\n if (!rows) return null;\n levels.push(rows);\n }\n const totalRows = answers[drillRequestKey([])];\n if (!totalRows) return null;\n const total = totalRows[0] ?? { groups: {}, measures: {}, row_count: 0 };\n const shown = measures.filter((m) => question.show.includes(m.key));\n\n const build = (level: number, parentGroups: Record<string, string | null>, parentKey: string): MatrxDrillNode[] => {\n const ref = by[level]!;\n // A PERIOD READS IN TIME ORDER unless the person sorted: months ranked by their count\n // (the door's order) read Aug, Jan, Feb, Nov — a calendar nobody keeps.\n const sort = question.sort ?? (parseDimensionRef(ref).grain ? { key: \"label\", direction: \"asc\" as const } : null);\n const across = question.across ?? null;\n const crossed = across ? answers[drillRequestKey([...by.slice(0, level + 1), across])] : undefined;\n const rows = sortRows(\n (levels[level] ?? []).filter((row) => matches(row, parentGroups)),\n ref,\n sort,\n across && crossed\n ? (row, measure, value) => {\n const groups = { ...parentGroups, [ref]: row.groups[ref] ?? null };\n const hit = crossed.find((r) => matches(r, groups) && (r.groups[across] ?? null) === value);\n return hit ? (hit.measures[measure] ?? null) : null;\n }\n : undefined,\n );\n return rows.map((row) => {\n const groups = { ...parentGroups, [ref]: row.groups[ref] ?? null };\n const key = `${parentKey}/${valueKey(row.groups[ref] ?? null)}`;\n const children = level + 1 < by.length ? build(level + 1, groups, key) : [];\n const childRows = children.map((c) => c.row);\n return {\n key,\n level,\n groups,\n row,\n children,\n other: level + 1 < by.length ? otherOf(row, childRows, shown) : null,\n };\n });\n };\n const nodes = build(0, {}, \"\");\n return { nodes, total, other: otherOf(total, nodes.map((n) => n.row), shown) };\n}\n\n// ── the pivot ──────────────────────────────────────────────────────────────\n\nexport interface MatrxDrillPivotColumn {\n /** `null` is the \"no value\" column; `OTHER` is everything past the cap. */\n value: string | null;\n other?: boolean;\n label: string;\n total: MatrxDrillAnswerRow;\n /** The rest column only: what it holds, in one sentence (\"Showing the latest 24 of 30 periods; …\"). */\n says?: string;\n}\n\nexport const MATRX_DRILL_OTHER_COLUMN = \"\\u0000other\";\n\n/** A period's start as a number to order by (an address may carry a space for the `T`). */\nfunction periodOrder(value: string): number {\n const ms = Date.parse(value.replace(\" \", \"T\"));\n return Number.isNaN(ms) ? Number.NaN : ms;\n}\n\nfunction byPeriod(a: string, b: string): number {\n const x = periodOrder(a);\n const y = periodOrder(b);\n if (Number.isNaN(x) || Number.isNaN(y)) return a < b ? -1 : a > b ? 1 : 0;\n return x - y;\n}\n\n/**\n * THE COLUMNS a pivot draws, capped at `cap` (the host's knob `drill.pivot_columns`), with ONE rest\n * column holding what the cap left out, so the columns always add up to the row total. `null`\n * while the pivot's own totals are unanswered.\n *\n * - Across a CATEGORY: the values largest first (the store's order); the rest column comes last\n * (\"6 more\").\n * - Across TIME: the periods in CALENDAR order, and when capped the LATEST periods are kept and\n * the earlier ones fold into the rest column, drawn first (\"6 earlier\") — the same rule the\n * door uses for a time-first grouping. A column of no date comes after the periods.\n *\n * The rest column says what it holds in `says`; the answer table prints it above the table.\n */\nexport function pivotColumns(\n question: MatrxDrillQuestion,\n answers: MatrxDrillAnswers,\n dimensions: readonly MatrxDrillDimension[],\n measures: readonly MatrxDrillMeasure[],\n cap = MATRX_DRILL_PIVOT_COLUMNS,\n): MatrxDrillPivotColumn[] | null {\n const across = question.across ?? null;\n if (!across || question.by.length === 0) return [];\n const rows = answers[drillRequestKey([across])];\n if (!rows) return null;\n const limit = Math.max(1, Math.floor(cap));\n const column = (row: MatrxDrillAnswerRow): MatrxDrillPivotColumn => ({\n value: row.groups[across] ?? null,\n label: drillValueLabel(dimensions, across, row.groups[across] ?? null),\n total: row,\n });\n const restOf = (rest: readonly MatrxDrillAnswerRow[], label: string, says: string): MatrxDrillPivotColumn => {\n const shown = measures.filter((m) => question.show.includes(m.key));\n const measuresOut: Record<string, number | null> = {};\n for (const m of shown) {\n measuresOut[m.key] = m.additive ? rest.reduce((sum, r) => sum + (r.measures[m.key] ?? 0), 0) : null;\n }\n return {\n value: MATRX_DRILL_OTHER_COLUMN,\n other: true,\n label,\n says,\n total: { groups: {}, measures: measuresOut, row_count: rest.reduce((sum, r) => sum + r.row_count, 0) },\n };\n };\n\n const isTime = parseDimensionRef(across).grain !== null || dimensionFor(dimensions, across)?.kind === \"time\";\n if (isTime) {\n const dated = rows.filter((r) => r.groups[across] !== null && r.groups[across] !== undefined && r.groups[across] !== \"\");\n const undated = rows.filter((r) => !dated.includes(r));\n const inOrder = [...dated].sort((a, b) => byPeriod(a.groups[across]!, b.groups[across]!));\n const room = Math.max(0, limit - undated.length);\n const earlier = inOrder.length > room ? inOrder.slice(0, inOrder.length - room) : [];\n const kept = inOrder.slice(earlier.length);\n const cols = kept.map(column);\n if (earlier.length > 0) {\n cols.unshift(\n restOf(\n earlier,\n `${earlier.length.toLocaleString()} earlier`,\n `Showing the latest ${kept.length.toLocaleString()} of ${inOrder.length.toLocaleString()} periods; the ${earlier.length.toLocaleString()} earlier ${earlier.length === 1 ? \"one is\" : \"ones are\"} added together in the first column. Ask by a coarser period or a shorter window to see them one by one.`,\n ),\n );\n }\n return [...cols, ...undated.map(column)];\n }\n\n const cols = rows.slice(0, limit).map(column);\n if (rows.length > limit) {\n const rest = rows.slice(limit);\n cols.push(\n restOf(\n rest,\n `${formatCount(rest.length)} more`,\n `Showing ${formatCount(limit)} of ${formatCount(rows.length)} columns; the other ${formatCount(rest.length)} are added together in the last column.`,\n ),\n );\n }\n return cols;\n}\n\n/**\n * One pivot cell: the row's values crossed with one column. The \"Other\" column is the rest of the\n * row — the row's own total minus the capped columns — for additive measures only.\n */\nexport function pivotCell(\n question: MatrxDrillQuestion,\n answers: MatrxDrillAnswers,\n node: Pick<MatrxDrillNode, \"level\" | \"groups\" | \"row\">,\n column: MatrxDrillPivotColumn,\n columns: readonly MatrxDrillPivotColumn[],\n measure: MatrxDrillMeasure,\n): number | null {\n const across = question.across;\n if (!across) return null;\n const prefix = question.by.slice(0, node.level + 1);\n const rows = answers[drillRequestKey([...prefix, across])] ?? [];\n const mine = rows.filter((row) => matches(row, node.groups));\n if (column.other) {\n if (!measure.additive) return null;\n const whole = node.row.measures[measure.key];\n if (whole === null || whole === undefined) return null;\n const named = columns.filter((c) => !c.other);\n const part = mine\n .filter((row) => named.some((c) => c.value === (row.groups[across] ?? null)))\n .reduce((sum, row) => sum + (row.measures[measure.key] ?? 0), 0);\n const rest = whole - part;\n return rest === 0 ? null : rest;\n }\n const hit = mine.find((row) => (row.groups[across] ?? null) === column.value);\n return hit ? (hit.measures[measure.key] ?? null) : null;\n}\n\n// ── the address ────────────────────────────────────────────────────────────\n\n/** \"(none)\" is the address's word for a blank value (the design's URL grammar, f.4). */\nexport const MATRX_DRILL_NONE = \"(none)\";\n\nconst QUESTION_KEYS = [\"by\", \"across\", \"show\", \"w\"] as const;\n\nfunction withPrefix(prefix: string | undefined, key: string): string {\n return prefix ? `${prefix}.${key}` : key;\n}\n\n/**\n * Does the address carry a drill QUESTION? `sort` and `path` only refine one — and other screens\n * write a bare `sort` of their own (`?sort=title.asc`) — so they never, alone, override a Saved\n * view's question.\n */\nexport function addressHasDrill(params: URLSearchParams, prefix?: string): boolean {\n for (const key of QUESTION_KEYS) if (params.has(withPrefix(prefix, key))) return true;\n const f = withPrefix(prefix, \"f.\");\n for (const key of params.keys()) if (key.startsWith(f)) return true;\n return false;\n}\n\n/**\n * THE ADDRESS → THE QUESTION. `?by=condition,room&across=last_serviced:month&show=count,sum_quantity\n * &f.condition=Needs%20repair&sort=-sum_quantity&path=condition,room`. The `f.` filters are read\n * in the order the address holds them — that order IS the breadcrumb.\n */\nexport function drillQuestionFromParams(\n params: URLSearchParams,\n fallback: MatrxDrillQuestion,\n prefix?: string,\n): MatrxDrillQuestion {\n if (!addressHasDrill(params, prefix)) return fallback;\n const list = (key: string): string[] | null => {\n const raw = params.get(withPrefix(prefix, key));\n if (raw === null) return null;\n return raw.split(\",\").map((s) => s.trim()).filter(Boolean);\n };\n const where: MatrxDrillFilter[] = [];\n const f = withPrefix(prefix, \"f.\");\n for (const [key, value] of params.entries()) {\n if (!key.startsWith(f)) continue;\n where.push({ dim: key.slice(f.length), value: value === MATRX_DRILL_NONE ? null : value });\n }\n const sortRaw = params.get(withPrefix(prefix, \"sort\"));\n const sort = sortRaw\n ? { key: sortRaw.replace(/^-/, \"\"), direction: sortRaw.startsWith(\"-\") ? (\"desc\" as const) : (\"asc\" as const) }\n : null;\n const by = list(\"by\") ?? [];\n const show = list(\"show\");\n const path = list(\"path\");\n const cmp = params.get(withPrefix(prefix, \"cmp\"));\n const compare: MatrxDrillCompare | null = cmp === \"prev\" ? \"previous_period\" : cmp === \"yoy\" ? \"same_period_last_year\" : null;\n const w = params.get(withPrefix(prefix, \"w\"));\n return {\n by: by.slice(0, MATRX_DRILL_MAX_LEVELS),\n across: params.get(withPrefix(prefix, \"across\")) || null,\n show: show && show.length > 0 ? show : fallback.show,\n where,\n ...(path && path.length > 0 ? { path } : {}),\n ...(sort ? { sort } : {}),\n ...(params.get(withPrefix(prefix, \"share\")) === \"1\" ? { share: true } : {}),\n ...(compare ? { compare } : {}),\n ...(w ? { window: w } : {}),\n };\n}\n\n/**\n * THE QUESTION → AN ADDRESS PATCH for `commitUrlParams`: every drill key set or cleared, and every\n * `f.` filter the address holds today cleared unless the question keeps it. `f.` keys are written\n * in the trail's order.\n */\nexport function drillQuestionToParams(\n question: MatrxDrillQuestion,\n current: URLSearchParams,\n prefix?: string,\n): Record<string, string | null> {\n const patch: Record<string, string | null> = {};\n const f = withPrefix(prefix, \"f.\");\n for (const key of current.keys()) if (key.startsWith(f)) patch[key] = null;\n patch[withPrefix(prefix, \"by\")] = question.by.length > 0 ? question.by.join(\",\") : null;\n patch[withPrefix(prefix, \"across\")] = question.across || null;\n patch[withPrefix(prefix, \"show\")] = question.show.length > 0 ? question.show.join(\",\") : null;\n patch[withPrefix(prefix, \"path\")] = question.path && question.path.length > 0 ? question.path.join(\",\") : null;\n patch[withPrefix(prefix, \"sort\")] = question.sort\n ? `${question.sort.direction === \"desc\" ? \"-\" : \"\"}${question.sort.key}`\n : null;\n patch[withPrefix(prefix, \"share\")] = question.share ? \"1\" : null;\n patch[withPrefix(prefix, \"cmp\")] =\n question.compare === \"previous_period\" ? \"prev\" : question.compare === \"same_period_last_year\" ? \"yoy\" : null;\n patch[withPrefix(prefix, \"w\")] = question.window || null;\n for (const w of question.where) patch[`${f}${w.dim}`] = w.value === null ? MATRX_DRILL_NONE : w.value;\n return patch;\n}\n\n/** Two questions ask the same thing (the address and a Saved view agree). */\nexport function sameDrillQuestion(a: MatrxDrillQuestion, b: MatrxDrillQuestion): boolean {\n return JSON.stringify(normalize(a)) === JSON.stringify(normalize(b));\n}\n\nfunction normalize(q: MatrxDrillQuestion) {\n return {\n by: q.by,\n across: q.across ?? null,\n show: q.show,\n where: q.where.map((w) => [w.dim, w.value]),\n path: q.path && q.path.length > 0 ? q.path : null,\n sort: q.sort ?? null,\n share: q.share ?? false,\n compare: q.compare ?? null,\n window: q.window ?? null,\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────────────────────\n// ADOPTED FROM THE USAGE SCREENS (lane DRILL-USAGE-PAGE, 2026-09-29) — what the Spend Explorer,\n// cx-dashboard usage and the per-user usage page each hand-rolled, once, over the same answer:\n// the grouped answer exported (CSV / text for a person or an AI), the coverage line, the time\n// grain chosen from the window, and the series an inline chart draws.\n// ─────────────────────────────────────────────────────────────────────────────────────────────\n\n/** One line of the grouped answer as a person reads it: the group's words per level, then each shown Measure. */\nexport interface MatrxDrillGridLine {\n kind: \"group\" | \"other\" | \"total\";\n level: number;\n /** One cell per asked Dimension (outer first); a deeper level repeats its parents' words. */\n labels: string[];\n values: Array<number | null>;\n row_count: number;\n}\n\n/**\n * THE GROUPED ANSWER, FLATTENED IN THE ORDER THE TABLE DRAWS IT (adopted from cx-dashboard's CSV/\n * JSON export and the Spend Explorer's Copy): every group, each nested level under its parent,\n * the rest a limit left out as its own \"N more\" line, and the total last — so an export adds up\n * exactly like the screen. `null` while an answer is still being read.\n */\nexport function drillAnswerGrid(\n question: MatrxDrillQuestion,\n answers: MatrxDrillAnswers,\n dimensions: readonly MatrxDrillDimension[],\n measures: readonly MatrxDrillMeasure[],\n emptyLabel = \"No value\",\n): { header: string[]; lines: MatrxDrillGridLine[] } | null {\n const tree = buildDrillTree(question, answers, measures);\n if (!tree) return null;\n const by = question.by.slice(0, MATRX_DRILL_MAX_LEVELS);\n const shown = question.show\n .map((key) => measures.find((m) => m.key === key))\n .filter((m): m is MatrxDrillMeasure => m !== undefined);\n const header = [...by.map((ref) => dimensionRefLabel(dimensions, ref)), ...shown.map((m) => m.label), \"Rows\"];\n const lines: MatrxDrillGridLine[] = [];\n const valuesOf = (source: Record<string, number | null>) => shown.map((m) => source[m.key] ?? null);\n const walk = (nodes: MatrxDrillNode[], parentLabels: string[], other: MatrxDrillOther | null, level: number) => {\n for (const node of nodes) {\n const ref = by[node.level]!;\n const labels = [...parentLabels, drillValueLabel(dimensions, ref, node.groups[ref] ?? null, emptyLabel)];\n lines.push({ kind: \"group\", level: node.level, labels: padLabels(labels, by.length), values: valuesOf(node.row.measures), row_count: node.row.row_count });\n if (node.children.length > 0) walk(node.children, labels, node.other, node.level + 1);\n }\n if (other) {\n const labels = [...parentLabels, \"Everything else\"];\n lines.push({ kind: \"other\", level, labels: padLabels(labels, by.length), values: valuesOf(other.measures), row_count: other.row_count });\n }\n };\n walk(tree.nodes, [], tree.other, 0);\n if (tree.total) {\n lines.push({ kind: \"total\", level: 0, labels: padLabels([\"Total\"], by.length), values: valuesOf(tree.total.measures), row_count: tree.total.row_count });\n }\n return { header, lines };\n}\n\nfunction padLabels(labels: string[], width: number): string[] {\n const out = labels.slice(0, Math.max(width, 1));\n while (out.length < Math.max(width, 1)) out.push(\"\");\n return out;\n}\n\nfunction csvCell(value: string): string {\n return /[\",\\n\\r]/.test(value) ? `\"${value.replace(/\"/g, '\"\"')}\"` : value;\n}\n\n/** The grouped answer as CSV: raw numbers (a spreadsheet does its own formatting), one header row. */\nexport function drillAnswerCsv(grid: { header: string[]; lines: MatrxDrillGridLine[] }): string {\n const rows = [grid.header, ...grid.lines.map((l) => [...l.labels, ...l.values.map((v) => (v === null ? \"\" : String(v))), String(l.row_count)])];\n return rows.map((r) => r.map(csvCell).join(\",\")).join(\"\\n\");\n}\n\n/**\n * The grouped answer as text a person pastes or an AI reads: what was asked (the trail, the\n * window), then one tab-separated line per group with each Measure in its own format.\n */\nexport function drillAnswerText(\n grid: { header: string[]; lines: MatrxDrillGridLine[] },\n measures: readonly MatrxDrillMeasure[],\n question: MatrxDrillQuestion,\n context: { title: string; trail?: string[] | undefined; window?: string | null | undefined },\n): string {\n const shown = question.show\n .map((key) => measures.find((m) => m.key === key))\n .filter((m): m is MatrxDrillMeasure => m !== undefined);\n const levels = Math.max(question.by.slice(0, MATRX_DRILL_MAX_LEVELS).length, 1);\n const head = [\n context.title,\n `Window: ${drillWindowLabel(context.window ?? question.window ?? null)}`,\n ...(context.trail && context.trail.length > 0 ? [`Narrowed to: ${context.trail.join(\" › \")}`] : []),\n \"\",\n grid.header.join(\"\\t\"),\n ];\n const body = grid.lines.map((l) => {\n const indent = l.kind === \"group\" ? \" \".repeat(l.level) : \"\";\n const labels = l.labels.slice(0, levels).map((x, i) => (i === l.level ? indent + x : x));\n const values = l.values.map((v, i) => {\n if (v === null) return \"not measured\";\n const m = shown[i];\n return m?.format ? m.format(v) : v.toLocaleString(undefined, { maximumFractionDigits: 2 });\n });\n return [...labels, ...values, l.row_count.toLocaleString()].join(\"\\t\");\n });\n return [...head, ...body].join(\"\\n\");\n}\n\n/**\n * THE GRAIN A WINDOW READS BEST AT (adopted from the Spend Explorer, which draws hours for short\n * windows and days otherwise; DRILL-DOWN-DESIGN (g) default): two days or less by hour, up to 90\n * days by day, up to a year by week, longer by month. `grains` = what the Dimension offers: a\n * grain it does not offer steps to the nearest coarser one it does (a date column never reads by hour).\n */\nexport function drillAutoGrain(\n window: string | null | undefined,\n now: Date = new Date(),\n grains: readonly MatrxDrillGrain[] = MATRX_DRILL_GRAINS,\n): MatrxDrillGrain {\n const range = drillWindowRange(window, now);\n const days = range ? (new Date(range.to).getTime() - new Date(range.from).getTime()) / 86_400_000 : NaN;\n const wanted: MatrxDrillGrain = !range ? \"month\" : !Number.isFinite(days) || days <= 2 ? \"hour\" : days <= 90 ? \"day\" : days <= 366 ? \"week\" : \"month\";\n return offeredGrain(wanted, grains);\n}\n\n/** The grain itself when offered, else the nearest coarser offered one (else the coarsest offered). */\nfunction offeredGrain(wanted: MatrxDrillGrain, grains: readonly MatrxDrillGrain[]): MatrxDrillGrain {\n if (grains.length === 0 || grains.includes(wanted)) return wanted;\n const at = MATRX_DRILL_GRAINS.indexOf(wanted);\n return MATRX_DRILL_GRAINS.slice(at).find((g) => grains.includes(g)) ?? grains[grains.length - 1]!;\n}\n\n/**\n * THE COVERAGE LINE (adopted from the Spend Explorer's filter chips: \"This slice is $X of the $Y\n * the whole window cost\"): what part of the unfiltered whole the narrowed answer is. `null` when\n * nothing is narrowed or the whole is not known yet.\n */\nexport function drillCoverage(\n slice: number | null | undefined,\n whole: number | null | undefined,\n format: (value: number) => string,\n): { share: number; sentence: string } | null {\n if (slice === null || slice === undefined || whole === null || whole === undefined || whole <= 0) return null;\n const share = slice / whole;\n const pctText = `${share >= 0.995 ? Math.round(share * 100) : (share * 100).toFixed(share * 100 < 10 ? 1 : 0)}%`;\n // a label, not a sentence (interface text is layout, 2026-09-30): the bar beside it and its tooltip\n // say it is this slice's share of the whole window\n return { share, sentence: `${format(slice)} of ${format(whole)} (${pctText})` };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────────────────────\n// THE CHART (lane DRILL-CHART, DRILL-FINISH decision 5) — stacked bars over time, as pure data.\n// The component (`./data-table/drill-chart`, recharts) only draws what these functions decide.\n//\n// WHAT THE DOOR CAN AND CANNOT SAY (read from platform._drill_compile on the nightly clone,\n// 2026-09-30): the group cap ranks the WHOLE group key. `by: [model, \"at:day\"]` with a limit of 10\n// keeps the ten largest (model, day) PAIRS and returns everything else as ONE \"other\" row with no\n// period — so the rest cannot be read per period from a two-level question. A pivot\n// (`across: model`) folds by COUNT, not by the chosen Measure, under its own knob, and is refused\n// for a high-cardinality Dimension. So the chart asks, in two rounds:\n// 1. `series` — by [split], sorted by the Measure, limit = seriesLimit: the top N over the WHOLE\n// window, their totals, and the window total;\n// `periods` — by [time]: every period's total (asked beside `series`);\n// 2. `cells` — by [time, split], where split ∈ the top N: each shown value per period.\n// Other per period = the period's total − its shown values, so every bar's stack sums to its\n// period's total and all bars sum to the window total. With no split, `periods` alone is one series.\n// ─────────────────────────────────────────────────────────────────────────────────────────────\n\n/** Series shown before Other (the host feeds the knob `drill.chart.top_n`; default 10). */\nexport const MATRX_DRILL_CHART_SERIES_LIMIT = 10;\n/** The store's page ceiling by default (`custom.page_ceiling`); a host with a lower one passes it. */\nexport const MATRX_DRILL_CHART_CEILING = 1000;\n/** The series key of Other — everything past the top N. */\nexport const MATRX_DRILL_CHART_OTHER = \"\\u0000other\";\n\n/** One row exactly as the door returned it: `kind` tells a group from the rest and the total. */\nexport interface MatrxDrillChartRow extends MatrxDrillAnswerRow {\n kind?: \"group\" | \"other\" | \"total\" | undefined;\n}\n\nexport type MatrxDrillChartRequestKey = \"series\" | \"periods\" | \"cells\";\n\n/** One question the host asks its door (`platform.drill_ask`), in the contract's own words. */\nexport interface MatrxDrillChartRequest {\n key: MatrxDrillChartRequestKey;\n /** Dimension references, outermost first. */\n by: string[];\n /** The trail as equalities (`null` = not set), plus — for `cells` — the split as a list of values. */\n where: Record<string, string | null | Array<string | null>>;\n show: string[];\n sort: { key: string; direction: \"asc\" | \"desc\" } | null;\n /** Groups before Other; `null` = the door's own default. */\n limit: number | null;\n /** The question's window, unchanged (the host maps it as it maps the table's). */\n window: string | null;\n}\n\nexport interface MatrxDrillChartPlan {\n /** The time reference the bars run along (`at:day`). */\n time: string | null;\n /** The Dimension the stack splits by; `null` = one series. */\n split: string | null;\n measure: string;\n /**\n * STACKED MEASURES (lane DRILL-FLIP-FIXES L3, the CX tab's input vs output tokens): two or more\n * Measures that add up, one series each, stacked per period; no split. Absent = one Measure.\n */\n stack?: string[] | undefined;\n seriesLimit: number;\n /** Why the chart cannot be drawn, in words — or `null`. */\n refused: string | null;\n /** Round one, asked together. Empty when refused. */\n first: MatrxDrillChartRequest[];\n /** @internal carried for round two */\n ceiling: number;\n periodsHint: number | null;\n}\n\nexport interface MatrxDrillChartContext {\n dimensions: readonly MatrxDrillDimension[];\n measures: readonly MatrxDrillMeasure[];\n /** The Measure stacked. Default: the question's first shown Measure. */\n measure?: string | null | undefined;\n /** Stack these Measures (two or more that add up) instead of splitting one — see `MatrxDrillChartPlan.stack`. */\n stack?: readonly string[] | null | undefined;\n /**\n * The time reference when the question does not group by time (`at:day`). Default: the first\n * time Dimension at the grain the window reads best at (`drillAutoGrain`).\n */\n time?: string | null | undefined;\n /** The store's page ceiling (default 1000). */\n ceiling?: number | undefined;\n now?: Date | undefined;\n}\n\nconst GRAIN_DAYS: Record<MatrxDrillGrain, number> = { hour: 1 / 24, day: 1, week: 7, month: 30.44, quarter: 91.31, year: 365.25 };\n\nfunction trailWhere(question: MatrxDrillQuestion): Record<string, string | null> {\n const where: Record<string, string | null> = {};\n for (const w of question.where) where[w.dim] = w.value;\n return where;\n}\n\nfunction clampLimit(n: number, ceiling: number): number {\n return Math.max(1, Math.min(ceiling, Math.ceil(n)));\n}\n\n/**\n * WHAT THE CHART MUST ASK, from the question the table answers. The bars run along the question's\n * time grouping (or `context.time`, or the first time Dimension at the window's grain); the stack\n * splits by the outermost non-time Dimension it groups by. A Measure that does not add up\n * (an average, a ratio, a distinct count) cannot be stacked across a split: the plan refuses it in\n * words, and the same Measure over time alone (no split) is drawn as one series.\n */\nexport function drillChartQuestions(\n question: MatrxDrillQuestion,\n seriesLimit: number = MATRX_DRILL_CHART_SERIES_LIMIT,\n context: MatrxDrillChartContext,\n): MatrxDrillChartPlan {\n const ceiling = Math.max(1, context.ceiling ?? MATRX_DRILL_CHART_CEILING);\n const limit = clampLimit(Number.isFinite(seriesLimit) ? seriesLimit : MATRX_DRILL_CHART_SERIES_LIMIT, ceiling);\n const measureKey = context.measure ?? question.show[0] ?? \"count\";\n const measure = context.measures.find((m) => m.key === measureKey);\n const base = { measure: measureKey, seriesLimit: limit, ceiling };\n\n const timeInBy = question.by.find((ref) => parseDimensionRef(ref).grain !== null);\n const firstTime = context.dimensions.find((d) => d.kind === \"time\");\n const time =\n timeInBy ??\n context.time ??\n (firstTime ? `${firstTime.key}:${drillAutoGrain(question.window ?? null, context.now, firstTime.grains ?? MATRX_DRILL_DEFAULT_GRAINS)}` : null);\n const stack = context.stack && context.stack.length >= 2 ? [...context.stack] : null;\n const split = stack\n ? null\n : question.by.find((ref) => parseDimensionRef(ref).grain === null && dimensionFor(context.dimensions, ref)?.kind !== \"time\") ?? null;\n\n const refuse = (sentence: string): MatrxDrillChartPlan => ({ ...base, time, split, refused: sentence, first: [], periodsHint: null });\n if (!time) return refuse(\"This answer has no time to chart it over.\");\n if (stack) {\n const missing = stack.find((k) => !context.measures.some((m) => m.key === k));\n if (missing) return refuse(`There is no Measure \"${missing}\" to chart.`);\n const loose = stack.map((k) => context.measures.find((m) => m.key === k)!).find((m) => !m.additive || m.moment);\n if (loose) return refuse(`${loose.label} does not add up, so it cannot be stacked.`);\n }\n if (!measure) return refuse(`There is no Measure \"${measureKey}\" to chart.`);\n if (split && !measure.additive) {\n return refuse(\n `${measure.label} does not add up across ${dimensionRefLabel(context.dimensions, split)}, so it cannot be stacked. ` +\n `Group by time alone to chart it, or show a Measure that adds up.`,\n );\n }\n\n const grain = parseDimensionRef(time).grain ?? \"day\";\n const range = drillWindowRange(question.window ?? null, context.now);\n const days = range ? (new Date(range.to).getTime() - new Date(range.from).getTime()) / 86_400_000 : NaN;\n const periodsHint = Number.isFinite(days) && days > 0 ? Math.ceil(days / GRAIN_DAYS[grain]) + 1 : null;\n const where = trailWhere(question);\n const window = question.window ?? null;\n const show = stack ?? [measureKey];\n const first: MatrxDrillChartRequest[] = [];\n if (split) {\n first.push({ key: \"series\", by: [split], where, show, sort: { key: measureKey, direction: \"desc\" }, limit, window });\n }\n first.push({ key: \"periods\", by: [time], where, show, sort: null, limit: periodsHint ? clampLimit(periodsHint, ceiling) : null, window });\n return { ...base, ...(stack ? { measure: stack[0]!, stack } : {}), time, split, refused: null, first, periodsHint };\n}\n\n/**\n * ROUND TWO: each shown value per period — `by [time, split]` restricted to the top N the `series`\n * answer named. `null` without a split, or when the window holds no group at all.\n */\nexport function drillChartCellsQuestion(\n plan: MatrxDrillChartPlan,\n question: MatrxDrillQuestion,\n series: readonly MatrxDrillChartRow[],\n): MatrxDrillChartRequest | null {\n if (plan.refused || !plan.split || !plan.time) return null;\n const split = plan.split;\n const values = series.filter((r) => rowKind(r) === \"group\").slice(0, plan.seriesLimit).map((r) => r.groups[split] ?? null);\n if (values.length === 0) return null;\n const cells = plan.periodsHint ? plan.periodsHint * values.length : plan.ceiling;\n return {\n key: \"cells\",\n by: [plan.time, split],\n where: { ...trailWhere(question), [split]: values },\n show: [plan.measure],\n sort: null,\n limit: clampLimit(cells, plan.ceiling),\n window: question.window ?? null,\n };\n}\n\nfunction rowKind(row: MatrxDrillChartRow): \"group\" | \"other\" | \"total\" {\n if (row.kind) return row.kind;\n return Object.keys(row.groups ?? {}).length > 0 ? \"group\" : \"total\";\n}\n\nexport interface MatrxDrillChartSeries {\n /** `v:<value>`, `∅` for \"no value\", or MATRX_DRILL_CHART_OTHER. */\n key: string;\n value: string | null;\n label: string;\n other: boolean;\n /** Its total over the whole window. */\n total: number | null;\n /** The colour its value keeps (the split Dimension's `colorFor`), as a style reads it. */\n color?: string | undefined;\n}\n\nexport interface MatrxDrillChartPeriod {\n key: string;\n /** The period as the door labelled it (its start). */\n value: string;\n label: string;\n total: number | null;\n /** Series key → its value in this period (Other = the period's total − the shown values). */\n values: Record<string, number | null>;\n}\n\nexport interface MatrxDrillChartData {\n time: string;\n split: string | null;\n measure: string;\n series: MatrxDrillChartSeries[];\n periods: MatrxDrillChartPeriod[];\n /** The window total (the `series` total with a split, else the `periods` total). */\n total: number | null;\n /** Honest sentences: periods or cells past a limit, Other's size. */\n says: string[];\n}\n\nexport type MatrxDrillChartAnswers = Readonly<Partial<Record<MatrxDrillChartRequestKey, readonly MatrxDrillChartRow[] | undefined>>>;\n\nexport function drillChartSeriesKey(value: string | null): string {\n return value === null ? \"∅\" : `v:${value}`;\n}\n\nconst EPSILON = 1e-9;\nfunction tidy(n: number, scale: number | null | undefined): number {\n return Math.abs(n) <= EPSILON * Math.max(1, Math.abs(scale ?? 0)) ? 0 : n;\n}\n\nfunction periodTime(value: string): number {\n const t = Date.parse(value);\n return Number.isFinite(t) ? t : NaN;\n}\n\n/**\n * THE CHART'S DATA from the three answers: periods in time order, the top N series in the answer's\n * rank (largest first) and Other last, each period's Other worked out from its own total — so a\n * bar's stack sums to its period's total and the bars sum to the window total. `null` while any\n * answer the plan needs is still being read (or the plan refused).\n */\nexport function drillChartData(\n plan: MatrxDrillChartPlan,\n answers: MatrxDrillChartAnswers,\n dimensions: readonly MatrxDrillDimension[],\n measures: readonly MatrxDrillMeasure[],\n emptyLabel = \"No value\",\n): MatrxDrillChartData | null {\n if (plan.refused || !plan.time) return null;\n const time = plan.time;\n const split = plan.split;\n const m = plan.measure;\n const measure = measures.find((x) => x.key === m);\n const periodRows = answers.periods;\n if (!periodRows) return null;\n const says: string[] = [];\n const read = (row: MatrxDrillChartRow | undefined): number | null => {\n if (!row) return null;\n const v = row.measures[m];\n return v === null || v === undefined ? (m === \"count\" ? row.row_count : null) : v;\n };\n\n const groups = periodRows\n .filter((r) => rowKind(r) === \"group\")\n .slice()\n .sort((a, b) => {\n const av = a.groups[time] ?? \"\";\n const bv = b.groups[time] ?? \"\";\n const at = periodTime(av);\n const bt = periodTime(bv);\n return Number.isFinite(at) && Number.isFinite(bt) ? at - bt : av.localeCompare(bv);\n });\n const periodsTotal = read(periodRows.find((r) => rowKind(r) === \"total\"));\n const earlier = periodRows.find((r) => rowKind(r) === \"other\");\n if (earlier) {\n const n = Math.max(0, (earlier.distinct_groups ?? groups.length) - groups.length);\n const fmt = measure?.format ?? ((v: number | null) => (v === null ? \"—\" : v.toLocaleString()));\n says.push(\n `${n > 0 ? `The ${n.toLocaleString()} earliest periods` : \"The earliest periods\"} (${fmt(read(earlier))}) are past the limit and not drawn; choose a coarser period or a shorter window to see them.`,\n );\n }\n const grain = parseDimensionRef(time).grain;\n const periodBase = groups.map((r) => {\n const value = r.groups[time] ?? \"\";\n return {\n key: value,\n value,\n label: grain ? bucketLabel(value, grain) : drillValueLabel(dimensions, time, value, emptyLabel),\n total: read(r),\n values: {} as Record<string, number | null>,\n };\n });\n\n if (plan.stack) {\n // ONE SERIES PER MEASURE, stacked: each period's bar sums the Measures (their units agree)\n const keys = plan.stack;\n const readOf = (row: MatrxDrillChartRow | undefined, k: string): number | null => {\n const v = row?.measures[k];\n return v === null || v === undefined ? null : v;\n };\n const totalRow = periodRows.find((r) => rowKind(r) === \"total\");\n for (const [i, p] of periodBase.entries()) {\n const row = groups[i];\n let sum: number | null = null;\n for (const k of keys) {\n const v = readOf(row, k);\n p.values[`m:${k}`] = v;\n if (v !== null) sum = (sum ?? 0) + v;\n }\n p.total = sum;\n }\n const totals = keys.map((k) => readOf(totalRow, k));\n const total = totals.every((t) => t === null) ? null : totals.reduce<number>((a, t) => a + (t ?? 0), 0);\n return {\n time,\n split: null,\n measure: m,\n series: keys.map((k, i) => ({ key: `m:${k}`, value: null, label: measures.find((x) => x.key === k)?.label ?? k, other: false, total: totals[i] ?? null })),\n periods: periodBase,\n total,\n says,\n };\n }\n\n if (!split) {\n const key = \"all\";\n for (const p of periodBase) p.values[key] = p.total;\n return {\n time,\n split: null,\n measure: m,\n series: [{ key, value: null, label: measure?.label ?? m, other: false, total: periodsTotal }],\n periods: periodBase,\n total: periodsTotal,\n says,\n };\n }\n\n const seriesRows = answers.series;\n if (!seriesRows) return null;\n const top = seriesRows.filter((r) => rowKind(r) === \"group\").slice(0, plan.seriesLimit);\n const windowTotal = read(seriesRows.find((r) => rowKind(r) === \"total\")) ?? periodsTotal;\n const cellRows = top.length > 0 ? answers.cells : [];\n if (!cellRows) return null;\n\n const series: MatrxDrillChartSeries[] = top.map((r) => {\n const value = r.groups[split] ?? null;\n const color = drillValueColor(dimensions, split, value);\n return { key: drillChartSeriesKey(value), value, label: drillValueLabel(dimensions, split, value, emptyLabel), other: false, total: read(r), ...(color ? { color } : {}) };\n });\n const shownKeys = new Set(series.map((s) => s.key));\n const cellsPast = cellRows.find((r) => rowKind(r) === \"other\");\n if (cellsPast) {\n says.push(\"Some period-by-series values were past the limit, so they are counted in Other.\");\n }\n const byPeriod = new Map<string, Map<string, number | null>>();\n for (const r of cellRows) {\n if (rowKind(r) !== \"group\") continue;\n const period = r.groups[time] ?? \"\";\n const key = drillChartSeriesKey(r.groups[split] ?? null);\n if (!shownKeys.has(key)) continue;\n let row = byPeriod.get(period);\n if (!row) byPeriod.set(period, (row = new Map()));\n row.set(key, read(r));\n }\n let anyOther = false;\n for (const p of periodBase) {\n const cells = byPeriod.get(p.value);\n let shown = 0;\n for (const s of series) {\n // No row for this value in this period is nothing counted (0); a row whose Measure is blank stays blank.\n const v = cells?.has(s.key) ? (cells.get(s.key) ?? null) : 0;\n p.values[s.key] = v;\n shown += v ?? 0;\n }\n const other = p.total === null ? null : tidy(p.total - shown, p.total);\n p.values[MATRX_DRILL_CHART_OTHER] = other;\n if (other !== null && other !== 0) anyOther = true;\n }\n const shownTotal = series.reduce((sum, s) => sum + (s.total ?? 0), 0);\n const otherTotal = windowTotal === null ? null : tidy(windowTotal - shownTotal, windowTotal);\n const restCount = Math.max(0, (seriesRows.find((r) => r.distinct_groups != null)?.distinct_groups ?? series.length) - series.length);\n if (anyOther || (otherTotal !== null && otherTotal !== 0)) {\n series.push({\n key: MATRX_DRILL_CHART_OTHER,\n value: null,\n label: restCount > 0 ? `Other (${restCount.toLocaleString()} more)` : \"Other\",\n other: true,\n total: otherTotal,\n });\n } else {\n for (const p of periodBase) delete p.values[MATRX_DRILL_CHART_OTHER];\n }\n return { time, split, measure: m, series, periods: periodBase, total: windowTotal, says };\n}\n\n/**\n * A CLICK ON A PERIOD narrows the window to that period (`from..to`, the grammar the address\n * already carries). When the question groups by that time, the grain moves to the one the new\n * window reads best at if that is finer — a month clicked reads by day.\n */\nexport function drillChartNarrowToPeriod(\n question: MatrxDrillQuestion,\n plan: Pick<MatrxDrillChartPlan, \"time\">,\n periodValue: string,\n dimensions: readonly MatrxDrillDimension[] = [],\n): MatrxDrillQuestion {\n const time = plan.time;\n const grain = time ? parseDimensionRef(time).grain : null;\n if (!time || !grain) return question;\n const span = bucketWindow(periodValue, grain);\n // An hour narrows to that hour (moments in UTC); a day and coarser to their dates.\n const window = grain === \"hour\" ? `${drillWindowMoment(span.from)}..${drillWindowMoment(span.to)}` : `${span.from.slice(0, 10)}..${span.to.slice(0, 10)}`;\n const offered = dimensionFor(dimensions, time)?.grains ?? MATRX_DRILL_DEFAULT_GRAINS;\n const finer = drillAutoGrain(window, new Date(span.to), offered);\n const { key } = parseDimensionRef(time);\n const by =\n MATRX_DRILL_GRAINS.indexOf(finer) < MATRX_DRILL_GRAINS.indexOf(grain)\n ? question.by.map((ref) => (ref === time ? `${key}:${finer}` : ref))\n : question.by;\n return { ...question, by, window };\n}\n\n/**\n * A CLICK ON A SEGMENT drills exactly as the answer table's drill does: the split's value becomes\n * a filter of the trail and the answer regroups by the next Dimension of the path (the records,\n * when the path is spent).\n */\nexport function drillChartDrill(\n question: MatrxDrillQuestion,\n plan: Pick<MatrxDrillChartPlan, \"split\">,\n value: string | null,\n dimensions: readonly MatrxDrillDimension[],\n paths: readonly (readonly string[])[] = [],\n): MatrxDrillQuestion {\n const split = plan.split;\n if (!split) return question;\n const scope: MatrxDrillQuestion = { ...question, by: [split] };\n const into = nextDrillLevel(scope, dimensions, paths);\n return drillInto(scope, { [split]: value }, dimensions, into, paths);\n}\n"],"mappings":"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","names":["formatCount","ErrorBox","jsx","m","Fragment","jsx","jsxs","formatCount"]}