@xenosystem/blocks 0.13.9 → 0.13.10

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.
@@ -1401,18 +1401,59 @@ declare const METRICS_PANEL_ID = "xeno.core.metrics";
1401
1401
  declare const metricsManifest: PanelManifest;
1402
1402
 
1403
1403
  /**
1404
- * The Metrics panel view — tiles, deltas, sparklines, threshold tone.
1404
+ * The Metrics panel AS A DECLARATION — the twelfth declared block: KPI tiles over a data-core result.
1405
1405
  *
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
1406
+ * Everything drawn is data: the range control and partial badge in the toolbar (only when ranges
1407
+ * exist), the error and empty states, a `TileGrid` of `StatTile`s — each with its level badge, its
1408
+ * display value (an em dash for a partial null, never a zero), a `Delta` toned by the metric's
1409
+ * direction, the partial note, the `Sparkline` — and the status bar. A tile activates as a real
1410
+ * button (`StatTile.onActivate`). No runtime-bound unit.
1411
1411
  */
1412
1412
 
1413
- /** Props for {@link MetricsPanelView}. */
1414
- interface MetricsPanelViewProps {
1415
- controller: MetricsController;
1413
+ interface MetricsViewTile {
1414
+ id: string;
1415
+ label: string;
1416
+ title?: string;
1417
+ value: string;
1418
+ level: string;
1419
+ hasLevel: boolean;
1420
+ tone: 'error' | 'warning';
1421
+ hasDelta: boolean;
1422
+ delta: number;
1423
+ deltaTone: 'good' | 'bad' | 'neutral';
1424
+ partial: boolean;
1425
+ hasSpark: boolean;
1426
+ spark: number[];
1427
+ sparkLabel: string;
1428
+ }
1429
+ interface MetricsViewModel {
1430
+ hasRanges: boolean;
1431
+ ranges: {
1432
+ value: string;
1433
+ label: string;
1434
+ }[];
1435
+ rangeId: string;
1436
+ partial: boolean;
1437
+ partialHint: string;
1438
+ errorMessage?: string;
1439
+ errored: boolean;
1440
+ empty: boolean;
1441
+ emptyHint: string;
1442
+ ready: boolean;
1443
+ layout: 'grid' | 'row' | 'list';
1444
+ columns: number;
1445
+ tiles: MetricsViewTile[];
1446
+ countLabel: string;
1447
+ partialLabel?: string;
1448
+ }
1449
+ type MetricsUiIntent = {
1450
+ kind: 'range';
1451
+ rangeId: string;
1452
+ } | {
1453
+ kind: 'activate';
1454
+ metricId: string;
1455
+ };
1456
+ interface MetricsViewOptions {
1416
1457
  layout?: 'grid' | 'row' | 'list';
1417
1458
  columns?: number;
1418
1459
  sparkline?: boolean;
@@ -1420,8 +1461,33 @@ interface MetricsPanelViewProps {
1420
1461
  emptyHint?: string;
1421
1462
  partialHint?: string;
1422
1463
  }
1464
+ /** Derive the view from the live controller — the runtime calls this on every draw. */
1465
+ declare function metricsViewState(c: MetricsController, options?: MetricsViewOptions): MetricsViewModel;
1466
+ declare function applyMetricsUi(c: MetricsController, intent: MetricsUiIntent): void;
1467
+ declare const metricsViewManifest: PanelManifest;
1468
+ declare const BEHAVIOURS$1: {
1469
+ 'data/metricsPanelView': (inputs: Record<string, unknown>) => MetricsViewModel;
1470
+ };
1471
+ declare const METRICS_RUNTIME_UNITS: readonly [];
1472
+ declare const METRICS_DECLARATION: BlockDeclaration;
1473
+
1474
+ /**
1475
+ * The Metrics panel view — the CODE VIEW of `METRICS_DECLARATION`, drawing the same
1476
+ * `MetricsViewModel` byte-identical (the equivalence suite holds the two together). No runtime-bound
1477
+ * unit; every intent goes through `applyMetricsUi`.
1478
+ *
1479
+ * Composes `@xenosystem/components/chrome`; the host must ensure `@xenosystem/workbench/primitives.css` is
1480
+ * present.
1481
+ *
1482
+ * @module
1483
+ */
1484
+
1485
+ /** Props for {@link MetricsPanelView}. */
1486
+ interface MetricsPanelViewProps extends MetricsViewOptions {
1487
+ controller: MetricsController;
1488
+ }
1423
1489
  /** The Metrics panel view. */
1424
- declare function MetricsPanelView({ controller, layout, columns, sparkline, showDelta, emptyHint, partialHint, }: MetricsPanelViewProps): ReactNode;
1490
+ declare function MetricsPanelView({ controller, ...options }: MetricsPanelViewProps): ReactNode;
1425
1491
 
1426
1492
  /**
1427
1493
  * The `xeno.core.fields` contract — the query AUTHOR.
@@ -3381,4 +3447,4 @@ interface ConnectorsPanelViewProps {
3381
3447
  /** The Connectors panel view. */
3382
3448
  declare function ConnectorsPanelView({ controller, emptyHint, }: ConnectorsPanelViewProps): ReactNode;
3383
3449
 
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 };
3450
+ 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, 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, applyFieldsUi, applyMetricsUi, 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, 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 };
@@ -198,8 +198,8 @@ function moveCell(from, delta, bounds) {
198
198
  }
199
199
 
200
200
  // src/data/table/numeric.ts
201
- function parseNumeric(text2) {
202
- const trimmed = String(text2).trim();
201
+ function parseNumeric(text3) {
202
+ const trimmed = String(text3).trim();
203
203
  if (trimmed.length === 0) return { ok: false, reason: "empty" };
204
204
  const match = /^[+-]?(\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/.exec(trimmed);
205
205
  if (!match) return { ok: false, reason: "nan" };
@@ -553,11 +553,11 @@ var TableController = class {
553
553
  return true;
554
554
  }
555
555
  /** Update the in-flight draft. Emits `editPreview` only when `livePreview` is on. */
556
- updateDraft(text2) {
556
+ updateDraft(text3) {
557
557
  if (!this.editing) return;
558
- this.editing = { ...this.editing, draft: text2 };
558
+ this.editing = { ...this.editing, draft: text3 };
559
559
  if (this.livePreview) {
560
- const parsed = this.parseDraft(this.editing.columnId, text2);
560
+ const parsed = this.parseDraft(this.editing.columnId, text3);
561
561
  if (parsed.ok) {
562
562
  this.host.emit("editPreview", this.buildEdit(this.editing, parsed.value));
563
563
  }
@@ -609,12 +609,12 @@ var TableController = class {
609
609
  };
610
610
  }
611
611
  /** Parse a draft according to the column's declared type. */
612
- parseDraft(columnId, text2) {
612
+ parseDraft(columnId, text3) {
613
613
  const column = this.columnById(columnId);
614
- const trimmed = text2.trim();
614
+ const trimmed = text3.trim();
615
615
  if (column?.type === "number" || column?.type === "integer" || column?.type === "duration") {
616
616
  if (trimmed === "") return { ok: true, value: null };
617
- const parsed = parseNumeric(text2);
617
+ const parsed = parseNumeric(text3);
618
618
  if (!parsed.ok) return { ok: false, reason: parsed.reason };
619
619
  return { ok: true, value: normalizeNumeric(parsed.value, constraintsOfColumn(column)) };
620
620
  }
@@ -631,7 +631,7 @@ var TableController = class {
631
631
  value: trimmed === "" ? [] : trimmed.split(",").map((part) => part.trim()).filter(Boolean)
632
632
  };
633
633
  }
634
- return { ok: true, value: trimmed === "" ? null : text2 };
634
+ return { ok: true, value: trimmed === "" ? null : text3 };
635
635
  }
636
636
  rowById(rowId) {
637
637
  return this.result?.rows.find((r) => r.id === rowId);
@@ -2912,138 +2912,185 @@ var metricsManifest = {
2912
2912
  import { useSyncExternalStore as useSyncExternalStore3 } from "react";
2913
2913
  import {
2914
2914
  Badge as Badge3,
2915
+ Column,
2916
+ Delta,
2915
2917
  EmptyState as EmptyState3,
2916
2918
  ErrorState as ErrorState3,
2917
2919
  ScrollArea as ScrollArea2,
2918
2920
  SegmentedControl as SegmentedControl3,
2919
2921
  Sparkline,
2922
+ Stack,
2920
2923
  StatTile,
2921
2924
  StatusBar as StatusBar3,
2925
+ Text,
2926
+ TileGrid,
2922
2927
  Toolbar as Toolbar3
2923
2928
  } 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
- ] });
2929
+
2930
+ // src/data/metrics/declaration.ts
2931
+ function metricsViewState(c, options = {}) {
2932
+ const s = c.getState();
2933
+ const sparkline = options.sparkline ?? true;
2934
+ const showDelta = options.showDelta ?? true;
2935
+ const partialHint = options.partialHint ?? "Some measures have not finished syncing.";
2936
+ const errored = !!s.errorMessage;
2937
+ const empty = !errored && s.metrics.length === 0;
2938
+ return {
2939
+ hasRanges: s.ranges.length > 0,
2940
+ ranges: s.ranges.map((r) => ({ value: r.id, label: r.label })),
2941
+ rangeId: s.rangeId ?? s.ranges[0]?.id ?? "",
2942
+ partial: s.partial,
2943
+ partialHint,
2944
+ errorMessage: s.errorMessage ?? void 0,
2945
+ errored,
2946
+ empty,
2947
+ emptyHint: options.emptyHint ?? "Configure a metric, or wire a result into this panel.",
2948
+ ready: !errored && !empty,
2949
+ layout: options.layout ?? "grid",
2950
+ columns: options.columns ?? 4,
2951
+ tiles: s.metrics.map((metric) => {
2952
+ const level = metric.level === "critical" ? "error" : metric.level === "warn" ? "warning" : void 0;
2953
+ const hasDelta = showDelta && metric.delta !== void 0;
2954
+ const hasSpark = sparkline && !!metric.spark && metric.spark.length > 1;
2955
+ return {
2956
+ id: metric.spec.id,
2957
+ label: metric.spec.label,
2958
+ title: metric.spec.description,
2959
+ // A partial result shows an em dash, never a zero — a zero is a lie the user cannot detect.
2960
+ value: metric.partial && metric.value === null ? "\u2014" : metric.display,
2961
+ level: String(metric.level),
2962
+ hasLevel: level !== void 0,
2963
+ tone: level ?? "warning",
2964
+ hasDelta,
2965
+ delta: metric.delta ?? 0,
2966
+ deltaTone: metric.deltaTone,
2967
+ partial: metric.partial === true,
2968
+ hasSpark,
2969
+ spark: hasSpark ? metric.spark : [],
2970
+ sparkLabel: `${metric.spec.label} trend`
2971
+ };
2972
+ }),
2973
+ countLabel: `${s.metrics.length} metric${s.metrics.length === 1 ? "" : "s"}`,
2974
+ partialLabel: s.partial ? partialHint : void 0
2975
+ };
2976
+ }
2977
+ function applyMetricsUi(c, intent) {
2978
+ switch (intent.kind) {
2979
+ case "range":
2980
+ c.setRange(intent.rangeId);
2981
+ break;
2982
+ case "activate":
2983
+ c.activate(intent.metricId);
2984
+ break;
2971
2985
  }
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
2986
  }
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();
2987
+ var metricsViewManifest = {
2988
+ id: metricsManifest.id,
2989
+ version: "1.0.0",
2990
+ title: "Metrics",
2991
+ category: "data",
2992
+ layout: { slot: "center" },
2993
+ inputs: [{ id: "view", type: "object", label: "View state", description: "The MetricsViewModel the runtime derives from the controller on every draw." }],
2994
+ outputs: [{ id: "ui", type: "object", label: "UI intent", description: "{ kind: range | activate, \u2026 } \u2014 applied by the runtime to the controller." }],
2995
+ commands: []
2996
+ };
2997
+ var EMPTY = { 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" };
2998
+ var BEHAVIOURS = {
2999
+ "data/metricsPanelView": (inputs) => inputs.view ?? EMPTY
3000
+ };
3001
+ var METRICS_RUNTIME_UNITS = [];
3002
+ var text = (b) => ({ component: "text", props: { text: b } });
3003
+ var ui = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
3004
+ var METRICS_DECLARATION = {
3005
+ format: "xeno.block@1",
3006
+ manifest: metricsViewManifest,
3007
+ behaviour: "data/metricsPanelView",
3008
+ composition: {
3009
+ component: "chrome/Column",
3010
+ children: [
3011
+ {
3012
+ component: "chrome/Toolbar",
3013
+ when: { input: "view.hasRanges" },
3014
+ props: {
3015
+ left: { node: { component: "chrome/SegmentedControl", props: { size: { value: "sm" }, label: { value: "Range" }, options: { input: "view.ranges" }, value: { input: "view.rangeId" } }, on: { onChange: ui("range", { rangeId: { arg: "" } }) } } },
3016
+ right: { node: { component: "chrome/Badge", when: { input: "view.partial" }, props: { tone: { value: "warning" }, title: { input: "view.partialHint" } }, children: [text({ value: "partial" })] } }
3013
3017
  }
3014
3018
  },
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",
3019
+ { component: "chrome/ErrorState", when: { input: "view.errored" }, props: { title: { value: "Could not load metrics" }, message: { input: "view.errorMessage" } } },
3020
+ { component: "chrome/EmptyState", when: { input: "view.empty" }, props: { title: { value: "No metrics" }, hint: { input: "view.emptyHint" } } },
3021
+ {
3022
+ component: "chrome/ScrollArea",
3023
+ when: { input: "view.ready" },
3024
+ children: [
3025
+ {
3026
+ component: "chrome/TileGrid",
3027
+ props: { layout: { input: "view.layout" }, columns: { input: "view.columns" } },
3028
+ children: [
3026
3029
  {
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
3030
+ component: "chrome/StatTile",
3031
+ each: { input: "view.tiles", key: "id" },
3032
+ props: {
3033
+ title: { item: "title" },
3034
+ label: { item: "label" },
3035
+ value: { item: "value" },
3036
+ icon: { node: { component: "chrome/Badge", when: { item: "hasLevel" }, props: { tone: { item: "tone" } }, children: [text({ item: "level" })] } },
3037
+ sub: {
3038
+ node: {
3039
+ component: "chrome/Stack",
3040
+ props: { direction: { value: "row" }, pad: { value: "none" } },
3041
+ children: [
3042
+ { component: "chrome/Delta", when: { item: "hasDelta" }, props: { value: { item: "delta" }, tone: { item: "deltaTone" } } },
3043
+ { component: "chrome/Text", when: { item: "partial" }, props: { role: { value: "meta" } }, children: [text({ input: "view.partialHint" })] },
3044
+ { component: "chrome/Sparkline", when: { item: "hasSpark" }, props: { data: { item: "spark" }, label: { item: "sparkLabel" } } }
3045
+ ]
3046
+ }
3047
+ }
3031
3048
  },
3032
- children: [
3033
- metric.delta > 0 ? "\u25B2" : "\u25BC",
3034
- " ",
3035
- Math.abs(metric.delta * 100).toFixed(1),
3036
- "%"
3037
- ]
3049
+ on: { onActivate: ui("activate", { metricId: { item: "id" } }) }
3038
3050
  }
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
- );
3051
+ ]
3052
+ }
3053
+ ]
3054
+ },
3055
+ { component: "chrome/StatusBar", when: { input: "view.ready" }, props: { left: { input: "view.countLabel" }, right: { input: "view.partialLabel" } } }
3056
+ ]
3057
+ }
3058
+ };
3059
+
3060
+ // src/data/metrics/react/MetricsPanelView.tsx
3061
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
3062
+ function MetricsPanelView({ controller, ...options }) {
3063
+ useSyncExternalStore3(controller.subscribe, controller.getState, controller.getState);
3064
+ const view = metricsViewState(controller, options);
3065
+ const ui3 = (intent) => applyMetricsUi(controller, intent);
3066
+ return /* @__PURE__ */ jsxs3(Column, { children: [
3067
+ view.hasRanges ? /* @__PURE__ */ jsx3(
3068
+ Toolbar3,
3069
+ {
3070
+ left: /* @__PURE__ */ jsx3(SegmentedControl3, { size: "sm", label: "Range", options: view.ranges, value: view.rangeId, onChange: (rangeId) => ui3({ kind: "range", rangeId }) }),
3071
+ right: view.partial ? /* @__PURE__ */ jsx3(Badge3, { tone: "warning", title: view.partialHint, children: "partial" }) : null
3072
+ }
3073
+ ) : null,
3074
+ view.errored ? /* @__PURE__ */ jsx3(ErrorState3, { title: "Could not load metrics", message: view.errorMessage }) : null,
3075
+ view.empty ? /* @__PURE__ */ jsx3(EmptyState3, { title: "No metrics", hint: view.emptyHint }) : null,
3076
+ view.ready ? /* @__PURE__ */ jsx3(ScrollArea2, { children: /* @__PURE__ */ jsx3(TileGrid, { layout: view.layout, columns: view.columns, children: view.tiles.map((tile) => /* @__PURE__ */ jsx3(
3077
+ StatTile,
3078
+ {
3079
+ title: tile.title,
3080
+ label: tile.label,
3081
+ value: tile.value,
3082
+ icon: tile.hasLevel ? /* @__PURE__ */ jsx3(Badge3, { tone: tile.tone, children: tile.level }) : null,
3083
+ sub: /* @__PURE__ */ jsxs3(Stack, { direction: "row", pad: "none", children: [
3084
+ tile.hasDelta ? /* @__PURE__ */ jsx3(Delta, { value: tile.delta, tone: tile.deltaTone }) : null,
3085
+ tile.partial ? /* @__PURE__ */ jsx3(Text, { role: "meta", children: view.partialHint }) : null,
3086
+ tile.hasSpark ? /* @__PURE__ */ jsx3(Sparkline, { data: tile.spark, label: tile.sparkLabel }) : null
3087
+ ] }),
3088
+ onActivate: () => ui3({ kind: "activate", metricId: tile.id })
3089
+ },
3090
+ tile.id
3091
+ )) }) }) : null,
3092
+ view.ready ? /* @__PURE__ */ jsx3(StatusBar3, { left: view.countLabel, right: view.partialLabel }) : null
3093
+ ] });
3047
3094
  }
3048
3095
 
3049
3096
  // src/data/metrics/panel.ts
@@ -3240,7 +3287,7 @@ function emptyFilterGroup() {
3240
3287
  function offerableFilterOps(column, sourceOps = []) {
3241
3288
  const common = ["eq", "ne", "isEmpty", "isNotEmpty", "in", "notIn"];
3242
3289
  const ordered = ["gt", "gte", "lt", "lte", "between"];
3243
- const text2 = ["contains", "notContains", "startsWith", "endsWith"];
3290
+ const text3 = ["contains", "notContains", "startsWith", "endsWith"];
3244
3291
  const temporal = ["relative"];
3245
3292
  let ops = [...common];
3246
3293
  switch (column?.type) {
@@ -3258,10 +3305,10 @@ function offerableFilterOps(column, sourceOps = []) {
3258
3305
  case "url":
3259
3306
  case "select":
3260
3307
  case "multiselect":
3261
- ops = [...ops, ...text2];
3308
+ ops = [...ops, ...text3];
3262
3309
  break;
3263
3310
  default:
3264
- ops = [...ops, ...text2, ...ordered];
3311
+ ops = [...ops, ...text3, ...ordered];
3265
3312
  }
3266
3313
  if (sourceOps.length === 0) return ops;
3267
3314
  return ops.filter((op) => sourceOps.includes(op));
@@ -3892,7 +3939,7 @@ var fieldsManifest = {
3892
3939
  import { useSyncExternalStore as useSyncExternalStore4 } from "react";
3893
3940
  import {
3894
3941
  Badge as Badge4,
3895
- Column,
3942
+ Column as Column2,
3896
3943
  EmptyState as EmptyState4,
3897
3944
  IconButton as IconButton3,
3898
3945
  Note,
@@ -4027,13 +4074,13 @@ var fieldsViewManifest = {
4027
4074
  outputs: [{ id: "ui", type: "object", label: "UI intent", description: "{ kind, \u2026 } \u2014 applied by the runtime to the controller." }],
4028
4075
  commands: []
4029
4076
  };
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
4077
+ var EMPTY2 = { hasColumns: false, noColumns: true, sampled: false, manualApply: false, applyDisabled: true, columns: [], shelves: [], filters: [], filterCount: 0, hasFilters: false, noFilters: true, combinator: "and", countLabel: "0 fields" };
4078
+ var BEHAVIOURS2 = {
4079
+ "data/fieldsPanelView": (inputs) => inputs.view ?? EMPTY2
4033
4080
  };
4034
4081
  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 } } });
4082
+ var text2 = (b) => ({ component: "text", props: { text: b } });
4083
+ var ui2 = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
4037
4084
  var removeButton = (label, on) => ({
4038
4085
  component: "chrome/IconButton",
4039
4086
  props: { size: { value: "sm" }, icon: { node: { component: "icons/Close", props: { size: { value: 11 } } } }, label },
@@ -4054,14 +4101,14 @@ var FIELDS_DECLARATION = {
4054
4101
  {
4055
4102
  component: "chrome/Toolbar",
4056
4103
  props: {
4057
- left: { node: { component: "chrome/Badge", when: { input: "view.sampled" }, props: { tone: { value: "warning" }, title: { input: "view.sampleWarning" } }, children: [text({ value: "sampled schema" })] } },
4104
+ left: { node: { component: "chrome/Badge", when: { input: "view.sampled" }, props: { tone: { value: "warning" }, title: { input: "view.sampleWarning" } }, children: [text2({ value: "sampled schema" })] } },
4058
4105
  right: {
4059
4106
  node: {
4060
4107
  component: "chrome/ToolbarGroup",
4061
4108
  props: { end: { value: true } },
4062
4109
  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" })] }
4110
+ { component: "chrome/TextButton", on: { onClick: ui2("autoAssign") }, children: [text2({ value: "Auto-assign" })] },
4111
+ { component: "chrome/TextButton", when: { input: "view.manualApply" }, props: { strong: { value: true }, disabled: { input: "view.applyDisabled" } }, on: { onClick: ui2("apply") }, children: [text2({ value: "Apply" })] }
4065
4112
  ]
4066
4113
  }
4067
4114
  }
@@ -4081,8 +4128,8 @@ var FIELDS_DECLARATION = {
4081
4128
  {
4082
4129
  component: "chrome/Row",
4083
4130
  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" } }) }
4131
+ props: { label: { item: "name" }, meta: { item: "role" }, selected: { item: "focused" }, trailing: { node: { component: "chrome/Badge", when: { item: "stat" }, props: { title: { item: "statTitle" } }, children: [text2({ item: "stat" })] } } },
4132
+ on: { onClick: ui2("focus", { columnId: { item: "id" } }) }
4086
4133
  }
4087
4134
  ]
4088
4135
  }
@@ -4091,9 +4138,9 @@ var FIELDS_DECLARATION = {
4091
4138
  {
4092
4139
  component: "chrome/Section",
4093
4140
  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" })] } } },
4141
+ props: { title: { item: "label" }, flush: { value: true }, badge: { node: { component: "chrome/Badge", when: { item: "hasEntries" }, children: [text2({ item: "count" })] } } },
4095
4142
  children: [
4096
- { component: "chrome/Note", when: { item: "empty" }, children: [text({ item: "hint" })] },
4143
+ { component: "chrome/Note", when: { item: "empty" }, children: [text2({ item: "hint" })] },
4097
4144
  {
4098
4145
  component: "chrome/RowList",
4099
4146
  when: { item: "hasEntries" },
@@ -4108,13 +4155,13 @@ var FIELDS_DECLARATION = {
4108
4155
  node: {
4109
4156
  component: "fragment",
4110
4157
  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" })] }
4158
+ { component: "chrome/Select", when: { item: "aggregated" }, props: { size: { value: "sm" }, label: { value: "Aggregate" }, options: { item: "aggregates" }, value: { item: "fn" } }, on: { onChange: ui2("aggregate", { shelfId: { item: "shelfId" }, columnId: { item: "columnId" }, fn: { arg: "" } }) } },
4159
+ { component: "chrome/TextButton", when: { item: "sortable" }, on: { onClick: ui2("direction", { shelfId: { item: "shelfId" }, columnId: { item: "columnId" } }) }, children: [text2({ item: "direction" })] },
4160
+ { component: "chrome/Badge", when: { item: "granularity" }, children: [text2({ item: "granularity" })] }
4114
4161
  ]
4115
4162
  }
4116
4163
  },
4117
- trailing: { node: removeButton({ item: "removeLabel" }, ui("remove", { shelfId: { item: "shelfId" }, columnId: { item: "columnId" } })) }
4164
+ trailing: { node: removeButton({ item: "removeLabel" }, ui2("remove", { shelfId: { item: "shelfId" }, columnId: { item: "columnId" } })) }
4118
4165
  }
4119
4166
  }
4120
4167
  ]
@@ -4126,11 +4173,11 @@ var FIELDS_DECLARATION = {
4126
4173
  props: {
4127
4174
  title: { value: "Filters" },
4128
4175
  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") } } }
4176
+ badge: { node: { component: "chrome/Badge", when: { input: "view.hasFilters" }, children: [text2({ input: "view.filterCount" })] } },
4177
+ actions: { node: { component: "chrome/IconButton", props: { size: { value: "sm" }, icon: { node: { component: "icons/Plus", props: { size: { value: 11 } } } }, label: { value: "Add filter" } }, on: { onClick: ui2("addFilter") } } }
4131
4178
  },
4132
4179
  children: [
4133
- { component: "chrome/Note", when: { input: "view.noFilters" }, children: [text({ value: "No filters. Everything passes." })] },
4180
+ { component: "chrome/Note", when: { input: "view.noFilters" }, children: [text2({ value: "No filters. Everything passes." })] },
4134
4181
  {
4135
4182
  component: "chrome/RowList",
4136
4183
  when: { input: "view.hasFilters" },
@@ -4140,8 +4187,8 @@ var FIELDS_DECLARATION = {
4140
4187
  each: { input: "view.filters", key: "key" },
4141
4188
  props: {
4142
4189
  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" } })) }
4190
+ meta: { node: { component: "chrome/Select", when: { item: "leaf" }, props: { size: { value: "sm" }, label: { value: "Operator" }, options: { item: "ops" }, value: { item: "op" } }, on: { onChange: ui2("filterOp", { index: { item: "index" }, op: { arg: "" } }) } } },
4191
+ trailing: { node: removeButton({ item: "removeLabel" }, ui2("removeFilter", { index: { item: "index" } })) }
4145
4192
  }
4146
4193
  }
4147
4194
  ]
@@ -4150,8 +4197,8 @@ var FIELDS_DECLARATION = {
4150
4197
  component: "chrome/Toolbar",
4151
4198
  props: {
4152
4199
  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" })] } }
4200
+ left: { node: { component: "chrome/SegmentedControl", props: { size: { value: "sm" }, label: { value: "Combinator" }, options: { value: COMBINATORS }, value: { input: "view.combinator" } }, on: { onChange: ui2("combinator", { combinator: { arg: "" } }) } } },
4201
+ right: { node: { component: "chrome/TextButton", on: { onClick: ui2("addGroup") }, children: [text2({ value: "+ group" })] } }
4155
4202
  }
4156
4203
  }
4157
4204
  ]
@@ -4173,18 +4220,18 @@ var COMBINATORS2 = [{ value: "and", label: "All" }, { value: "or", label: "Any"
4173
4220
  function FieldsPanelView({ controller, showStats, manualApply }) {
4174
4221
  useSyncExternalStore4(controller.subscribe, controller.getState, controller.getState);
4175
4222
  const view = fieldsViewState(controller, { showStats, manualApply });
4176
- const ui2 = (intent) => applyFieldsUi(controller, intent);
4223
+ const ui3 = (intent) => applyFieldsUi(controller, intent);
4177
4224
  if (view.noColumns) {
4178
4225
  return /* @__PURE__ */ jsx4(EmptyState4, { title: "No fields", hint: "Connect a source \u2014 its columns appear here, ready to drop onto a shelf." });
4179
4226
  }
4180
- return /* @__PURE__ */ jsxs4(Column, { children: [
4227
+ return /* @__PURE__ */ jsxs4(Column2, { children: [
4181
4228
  /* @__PURE__ */ jsx4(
4182
4229
  Toolbar4,
4183
4230
  {
4184
4231
  left: view.sampled ? /* @__PURE__ */ jsx4(Badge4, { tone: "warning", title: view.sampleWarning, children: "sampled schema" }) : null,
4185
4232
  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
4233
+ /* @__PURE__ */ jsx4(TextButton, { onClick: () => ui3({ kind: "autoAssign" }), children: "Auto-assign" }),
4234
+ view.manualApply ? /* @__PURE__ */ jsx4(TextButton, { strong: true, disabled: view.applyDisabled, onClick: () => ui3({ kind: "apply" }), children: "Apply" }) : null
4188
4235
  ] })
4189
4236
  }
4190
4237
  ),
@@ -4196,7 +4243,7 @@ function FieldsPanelView({ controller, showStats, manualApply }) {
4196
4243
  meta: column.role,
4197
4244
  selected: column.focused,
4198
4245
  trailing: column.stat ? /* @__PURE__ */ jsx4(Badge4, { title: column.statTitle, children: column.stat }) : null,
4199
- onClick: () => ui2({ kind: "focus", columnId: column.id })
4246
+ onClick: () => ui3({ kind: "focus", columnId: column.id })
4200
4247
  },
4201
4248
  column.id
4202
4249
  )) }) }),
@@ -4208,11 +4255,11 @@ function FieldsPanelView({ controller, showStats, manualApply }) {
4208
4255
  label: entry.label,
4209
4256
  dimmed: entry.suggested,
4210
4257
  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,
4258
+ entry.aggregated ? /* @__PURE__ */ jsx4(Select, { size: "sm", label: "Aggregate", options: entry.aggregates, value: entry.fn, onChange: (fn) => ui3({ kind: "aggregate", shelfId: entry.shelfId, columnId: entry.columnId, fn }) }) : null,
4259
+ entry.sortable ? /* @__PURE__ */ jsx4(TextButton, { onClick: () => ui3({ kind: "direction", shelfId: entry.shelfId, columnId: entry.columnId }), children: entry.direction }) : null,
4213
4260
  entry.granularity ? /* @__PURE__ */ jsx4(Badge4, { children: entry.granularity }) : null
4214
4261
  ] }),
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 }) })
4262
+ trailing: /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(CloseGlyph, { size: 11 }), label: entry.removeLabel, onClick: () => ui3({ kind: "remove", shelfId: entry.shelfId, columnId: entry.columnId }) })
4216
4263
  },
4217
4264
  entry.columnId
4218
4265
  )) }) : null
@@ -4223,15 +4270,15 @@ function FieldsPanelView({ controller, showStats, manualApply }) {
4223
4270
  title: "Filters",
4224
4271
  flush: true,
4225
4272
  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" }) }),
4273
+ actions: /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(PlusGlyph, { size: 11 }), label: "Add filter", onClick: () => ui3({ kind: "addFilter" }) }),
4227
4274
  children: [
4228
4275
  view.noFilters ? /* @__PURE__ */ jsx4(Note, { children: "No filters. Everything passes." }) : null,
4229
4276
  view.hasFilters ? /* @__PURE__ */ jsx4(RowList, { children: view.filters.map((filter) => /* @__PURE__ */ jsx4(
4230
4277
  Row,
4231
4278
  {
4232
4279
  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 }) })
4280
+ meta: filter.leaf ? /* @__PURE__ */ jsx4(Select, { size: "sm", label: "Operator", options: filter.ops, value: filter.op, onChange: (op) => ui3({ kind: "filterOp", index: filter.index, op }) }) : null,
4281
+ trailing: /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(CloseGlyph, { size: 11 }), label: filter.removeLabel, onClick: () => ui3({ kind: "removeFilter", index: filter.index }) })
4235
4282
  },
4236
4283
  filter.key
4237
4284
  )) }) : null,
@@ -4239,8 +4286,8 @@ function FieldsPanelView({ controller, showStats, manualApply }) {
4239
4286
  Toolbar4,
4240
4287
  {
4241
4288
  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" })
4289
+ left: /* @__PURE__ */ jsx4(SegmentedControl4, { size: "sm", label: "Combinator", options: COMBINATORS2, value: view.combinator, onChange: (combinator) => ui3({ kind: "combinator", combinator }) }),
4290
+ right: /* @__PURE__ */ jsx4(TextButton, { onClick: () => ui3({ kind: "addGroup" }), children: "+ group" })
4244
4291
  }
4245
4292
  )
4246
4293
  ]
@@ -5376,10 +5423,10 @@ function parseJsonPath(path) {
5376
5423
  }
5377
5424
  return steps;
5378
5425
  }
5379
- function findBracketEnd(text2, start) {
5426
+ function findBracketEnd(text3, start) {
5380
5427
  let quote = null;
5381
- for (let i = start + 1; i < text2.length; i += 1) {
5382
- const char = text2[i];
5428
+ for (let i = start + 1; i < text3.length; i += 1) {
5429
+ const char = text3[i];
5383
5430
  if (quote) {
5384
5431
  if (char === "\\") i += 1;
5385
5432
  else if (char === quote) quote = null;
@@ -5803,9 +5850,9 @@ function firstGraphqlError(errors) {
5803
5850
  }
5804
5851
 
5805
5852
  // src/data/connectors/executors/csv.ts
5806
- function sniffDelimiter(text2) {
5853
+ function sniffDelimiter(text3) {
5807
5854
  const candidates = [",", ";", " ", "|"];
5808
- const line = firstLogicalLine(text2);
5855
+ const line = firstLogicalLine(text3);
5809
5856
  let best = ",";
5810
5857
  let bestCount = 0;
5811
5858
  for (const candidate of candidates) {
@@ -5827,21 +5874,21 @@ function sniffDelimiter(text2) {
5827
5874
  }
5828
5875
  return best;
5829
5876
  }
5830
- function firstLogicalLine(text2) {
5877
+ function firstLogicalLine(text3) {
5831
5878
  let inQuotes = false;
5832
- for (let i = 0; i < text2.length; i += 1) {
5833
- const char = text2[i];
5879
+ for (let i = 0; i < text3.length; i += 1) {
5880
+ const char = text3[i];
5834
5881
  if (char === '"') {
5835
- if (inQuotes && text2[i + 1] === '"') i += 1;
5882
+ if (inQuotes && text3[i + 1] === '"') i += 1;
5836
5883
  else inQuotes = !inQuotes;
5837
5884
  } else if ((char === "\n" || char === "\r") && !inQuotes) {
5838
- return text2.slice(0, i);
5885
+ return text3.slice(0, i);
5839
5886
  }
5840
5887
  }
5841
- return text2;
5888
+ return text3;
5842
5889
  }
5843
- function parseCsv(text2, options = {}) {
5844
- const source = text2.charCodeAt(0) === 65279 ? text2.slice(1) : text2;
5890
+ function parseCsv(text3, options = {}) {
5891
+ const source = text3.charCodeAt(0) === 65279 ? text3.slice(1) : text3;
5845
5892
  const delimiter = options.delimiter && options.delimiter.length > 0 ? options.delimiter : sniffDelimiter(source);
5846
5893
  const quote = options.quote && options.quote.length > 0 ? options.quote : '"';
5847
5894
  const hasHeader = options.hasHeader ?? true;
@@ -5876,17 +5923,17 @@ function dedupe(names) {
5876
5923
  return count === 0 ? name : `${name}_${count + 1}`;
5877
5924
  });
5878
5925
  }
5879
- function scanRecords(text2, delimiter, quote) {
5926
+ function scanRecords(text3, delimiter, quote) {
5880
5927
  const records = [];
5881
5928
  let fields = [];
5882
5929
  let field = "";
5883
5930
  let inQuotes = false;
5884
5931
  let i = 0;
5885
- while (i < text2.length) {
5886
- const char = text2[i];
5932
+ while (i < text3.length) {
5933
+ const char = text3[i];
5887
5934
  if (inQuotes) {
5888
5935
  if (char === quote) {
5889
- if (text2[i + 1] === quote) {
5936
+ if (text3[i + 1] === quote) {
5890
5937
  field += quote;
5891
5938
  i += 2;
5892
5939
  continue;
@@ -5904,7 +5951,7 @@ function scanRecords(text2, delimiter, quote) {
5904
5951
  i += 1;
5905
5952
  continue;
5906
5953
  }
5907
- if (text2.startsWith(delimiter, i)) {
5954
+ if (text3.startsWith(delimiter, i)) {
5908
5955
  fields.push(field);
5909
5956
  field = "";
5910
5957
  i += delimiter.length;
@@ -5915,7 +5962,7 @@ function scanRecords(text2, delimiter, quote) {
5915
5962
  records.push(fields);
5916
5963
  fields = [];
5917
5964
  field = "";
5918
- i += char === "\r" && text2[i + 1] === "\n" ? 2 : 1;
5965
+ i += char === "\r" && text3[i + 1] === "\n" ? 2 : 1;
5919
5966
  continue;
5920
5967
  }
5921
5968
  field += char;
@@ -7066,14 +7113,17 @@ export {
7066
7113
  DEFAULT_RANGES,
7067
7114
  EMPTY_LAYOUT,
7068
7115
  EXPIRY_SKEW_MS,
7069
- BEHAVIOURS as FIELDS_BEHAVIOURS,
7116
+ BEHAVIOURS2 as FIELDS_BEHAVIOURS,
7070
7117
  FIELDS_DECLARATION,
7071
7118
  FIELDS_PANEL_ID,
7072
7119
  FIELDS_RUNTIME_UNITS,
7073
7120
  FieldsController,
7074
7121
  FieldsPanelView,
7075
7122
  IMPLEMENTED_VIEW_MODES,
7123
+ BEHAVIOURS as METRICS_BEHAVIOURS,
7124
+ METRICS_DECLARATION,
7076
7125
  METRICS_PANEL_ID,
7126
+ METRICS_RUNTIME_UNITS,
7077
7127
  MIN_COLUMN_WIDTH,
7078
7128
  MONO_FILL_PALETTE,
7079
7129
  MONO_PALETTE,
@@ -7085,6 +7135,7 @@ export {
7085
7135
  TableController,
7086
7136
  TablePanelView,
7087
7137
  applyFieldsUi,
7138
+ applyMetricsUi,
7088
7139
  applyPrecision,
7089
7140
  base64Url,
7090
7141
  breachSideOf,
@@ -7150,6 +7201,8 @@ export {
7150
7201
  mergeRefreshResponse,
7151
7202
  metricsManifest,
7152
7203
  metricsPanel,
7204
+ metricsViewManifest,
7205
+ metricsViewState,
7153
7206
  moveCell,
7154
7207
  moveColumn,
7155
7208
  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.10",
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.0 <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.0",
85
85
  "@xenosystem/data-core": "^0.1.0",
86
86
  "@xenosystem/elements": "0.0.1",
87
87
  "@xenosystem/elements-react": "0.0.1",