@xenosystem/blocks 0.13.9 → 0.13.11

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.
@@ -1,7 +1,7 @@
1
1
  import { PanelModule, PanelManifest, BlockDeclaration, LogSink } from '@xenosystem/block-sdk';
2
2
  import * as _xenosystem_data_core from '@xenosystem/data-core';
3
3
  import { XenoColumn, XenoQuery, XenoResultSet, XenoValue, XenoRow, XenoAggFn, XenoValueFormat, XenoFilterGroup, XenoColumnRole, XenoSchemaCatalog, XenoFilter, XenoSourceSchema } from '@xenosystem/data-core';
4
- import { ReactNode } from 'react';
4
+ import { ReactNode, ReactElement } from 'react';
5
5
  import { C as ConnectorExecuteInput, a as ConnectorExecuteOutput, b as ConnectorsPanelState, S as SubstrateFacilities, c as ConnectorsViewState, d as XenoConnection, e as XenoCredentialFormSpec, f as XenoCredentialResult, g as XenoConnectionStatus, P as PushdownSplit, h as XenoConnectorQueryRequest, i as XenoRestPaging, j as XenoConnectorFetch, k as XenoResolverErrorCode, l as XenoConnectorFileReader, X as XenoSqlEngine, m as ConnectorTransport, n as XenoCredentialField } from '../transport-B1cdciP8.js';
6
6
  export { B as BrowserFetchOptions, o as ConnectorTransportError, p as SECRET_HEADER_NAMES, q as XenoConnectionConfig, r as XenoConnectionHealth, s as XenoConnectionKind, t as XenoConnectionState, u as XenoConnectorCapabilities, v as XenoCredentialSubmission, w as XenoFetchRequest, x as XenoFetchResponse, y as XenoFileReadRequest, z as XenoFileReadResponse, A as XenoRefreshPolicy, D as XenoSqlTable, E as browserFetch, F as classifyBrowserFetchFailure, G as defaultCapabilitiesFor, H as errorResult, I as evaluateSupport, J as facilitiesOf, K as normalizePaging, L as secretHeaderNames } from '../transport-B1cdciP8.js';
7
7
 
@@ -1107,25 +1107,79 @@ declare function buildChartData(result: XenoResultSet, encoding: XenoChartEncodi
1107
1107
  declare function rowsForMark(result: XenoResultSet, encoding: XenoChartEncoding, label: string, seriesId?: string): string[];
1108
1108
 
1109
1109
  /**
1110
- * The Chart panel view.
1110
+ * The Chart panel AS A DECLARATION — the thirteenth declared block.
1111
1111
  *
1112
- * Composes `@xenosystem/components/chrome`; the host must ensure `@xenosystem/workbench/primitives.css` is
1113
- * present. The plot area delegates to the installed renderer (see `renderer.ts`) — this view owns
1114
- * the chrome and the states, not the marks.
1112
+ * Everything drawn is data: the chart-type control, the partial / aggregating badges and the export
1113
+ * button in the toolbar, the four body states (error, no data, nothing to plot, no renderer), the
1114
+ * status bar. The PLOT is the one thing a declaration cannot hold — `chart/Plot` is the runtime-bound
1115
+ * unit: the element the host's renderer (Chart.js or another) draws the marks into, re-rendered
1116
+ * from the controller's data and highlights. The panel ships the model; the host ships the marks.
1117
+ */
1118
+
1119
+ declare const CHART_TYPE_LABELS: Record<string, string>;
1120
+ declare const DEFAULT_CHART_OPTIONS: ChartRenderOptions;
1121
+ interface ChartViewModel {
1122
+ types: {
1123
+ value: string;
1124
+ label: string;
1125
+ }[];
1126
+ type: string;
1127
+ partial: boolean;
1128
+ aggregating: boolean;
1129
+ aggregatingHint?: string;
1130
+ errorMessage?: string;
1131
+ errored: boolean;
1132
+ empty: boolean;
1133
+ emptyHint: string;
1134
+ unplottable: boolean;
1135
+ encodingProblem?: string;
1136
+ noRenderer: boolean;
1137
+ plot: boolean;
1138
+ countLabel?: string;
1139
+ highlightLabel?: string;
1140
+ }
1141
+ type ChartUiIntent = {
1142
+ kind: 'type';
1143
+ type: XenoChartType;
1144
+ } | {
1145
+ kind: 'export';
1146
+ };
1147
+ interface ChartViewOptions {
1148
+ emptyHint?: string;
1149
+ }
1150
+ /** Derive the view from the live controller — the runtime calls this on every draw. */
1151
+ declare function chartViewState(c: ChartController, options?: ChartViewOptions): ChartViewModel;
1152
+ declare function applyChartUi(c: ChartController, intent: ChartUiIntent): void;
1153
+ declare const chartViewManifest: PanelManifest;
1154
+ declare const BEHAVIOURS$2: {
1155
+ 'data/chartPanelView': (inputs: Record<string, unknown>) => ChartViewModel;
1156
+ };
1157
+ /** The runtime-bound unit: the plot surface the host renderer draws into. */
1158
+ declare const CHART_RUNTIME_UNITS: readonly ["chart/Plot"];
1159
+ declare const CHART_DECLARATION: BlockDeclaration;
1160
+
1161
+ /**
1162
+ * The Chart panel view — the CODE VIEW of `CHART_DECLARATION`, drawing the same `ChartViewModel`
1163
+ * byte-identical (the equivalence suite holds the two together). The one thing this file owns that
1164
+ * the declaration cannot is `chart/Plot`: the element the host renderer draws the marks into.
1115
1165
  *
1116
- * **There is no shelf UI here.** The encoding editor is `xeno.core.fields` with chart shelves,
1117
- * wired as a sibling panel; building a second shelf UI is exactly what sequencing chart after fields
1118
- * was meant to avoid.
1166
+ * Composes `@xenosystem/components/chrome`; the host must ensure `@xenosystem/workbench/primitives.css` is
1167
+ * present.
1119
1168
  *
1120
1169
  * @module
1121
1170
  */
1122
1171
 
1123
1172
  /** Props for {@link ChartPanelView}. */
1124
- interface ChartPanelViewProps {
1173
+ interface ChartPanelViewProps extends ChartViewOptions {
1125
1174
  controller: ChartController;
1126
1175
  options?: Partial<ChartRenderOptions>;
1127
- emptyHint?: string;
1128
1176
  }
1177
+ /** `chart/Plot` — the plot surface. The runtime registers it bound to its controller and render options. */
1178
+ declare function ChartPlot({ controller, options }: {
1179
+ controller: ChartController;
1180
+ options?: Partial<ChartRenderOptions>;
1181
+ }): ReactElement;
1182
+ declare function chartRuntimeComponents(controller: ChartController, options?: Partial<ChartRenderOptions>): Record<(typeof CHART_RUNTIME_UNITS)[number], React.ComponentType<any>>;
1129
1183
  /** The Chart panel view. */
1130
1184
  declare function ChartPanelView({ controller, options, emptyHint }: ChartPanelViewProps): ReactNode;
1131
1185
 
@@ -1401,18 +1455,59 @@ declare const METRICS_PANEL_ID = "xeno.core.metrics";
1401
1455
  declare const metricsManifest: PanelManifest;
1402
1456
 
1403
1457
  /**
1404
- * The Metrics panel view — tiles, deltas, sparklines, threshold tone.
1458
+ * The Metrics panel AS A DECLARATION — the twelfth declared block: KPI tiles over a data-core result.
1405
1459
  *
1406
- * Composes `@xenosystem/components/chrome` (`StatTile`, `Badge`, `SegmentedControl`, `Sparkline`); the
1407
- * host must ensure `@xenosystem/workbench/primitives.css` is present. Nothing here is a local re-cut —
1408
- * the sparkline moved into the primitives so there is exactly one.
1409
- *
1410
- * @module
1460
+ * Everything drawn is data: the range control and partial badge in the toolbar (only when ranges
1461
+ * exist), the error and empty states, a `TileGrid` of `StatTile`s — each with its level badge, its
1462
+ * display value (an em dash for a partial null, never a zero), a `Delta` toned by the metric's
1463
+ * direction, the partial note, the `Sparkline` — and the status bar. A tile activates as a real
1464
+ * button (`StatTile.onActivate`). No runtime-bound unit.
1411
1465
  */
1412
1466
 
1413
- /** Props for {@link MetricsPanelView}. */
1414
- interface MetricsPanelViewProps {
1415
- controller: MetricsController;
1467
+ interface MetricsViewTile {
1468
+ id: string;
1469
+ label: string;
1470
+ title?: string;
1471
+ value: string;
1472
+ level: string;
1473
+ hasLevel: boolean;
1474
+ tone: 'error' | 'warning';
1475
+ hasDelta: boolean;
1476
+ delta: number;
1477
+ deltaTone: 'good' | 'bad' | 'neutral';
1478
+ partial: boolean;
1479
+ hasSpark: boolean;
1480
+ spark: number[];
1481
+ sparkLabel: string;
1482
+ }
1483
+ interface MetricsViewModel {
1484
+ hasRanges: boolean;
1485
+ ranges: {
1486
+ value: string;
1487
+ label: string;
1488
+ }[];
1489
+ rangeId: string;
1490
+ partial: boolean;
1491
+ partialHint: string;
1492
+ errorMessage?: string;
1493
+ errored: boolean;
1494
+ empty: boolean;
1495
+ emptyHint: string;
1496
+ ready: boolean;
1497
+ layout: 'grid' | 'row' | 'list';
1498
+ columns: number;
1499
+ tiles: MetricsViewTile[];
1500
+ countLabel: string;
1501
+ partialLabel?: string;
1502
+ }
1503
+ type MetricsUiIntent = {
1504
+ kind: 'range';
1505
+ rangeId: string;
1506
+ } | {
1507
+ kind: 'activate';
1508
+ metricId: string;
1509
+ };
1510
+ interface MetricsViewOptions {
1416
1511
  layout?: 'grid' | 'row' | 'list';
1417
1512
  columns?: number;
1418
1513
  sparkline?: boolean;
@@ -1420,8 +1515,33 @@ interface MetricsPanelViewProps {
1420
1515
  emptyHint?: string;
1421
1516
  partialHint?: string;
1422
1517
  }
1518
+ /** Derive the view from the live controller — the runtime calls this on every draw. */
1519
+ declare function metricsViewState(c: MetricsController, options?: MetricsViewOptions): MetricsViewModel;
1520
+ declare function applyMetricsUi(c: MetricsController, intent: MetricsUiIntent): void;
1521
+ declare const metricsViewManifest: PanelManifest;
1522
+ declare const BEHAVIOURS$1: {
1523
+ 'data/metricsPanelView': (inputs: Record<string, unknown>) => MetricsViewModel;
1524
+ };
1525
+ declare const METRICS_RUNTIME_UNITS: readonly [];
1526
+ declare const METRICS_DECLARATION: BlockDeclaration;
1527
+
1528
+ /**
1529
+ * The Metrics panel view — the CODE VIEW of `METRICS_DECLARATION`, drawing the same
1530
+ * `MetricsViewModel` byte-identical (the equivalence suite holds the two together). No runtime-bound
1531
+ * unit; every intent goes through `applyMetricsUi`.
1532
+ *
1533
+ * Composes `@xenosystem/components/chrome`; the host must ensure `@xenosystem/workbench/primitives.css` is
1534
+ * present.
1535
+ *
1536
+ * @module
1537
+ */
1538
+
1539
+ /** Props for {@link MetricsPanelView}. */
1540
+ interface MetricsPanelViewProps extends MetricsViewOptions {
1541
+ controller: MetricsController;
1542
+ }
1423
1543
  /** The Metrics panel view. */
1424
- declare function MetricsPanelView({ controller, layout, columns, sparkline, showDelta, emptyHint, partialHint, }: MetricsPanelViewProps): ReactNode;
1544
+ declare function MetricsPanelView({ controller, ...options }: MetricsPanelViewProps): ReactNode;
1425
1545
 
1426
1546
  /**
1427
1547
  * The `xeno.core.fields` contract — the query AUTHOR.
@@ -3381,4 +3501,4 @@ interface ConnectorsPanelViewProps {
3381
3501
  /** The Connectors panel view. */
3382
3502
  declare function ConnectorsPanelView({ controller, emptyHint, }: ConnectorsPanelViewProps): ReactNode;
3383
3503
 
3384
- export { AGENT_HIDDEN_COMMANDS, ALL_AGGREGATES, ALL_CHART_TYPES, type BuildChartOptions, type BuildSelectOptions, type BuildSelectResult, CHART_EMPTY_FILL, CHART_PALETTE_STEPS, CHART_PANEL_ID, CONNECTORS_PANEL_ID, type CellAddress, ChartController, type ChartControllerOptions, type ChartHostBridge, type ChartPanelState, ChartPanelView, type ChartPanelViewProps, type ChartRenderContext, type ChartRenderInput, type ChartRenderOptions, type ChartRenderer, type ChartViewState, type ColumnLayout, type ColumnWindow, ConnectorExecuteInput, ConnectorExecuteOutput, ConnectorTransport, ConnectorsController, type ConnectorsControllerOptions, type ConnectorsHostBridge, ConnectorsPanelState, ConnectorsPanelView, type ConnectorsPanelViewProps, type ConnectorsRenderContext, ConnectorsViewState, type CreateChartPanelOptions, type CreateConnectorExecutorOptions, type CreateConnectorsPanelOptions, type CreateFieldsPanelOptions, type CreateMetricsPanelOptions, type CreateTablePanelOptions, type CredentialDraft, type CredentialErrors, DEFAULT_COLUMN_WIDTH, DEFAULT_PAGE_SIZE, DEFAULT_RANGES, type DeltaDirection, EMPTY_LAYOUT, EXPIRY_SKEW_MS, type ExecuteError, BEHAVIOURS as FIELDS_BEHAVIOURS, FIELDS_DECLARATION, FIELDS_PANEL_ID, FIELDS_RUNTIME_UNITS, FieldsController, type FieldsControllerOptions, type FieldsHostBridge, type FieldsPanelState, FieldsPanelView, type FieldsPanelViewProps, type FieldsRenderContext, type FieldsUiIntent, type FieldsViewColumn, type FieldsViewEntry, type FieldsViewFilter, type FieldsViewModel, type FieldsViewOptions, type FieldsViewShelf, type FieldsViewState, type HostSqlResolver, IMPLEMENTED_VIEW_MODES, type LoopbackRequest, type LoopbackVerdict, METRICS_PANEL_ID, MIN_COLUMN_WIDTH, MONO_FILL_PALETTE, MONO_PALETTE, MetricsController, type MetricsControllerOptions, type MetricsHostBridge, type MetricsPanelState, MetricsPanelView, type MetricsPanelViewProps, type MetricsRenderContext, type MetricsViewState, type NumericConstraints, type OAuthStateRecord, type OAuthStateStore, type OAuthStateStoreOptions, type ParseCsvOptions, type ParseResult, type ParsedCsv, type PkcePair, PushdownSplit, type RefreshCoordinator, type ResolvedColumn, SHELF_PRESETS, type StoredOAuthToken, SubstrateFacilities, TABLE_PANEL_ID, TableController, type TableControllerOptions, type TableHostBridge, type TablePanelState, TablePanelView, type TablePanelViewProps, type TableRenderContext, type TableViewMode, type TableViewState, type TokenEndpointResponse, type VirtualWindow, type XenoAlertLevel, type XenoCalculatedFieldRequest, type XenoCellEdit, type XenoChartData, type XenoChartDrillDown, type XenoChartEncoding, type XenoChartExportRequest, type XenoChartHighlight, type XenoChartSeries, type XenoChartType, XenoConnection, XenoConnectionStatus, XenoConnectorFetch, XenoConnectorFileReader, XenoConnectorQueryRequest, XenoCredentialField, XenoCredentialFormSpec, XenoCredentialResult, type XenoFieldStat, type XenoMetricAlert, type XenoMetricRange, type XenoMetricSpec, type XenoMetricThreshold, type XenoMetricValue, XenoResolverErrorCode, XenoRestPaging, type XenoRowActivate, type XenoShelfEntry, type XenoShelfSpec, XenoSqlEngine, applyFieldsUi, applyPrecision, base64Url, breachSideOf, buildChartData, buildRestUrl, buildSelect, chartManifest, chartPanel, clamp, classifyHttpStatus, coerceCsvValues, columnIndexAt, columnsOf, compileForHost, computeColumnWindow, computeWindow, connectorsManifest, connectorsPanel, constraintsOfColumn, createChartPanel, createConnectorExecutor, createConnectorsPanel, createFieldsPanel, createMetricsPanel, createOAuthStateStore, createPkcePair, createRefreshCoordinator, createTablePanel, credentialSpecProblem, defaultAggregateFor, defaultEncoding, deltaToneOf, emptyFilterGroup, entityOf, escapeLikePattern, executeCsvFile, executeDuckDb, executeGraphql, executeJsonFile, executeRest, executeSql, extractJsonPath, extractRows, fieldsManifest, fieldsPanel, fieldsViewManifest, fieldsViewState, firstGraphqlError, frozenWidth, getChartRenderer, isCategorical, isFieldVisible, isFilled, isLeafFilter, isLoopbackHost, isSafeTableName, mergeRefreshResponse, metricsManifest, metricsPanel, moveCell, moveColumn, needsRefresh, nextPageToken, normalizeLayout, normalizeNumeric, offerableAggregates, offerableFilterOps, parseCsv, parseJsonBody, parseJsonPath, parseLinkHeader, parseNumeric, pruneHiddenValues, queriesEqual, quoteIdent, quoteLiteral, redirectUriFor, renderFilterGroup, resolveChartColors, resolveColumns, rowsForMark, scrollLeftToReveal, scrollTopToReveal, setChartRenderer, setColumnVisible, setColumnWidth, setFrozenCount, sniffDelimiter, suggestedShelfFor, tableManifest, tablePanel, toChartJsConfig, toExecuteError, totalWidth, uniqueTableName, unsupportedMessage, validateCredentialDraft, validateEncoding, validateLoopbackRequest, visibleFields };
3504
+ export { AGENT_HIDDEN_COMMANDS, ALL_AGGREGATES, ALL_CHART_TYPES, type BuildChartOptions, type BuildSelectOptions, type BuildSelectResult, BEHAVIOURS$2 as CHART_BEHAVIOURS, CHART_DECLARATION, CHART_EMPTY_FILL, CHART_PALETTE_STEPS, CHART_PANEL_ID, CHART_RUNTIME_UNITS, CHART_TYPE_LABELS, CONNECTORS_PANEL_ID, type CellAddress, ChartController, type ChartControllerOptions, type ChartHostBridge, type ChartPanelState, ChartPanelView, type ChartPanelViewProps, ChartPlot, type ChartRenderContext, type ChartRenderInput, type ChartRenderOptions, type ChartRenderer, type ChartUiIntent, type ChartViewModel, type ChartViewOptions, type ChartViewState, type ColumnLayout, type ColumnWindow, ConnectorExecuteInput, ConnectorExecuteOutput, ConnectorTransport, ConnectorsController, type ConnectorsControllerOptions, type ConnectorsHostBridge, ConnectorsPanelState, ConnectorsPanelView, type ConnectorsPanelViewProps, type ConnectorsRenderContext, ConnectorsViewState, type CreateChartPanelOptions, type CreateConnectorExecutorOptions, type CreateConnectorsPanelOptions, type CreateFieldsPanelOptions, type CreateMetricsPanelOptions, type CreateTablePanelOptions, type CredentialDraft, type CredentialErrors, DEFAULT_CHART_OPTIONS, DEFAULT_COLUMN_WIDTH, DEFAULT_PAGE_SIZE, DEFAULT_RANGES, type DeltaDirection, EMPTY_LAYOUT, EXPIRY_SKEW_MS, type ExecuteError, BEHAVIOURS as FIELDS_BEHAVIOURS, FIELDS_DECLARATION, FIELDS_PANEL_ID, FIELDS_RUNTIME_UNITS, FieldsController, type FieldsControllerOptions, type FieldsHostBridge, type FieldsPanelState, FieldsPanelView, type FieldsPanelViewProps, type FieldsRenderContext, type FieldsUiIntent, type FieldsViewColumn, type FieldsViewEntry, type FieldsViewFilter, type FieldsViewModel, type FieldsViewOptions, type FieldsViewShelf, type FieldsViewState, type HostSqlResolver, IMPLEMENTED_VIEW_MODES, type LoopbackRequest, type LoopbackVerdict, BEHAVIOURS$1 as METRICS_BEHAVIOURS, METRICS_DECLARATION, METRICS_PANEL_ID, METRICS_RUNTIME_UNITS, MIN_COLUMN_WIDTH, MONO_FILL_PALETTE, MONO_PALETTE, MetricsController, type MetricsControllerOptions, type MetricsHostBridge, type MetricsPanelState, MetricsPanelView, type MetricsPanelViewProps, type MetricsRenderContext, type MetricsUiIntent, type MetricsViewModel, type MetricsViewOptions, type MetricsViewState, type MetricsViewTile, type NumericConstraints, type OAuthStateRecord, type OAuthStateStore, type OAuthStateStoreOptions, type ParseCsvOptions, type ParseResult, type ParsedCsv, type PkcePair, PushdownSplit, type RefreshCoordinator, type ResolvedColumn, SHELF_PRESETS, type StoredOAuthToken, SubstrateFacilities, TABLE_PANEL_ID, TableController, type TableControllerOptions, type TableHostBridge, type TablePanelState, TablePanelView, type TablePanelViewProps, type TableRenderContext, type TableViewMode, type TableViewState, type TokenEndpointResponse, type VirtualWindow, type XenoAlertLevel, type XenoCalculatedFieldRequest, type XenoCellEdit, type XenoChartData, type XenoChartDrillDown, type XenoChartEncoding, type XenoChartExportRequest, type XenoChartHighlight, type XenoChartSeries, type XenoChartType, XenoConnection, XenoConnectionStatus, XenoConnectorFetch, XenoConnectorFileReader, XenoConnectorQueryRequest, XenoCredentialField, XenoCredentialFormSpec, XenoCredentialResult, type XenoFieldStat, type XenoMetricAlert, type XenoMetricRange, type XenoMetricSpec, type XenoMetricThreshold, type XenoMetricValue, XenoResolverErrorCode, XenoRestPaging, type XenoRowActivate, type XenoShelfEntry, type XenoShelfSpec, XenoSqlEngine, applyChartUi, applyFieldsUi, applyMetricsUi, applyPrecision, base64Url, breachSideOf, buildChartData, buildRestUrl, buildSelect, chartManifest, chartPanel, chartRuntimeComponents, chartViewManifest, chartViewState, clamp, classifyHttpStatus, coerceCsvValues, columnIndexAt, columnsOf, compileForHost, computeColumnWindow, computeWindow, connectorsManifest, connectorsPanel, constraintsOfColumn, createChartPanel, createConnectorExecutor, createConnectorsPanel, createFieldsPanel, createMetricsPanel, createOAuthStateStore, createPkcePair, createRefreshCoordinator, createTablePanel, credentialSpecProblem, defaultAggregateFor, defaultEncoding, deltaToneOf, emptyFilterGroup, entityOf, escapeLikePattern, executeCsvFile, executeDuckDb, executeGraphql, executeJsonFile, executeRest, executeSql, extractJsonPath, extractRows, fieldsManifest, fieldsPanel, fieldsViewManifest, fieldsViewState, firstGraphqlError, frozenWidth, getChartRenderer, isCategorical, isFieldVisible, isFilled, isLeafFilter, isLoopbackHost, isSafeTableName, mergeRefreshResponse, metricsManifest, metricsPanel, metricsViewManifest, metricsViewState, moveCell, moveColumn, needsRefresh, nextPageToken, normalizeLayout, normalizeNumeric, offerableAggregates, offerableFilterOps, parseCsv, parseJsonBody, parseJsonPath, parseLinkHeader, parseNumeric, pruneHiddenValues, queriesEqual, quoteIdent, quoteLiteral, redirectUriFor, renderFilterGroup, resolveChartColors, resolveColumns, rowsForMark, scrollLeftToReveal, scrollTopToReveal, setChartRenderer, setColumnVisible, setColumnWidth, setFrozenCount, sniffDelimiter, suggestedShelfFor, tableManifest, tablePanel, toChartJsConfig, toExecuteError, totalWidth, uniqueTableName, unsupportedMessage, validateCredentialDraft, validateEncoding, validateLoopbackRequest, visibleFields };
@@ -11,7 +11,6 @@ import {
11
11
  } from "../chunk-W4PTX2Y4.js";
12
12
  import {
13
13
  ChevronGlyph,
14
- DownloadGlyph,
15
14
  PlugGlyph,
16
15
  RefreshGlyph,
17
16
  SortAscGlyph,
@@ -198,8 +197,8 @@ function moveCell(from, delta, bounds) {
198
197
  }
199
198
 
200
199
  // src/data/table/numeric.ts
201
- function parseNumeric(text2) {
202
- const trimmed = String(text2).trim();
200
+ function parseNumeric(text4) {
201
+ const trimmed = String(text4).trim();
203
202
  if (trimmed.length === 0) return { ok: false, reason: "empty" };
204
203
  const match = /^[+-]?(\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/.exec(trimmed);
205
204
  if (!match) return { ok: false, reason: "nan" };
@@ -553,11 +552,11 @@ var TableController = class {
553
552
  return true;
554
553
  }
555
554
  /** Update the in-flight draft. Emits `editPreview` only when `livePreview` is on. */
556
- updateDraft(text2) {
555
+ updateDraft(text4) {
557
556
  if (!this.editing) return;
558
- this.editing = { ...this.editing, draft: text2 };
557
+ this.editing = { ...this.editing, draft: text4 };
559
558
  if (this.livePreview) {
560
- const parsed = this.parseDraft(this.editing.columnId, text2);
559
+ const parsed = this.parseDraft(this.editing.columnId, text4);
561
560
  if (parsed.ok) {
562
561
  this.host.emit("editPreview", this.buildEdit(this.editing, parsed.value));
563
562
  }
@@ -609,12 +608,12 @@ var TableController = class {
609
608
  };
610
609
  }
611
610
  /** Parse a draft according to the column's declared type. */
612
- parseDraft(columnId, text2) {
611
+ parseDraft(columnId, text4) {
613
612
  const column = this.columnById(columnId);
614
- const trimmed = text2.trim();
613
+ const trimmed = text4.trim();
615
614
  if (column?.type === "number" || column?.type === "integer" || column?.type === "duration") {
616
615
  if (trimmed === "") return { ok: true, value: null };
617
- const parsed = parseNumeric(text2);
616
+ const parsed = parseNumeric(text4);
618
617
  if (!parsed.ok) return { ok: false, reason: parsed.reason };
619
618
  return { ok: true, value: normalizeNumeric(parsed.value, constraintsOfColumn(column)) };
620
619
  }
@@ -631,7 +630,7 @@ var TableController = class {
631
630
  value: trimmed === "" ? [] : trimmed.split(",").map((part) => part.trim()).filter(Boolean)
632
631
  };
633
632
  }
634
- return { ok: true, value: trimmed === "" ? null : text2 };
633
+ return { ok: true, value: trimmed === "" ? null : text4 };
635
634
  }
636
635
  rowById(rowId) {
637
636
  return this.result?.rows.find((r) => r.id === rowId);
@@ -2205,16 +2204,7 @@ var chartManifest = {
2205
2204
 
2206
2205
  // src/data/chart/react/ChartPanelView.tsx
2207
2206
  import { useEffect as useEffect2, useRef as useRef2, useSyncExternalStore as useSyncExternalStore2 } from "react";
2208
- import {
2209
- Badge as Badge2,
2210
- EmptyState as EmptyState2,
2211
- ErrorState as ErrorState2,
2212
- IconButton as IconButton2,
2213
- SegmentedControl as SegmentedControl2,
2214
- StatusBar as StatusBar2,
2215
- Toolbar as Toolbar2,
2216
- ToolbarGroup as ToolbarGroup2
2217
- } from "@xenosystem/components/chrome";
2207
+ import { Badge as Badge2, Column, EmptyState as EmptyState2, ErrorState as ErrorState2, IconButton as IconButton2, PlotSurface, SegmentedControl as SegmentedControl2, StatusBar as StatusBar2, Toolbar as Toolbar2, ToolbarGroup as ToolbarGroup2 } from "@xenosystem/components/chrome";
2218
2208
 
2219
2209
  // src/data/chart/renderer.ts
2220
2210
  var renderer = null;
@@ -2268,19 +2258,102 @@ function shade(index) {
2268
2258
  return MONO_PALETTE[index % MONO_PALETTE.length];
2269
2259
  }
2270
2260
 
2261
+ // src/data/chart/declaration.ts
2262
+ var CHART_TYPE_LABELS = { bar: "Bar", horizontalBar: "H-Bar", line: "Line", area: "Area", scatter: "Scatter", pie: "Pie", doughnut: "Ring" };
2263
+ var DEFAULT_CHART_OPTIONS = { showLegend: true, showGrid: true, showTooltip: true, stacked: false, smoothing: 0 };
2264
+ function chartViewState(c, options = {}) {
2265
+ const s = c.getState();
2266
+ const errored = !!s.errorMessage;
2267
+ const empty = !errored && s.empty;
2268
+ const unplottable = !errored && !empty && !!s.encodingProblem;
2269
+ const noRenderer = !errored && !empty && !unplottable && !getChartRenderer();
2270
+ return {
2271
+ types: s.allowedTypes.map((t) => ({ value: t, label: CHART_TYPE_LABELS[t] ?? t })),
2272
+ type: s.encoding.type,
2273
+ partial: s.partial,
2274
+ aggregating: !!s.data?.tooDense,
2275
+ aggregatingHint: s.data?.tooDense ? `${s.data.tooDense.points} points exceeds the ${s.data.tooDense.limit} plot limit \u2014 an aggregated query was requested.` : void 0,
2276
+ errorMessage: s.errorMessage ?? void 0,
2277
+ errored,
2278
+ empty,
2279
+ emptyHint: options.emptyHint ?? "Wire a result into this chart to plot it.",
2280
+ unplottable,
2281
+ encodingProblem: s.encodingProblem ?? void 0,
2282
+ noRenderer,
2283
+ plot: !errored && !empty && !unplottable && !noRenderer,
2284
+ countLabel: s.data ? `${s.data.series.length} series \xB7 ${s.data.labels.length} points` : void 0,
2285
+ highlightLabel: s.highlightedRowIds.length > 0 ? `${s.highlightedRowIds.length} highlighted` : void 0
2286
+ };
2287
+ }
2288
+ function applyChartUi(c, intent) {
2289
+ switch (intent.kind) {
2290
+ case "type":
2291
+ c.setType(intent.type);
2292
+ break;
2293
+ case "export":
2294
+ c.requestExport("png");
2295
+ break;
2296
+ }
2297
+ }
2298
+ var chartViewManifest = {
2299
+ id: chartManifest.id,
2300
+ version: "1.0.0",
2301
+ title: "Chart",
2302
+ category: "data",
2303
+ layout: { slot: "center" },
2304
+ inputs: [{ id: "view", type: "object", label: "View state", description: "The ChartViewModel the runtime derives from the controller on every draw." }],
2305
+ outputs: [{ id: "ui", type: "object", label: "UI intent", description: "{ kind: type | export, \u2026 } \u2014 applied by the runtime to the controller." }],
2306
+ commands: []
2307
+ };
2308
+ var EMPTY = { types: [], type: "bar", partial: false, aggregating: false, errored: false, empty: true, emptyHint: "Wire a result into this chart to plot it.", unplottable: false, noRenderer: false, plot: false };
2309
+ var BEHAVIOURS = {
2310
+ "data/chartPanelView": (inputs) => inputs.view ?? EMPTY
2311
+ };
2312
+ var CHART_RUNTIME_UNITS = ["chart/Plot"];
2313
+ var text = (b) => ({ component: "text", props: { text: b } });
2314
+ var ui = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
2315
+ var CHART_DECLARATION = {
2316
+ format: "xeno.block@1",
2317
+ manifest: chartViewManifest,
2318
+ behaviour: "data/chartPanelView",
2319
+ composition: {
2320
+ component: "chrome/Column",
2321
+ children: [
2322
+ {
2323
+ component: "chrome/Toolbar",
2324
+ props: {
2325
+ left: { node: { component: "chrome/SegmentedControl", props: { size: { value: "sm" }, label: { value: "Chart type" }, options: { input: "view.types" }, value: { input: "view.type" } }, on: { onChange: ui("type", { type: { arg: "" } }) } } },
2326
+ right: {
2327
+ node: {
2328
+ component: "chrome/ToolbarGroup",
2329
+ props: { end: { value: true } },
2330
+ children: [
2331
+ { component: "chrome/Badge", when: { input: "view.partial" }, props: { tone: { value: "warning" }, title: { value: "Some measures have not synced yet." } }, children: [text({ value: "partial" })] },
2332
+ { component: "chrome/Badge", when: { input: "view.aggregating" }, props: { tone: { value: "warning" }, title: { input: "view.aggregatingHint" } }, children: [text({ value: "aggregating" })] },
2333
+ { component: "chrome/IconButton", props: { icon: { node: { component: "icons/Download" } }, label: { value: "Export chart" } }, on: { onClick: ui("export") } }
2334
+ ]
2335
+ }
2336
+ }
2337
+ }
2338
+ },
2339
+ { component: "chrome/ErrorState", when: { input: "view.errored" }, props: { title: { value: "Could not load data" }, message: { input: "view.errorMessage" } } },
2340
+ { component: "chrome/EmptyState", when: { input: "view.empty" }, props: { title: { value: "No data" }, hint: { input: "view.emptyHint" } } },
2341
+ { component: "chrome/EmptyState", when: { input: "view.unplottable" }, props: { title: { value: "Nothing to plot" }, hint: { input: "view.encodingProblem" } } },
2342
+ { component: "chrome/EmptyState", when: { input: "view.noRenderer" }, props: { title: { value: "No renderer installed" }, hint: { value: "Call setChartRenderer() with a Chart.js (or other) renderer \u2014 the panel ships the model, the host ships the marks." } } },
2343
+ { component: "chart/Plot", when: { input: "view.plot" } },
2344
+ { component: "chrome/StatusBar", props: { left: { input: "view.countLabel" }, right: { input: "view.highlightLabel" } } }
2345
+ ]
2346
+ }
2347
+ };
2348
+
2271
2349
  // src/data/chart/react/ChartPanelView.tsx
2272
2350
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
2273
- var DEFAULT_OPTIONS = {
2274
- showLegend: true,
2275
- showGrid: true,
2276
- showTooltip: true,
2277
- stacked: false,
2278
- smoothing: 0
2279
- };
2280
- function ChartPanelView({ controller, options, emptyHint }) {
2351
+ var DownloadGlyph = ICONS["icons/Download"];
2352
+ function ChartPlot({ controller, options }) {
2281
2353
  const state = useSyncExternalStore2(controller.subscribe, controller.getState, controller.getState);
2282
2354
  const plotRef = useRef2(null);
2283
- const resolved = { ...DEFAULT_OPTIONS, ...options };
2355
+ const resolved = { ...DEFAULT_CHART_OPTIONS, ...options };
2356
+ const resolvedKey = JSON.stringify(resolved);
2284
2357
  useEffect2(() => {
2285
2358
  const element = plotRef.current;
2286
2359
  const renderer2 = getChartRenderer();
@@ -2293,83 +2366,38 @@ function ChartPanelView({ controller, options, emptyHint }) {
2293
2366
  highlightedRowIds: state.highlightedRowIds,
2294
2367
  onMarkActivate: (label, seriesId) => controller.activateMark(label, seriesId)
2295
2368
  });
2296
- }, [state.data, state.highlightedRowIds, controller, JSON.stringify(resolved)]);
2297
- const toolbar = /* @__PURE__ */ jsx2(
2298
- Toolbar2,
2299
- {
2300
- left: /* @__PURE__ */ jsx2(
2301
- SegmentedControl2,
2302
- {
2303
- size: "sm",
2304
- label: "Chart type",
2305
- options: state.allowedTypes.map((t) => ({ value: t, label: LABELS[t] ?? t })),
2306
- value: state.encoding.type,
2307
- onChange: (t) => controller.setType(t)
2308
- }
2309
- ),
2310
- right: /* @__PURE__ */ jsxs2(ToolbarGroup2, { end: true, children: [
2311
- state.partial ? /* @__PURE__ */ jsx2(Badge2, { tone: "warning", title: "Some measures have not synced yet.", children: "partial" }) : null,
2312
- state.data?.tooDense ? /* @__PURE__ */ jsx2(
2313
- Badge2,
2314
- {
2315
- tone: "warning",
2316
- title: `${state.data.tooDense.points} points exceeds the ${state.data.tooDense.limit} plot limit \u2014 an aggregated query was requested.`,
2317
- children: "aggregating"
2318
- }
2319
- ) : null,
2320
- /* @__PURE__ */ jsx2(
2321
- IconButton2,
2322
- {
2323
- icon: /* @__PURE__ */ jsx2(DownloadGlyph, {}),
2324
- label: "Export chart",
2325
- onClick: () => controller.requestExport("png")
2326
- }
2327
- )
2328
- ] })
2329
- }
2330
- );
2331
- const body = (() => {
2332
- if (state.errorMessage) {
2333
- return /* @__PURE__ */ jsx2(ErrorState2, { title: "Could not load data", message: state.errorMessage });
2334
- }
2335
- if (state.empty) {
2336
- return /* @__PURE__ */ jsx2(EmptyState2, { title: "No data", hint: emptyHint ?? "Wire a result into this chart to plot it." });
2337
- }
2338
- if (state.encodingProblem) {
2339
- return /* @__PURE__ */ jsx2(EmptyState2, { title: "Nothing to plot", hint: state.encodingProblem });
2340
- }
2341
- if (!getChartRenderer()) {
2342
- return /* @__PURE__ */ jsx2(
2343
- EmptyState2,
2344
- {
2345
- title: "No renderer installed",
2346
- hint: "Call setChartRenderer() with a Chart.js (or other) renderer \u2014 the panel ships the model, the host ships the marks."
2347
- }
2348
- );
2349
- }
2350
- return /* @__PURE__ */ jsx2("div", { ref: plotRef, style: { flex: "1 1 0", minHeight: 0, position: "relative" } });
2351
- })();
2352
- return /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }, children: [
2353
- toolbar,
2354
- body,
2369
+ }, [state.data, state.highlightedRowIds, controller, resolvedKey]);
2370
+ return /* @__PURE__ */ jsx2(PlotSurface, { ref: plotRef, label: "Chart" });
2371
+ }
2372
+ function chartRuntimeComponents(controller, options) {
2373
+ return {
2374
+ "chart/Plot": () => /* @__PURE__ */ jsx2(ChartPlot, { controller, options })
2375
+ };
2376
+ }
2377
+ function ChartPanelView({ controller, options, emptyHint }) {
2378
+ useSyncExternalStore2(controller.subscribe, controller.getState, controller.getState);
2379
+ const view = chartViewState(controller, { emptyHint });
2380
+ const ui4 = (intent) => applyChartUi(controller, intent);
2381
+ return /* @__PURE__ */ jsxs2(Column, { children: [
2355
2382
  /* @__PURE__ */ jsx2(
2356
- StatusBar2,
2383
+ Toolbar2,
2357
2384
  {
2358
- left: state.data ? `${state.data.series.length} series \xB7 ${state.data.labels.length} points` : void 0,
2359
- right: state.highlightedRowIds.length > 0 ? `${state.highlightedRowIds.length} highlighted` : void 0
2385
+ left: /* @__PURE__ */ jsx2(SegmentedControl2, { size: "sm", label: "Chart type", options: view.types, value: view.type, onChange: (type) => ui4({ kind: "type", type }) }),
2386
+ right: /* @__PURE__ */ jsxs2(ToolbarGroup2, { end: true, children: [
2387
+ view.partial ? /* @__PURE__ */ jsx2(Badge2, { tone: "warning", title: "Some measures have not synced yet.", children: "partial" }) : null,
2388
+ view.aggregating ? /* @__PURE__ */ jsx2(Badge2, { tone: "warning", title: view.aggregatingHint, children: "aggregating" }) : null,
2389
+ /* @__PURE__ */ jsx2(IconButton2, { icon: /* @__PURE__ */ jsx2(DownloadGlyph, {}), label: "Export chart", onClick: () => ui4({ kind: "export" }) })
2390
+ ] })
2360
2391
  }
2361
- )
2392
+ ),
2393
+ view.errored ? /* @__PURE__ */ jsx2(ErrorState2, { title: "Could not load data", message: view.errorMessage }) : null,
2394
+ view.empty ? /* @__PURE__ */ jsx2(EmptyState2, { title: "No data", hint: view.emptyHint }) : null,
2395
+ view.unplottable ? /* @__PURE__ */ jsx2(EmptyState2, { title: "Nothing to plot", hint: view.encodingProblem }) : null,
2396
+ view.noRenderer ? /* @__PURE__ */ jsx2(EmptyState2, { title: "No renderer installed", hint: "Call setChartRenderer() with a Chart.js (or other) renderer \u2014 the panel ships the model, the host ships the marks." }) : null,
2397
+ view.plot ? /* @__PURE__ */ jsx2(ChartPlot, { controller, options }) : null,
2398
+ /* @__PURE__ */ jsx2(StatusBar2, { left: view.countLabel, right: view.highlightLabel })
2362
2399
  ] });
2363
2400
  }
2364
- var LABELS = {
2365
- bar: "Bar",
2366
- horizontalBar: "H-Bar",
2367
- line: "Line",
2368
- area: "Area",
2369
- scatter: "Scatter",
2370
- pie: "Pie",
2371
- doughnut: "Ring"
2372
- };
2373
2401
 
2374
2402
  // src/data/chart/panel.ts
2375
2403
  function createChartPanel(options = {}) {
@@ -2912,138 +2940,185 @@ var metricsManifest = {
2912
2940
  import { useSyncExternalStore as useSyncExternalStore3 } from "react";
2913
2941
  import {
2914
2942
  Badge as Badge3,
2943
+ Column as Column2,
2944
+ Delta,
2915
2945
  EmptyState as EmptyState3,
2916
2946
  ErrorState as ErrorState3,
2917
2947
  ScrollArea as ScrollArea2,
2918
2948
  SegmentedControl as SegmentedControl3,
2919
2949
  Sparkline,
2950
+ Stack,
2920
2951
  StatTile,
2921
2952
  StatusBar as StatusBar3,
2953
+ Text,
2954
+ TileGrid,
2922
2955
  Toolbar as Toolbar3
2923
2956
  } from "@xenosystem/components/chrome";
2924
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
2925
- function MetricsPanelView({
2926
- controller,
2927
- layout = "grid",
2928
- columns = 4,
2929
- sparkline = true,
2930
- showDelta = true,
2931
- emptyHint,
2932
- partialHint = "Some measures have not finished syncing."
2933
- }) {
2934
- const state = useSyncExternalStore3(controller.subscribe, controller.getState, controller.getState);
2935
- const toolbar = state.ranges.length > 0 ? /* @__PURE__ */ jsx3(
2936
- Toolbar3,
2937
- {
2938
- left: /* @__PURE__ */ jsx3(
2939
- SegmentedControl3,
2940
- {
2941
- size: "sm",
2942
- label: "Range",
2943
- options: state.ranges.map((r) => ({ value: r.id, label: r.label })),
2944
- value: state.rangeId ?? state.ranges[0]?.id ?? "",
2945
- onChange: (id) => controller.setRange(id)
2946
- }
2947
- ),
2948
- right: state.partial ? (
2949
- // The honest state: rows are real, measures are still landing.
2950
- /* @__PURE__ */ jsx3(Badge3, { tone: "warning", title: partialHint, children: "partial" })
2951
- ) : void 0
2952
- }
2953
- ) : null;
2954
- if (state.errorMessage) {
2955
- return /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }, children: [
2956
- toolbar,
2957
- /* @__PURE__ */ jsx3(ErrorState3, { title: "Could not load metrics", message: state.errorMessage })
2958
- ] });
2959
- }
2960
- if (state.metrics.length === 0) {
2961
- return /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }, children: [
2962
- toolbar,
2963
- /* @__PURE__ */ jsx3(
2964
- EmptyState3,
2965
- {
2966
- title: "No metrics",
2967
- hint: emptyHint ?? "Configure a metric, or wire a result into this panel."
2968
- }
2969
- )
2970
- ] });
2957
+
2958
+ // src/data/metrics/declaration.ts
2959
+ function metricsViewState(c, options = {}) {
2960
+ const s = c.getState();
2961
+ const sparkline = options.sparkline ?? true;
2962
+ const showDelta = options.showDelta ?? true;
2963
+ const partialHint = options.partialHint ?? "Some measures have not finished syncing.";
2964
+ const errored = !!s.errorMessage;
2965
+ const empty = !errored && s.metrics.length === 0;
2966
+ return {
2967
+ hasRanges: s.ranges.length > 0,
2968
+ ranges: s.ranges.map((r) => ({ value: r.id, label: r.label })),
2969
+ rangeId: s.rangeId ?? s.ranges[0]?.id ?? "",
2970
+ partial: s.partial,
2971
+ partialHint,
2972
+ errorMessage: s.errorMessage ?? void 0,
2973
+ errored,
2974
+ empty,
2975
+ emptyHint: options.emptyHint ?? "Configure a metric, or wire a result into this panel.",
2976
+ ready: !errored && !empty,
2977
+ layout: options.layout ?? "grid",
2978
+ columns: options.columns ?? 4,
2979
+ tiles: s.metrics.map((metric) => {
2980
+ const level = metric.level === "critical" ? "error" : metric.level === "warn" ? "warning" : void 0;
2981
+ const hasDelta = showDelta && metric.delta !== void 0;
2982
+ const hasSpark = sparkline && !!metric.spark && metric.spark.length > 1;
2983
+ return {
2984
+ id: metric.spec.id,
2985
+ label: metric.spec.label,
2986
+ title: metric.spec.description,
2987
+ // A partial result shows an em dash, never a zero — a zero is a lie the user cannot detect.
2988
+ value: metric.partial && metric.value === null ? "\u2014" : metric.display,
2989
+ level: String(metric.level),
2990
+ hasLevel: level !== void 0,
2991
+ tone: level ?? "warning",
2992
+ hasDelta,
2993
+ delta: metric.delta ?? 0,
2994
+ deltaTone: metric.deltaTone,
2995
+ partial: metric.partial === true,
2996
+ hasSpark,
2997
+ spark: hasSpark ? metric.spark : [],
2998
+ sparkLabel: `${metric.spec.label} trend`
2999
+ };
3000
+ }),
3001
+ countLabel: `${s.metrics.length} metric${s.metrics.length === 1 ? "" : "s"}`,
3002
+ partialLabel: s.partial ? partialHint : void 0
3003
+ };
3004
+ }
3005
+ function applyMetricsUi(c, intent) {
3006
+ switch (intent.kind) {
3007
+ case "range":
3008
+ c.setRange(intent.rangeId);
3009
+ break;
3010
+ case "activate":
3011
+ c.activate(intent.metricId);
3012
+ break;
2971
3013
  }
2972
- const gridStyle = layout === "grid" ? { display: "grid", gridTemplateColumns: `repeat(${Math.max(1, columns)}, minmax(0, 1fr))`, gap: 6 } : layout === "row" ? { display: "flex", gap: 6, overflowX: "auto" } : { display: "flex", flexDirection: "column", gap: 6 };
2973
- return /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }, children: [
2974
- toolbar,
2975
- /* @__PURE__ */ jsx3(ScrollArea2, { children: /* @__PURE__ */ jsx3("div", { style: { ...gridStyle, padding: 6 }, children: state.metrics.map((metric) => /* @__PURE__ */ jsx3(
2976
- Tile,
2977
- {
2978
- metric,
2979
- sparkline,
2980
- showDelta,
2981
- partialHint,
2982
- onActivate: () => controller.activate(metric.spec.id)
2983
- },
2984
- metric.spec.id
2985
- )) }) }),
2986
- /* @__PURE__ */ jsx3(
2987
- StatusBar3,
2988
- {
2989
- left: `${state.metrics.length} metric${state.metrics.length === 1 ? "" : "s"}`,
2990
- right: state.partial ? partialHint : void 0
2991
- }
2992
- )
2993
- ] });
2994
3014
  }
2995
- function Tile({
2996
- metric,
2997
- sparkline,
2998
- showDelta,
2999
- partialHint,
3000
- onActivate
3001
- }) {
3002
- const tone = metric.level === "critical" ? "error" : metric.level === "warn" ? "warning" : void 0;
3003
- return /* @__PURE__ */ jsx3(
3004
- "div",
3005
- {
3006
- role: "button",
3007
- tabIndex: 0,
3008
- onClick: onActivate,
3009
- onKeyDown: (e) => {
3010
- if (e.key === "Enter" || e.key === " ") {
3011
- e.preventDefault();
3012
- onActivate();
3015
+ var metricsViewManifest = {
3016
+ id: metricsManifest.id,
3017
+ version: "1.0.0",
3018
+ title: "Metrics",
3019
+ category: "data",
3020
+ layout: { slot: "center" },
3021
+ inputs: [{ id: "view", type: "object", label: "View state", description: "The MetricsViewModel the runtime derives from the controller on every draw." }],
3022
+ outputs: [{ id: "ui", type: "object", label: "UI intent", description: "{ kind: range | activate, \u2026 } \u2014 applied by the runtime to the controller." }],
3023
+ commands: []
3024
+ };
3025
+ var EMPTY2 = { hasRanges: false, ranges: [], rangeId: "", partial: false, partialHint: "", errored: false, empty: true, emptyHint: "Configure a metric, or wire a result into this panel.", ready: false, layout: "grid", columns: 4, tiles: [], countLabel: "0 metrics" };
3026
+ var BEHAVIOURS2 = {
3027
+ "data/metricsPanelView": (inputs) => inputs.view ?? EMPTY2
3028
+ };
3029
+ var METRICS_RUNTIME_UNITS = [];
3030
+ var text2 = (b) => ({ component: "text", props: { text: b } });
3031
+ var ui2 = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
3032
+ var METRICS_DECLARATION = {
3033
+ format: "xeno.block@1",
3034
+ manifest: metricsViewManifest,
3035
+ behaviour: "data/metricsPanelView",
3036
+ composition: {
3037
+ component: "chrome/Column",
3038
+ children: [
3039
+ {
3040
+ component: "chrome/Toolbar",
3041
+ when: { input: "view.hasRanges" },
3042
+ props: {
3043
+ left: { node: { component: "chrome/SegmentedControl", props: { size: { value: "sm" }, label: { value: "Range" }, options: { input: "view.ranges" }, value: { input: "view.rangeId" } }, on: { onChange: ui2("range", { rangeId: { arg: "" } }) } } },
3044
+ right: { node: { component: "chrome/Badge", when: { input: "view.partial" }, props: { tone: { value: "warning" }, title: { input: "view.partialHint" } }, children: [text2({ value: "partial" })] } }
3013
3045
  }
3014
3046
  },
3015
- style: { cursor: "pointer", minWidth: 0 },
3016
- children: /* @__PURE__ */ jsx3(
3017
- StatTile,
3018
- {
3019
- title: metric.spec.description,
3020
- label: metric.spec.label,
3021
- value: metric.partial && metric.value === null ? "\u2014" : metric.display,
3022
- icon: tone ? /* @__PURE__ */ jsx3(Badge3, { tone, children: metric.level }) : void 0,
3023
- sub: /* @__PURE__ */ jsxs3("span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
3024
- showDelta && metric.delta !== void 0 ? /* @__PURE__ */ jsxs3(
3025
- "span",
3047
+ { component: "chrome/ErrorState", when: { input: "view.errored" }, props: { title: { value: "Could not load metrics" }, message: { input: "view.errorMessage" } } },
3048
+ { component: "chrome/EmptyState", when: { input: "view.empty" }, props: { title: { value: "No metrics" }, hint: { input: "view.emptyHint" } } },
3049
+ {
3050
+ component: "chrome/ScrollArea",
3051
+ when: { input: "view.ready" },
3052
+ children: [
3053
+ {
3054
+ component: "chrome/TileGrid",
3055
+ props: { layout: { input: "view.layout" }, columns: { input: "view.columns" } },
3056
+ children: [
3026
3057
  {
3027
- style: {
3028
- // Tone comes from deltaDirection, NEVER from the sign: a rising error rate is
3029
- // not good news.
3030
- color: metric.deltaTone === "good" ? "var(--xeno-success)" : metric.deltaTone === "bad" ? "var(--xeno-danger)" : void 0
3058
+ component: "chrome/StatTile",
3059
+ each: { input: "view.tiles", key: "id" },
3060
+ props: {
3061
+ title: { item: "title" },
3062
+ label: { item: "label" },
3063
+ value: { item: "value" },
3064
+ icon: { node: { component: "chrome/Badge", when: { item: "hasLevel" }, props: { tone: { item: "tone" } }, children: [text2({ item: "level" })] } },
3065
+ sub: {
3066
+ node: {
3067
+ component: "chrome/Stack",
3068
+ props: { direction: { value: "row" }, pad: { value: "none" } },
3069
+ children: [
3070
+ { component: "chrome/Delta", when: { item: "hasDelta" }, props: { value: { item: "delta" }, tone: { item: "deltaTone" } } },
3071
+ { component: "chrome/Text", when: { item: "partial" }, props: { role: { value: "meta" } }, children: [text2({ input: "view.partialHint" })] },
3072
+ { component: "chrome/Sparkline", when: { item: "hasSpark" }, props: { data: { item: "spark" }, label: { item: "sparkLabel" } } }
3073
+ ]
3074
+ }
3075
+ }
3031
3076
  },
3032
- children: [
3033
- metric.delta > 0 ? "\u25B2" : "\u25BC",
3034
- " ",
3035
- Math.abs(metric.delta * 100).toFixed(1),
3036
- "%"
3037
- ]
3077
+ on: { onActivate: ui2("activate", { metricId: { item: "id" } }) }
3038
3078
  }
3039
- ) : null,
3040
- metric.partial ? /* @__PURE__ */ jsx3("span", { style: { opacity: 0.6 }, children: partialHint }) : null,
3041
- sparkline && metric.spark && metric.spark.length > 1 ? /* @__PURE__ */ jsx3(Sparkline, { data: metric.spark, label: `${metric.spec.label} trend` }) : null
3042
- ] })
3043
- }
3044
- )
3045
- }
3046
- );
3079
+ ]
3080
+ }
3081
+ ]
3082
+ },
3083
+ { component: "chrome/StatusBar", when: { input: "view.ready" }, props: { left: { input: "view.countLabel" }, right: { input: "view.partialLabel" } } }
3084
+ ]
3085
+ }
3086
+ };
3087
+
3088
+ // src/data/metrics/react/MetricsPanelView.tsx
3089
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
3090
+ function MetricsPanelView({ controller, ...options }) {
3091
+ useSyncExternalStore3(controller.subscribe, controller.getState, controller.getState);
3092
+ const view = metricsViewState(controller, options);
3093
+ const ui4 = (intent) => applyMetricsUi(controller, intent);
3094
+ return /* @__PURE__ */ jsxs3(Column2, { children: [
3095
+ view.hasRanges ? /* @__PURE__ */ jsx3(
3096
+ Toolbar3,
3097
+ {
3098
+ left: /* @__PURE__ */ jsx3(SegmentedControl3, { size: "sm", label: "Range", options: view.ranges, value: view.rangeId, onChange: (rangeId) => ui4({ kind: "range", rangeId }) }),
3099
+ right: view.partial ? /* @__PURE__ */ jsx3(Badge3, { tone: "warning", title: view.partialHint, children: "partial" }) : null
3100
+ }
3101
+ ) : null,
3102
+ view.errored ? /* @__PURE__ */ jsx3(ErrorState3, { title: "Could not load metrics", message: view.errorMessage }) : null,
3103
+ view.empty ? /* @__PURE__ */ jsx3(EmptyState3, { title: "No metrics", hint: view.emptyHint }) : null,
3104
+ view.ready ? /* @__PURE__ */ jsx3(ScrollArea2, { children: /* @__PURE__ */ jsx3(TileGrid, { layout: view.layout, columns: view.columns, children: view.tiles.map((tile) => /* @__PURE__ */ jsx3(
3105
+ StatTile,
3106
+ {
3107
+ title: tile.title,
3108
+ label: tile.label,
3109
+ value: tile.value,
3110
+ icon: tile.hasLevel ? /* @__PURE__ */ jsx3(Badge3, { tone: tile.tone, children: tile.level }) : null,
3111
+ sub: /* @__PURE__ */ jsxs3(Stack, { direction: "row", pad: "none", children: [
3112
+ tile.hasDelta ? /* @__PURE__ */ jsx3(Delta, { value: tile.delta, tone: tile.deltaTone }) : null,
3113
+ tile.partial ? /* @__PURE__ */ jsx3(Text, { role: "meta", children: view.partialHint }) : null,
3114
+ tile.hasSpark ? /* @__PURE__ */ jsx3(Sparkline, { data: tile.spark, label: tile.sparkLabel }) : null
3115
+ ] }),
3116
+ onActivate: () => ui4({ kind: "activate", metricId: tile.id })
3117
+ },
3118
+ tile.id
3119
+ )) }) }) : null,
3120
+ view.ready ? /* @__PURE__ */ jsx3(StatusBar3, { left: view.countLabel, right: view.partialLabel }) : null
3121
+ ] });
3047
3122
  }
3048
3123
 
3049
3124
  // src/data/metrics/panel.ts
@@ -3240,7 +3315,7 @@ function emptyFilterGroup() {
3240
3315
  function offerableFilterOps(column, sourceOps = []) {
3241
3316
  const common = ["eq", "ne", "isEmpty", "isNotEmpty", "in", "notIn"];
3242
3317
  const ordered = ["gt", "gte", "lt", "lte", "between"];
3243
- const text2 = ["contains", "notContains", "startsWith", "endsWith"];
3318
+ const text4 = ["contains", "notContains", "startsWith", "endsWith"];
3244
3319
  const temporal = ["relative"];
3245
3320
  let ops = [...common];
3246
3321
  switch (column?.type) {
@@ -3258,10 +3333,10 @@ function offerableFilterOps(column, sourceOps = []) {
3258
3333
  case "url":
3259
3334
  case "select":
3260
3335
  case "multiselect":
3261
- ops = [...ops, ...text2];
3336
+ ops = [...ops, ...text4];
3262
3337
  break;
3263
3338
  default:
3264
- ops = [...ops, ...text2, ...ordered];
3339
+ ops = [...ops, ...text4, ...ordered];
3265
3340
  }
3266
3341
  if (sourceOps.length === 0) return ops;
3267
3342
  return ops.filter((op) => sourceOps.includes(op));
@@ -3892,7 +3967,7 @@ var fieldsManifest = {
3892
3967
  import { useSyncExternalStore as useSyncExternalStore4 } from "react";
3893
3968
  import {
3894
3969
  Badge as Badge4,
3895
- Column,
3970
+ Column as Column3,
3896
3971
  EmptyState as EmptyState4,
3897
3972
  IconButton as IconButton3,
3898
3973
  Note,
@@ -4027,13 +4102,13 @@ var fieldsViewManifest = {
4027
4102
  outputs: [{ id: "ui", type: "object", label: "UI intent", description: "{ kind, \u2026 } \u2014 applied by the runtime to the controller." }],
4028
4103
  commands: []
4029
4104
  };
4030
- var EMPTY = { hasColumns: false, noColumns: true, sampled: false, manualApply: false, applyDisabled: true, columns: [], shelves: [], filters: [], filterCount: 0, hasFilters: false, noFilters: true, combinator: "and", countLabel: "0 fields" };
4031
- var BEHAVIOURS = {
4032
- "data/fieldsPanelView": (inputs) => inputs.view ?? EMPTY
4105
+ var EMPTY3 = { hasColumns: false, noColumns: true, sampled: false, manualApply: false, applyDisabled: true, columns: [], shelves: [], filters: [], filterCount: 0, hasFilters: false, noFilters: true, combinator: "and", countLabel: "0 fields" };
4106
+ var BEHAVIOURS3 = {
4107
+ "data/fieldsPanelView": (inputs) => inputs.view ?? EMPTY3
4033
4108
  };
4034
4109
  var FIELDS_RUNTIME_UNITS = [];
4035
- var text = (b) => ({ component: "text", props: { text: b } });
4036
- var ui = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
4110
+ var text3 = (b) => ({ component: "text", props: { text: b } });
4111
+ var ui3 = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
4037
4112
  var removeButton = (label, on) => ({
4038
4113
  component: "chrome/IconButton",
4039
4114
  props: { size: { value: "sm" }, icon: { node: { component: "icons/Close", props: { size: { value: 11 } } } }, label },
@@ -4054,14 +4129,14 @@ var FIELDS_DECLARATION = {
4054
4129
  {
4055
4130
  component: "chrome/Toolbar",
4056
4131
  props: {
4057
- left: { node: { component: "chrome/Badge", when: { input: "view.sampled" }, props: { tone: { value: "warning" }, title: { input: "view.sampleWarning" } }, children: [text({ value: "sampled schema" })] } },
4132
+ left: { node: { component: "chrome/Badge", when: { input: "view.sampled" }, props: { tone: { value: "warning" }, title: { input: "view.sampleWarning" } }, children: [text3({ value: "sampled schema" })] } },
4058
4133
  right: {
4059
4134
  node: {
4060
4135
  component: "chrome/ToolbarGroup",
4061
4136
  props: { end: { value: true } },
4062
4137
  children: [
4063
- { component: "chrome/TextButton", on: { onClick: ui("autoAssign") }, children: [text({ value: "Auto-assign" })] },
4064
- { component: "chrome/TextButton", when: { input: "view.manualApply" }, props: { strong: { value: true }, disabled: { input: "view.applyDisabled" } }, on: { onClick: ui("apply") }, children: [text({ value: "Apply" })] }
4138
+ { component: "chrome/TextButton", on: { onClick: ui3("autoAssign") }, children: [text3({ value: "Auto-assign" })] },
4139
+ { component: "chrome/TextButton", when: { input: "view.manualApply" }, props: { strong: { value: true }, disabled: { input: "view.applyDisabled" } }, on: { onClick: ui3("apply") }, children: [text3({ value: "Apply" })] }
4065
4140
  ]
4066
4141
  }
4067
4142
  }
@@ -4081,8 +4156,8 @@ var FIELDS_DECLARATION = {
4081
4156
  {
4082
4157
  component: "chrome/Row",
4083
4158
  each: { input: "view.columns", key: "id" },
4084
- props: { label: { item: "name" }, meta: { item: "role" }, selected: { item: "focused" }, trailing: { node: { component: "chrome/Badge", when: { item: "stat" }, props: { title: { item: "statTitle" } }, children: [text({ item: "stat" })] } } },
4085
- on: { onClick: ui("focus", { columnId: { item: "id" } }) }
4159
+ props: { label: { item: "name" }, meta: { item: "role" }, selected: { item: "focused" }, trailing: { node: { component: "chrome/Badge", when: { item: "stat" }, props: { title: { item: "statTitle" } }, children: [text3({ item: "stat" })] } } },
4160
+ on: { onClick: ui3("focus", { columnId: { item: "id" } }) }
4086
4161
  }
4087
4162
  ]
4088
4163
  }
@@ -4091,9 +4166,9 @@ var FIELDS_DECLARATION = {
4091
4166
  {
4092
4167
  component: "chrome/Section",
4093
4168
  each: { input: "view.shelves", key: "id" },
4094
- props: { title: { item: "label" }, flush: { value: true }, badge: { node: { component: "chrome/Badge", when: { item: "hasEntries" }, children: [text({ item: "count" })] } } },
4169
+ props: { title: { item: "label" }, flush: { value: true }, badge: { node: { component: "chrome/Badge", when: { item: "hasEntries" }, children: [text3({ item: "count" })] } } },
4095
4170
  children: [
4096
- { component: "chrome/Note", when: { item: "empty" }, children: [text({ item: "hint" })] },
4171
+ { component: "chrome/Note", when: { item: "empty" }, children: [text3({ item: "hint" })] },
4097
4172
  {
4098
4173
  component: "chrome/RowList",
4099
4174
  when: { item: "hasEntries" },
@@ -4108,13 +4183,13 @@ var FIELDS_DECLARATION = {
4108
4183
  node: {
4109
4184
  component: "fragment",
4110
4185
  children: [
4111
- { component: "chrome/Select", when: { item: "aggregated" }, props: { size: { value: "sm" }, label: { value: "Aggregate" }, options: { item: "aggregates" }, value: { item: "fn" } }, on: { onChange: ui("aggregate", { shelfId: { item: "shelfId" }, columnId: { item: "columnId" }, fn: { arg: "" } }) } },
4112
- { component: "chrome/TextButton", when: { item: "sortable" }, on: { onClick: ui("direction", { shelfId: { item: "shelfId" }, columnId: { item: "columnId" } }) }, children: [text({ item: "direction" })] },
4113
- { component: "chrome/Badge", when: { item: "granularity" }, children: [text({ item: "granularity" })] }
4186
+ { component: "chrome/Select", when: { item: "aggregated" }, props: { size: { value: "sm" }, label: { value: "Aggregate" }, options: { item: "aggregates" }, value: { item: "fn" } }, on: { onChange: ui3("aggregate", { shelfId: { item: "shelfId" }, columnId: { item: "columnId" }, fn: { arg: "" } }) } },
4187
+ { component: "chrome/TextButton", when: { item: "sortable" }, on: { onClick: ui3("direction", { shelfId: { item: "shelfId" }, columnId: { item: "columnId" } }) }, children: [text3({ item: "direction" })] },
4188
+ { component: "chrome/Badge", when: { item: "granularity" }, children: [text3({ item: "granularity" })] }
4114
4189
  ]
4115
4190
  }
4116
4191
  },
4117
- trailing: { node: removeButton({ item: "removeLabel" }, ui("remove", { shelfId: { item: "shelfId" }, columnId: { item: "columnId" } })) }
4192
+ trailing: { node: removeButton({ item: "removeLabel" }, ui3("remove", { shelfId: { item: "shelfId" }, columnId: { item: "columnId" } })) }
4118
4193
  }
4119
4194
  }
4120
4195
  ]
@@ -4126,11 +4201,11 @@ var FIELDS_DECLARATION = {
4126
4201
  props: {
4127
4202
  title: { value: "Filters" },
4128
4203
  flush: { value: true },
4129
- badge: { node: { component: "chrome/Badge", when: { input: "view.hasFilters" }, children: [text({ input: "view.filterCount" })] } },
4130
- actions: { node: { component: "chrome/IconButton", props: { size: { value: "sm" }, icon: { node: { component: "icons/Plus", props: { size: { value: 11 } } } }, label: { value: "Add filter" } }, on: { onClick: ui("addFilter") } } }
4204
+ badge: { node: { component: "chrome/Badge", when: { input: "view.hasFilters" }, children: [text3({ input: "view.filterCount" })] } },
4205
+ actions: { node: { component: "chrome/IconButton", props: { size: { value: "sm" }, icon: { node: { component: "icons/Plus", props: { size: { value: 11 } } } }, label: { value: "Add filter" } }, on: { onClick: ui3("addFilter") } } }
4131
4206
  },
4132
4207
  children: [
4133
- { component: "chrome/Note", when: { input: "view.noFilters" }, children: [text({ value: "No filters. Everything passes." })] },
4208
+ { component: "chrome/Note", when: { input: "view.noFilters" }, children: [text3({ value: "No filters. Everything passes." })] },
4134
4209
  {
4135
4210
  component: "chrome/RowList",
4136
4211
  when: { input: "view.hasFilters" },
@@ -4140,8 +4215,8 @@ var FIELDS_DECLARATION = {
4140
4215
  each: { input: "view.filters", key: "key" },
4141
4216
  props: {
4142
4217
  label: { item: "label" },
4143
- meta: { node: { component: "chrome/Select", when: { item: "leaf" }, props: { size: { value: "sm" }, label: { value: "Operator" }, options: { item: "ops" }, value: { item: "op" } }, on: { onChange: ui("filterOp", { index: { item: "index" }, op: { arg: "" } }) } } },
4144
- trailing: { node: removeButton({ item: "removeLabel" }, ui("removeFilter", { index: { item: "index" } })) }
4218
+ meta: { node: { component: "chrome/Select", when: { item: "leaf" }, props: { size: { value: "sm" }, label: { value: "Operator" }, options: { item: "ops" }, value: { item: "op" } }, on: { onChange: ui3("filterOp", { index: { item: "index" }, op: { arg: "" } }) } } },
4219
+ trailing: { node: removeButton({ item: "removeLabel" }, ui3("removeFilter", { index: { item: "index" } })) }
4145
4220
  }
4146
4221
  }
4147
4222
  ]
@@ -4150,8 +4225,8 @@ var FIELDS_DECLARATION = {
4150
4225
  component: "chrome/Toolbar",
4151
4226
  props: {
4152
4227
  divided: { value: false },
4153
- left: { node: { component: "chrome/SegmentedControl", props: { size: { value: "sm" }, label: { value: "Combinator" }, options: { value: COMBINATORS }, value: { input: "view.combinator" } }, on: { onChange: ui("combinator", { combinator: { arg: "" } }) } } },
4154
- right: { node: { component: "chrome/TextButton", on: { onClick: ui("addGroup") }, children: [text({ value: "+ group" })] } }
4228
+ left: { node: { component: "chrome/SegmentedControl", props: { size: { value: "sm" }, label: { value: "Combinator" }, options: { value: COMBINATORS }, value: { input: "view.combinator" } }, on: { onChange: ui3("combinator", { combinator: { arg: "" } }) } } },
4229
+ right: { node: { component: "chrome/TextButton", on: { onClick: ui3("addGroup") }, children: [text3({ value: "+ group" })] } }
4155
4230
  }
4156
4231
  }
4157
4232
  ]
@@ -4173,18 +4248,18 @@ var COMBINATORS2 = [{ value: "and", label: "All" }, { value: "or", label: "Any"
4173
4248
  function FieldsPanelView({ controller, showStats, manualApply }) {
4174
4249
  useSyncExternalStore4(controller.subscribe, controller.getState, controller.getState);
4175
4250
  const view = fieldsViewState(controller, { showStats, manualApply });
4176
- const ui2 = (intent) => applyFieldsUi(controller, intent);
4251
+ const ui4 = (intent) => applyFieldsUi(controller, intent);
4177
4252
  if (view.noColumns) {
4178
4253
  return /* @__PURE__ */ jsx4(EmptyState4, { title: "No fields", hint: "Connect a source \u2014 its columns appear here, ready to drop onto a shelf." });
4179
4254
  }
4180
- return /* @__PURE__ */ jsxs4(Column, { children: [
4255
+ return /* @__PURE__ */ jsxs4(Column3, { children: [
4181
4256
  /* @__PURE__ */ jsx4(
4182
4257
  Toolbar4,
4183
4258
  {
4184
4259
  left: view.sampled ? /* @__PURE__ */ jsx4(Badge4, { tone: "warning", title: view.sampleWarning, children: "sampled schema" }) : null,
4185
4260
  right: /* @__PURE__ */ jsxs4(ToolbarGroup3, { end: true, children: [
4186
- /* @__PURE__ */ jsx4(TextButton, { onClick: () => ui2({ kind: "autoAssign" }), children: "Auto-assign" }),
4187
- view.manualApply ? /* @__PURE__ */ jsx4(TextButton, { strong: true, disabled: view.applyDisabled, onClick: () => ui2({ kind: "apply" }), children: "Apply" }) : null
4261
+ /* @__PURE__ */ jsx4(TextButton, { onClick: () => ui4({ kind: "autoAssign" }), children: "Auto-assign" }),
4262
+ view.manualApply ? /* @__PURE__ */ jsx4(TextButton, { strong: true, disabled: view.applyDisabled, onClick: () => ui4({ kind: "apply" }), children: "Apply" }) : null
4188
4263
  ] })
4189
4264
  }
4190
4265
  ),
@@ -4196,7 +4271,7 @@ function FieldsPanelView({ controller, showStats, manualApply }) {
4196
4271
  meta: column.role,
4197
4272
  selected: column.focused,
4198
4273
  trailing: column.stat ? /* @__PURE__ */ jsx4(Badge4, { title: column.statTitle, children: column.stat }) : null,
4199
- onClick: () => ui2({ kind: "focus", columnId: column.id })
4274
+ onClick: () => ui4({ kind: "focus", columnId: column.id })
4200
4275
  },
4201
4276
  column.id
4202
4277
  )) }) }),
@@ -4208,11 +4283,11 @@ function FieldsPanelView({ controller, showStats, manualApply }) {
4208
4283
  label: entry.label,
4209
4284
  dimmed: entry.suggested,
4210
4285
  meta: /* @__PURE__ */ jsxs4(Fragment2, { children: [
4211
- entry.aggregated ? /* @__PURE__ */ jsx4(Select, { size: "sm", label: "Aggregate", options: entry.aggregates, value: entry.fn, onChange: (fn) => ui2({ kind: "aggregate", shelfId: entry.shelfId, columnId: entry.columnId, fn }) }) : null,
4212
- entry.sortable ? /* @__PURE__ */ jsx4(TextButton, { onClick: () => ui2({ kind: "direction", shelfId: entry.shelfId, columnId: entry.columnId }), children: entry.direction }) : null,
4286
+ entry.aggregated ? /* @__PURE__ */ jsx4(Select, { size: "sm", label: "Aggregate", options: entry.aggregates, value: entry.fn, onChange: (fn) => ui4({ kind: "aggregate", shelfId: entry.shelfId, columnId: entry.columnId, fn }) }) : null,
4287
+ entry.sortable ? /* @__PURE__ */ jsx4(TextButton, { onClick: () => ui4({ kind: "direction", shelfId: entry.shelfId, columnId: entry.columnId }), children: entry.direction }) : null,
4213
4288
  entry.granularity ? /* @__PURE__ */ jsx4(Badge4, { children: entry.granularity }) : null
4214
4289
  ] }),
4215
- trailing: /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(CloseGlyph, { size: 11 }), label: entry.removeLabel, onClick: () => ui2({ kind: "remove", shelfId: entry.shelfId, columnId: entry.columnId }) })
4290
+ trailing: /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(CloseGlyph, { size: 11 }), label: entry.removeLabel, onClick: () => ui4({ kind: "remove", shelfId: entry.shelfId, columnId: entry.columnId }) })
4216
4291
  },
4217
4292
  entry.columnId
4218
4293
  )) }) : null
@@ -4223,15 +4298,15 @@ function FieldsPanelView({ controller, showStats, manualApply }) {
4223
4298
  title: "Filters",
4224
4299
  flush: true,
4225
4300
  badge: view.hasFilters ? /* @__PURE__ */ jsx4(Badge4, { children: view.filterCount }) : null,
4226
- actions: /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(PlusGlyph, { size: 11 }), label: "Add filter", onClick: () => ui2({ kind: "addFilter" }) }),
4301
+ actions: /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(PlusGlyph, { size: 11 }), label: "Add filter", onClick: () => ui4({ kind: "addFilter" }) }),
4227
4302
  children: [
4228
4303
  view.noFilters ? /* @__PURE__ */ jsx4(Note, { children: "No filters. Everything passes." }) : null,
4229
4304
  view.hasFilters ? /* @__PURE__ */ jsx4(RowList, { children: view.filters.map((filter) => /* @__PURE__ */ jsx4(
4230
4305
  Row,
4231
4306
  {
4232
4307
  label: filter.label,
4233
- meta: filter.leaf ? /* @__PURE__ */ jsx4(Select, { size: "sm", label: "Operator", options: filter.ops, value: filter.op, onChange: (op) => ui2({ kind: "filterOp", index: filter.index, op }) }) : null,
4234
- trailing: /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(CloseGlyph, { size: 11 }), label: filter.removeLabel, onClick: () => ui2({ kind: "removeFilter", index: filter.index }) })
4308
+ meta: filter.leaf ? /* @__PURE__ */ jsx4(Select, { size: "sm", label: "Operator", options: filter.ops, value: filter.op, onChange: (op) => ui4({ kind: "filterOp", index: filter.index, op }) }) : null,
4309
+ trailing: /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(CloseGlyph, { size: 11 }), label: filter.removeLabel, onClick: () => ui4({ kind: "removeFilter", index: filter.index }) })
4235
4310
  },
4236
4311
  filter.key
4237
4312
  )) }) : null,
@@ -4239,8 +4314,8 @@ function FieldsPanelView({ controller, showStats, manualApply }) {
4239
4314
  Toolbar4,
4240
4315
  {
4241
4316
  divided: false,
4242
- left: /* @__PURE__ */ jsx4(SegmentedControl4, { size: "sm", label: "Combinator", options: COMBINATORS2, value: view.combinator, onChange: (combinator) => ui2({ kind: "combinator", combinator }) }),
4243
- right: /* @__PURE__ */ jsx4(TextButton, { onClick: () => ui2({ kind: "addGroup" }), children: "+ group" })
4317
+ left: /* @__PURE__ */ jsx4(SegmentedControl4, { size: "sm", label: "Combinator", options: COMBINATORS2, value: view.combinator, onChange: (combinator) => ui4({ kind: "combinator", combinator }) }),
4318
+ right: /* @__PURE__ */ jsx4(TextButton, { onClick: () => ui4({ kind: "addGroup" }), children: "+ group" })
4244
4319
  }
4245
4320
  )
4246
4321
  ]
@@ -5376,10 +5451,10 @@ function parseJsonPath(path) {
5376
5451
  }
5377
5452
  return steps;
5378
5453
  }
5379
- function findBracketEnd(text2, start) {
5454
+ function findBracketEnd(text4, start) {
5380
5455
  let quote = null;
5381
- for (let i = start + 1; i < text2.length; i += 1) {
5382
- const char = text2[i];
5456
+ for (let i = start + 1; i < text4.length; i += 1) {
5457
+ const char = text4[i];
5383
5458
  if (quote) {
5384
5459
  if (char === "\\") i += 1;
5385
5460
  else if (char === quote) quote = null;
@@ -5803,9 +5878,9 @@ function firstGraphqlError(errors) {
5803
5878
  }
5804
5879
 
5805
5880
  // src/data/connectors/executors/csv.ts
5806
- function sniffDelimiter(text2) {
5881
+ function sniffDelimiter(text4) {
5807
5882
  const candidates = [",", ";", " ", "|"];
5808
- const line = firstLogicalLine(text2);
5883
+ const line = firstLogicalLine(text4);
5809
5884
  let best = ",";
5810
5885
  let bestCount = 0;
5811
5886
  for (const candidate of candidates) {
@@ -5827,21 +5902,21 @@ function sniffDelimiter(text2) {
5827
5902
  }
5828
5903
  return best;
5829
5904
  }
5830
- function firstLogicalLine(text2) {
5905
+ function firstLogicalLine(text4) {
5831
5906
  let inQuotes = false;
5832
- for (let i = 0; i < text2.length; i += 1) {
5833
- const char = text2[i];
5907
+ for (let i = 0; i < text4.length; i += 1) {
5908
+ const char = text4[i];
5834
5909
  if (char === '"') {
5835
- if (inQuotes && text2[i + 1] === '"') i += 1;
5910
+ if (inQuotes && text4[i + 1] === '"') i += 1;
5836
5911
  else inQuotes = !inQuotes;
5837
5912
  } else if ((char === "\n" || char === "\r") && !inQuotes) {
5838
- return text2.slice(0, i);
5913
+ return text4.slice(0, i);
5839
5914
  }
5840
5915
  }
5841
- return text2;
5916
+ return text4;
5842
5917
  }
5843
- function parseCsv(text2, options = {}) {
5844
- const source = text2.charCodeAt(0) === 65279 ? text2.slice(1) : text2;
5918
+ function parseCsv(text4, options = {}) {
5919
+ const source = text4.charCodeAt(0) === 65279 ? text4.slice(1) : text4;
5845
5920
  const delimiter = options.delimiter && options.delimiter.length > 0 ? options.delimiter : sniffDelimiter(source);
5846
5921
  const quote = options.quote && options.quote.length > 0 ? options.quote : '"';
5847
5922
  const hasHeader = options.hasHeader ?? true;
@@ -5876,17 +5951,17 @@ function dedupe(names) {
5876
5951
  return count === 0 ? name : `${name}_${count + 1}`;
5877
5952
  });
5878
5953
  }
5879
- function scanRecords(text2, delimiter, quote) {
5954
+ function scanRecords(text4, delimiter, quote) {
5880
5955
  const records = [];
5881
5956
  let fields = [];
5882
5957
  let field = "";
5883
5958
  let inQuotes = false;
5884
5959
  let i = 0;
5885
- while (i < text2.length) {
5886
- const char = text2[i];
5960
+ while (i < text4.length) {
5961
+ const char = text4[i];
5887
5962
  if (inQuotes) {
5888
5963
  if (char === quote) {
5889
- if (text2[i + 1] === quote) {
5964
+ if (text4[i + 1] === quote) {
5890
5965
  field += quote;
5891
5966
  i += 2;
5892
5967
  continue;
@@ -5904,7 +5979,7 @@ function scanRecords(text2, delimiter, quote) {
5904
5979
  i += 1;
5905
5980
  continue;
5906
5981
  }
5907
- if (text2.startsWith(delimiter, i)) {
5982
+ if (text4.startsWith(delimiter, i)) {
5908
5983
  fields.push(field);
5909
5984
  field = "";
5910
5985
  i += delimiter.length;
@@ -5915,7 +5990,7 @@ function scanRecords(text2, delimiter, quote) {
5915
5990
  records.push(fields);
5916
5991
  fields = [];
5917
5992
  field = "";
5918
- i += char === "\r" && text2[i + 1] === "\n" ? 2 : 1;
5993
+ i += char === "\r" && text4[i + 1] === "\n" ? 2 : 1;
5919
5994
  continue;
5920
5995
  }
5921
5996
  field += char;
@@ -7052,28 +7127,37 @@ export {
7052
7127
  AGENT_HIDDEN_COMMANDS,
7053
7128
  ALL_AGGREGATES,
7054
7129
  ALL_CHART_TYPES,
7130
+ BEHAVIOURS as CHART_BEHAVIOURS,
7131
+ CHART_DECLARATION,
7055
7132
  CHART_EMPTY_FILL,
7056
7133
  CHART_PALETTE_STEPS,
7057
7134
  CHART_PANEL_ID,
7135
+ CHART_RUNTIME_UNITS,
7136
+ CHART_TYPE_LABELS,
7058
7137
  CONNECTORS_PANEL_ID,
7059
7138
  ChartController,
7060
7139
  ChartPanelView,
7140
+ ChartPlot,
7061
7141
  ConnectorTransportError,
7062
7142
  ConnectorsController,
7063
7143
  ConnectorsPanelView,
7144
+ DEFAULT_CHART_OPTIONS,
7064
7145
  DEFAULT_COLUMN_WIDTH,
7065
7146
  DEFAULT_PAGE_SIZE,
7066
7147
  DEFAULT_RANGES,
7067
7148
  EMPTY_LAYOUT,
7068
7149
  EXPIRY_SKEW_MS,
7069
- BEHAVIOURS as FIELDS_BEHAVIOURS,
7150
+ BEHAVIOURS3 as FIELDS_BEHAVIOURS,
7070
7151
  FIELDS_DECLARATION,
7071
7152
  FIELDS_PANEL_ID,
7072
7153
  FIELDS_RUNTIME_UNITS,
7073
7154
  FieldsController,
7074
7155
  FieldsPanelView,
7075
7156
  IMPLEMENTED_VIEW_MODES,
7157
+ BEHAVIOURS2 as METRICS_BEHAVIOURS,
7158
+ METRICS_DECLARATION,
7076
7159
  METRICS_PANEL_ID,
7160
+ METRICS_RUNTIME_UNITS,
7077
7161
  MIN_COLUMN_WIDTH,
7078
7162
  MONO_FILL_PALETTE,
7079
7163
  MONO_PALETTE,
@@ -7084,7 +7168,9 @@ export {
7084
7168
  TABLE_PANEL_ID,
7085
7169
  TableController,
7086
7170
  TablePanelView,
7171
+ applyChartUi,
7087
7172
  applyFieldsUi,
7173
+ applyMetricsUi,
7088
7174
  applyPrecision,
7089
7175
  base64Url,
7090
7176
  breachSideOf,
@@ -7094,6 +7180,9 @@ export {
7094
7180
  buildSelect,
7095
7181
  chartManifest,
7096
7182
  chartPanel,
7183
+ chartRuntimeComponents,
7184
+ chartViewManifest,
7185
+ chartViewState,
7097
7186
  clamp,
7098
7187
  classifyBrowserFetchFailure,
7099
7188
  classifyHttpStatus,
@@ -7150,6 +7239,8 @@ export {
7150
7239
  mergeRefreshResponse,
7151
7240
  metricsManifest,
7152
7241
  metricsPanel,
7242
+ metricsViewManifest,
7243
+ metricsViewState,
7153
7244
  moveCell,
7154
7245
  moveColumn,
7155
7246
  needsRefresh,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenosystem/blocks",
3
- "version": "0.13.9",
3
+ "version": "0.13.11",
4
4
  "description": "XENO Blocks — rung 3 of the front ladder: components composed into a contract-bearing unit (manifest: ports, intents, commands, agent surface), rendered by @xenosystem/workbench. ONE package, one subpath per family (/auth, /edit, /data, /time, /ops, /trust, /agent), one named export per block — XENO PACKAGE NAMING - STANDARD.md.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -64,7 +64,7 @@
64
64
  "peerDependencies": {
65
65
  "@duckdb/duckdb-wasm": ">=1.28.0",
66
66
  "@xenosystem/block-sdk": ">=0.3.4 <1",
67
- "@xenosystem/components": ">=0.20.0 <1",
67
+ "@xenosystem/components": ">=0.21.1 <1",
68
68
  "@xenosystem/data-core": "^0.1.0",
69
69
  "@xenosystem/elements": ">=0.0.1",
70
70
  "@xenosystem/elements-react": ">=0.0.1",
@@ -81,7 +81,7 @@
81
81
  "@types/react": "^19.0.0",
82
82
  "@types/react-dom": "^19.0.0",
83
83
  "@xenosystem/block-sdk": "^0.3.4",
84
- "@xenosystem/components": "^0.20.0",
84
+ "@xenosystem/components": "^0.21.1",
85
85
  "@xenosystem/data-core": "^0.1.0",
86
86
  "@xenosystem/elements": "0.0.1",
87
87
  "@xenosystem/elements-react": "0.0.1",