@datalayer/core 1.2.27 → 1.2.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/lib/api/evals/__tests__/external-definition.golden.json +110 -0
  2. package/lib/api/evals/client.d.ts +13 -1
  3. package/lib/api/evals/client.js +13 -0
  4. package/lib/api/evals/types.d.ts +168 -1
  5. package/lib/api/mcp/agentAccess.d.ts +10 -0
  6. package/lib/api/mcp/agentAccess.js +28 -0
  7. package/lib/api/mcp/generated.js +20 -0
  8. package/lib/api/mcp/index.d.ts +1 -0
  9. package/lib/api/mcp/index.js +1 -0
  10. package/lib/api/mcp/observability.d.ts +1 -1
  11. package/lib/api/mcp/observability.js +2 -0
  12. package/lib/api/mcp/operations.d.ts +28 -10
  13. package/lib/api/mcp/tasks.d.ts +9 -1
  14. package/lib/api/otel/firewall.d.ts +74 -0
  15. package/lib/api/otel/firewall.js +42 -0
  16. package/lib/api/otel/index.d.ts +1 -0
  17. package/lib/api/otel/index.js +1 -0
  18. package/lib/cli-login.js +2 -2
  19. package/lib/components/anonymous/AnonymousKeyExpired.js +15 -3
  20. package/lib/components/index.d.ts +1 -0
  21. package/lib/components/index.js +1 -0
  22. package/lib/components/mcp/ScopeList.js +10 -1
  23. package/lib/components/principal/PrincipalSwitcherMenu.js +4 -4
  24. package/lib/components/sharing/ShareAccessComponent.js +33 -8
  25. package/lib/components/toast/ToastPanel.d.ts +11 -0
  26. package/lib/components/toast/ToastPanel.js +42 -0
  27. package/lib/components/toast/index.d.ts +2 -0
  28. package/lib/components/toast/index.js +6 -0
  29. package/lib/components/toast/toastQueue.d.ts +35 -0
  30. package/lib/components/toast/toastQueue.js +47 -0
  31. package/lib/hooks/index.d.ts +1 -0
  32. package/lib/hooks/index.js +1 -0
  33. package/lib/hooks/useCache.d.ts +4 -2
  34. package/lib/hooks/useCache.js +13 -4
  35. package/lib/hooks/useDatalayer.js +12 -16
  36. package/lib/hooks/useKeptScroll.d.ts +6 -0
  37. package/lib/hooks/useKeptScroll.js +54 -0
  38. package/lib/hooks/useMcp.d.ts +2 -2
  39. package/lib/hooks/useMcp.js +5 -2
  40. package/lib/hooks/useNavigate.d.ts +16 -2
  41. package/lib/hooks/useNavigate.js +65 -5
  42. package/lib/hooks/useToast.d.ts +6 -0
  43. package/lib/hooks/useToast.js +40 -1
  44. package/lib/models/McpAgentAccess.d.ts +95 -0
  45. package/lib/models/McpAgentAccess.js +5 -0
  46. package/lib/models/Usage.d.ts +13 -0
  47. package/lib/models/Usage.js +5 -0
  48. package/lib/models/index.d.ts +1 -0
  49. package/lib/models/index.js +1 -0
  50. package/lib/otel/client/OtelClient.d.ts +56 -0
  51. package/lib/otel/client/OtelClient.js +25 -0
  52. package/lib/otel/client/index.d.ts +1 -1
  53. package/lib/otel/index.d.ts +1 -1
  54. package/lib/otel/views/OtelLive.js +3 -3
  55. package/lib/otel/views/OtelLiveSpans.js +2 -2
  56. package/lib/otel/views/OtelLogsList.js +3 -3
  57. package/lib/otel/views/OtelMetricsChart.d.ts +13 -0
  58. package/lib/otel/views/OtelMetricsChart.js +109 -26
  59. package/lib/otel/views/OtelSpanDetail.js +3 -3
  60. package/lib/otel/views/OtelSqlView.js +4 -4
  61. package/lib/otel/views/OtelSystemView.js +2 -2
  62. package/lib/otel/views/OtelTimelineRangeSlider.js +2 -2
  63. package/lib/otel/views/OtelTracesList.js +2 -2
  64. package/lib/state/substates/LayoutState.js +24 -3
  65. package/lib/state/substates/NavigationState.d.ts +11 -3
  66. package/lib/state/substates/NavigationState.js +48 -5
  67. package/lib/views/mcp/AuditLog.js +1 -1
  68. package/lib/views/mcp/ConnectedAgents.d.ts +1 -1
  69. package/lib/views/mcp/ConnectedAgents.js +13 -10
  70. package/lib/views/mcp/McpDashboard.d.ts +1 -1
  71. package/lib/views/mcp/McpDashboard.js +6 -6
  72. package/lib/views/mcp/McpHome.d.ts +3 -1
  73. package/lib/views/mcp/McpHome.js +19 -9
  74. package/lib/views/mcp/McpObservability.d.ts +22 -3
  75. package/lib/views/mcp/McpObservability.js +65 -17
  76. package/lib/views/mcp/Policies.js +1 -1
  77. package/lib/views/mcp/RunDetail.js +2 -2
  78. package/lib/views/mcp/types.d.ts +8 -4
  79. package/lib/views/otel/OtelHeader.js +2 -2
  80. package/package.json +2 -2
@@ -13,7 +13,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
13
13
  * @module otel/OtelLive
14
14
  */
15
15
  import { useState, useCallback, useMemo, useEffect, useRef, } from 'react';
16
- import { Box } from '@datalayer/primer-addons';
16
+ import { Box, Card } from '@datalayer/primer-addons';
17
17
  import { Text, Button, Label } from '@primer/react';
18
18
  import { GitBranchIcon, ClockIcon } from '@primer/octicons-react';
19
19
  import { getOtelConsumeUrl } from '../../state/substates/CoreState.js';
@@ -288,11 +288,11 @@ export const OtelLive = ({ baseUrl = getOtelConsumeUrl(), wsBaseUrl, token, auto
288
288
  return ts >= s && ts <= e;
289
289
  });
290
290
  }, [metrics, isRangeActive, rangeStart, rangeEnd]);
291
- return (_jsxs(Box, { display: "flex", flexDirection: "column", flex: 1, minHeight: 0, height: "100%", color: "fg.default", bg: "canvas.default", border: "1px solid", borderColor: "border.default", borderRadius: 2, overflow: "hidden", children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 0, children: [_jsx(Box, { flex: 1, children: _jsx(OtelSearchBar, { signal: signal, onSignalChange: setSignal, services: effectiveServices, selectedService: service, onServiceChange: setService, query: query, onQueryChange: setQuery, onRefresh: handleRefresh, loading: signal === 'traces'
291
+ return (_jsxs(Card, { border: true, shadow: "none", display: "flex", flexDirection: "column", flex: 1, minHeight: 0, height: "100%", color: "fg.default", overflow: "hidden", children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 0, children: [_jsx(Box, { flex: 1, children: _jsx(OtelSearchBar, { signal: signal, onSignalChange: setSignal, services: effectiveServices, selectedService: service, onServiceChange: setService, query: query, onQueryChange: setQuery, onRefresh: handleRefresh, loading: signal === 'traces'
292
292
  ? tracesLoading
293
293
  : signal === 'logs'
294
294
  ? logsLoading
295
- : metricsLoading }) }), _jsx(Box, { pr: 2, flexShrink: 0, children: _jsx(Label, { variant: wsConnected ? 'success' : 'secondary', size: "small", children: wsConnected ? '● Live' : '○ Polling' }) })] }), timelineBounds && rangeStart && rangeEnd && (_jsx(Box, { px: 3, pt: 2, pb: 1, borderBottom: "1px solid", borderColor: "border.default", bg: "canvas.subtle", flexShrink: 0, position: "relative", zIndex: 0, children: _jsx(OtelTimelineRangeSlider, { timelineStart: timelineBounds.start, timelineEnd: timelineBounds.end, selectedStart: rangeStart, selectedEnd: rangeEnd, onRangeChange: handleRangeChange, histogram: histogram, height: 48, tickCount: 6 }) })), _jsxs(Box, { display: "flex", flex: 1, minHeight: 0, overflow: "hidden", children: [_jsxs(Box, { display: "flex", flexDirection: "column", flex: hasDetail ? '0 0 55%' : '1 1 100%', minHeight: 0, overflow: "hidden", children: [signal === 'traces' && (_jsx(OtelTracesList, { spans: filteredTraces, loading: tracesLoading, selectedSpanId: selectedSpan?.span_id, onSelectSpan: handleSpanSelect })), signal === 'logs' && (_jsx(OtelLogsList, { logs: filteredLogs, loading: logsLoading, selectedLogIndex: selectedLogIdx, onSelectLog: handleLogSelect })), signal === 'metrics' && (_jsx(OtelMetricsList, { metrics: filteredMetrics, loading: metricsLoading }))] }), hasDetail && (_jsxs(Box, { flex: "0 0 45%", minHeight: 0, overflow: "auto", borderLeft: "1px solid", borderColor: "border.default", children: [signal === 'traces' && selectedSpan && (_jsx(OtelSpanDetail, { span: selectedSpan, traceSpans: traceSpans ?? undefined, onClose: handleCloseDetail })), signal === 'logs' && selectedLogIdx !== null && logs && (_jsxs(Box, { p: 3, children: [_jsxs(Box, { display: "flex", justifyContent: "space-between", mb: 3, children: [_jsx(Text, { sx: { fontWeight: 'bold', fontSize: 2 }, children: "Log Detail" }), _jsx(Button, { size: "small", variant: "invisible", onClick: handleCloseDetail, children: "\u2715" })] }), _jsx(Box, { as: "pre", m: 0, fontSize: 1, fontFamily: "mono", whiteSpace: "pre-wrap", wordBreak: "break-word", children: JSON.stringify(logs[selectedLogIdx], null, 2) })] }))] }))] }), signal === 'traces' && selectedSpan && spanTree.length > 0 && (_jsxs(_Fragment, { children: [_jsxs(Box, { display: "flex", gap: 1, px: 3, py: 1, bg: "canvas.subtle", borderTop: "1px solid", borderColor: "border.default", flexShrink: 0, children: [_jsx(Button, { size: "small", variant: bottomPane === 'timeline' ? 'primary' : 'invisible', leadingVisual: ClockIcon, onClick: () => toggleBottomPane('timeline'), children: "Timeline" }), _jsx(Button, { size: "small", variant: bottomPane === 'tree' ? 'primary' : 'invisible', leadingVisual: GitBranchIcon, onClick: () => toggleBottomPane('tree'), children: "Span Tree" })] }), bottomPane && (_jsxs(Box, { height: 260, overflow: "auto", borderTop: "1px solid", borderColor: "border.default", flexShrink: 0, children: [bottomPane === 'timeline' && (_jsx(OtelTimeline, { spans: traceSpans ?? [], selectedSpanId: selectedSpan?.span_id, onSelectSpan: handleSpanSelect })), bottomPane === 'tree' && (_jsx(OtelSpanTree, { spans: spanTree, selectedSpanId: selectedSpan?.span_id, onSelectSpan: handleSpanSelect, defaultExpandDepth: 3 }))] }))] }))] }));
295
+ : metricsLoading }) }), wsConnected ? null : (_jsx(Box, { pr: 2, flexShrink: 0, children: _jsx(Label, { variant: "secondary", size: "small", children: "\u25CB Polling" }) }))] }), timelineBounds && rangeStart && rangeEnd && (_jsx(Box, { px: 3, pt: 2, pb: 1, borderBottom: "1px solid", borderColor: "border.default", bg: "canvas.subtle", flexShrink: 0, position: "relative", zIndex: 0, children: _jsx(OtelTimelineRangeSlider, { timelineStart: timelineBounds.start, timelineEnd: timelineBounds.end, selectedStart: rangeStart, selectedEnd: rangeEnd, onRangeChange: handleRangeChange, histogram: histogram, height: 48, tickCount: 6 }) })), _jsxs(Box, { display: "flex", flex: 1, minHeight: 0, overflow: "hidden", children: [_jsxs(Box, { display: "flex", flexDirection: "column", flex: hasDetail ? '0 0 55%' : '1 1 100%', minHeight: 0, overflow: "hidden", children: [signal === 'traces' && (_jsx(OtelTracesList, { spans: filteredTraces, loading: tracesLoading, selectedSpanId: selectedSpan?.span_id, onSelectSpan: handleSpanSelect })), signal === 'logs' && (_jsx(OtelLogsList, { logs: filteredLogs, loading: logsLoading, selectedLogIndex: selectedLogIdx, onSelectLog: handleLogSelect })), signal === 'metrics' && (_jsx(OtelMetricsList, { metrics: filteredMetrics, loading: metricsLoading }))] }), hasDetail && (_jsxs(Box, { flex: "0 0 45%", minHeight: 0, overflow: "auto", borderLeft: "1px solid", borderColor: "border.default", children: [signal === 'traces' && selectedSpan && (_jsx(OtelSpanDetail, { span: selectedSpan, traceSpans: traceSpans ?? undefined, onClose: handleCloseDetail })), signal === 'logs' && selectedLogIdx !== null && logs && (_jsxs(Box, { p: 3, children: [_jsxs(Box, { display: "flex", justifyContent: "space-between", mb: 3, children: [_jsx(Text, { sx: { fontWeight: 'bold', fontSize: 2 }, children: "Log Detail" }), _jsx(Button, { size: "small", variant: "invisible", onClick: handleCloseDetail, children: "\u2715" })] }), _jsx(Box, { as: "pre", m: 0, fontSize: 1, fontFamily: "mono", whiteSpace: "pre-wrap", wordBreak: "break-word", children: JSON.stringify(logs[selectedLogIdx], null, 2) })] }))] }))] }), signal === 'traces' && selectedSpan && spanTree.length > 0 && (_jsxs(_Fragment, { children: [_jsxs(Box, { display: "flex", gap: 1, px: 3, py: 1, bg: "canvas.subtle", borderTop: "1px solid", borderColor: "border.default", flexShrink: 0, children: [_jsx(Button, { size: "small", variant: bottomPane === 'timeline' ? 'primary' : 'invisible', leadingVisual: ClockIcon, onClick: () => toggleBottomPane('timeline'), children: "Timeline" }), _jsx(Button, { size: "small", variant: bottomPane === 'tree' ? 'primary' : 'invisible', leadingVisual: GitBranchIcon, onClick: () => toggleBottomPane('tree'), children: "Span Tree" })] }), bottomPane && (_jsxs(Box, { height: 260, overflow: "auto", borderTop: "1px solid", borderColor: "border.default", flexShrink: 0, children: [bottomPane === 'timeline' && (_jsx(OtelTimeline, { spans: traceSpans ?? [], selectedSpanId: selectedSpan?.span_id, onSelectSpan: handleSpanSelect })), bottomPane === 'tree' && (_jsx(OtelSpanTree, { spans: spanTree, selectedSpanId: selectedSpan?.span_id, onSelectSpan: handleSpanSelect, defaultExpandDepth: 3 }))] }))] }))] }));
296
296
  };
297
297
  // ── Client-side filter helpers ──────────────────────────────────────
298
298
  function filterSpans(spans, q) {
@@ -16,7 +16,7 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
16
16
  * @module otel/views/OtelLiveSpans
17
17
  */
18
18
  import { useMemo, useState } from 'react';
19
- import { Box } from '@datalayer/primer-addons';
19
+ import { Box, Card } from '@datalayer/primer-addons';
20
20
  import { ActionList, ActionMenu, Button, Text } from '@primer/react';
21
21
  import { DownloadIcon, PauseIcon, PlayIcon, TrashIcon, } from '@primer/octicons-react';
22
22
  import { toMs } from '../utils.js';
@@ -94,7 +94,7 @@ export const OtelLiveSpans = ({ tracer, spans: given, service, facets = DEFAULT_
94
94
  ? everything.filter(span => span.trace_id === selectedSpan.trace_id)
95
95
  : [], [everything, selectedSpan]);
96
96
  const side = detailPlacement === 'side' && !compact;
97
- return (_jsxs(Box, { "data-otel-live-spans": "", border: "1px solid", borderColor: "border.default", borderRadius: 2, bg: "canvas.default", color: "fg.default", minWidth: 0, children: [_jsxs(Box, { role: "toolbar", "aria-label": label, display: "flex", flexWrap: "wrap", gap: 2, alignItems: "center", p: 2, borderBottom: "1px solid", borderColor: "border.default", children: [facets.map((facet, index) => (_jsx(FacetMenu, { facet: facet, options: options[index] ?? [], selected: selected[index] ?? new Set(), onChange: next => setSelected(prev => facets.map((_, at) => at === index ? next : (prev[at] ?? new Set()))) }, facet.label))), _jsx(Box, { flex: 1 }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, "data-otel-span-count": "", children: [rows.length === shown.length
97
+ return (_jsxs(Card, { border: true, shadow: "none", display: "block", "data-otel-live-spans": "", color: "fg.default", minWidth: 0, children: [_jsxs(Box, { role: "toolbar", "aria-label": label, display: "flex", flexWrap: "wrap", gap: 2, alignItems: "center", p: 2, borderBottom: "1px solid", borderColor: "border.default", children: [facets.map((facet, index) => (_jsx(FacetMenu, { facet: facet, options: options[index] ?? [], selected: selected[index] ?? new Set(), onChange: next => setSelected(prev => facets.map((_, at) => at === index ? next : (prev[at] ?? new Set()))) }, facet.label))), _jsx(Box, { flex: 1 }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, "data-otel-span-count": "", children: [rows.length === shown.length
98
98
  ? `${rows.length} ${rows.length === 1 ? 'span' : 'spans'}`
99
99
  : `${rows.length} of ${shown.length}`, waiting > 0 ? ` · ${waiting} new` : ''] }), _jsx(Button, { size: "small", leadingVisual: held ? PlayIcon : PauseIcon, "aria-pressed": held !== null, onClick: () => setHeld(held ? null : recorded), "data-otel-pause": "", children: held ? 'Resume' : 'Pause' }), _jsx(Button, { size: "small", leadingVisual: TrashIcon, onClick: () => {
100
100
  setHeld(null);
@@ -12,7 +12,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
12
12
  * @module otel/OtelLogsList
13
13
  */
14
14
  import React, { useState } from 'react';
15
- import { Box } from '@datalayer/primer-addons';
15
+ import { Box, Card } from '@datalayer/primer-addons';
16
16
  import { Text, Label, Spinner } from '@primer/react';
17
17
  import { Blankslate } from '@primer/react/experimental';
18
18
  import { LogIcon } from '@primer/octicons-react';
@@ -27,13 +27,13 @@ const LogDetail = ({ log }) => (_jsxs(Box, { gridColumn: "1 / -1", bg: "canvas.s
27
27
  color: 'fg.muted',
28
28
  display: 'block',
29
29
  mb: 1,
30
- }, children: "Body" }), _jsx(Box, { as: "pre", fontSize: 1, fontFamily: "mono", whiteSpace: "pre-wrap", wordBreak: "break-word", m: 0, bg: "canvas.default", border: "1px solid", borderColor: "border.default", borderRadius: 2, p: 2, children: log.body })] }), log.trace_id && (_jsxs(Box, { display: "flex", gap: 3, mb: 2, children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', fontWeight: 'bold' }, children: "trace_id" }), _jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0 }, children: log.trace_id }), log.span_id && (_jsxs(_Fragment, { children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', fontWeight: 'bold' }, children: "span_id" }), _jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0 }, children: log.span_id })] }))] })), log.attributes && Object.keys(log.attributes).length > 0 && (_jsxs(Box, { children: [_jsx(Text, { sx: {
30
+ }, children: "Body" }), _jsx(Card, { border: true, shadow: "none", display: "block", as: "pre", fontSize: 1, fontFamily: "mono", whiteSpace: "pre-wrap", wordBreak: "break-word", m: 0, p: 2, children: log.body })] }), log.trace_id && (_jsxs(Box, { display: "flex", gap: 3, mb: 2, children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', fontWeight: 'bold' }, children: "trace_id" }), _jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0 }, children: log.trace_id }), log.span_id && (_jsxs(_Fragment, { children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', fontWeight: 'bold' }, children: "span_id" }), _jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0 }, children: log.span_id })] }))] })), log.attributes && Object.keys(log.attributes).length > 0 && (_jsxs(Box, { children: [_jsx(Text, { sx: {
31
31
  fontSize: 0,
32
32
  fontWeight: 'bold',
33
33
  color: 'fg.muted',
34
34
  display: 'block',
35
35
  mb: 1,
36
- }, children: "Attributes" }), _jsx(Box, { bg: "canvas.default", border: "1px solid", borderColor: "border.default", borderRadius: 2, p: 2, children: Object.entries(log.attributes).map(([k, v]) => (_jsxs(Box, { display: "flex", gap: 2, py: 1, borderBottom: "1px solid", borderColor: "border.muted", children: [_jsx(Text, { sx: {
36
+ }, children: "Attributes" }), _jsx(Card, { border: true, shadow: "none", display: "block", p: 2, children: Object.entries(log.attributes).map(([k, v]) => (_jsxs(Box, { display: "flex", gap: 2, py: 1, borderBottom: "1px solid", borderColor: "border.muted", children: [_jsx(Text, { sx: {
37
37
  color: 'accent.fg',
38
38
  fontSize: 0,
39
39
  fontFamily: 'mono',
@@ -13,6 +13,19 @@
13
13
  */
14
14
  import React from 'react';
15
15
  import type { OtelMetric } from '../types';
16
+ /**
17
+ * The colours ECharts draws its text, axes, grid lines and tooltip with.
18
+ * A canvas reads no CSS variable, so they are read off a probe the theme
19
+ * colours (fg.default, fg.muted, border.muted, canvas.overlay), resolved to
20
+ * what the colour mode makes them: hard-coded, the axes and the tooltip were
21
+ * light-mode only and unreadable in the dark (28 October 2026).
22
+ */
23
+ export interface ChartInk {
24
+ text: string;
25
+ muted: string;
26
+ line: string;
27
+ surface: string;
28
+ }
16
29
  export interface OtelMetricsChartProps {
17
30
  metrics: OtelMetric[];
18
31
  /** Height per chart panel in px. Default 240. */
@@ -1,4 +1,22 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2023-2025 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ /**
7
+ * OtelMetricsChart – Type-aware ECharts visualisation for OTEL metrics.
8
+ *
9
+ * Renders one chart section per `metric_type` (`sum`, `histogram`, `gauge`,
10
+ * and any other type present) with an appropriate chart style:
11
+ *
12
+ * - **sum** (counters): area-line chart with gradient fill
13
+ * - **histogram**: bar chart with grouped series
14
+ * - **gauge**: plain line chart (no area fill)
15
+ * - **other**: falls back to area-line
16
+ *
17
+ * @module otel/OtelMetricsChart
18
+ */
19
+ import React, { useLayoutEffect, useRef, useState } from 'react';
2
20
  import * as echarts from 'echarts';
3
21
  import ReactECharts from 'echarts-for-react';
4
22
  import { Box } from '@datalayer/primer-addons';
@@ -108,30 +126,90 @@ const TYPE_ORDER = {
108
126
  gauge: 2,
109
127
  exponentialHistogram: 3,
110
128
  };
129
+ /** Light-mode values, until the probe has been read. */
130
+ const DEFAULT_INK = {
131
+ text: '#1f2328',
132
+ muted: '#59636e',
133
+ line: '#d1d9e0',
134
+ surface: '#ffffff',
135
+ };
136
+ function useChartInk() {
137
+ const probe = useRef(null);
138
+ const [ink, setInk] = useState(DEFAULT_INK);
139
+ useLayoutEffect(() => {
140
+ const node = probe.current;
141
+ if (!node || typeof getComputedStyle === 'undefined') {
142
+ return;
143
+ }
144
+ const read = () => {
145
+ const style = getComputedStyle(node);
146
+ const muted = node.firstElementChild
147
+ ? getComputedStyle(node.firstElementChild).color
148
+ : style.color;
149
+ const next = {
150
+ text: style.color || DEFAULT_INK.text,
151
+ muted: muted || DEFAULT_INK.muted,
152
+ line: style.borderTopColor || DEFAULT_INK.line,
153
+ surface: style.backgroundColor || DEFAULT_INK.surface,
154
+ };
155
+ setInk(current => current.text === next.text &&
156
+ current.muted === next.muted &&
157
+ current.line === next.line &&
158
+ current.surface === next.surface
159
+ ? current
160
+ : next);
161
+ };
162
+ read();
163
+ // The colour mode changes on the document's attributes.
164
+ const observer = new MutationObserver(read);
165
+ observer.observe(document.documentElement, {
166
+ attributes: true,
167
+ subtree: false,
168
+ });
169
+ observer.observe(document.body, { attributes: true, subtree: false });
170
+ return () => observer.disconnect();
171
+ });
172
+ return [probe, ink];
173
+ }
174
+ /** The probe the ink is read off: drawn, never seen. */
175
+ const InkProbe = React.forwardRef((_, ref) => (_jsx(Box, { ref: ref, "aria-hidden": true, color: "fg.default", bg: "canvas.overlay", borderTop: "1px solid", borderColor: "border.muted", sx: {
176
+ position: 'absolute',
177
+ width: 0,
178
+ height: 0,
179
+ overflow: 'hidden',
180
+ pointerEvents: 'none',
181
+ }, children: _jsx(Box, { as: "span", color: "fg.muted" }) })));
182
+ InkProbe.displayName = 'InkProbe';
111
183
  // ── Build ECharts option per type ──────────────────────────────────
112
- function buildOption(mtype, nameMap) {
184
+ function buildOption(mtype, nameMap, ink = DEFAULT_INK) {
113
185
  const names = [...nameMap.keys()];
114
186
  const baseLegend = {
115
187
  data: names,
116
188
  top: 6,
117
- textStyle: { fontSize: 11 },
189
+ textStyle: { fontSize: 11, color: ink.muted },
118
190
  };
119
191
  const baseGrid = { left: 50, right: 20, top: 40, bottom: 30 };
120
192
  const baseTooltip = {
121
193
  trigger: 'axis',
122
- axisPointer: { type: 'cross', label: { backgroundColor: '#6a7985' } },
194
+ axisPointer: {
195
+ type: 'cross',
196
+ label: { backgroundColor: ink.muted, color: ink.surface },
197
+ },
198
+ backgroundColor: ink.surface,
199
+ borderColor: ink.line,
200
+ textStyle: { color: ink.text },
123
201
  };
124
202
  const baseXAxis = {
125
203
  type: 'time',
126
204
  boundaryGap: false,
127
- axisLine: { lineStyle: { color: '#ccc' } },
128
- axisLabel: { fontSize: 10 },
205
+ axisLine: { lineStyle: { color: ink.line } },
206
+ axisLabel: { fontSize: 10, color: ink.muted },
129
207
  };
130
208
  const baseYAxis = {
131
209
  type: 'value',
132
- splitLine: { lineStyle: { type: 'dashed', color: '#e8e8e8' } },
210
+ splitLine: { lineStyle: { type: 'dashed', color: ink.line } },
133
211
  axisLine: { show: false },
134
- axisLabel: { fontSize: 10 },
212
+ axisLabel: { fontSize: 10, color: ink.muted },
135
213
  };
136
214
  if (mtype === 'histogram' || mtype === 'exponentialHistogram') {
137
215
  // ── Bar chart ──
@@ -257,29 +335,34 @@ function formatDuration(ms) {
257
335
  }
258
336
  // ── Component ───────────────────────────────────────────────────────
259
337
  export const OtelMetricsChart = ({ metrics, height = 240, }) => {
338
+ const [probe, ink] = useChartInk();
260
339
  if (!metrics || metrics.length === 0) {
261
- return (_jsx(ReactECharts, { echarts: echarts, option: {
262
- title: {
263
- text: 'No metrics data',
264
- left: 'center',
265
- top: 'center',
266
- textStyle: { color: '#999', fontSize: 14, fontWeight: 'normal' },
267
- },
268
- }, style: { width: '100%', height: `${height}px` }, notMerge: true }));
340
+ return (_jsxs(Box, { position: "relative", children: [_jsx(InkProbe, { ref: probe }), _jsx(ReactECharts, { echarts: echarts, option: {
341
+ title: {
342
+ text: 'No metrics data',
343
+ left: 'center',
344
+ top: 'center',
345
+ textStyle: {
346
+ color: ink.muted,
347
+ fontSize: 14,
348
+ fontWeight: 'normal',
349
+ },
350
+ },
351
+ }, style: { width: '100%', height: `${height}px` }, notMerge: true })] }));
269
352
  }
270
353
  const byType = groupByType(metrics);
271
354
  // Sort types in a stable, user-friendly order.
272
355
  const sortedTypes = [...byType.keys()].sort((a, b) => (TYPE_ORDER[a] ?? 99) - (TYPE_ORDER[b] ?? 99));
273
- return (_jsx(Box, { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 2, children: sortedTypes.map(mtype => {
274
- const nameMap = byType.get(mtype);
275
- const option = buildOption(mtype, nameMap);
276
- const stats = computeStats(nameMap);
277
- const durationMs = stats.startTs !== null && stats.endTs !== null
278
- ? stats.endTs - stats.startTs
279
- : null;
280
- return (_jsxs(Box, { children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, px: 1, mb: 1, children: [_jsx(Label, { size: "small", variant: typeVariant(mtype), children: mtype }), _jsx(Text, { sx: { fontSize: 1, fontWeight: 'bold', color: 'fg.muted' }, children: typeTitle(mtype) }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: ["(", [...nameMap.keys()].join(', '), ")"] })] }), _jsxs(Box, { display: "flex", alignItems: "center", gap: 3, px: 1, mb: 1, children: [_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: [_jsx(Text, { as: "span", sx: { fontWeight: 'bold', color: 'fg.default' }, children: "Sum:" }), ' ', stats.total.toLocaleString(undefined, {
281
- maximumFractionDigits: 4,
282
- })] }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: [_jsx(Text, { as: "span", sx: { fontWeight: 'bold', color: 'fg.default' }, children: "Points:" }), ' ', stats.count] }), stats.startTs !== null && stats.endTs !== null && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: [_jsx(Text, { as: "span", sx: { fontWeight: 'bold', color: 'fg.default' }, children: "Interval:" }), ' ', formatTs(stats.startTs), " \u2192 ", formatTs(stats.endTs), durationMs !== null && durationMs > 0 && (_jsxs(Text, { as: "span", sx: { color: 'fg.subtle' }, children: [' ', "(", formatDuration(durationMs), ")"] }))] }))] }), _jsx(ReactECharts, { echarts: echarts, option: option, style: { width: '100%', height: `${height}px` }, notMerge: true })] }, mtype));
283
- }) }));
356
+ return (_jsxs(Box, { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 2, position: "relative", children: [_jsx(InkProbe, { ref: probe }), sortedTypes.map(mtype => {
357
+ const nameMap = byType.get(mtype);
358
+ const option = buildOption(mtype, nameMap, ink);
359
+ const stats = computeStats(nameMap);
360
+ const durationMs = stats.startTs !== null && stats.endTs !== null
361
+ ? stats.endTs - stats.startTs
362
+ : null;
363
+ return (_jsxs(Box, { children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, px: 1, mb: 1, children: [_jsx(Label, { size: "small", variant: typeVariant(mtype), children: mtype }), _jsx(Text, { sx: { fontSize: 1, fontWeight: 'bold', color: 'fg.muted' }, children: typeTitle(mtype) }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: ["(", [...nameMap.keys()].join(', '), ")"] })] }), _jsxs(Box, { display: "flex", alignItems: "center", gap: 3, px: 1, mb: 1, children: [_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: [_jsx(Text, { as: "span", sx: { fontWeight: 'bold', color: 'fg.default' }, children: "Sum:" }), ' ', stats.total.toLocaleString(undefined, {
364
+ maximumFractionDigits: 4,
365
+ })] }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: [_jsx(Text, { as: "span", sx: { fontWeight: 'bold', color: 'fg.default' }, children: "Points:" }), ' ', stats.count] }), stats.startTs !== null && stats.endTs !== null && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: [_jsx(Text, { as: "span", sx: { fontWeight: 'bold', color: 'fg.default' }, children: "Interval:" }), ' ', formatTs(stats.startTs), " \u2192 ", formatTs(stats.endTs), durationMs !== null && durationMs > 0 && (_jsxs(Text, { as: "span", sx: { color: 'fg.subtle' }, children: [' ', "(", formatDuration(durationMs), ")"] }))] }))] }), _jsx(ReactECharts, { echarts: echarts, option: option, style: { width: '100%', height: `${height}px` }, notMerge: true })] }, mtype));
366
+ })] }));
284
367
  };
285
368
  export default OtelMetricsChart;
@@ -12,7 +12,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
12
  * @module otel/OtelSpanDetail
13
13
  */
14
14
  import { useMemo, useState } from 'react';
15
- import { Box } from '@datalayer/primer-addons';
15
+ import { Box, Card } from '@datalayer/primer-addons';
16
16
  import { Text, IconButton, UnderlineNav, CounterLabel, Label, } from '@primer/react';
17
17
  import { XIcon, ChevronDownIcon, ChevronRightIcon, } from '@primer/octicons-react';
18
18
  import { formatDuration, buildSpanTree } from '../utils.js';
@@ -39,7 +39,7 @@ const CollapsibleSection = ({ title, data, defaultOpen = false }) => {
39
39
  const [open, setOpen] = useState(defaultOpen);
40
40
  if (!data || Object.keys(data).length === 0)
41
41
  return null;
42
- return (_jsxs(Box, { mt: 3, children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 1, cursor: "pointer", py: 1, userSelect: "none", onClick: () => setOpen(!open), children: [open ? _jsx(ChevronDownIcon, { size: 16 }) : _jsx(ChevronRightIcon, { size: 16 }), _jsx(Text, { sx: { fontSize: 1, fontWeight: 'bold' }, children: title }), _jsx(CounterLabel, { children: Object.keys(data).length })] }), open && (_jsx(Box, { bg: "canvas.subtle", borderRadius: 2, border: "1px solid", borderColor: "border.default", p: 2, mt: 1, overflowX: "auto", children: Object.entries(data).map(([key, val]) => (_jsx(AttributeRow, { attrKey: key, value: val, depth: 0 }, key))) }))] }));
42
+ return (_jsxs(Box, { mt: 3, children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 1, cursor: "pointer", py: 1, userSelect: "none", onClick: () => setOpen(!open), children: [open ? _jsx(ChevronDownIcon, { size: 16 }) : _jsx(ChevronRightIcon, { size: 16 }), _jsx(Text, { sx: { fontSize: 1, fontWeight: 'bold' }, children: title }), _jsx(CounterLabel, { children: Object.keys(data).length })] }), open && (_jsx(Card, { border: true, shadow: "none", variant: "subtle", display: "block", p: 2, mt: 1, overflowX: "auto", children: Object.entries(data).map(([key, val]) => (_jsx(AttributeRow, { attrKey: key, value: val, depth: 0 }, key))) }))] }));
43
43
  };
44
44
  /**
45
45
  * A value as the detail unfolds it: JSON text of an object or an array (the
@@ -107,7 +107,7 @@ export const OtelSpanDetail = ({ span, traceSpans, onClose, }) => {
107
107
  }
108
108
  // Build tree from traceSpans for tree tab
109
109
  const tree = traceSpans && traceSpans.length > 0 ? buildSpanTree(traceSpans) : undefined;
110
- return (_jsxs(Box, { height: "100%", overflow: "auto", borderLeft: "1px solid", borderColor: "border.default", bg: "canvas.default", display: "flex", flexDirection: "column", children: [_jsxs(Box, { px: 3, py: 2, borderBottom: "1px solid", borderColor: "border.default", display: "flex", justifyContent: "space-between", alignItems: "center", bg: "canvas.subtle", flexShrink: 0, children: [_jsx(Text, { sx: { fontSize: 2, fontWeight: 'bold' }, children: span.span_name }), onClose && (_jsx(IconButton, { icon: XIcon, "aria-label": "Close detail panel", variant: "invisible", size: "small", onClick: onClose }))] }), _jsxs(UnderlineNav, { "aria-label": "Span detail tabs", children: [_jsx(UnderlineNav.Item, { "aria-current": activeTab === 'details' ? 'page' : undefined, onClick: () => setActiveTab('details'), children: "Details" }), tree && (_jsx(UnderlineNav.Item, { "aria-current": activeTab === 'tree' ? 'page' : undefined, onClick: () => setActiveTab('tree'), children: "Trace Tree" })), _jsx(UnderlineNav.Item, { "aria-current": activeTab === 'raw' ? 'page' : undefined, onClick: () => setActiveTab('raw'), children: "Raw Data" })] }), _jsxs(Box, { flex: 1, overflow: "auto", children: [activeTab === 'details' && (_jsxs(Box, { p: 3, children: [_jsx(MetadataRow, { label: "span_name", value: span.span_name }), _jsx(MetadataRow, { label: "service_name", value: span.service_name }), _jsx(MetadataRow, { label: "otel_scope_name", value: span.otel_scope_name }), _jsx(MetadataRow, { label: "kind", value: span.kind }), _jsx(MetadataRow, { label: "trace_id", value: span.trace_id, mono: true }), _jsx(MetadataRow, { label: "span_id", value: span.span_id, mono: true }), _jsx(MetadataRow, { label: "parent_span_id", value: span.parent_span_id, mono: true }), _jsx(MetadataRow, { label: "duration", value: formatDuration(span.duration_ms) }), _jsx(MetadataRow, { label: "start_time", value: span.start_time, mono: true }), _jsx(MetadataRow, { label: "end_time", value: span.end_time, mono: true }), _jsx(MetadataRow, { label: "status_code", value: span.status_code }), _jsx(MetadataRow, { label: "status_message", value: span.status_message }), _jsx(CollapsibleSection, { title: "Arguments", data: genAiAttrs, defaultOpen: Object.keys(genAiAttrs).length > 0 }), _jsx(CollapsibleSection, { title: "Attributes", data: otherAttrs }), span.events && span.events.length > 0 && (_jsxs(Box, { mt: 3, children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 1, children: [_jsx(Text, { sx: { fontWeight: 'bold', fontSize: 1 }, children: "Events" }), _jsx(CounterLabel, { children: span.events.length })] }), span.events.map((ev, idx) => (_jsxs(Box, { bg: "canvas.subtle", borderRadius: 2, border: "1px solid", borderColor: "border.default", p: 2, mt: 2, children: [_jsxs(Box, { display: "flex", gap: 2, children: [_jsx(Label, { children: ev.name }), _jsx(Text, { sx: {
110
+ return (_jsxs(Box, { height: "100%", overflow: "auto", borderLeft: "1px solid", borderColor: "border.default", bg: "canvas.default", display: "flex", flexDirection: "column", children: [_jsxs(Box, { px: 3, py: 2, borderBottom: "1px solid", borderColor: "border.default", display: "flex", justifyContent: "space-between", alignItems: "center", bg: "canvas.subtle", flexShrink: 0, children: [_jsx(Text, { sx: { fontSize: 2, fontWeight: 'bold' }, children: span.span_name }), onClose && (_jsx(IconButton, { icon: XIcon, "aria-label": "Close detail panel", variant: "invisible", size: "small", onClick: onClose }))] }), _jsxs(UnderlineNav, { "aria-label": "Span detail tabs", children: [_jsx(UnderlineNav.Item, { "aria-current": activeTab === 'details' ? 'page' : undefined, onClick: () => setActiveTab('details'), children: "Details" }), tree && (_jsx(UnderlineNav.Item, { "aria-current": activeTab === 'tree' ? 'page' : undefined, onClick: () => setActiveTab('tree'), children: "Trace Tree" })), _jsx(UnderlineNav.Item, { "aria-current": activeTab === 'raw' ? 'page' : undefined, onClick: () => setActiveTab('raw'), children: "Raw Data" })] }), _jsxs(Box, { flex: 1, overflow: "auto", children: [activeTab === 'details' && (_jsxs(Box, { p: 3, children: [_jsx(MetadataRow, { label: "span_name", value: span.span_name }), _jsx(MetadataRow, { label: "service_name", value: span.service_name }), _jsx(MetadataRow, { label: "otel_scope_name", value: span.otel_scope_name }), _jsx(MetadataRow, { label: "kind", value: span.kind }), _jsx(MetadataRow, { label: "trace_id", value: span.trace_id, mono: true }), _jsx(MetadataRow, { label: "span_id", value: span.span_id, mono: true }), _jsx(MetadataRow, { label: "parent_span_id", value: span.parent_span_id, mono: true }), _jsx(MetadataRow, { label: "duration", value: formatDuration(span.duration_ms) }), _jsx(MetadataRow, { label: "start_time", value: span.start_time, mono: true }), _jsx(MetadataRow, { label: "end_time", value: span.end_time, mono: true }), _jsx(MetadataRow, { label: "status_code", value: span.status_code }), _jsx(MetadataRow, { label: "status_message", value: span.status_message }), _jsx(CollapsibleSection, { title: "Arguments", data: genAiAttrs, defaultOpen: Object.keys(genAiAttrs).length > 0 }), _jsx(CollapsibleSection, { title: "Attributes", data: otherAttrs }), span.events && span.events.length > 0 && (_jsxs(Box, { mt: 3, children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 1, children: [_jsx(Text, { sx: { fontWeight: 'bold', fontSize: 1 }, children: "Events" }), _jsx(CounterLabel, { children: span.events.length })] }), span.events.map((ev, idx) => (_jsxs(Card, { border: true, shadow: "none", variant: "subtle", display: "block", p: 2, mt: 2, children: [_jsxs(Box, { display: "flex", gap: 2, children: [_jsx(Label, { children: ev.name }), _jsx(Text, { sx: {
111
111
  fontSize: 0,
112
112
  color: 'fg.muted',
113
113
  fontFamily: 'mono',
@@ -14,7 +14,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
14
  * Available tables: `spans`, `metrics`, `logs`.
15
15
  */
16
16
  import { useState, useCallback } from 'react';
17
- import { Box } from '@datalayer/primer-addons';
17
+ import { Box, Card } from '@datalayer/primer-addons';
18
18
  import { ActionList, ActionMenu, Button, Text, Textarea, Spinner, } from '@primer/react';
19
19
  import { HistoryIcon, TableIcon, XCircleIcon, DownloadIcon, } from '@primer/octicons-react';
20
20
  import { useOtelQuery } from '../hooks/index.js';
@@ -105,7 +105,7 @@ export const OtelSqlView = ({ token, baseUrl = '', }) => {
105
105
  }
106
106
  };
107
107
  const columns = rows.length > 0 ? Object.keys(rows[0]) : [];
108
- return (_jsxs(Box, { display: "flex", flexDirection: "column", flex: 1, minHeight: 0, height: "100%", gap: 3, p: 4, bg: "canvas.default", color: "fg.default", overflow: "auto", children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, flexWrap: "wrap", children: [_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Button, { size: "small", children: "Preset queries" }), _jsx(ActionMenu.Overlay, { width: "large", children: _jsx(ActionList, { children: PRESET_GROUPS.map(group => (_jsx(ActionList.Group, { title: group, children: PRESETS.filter(p => p.group === group).map(p => (_jsx(ActionList.Item, { onSelect: () => setSql(p.sql), children: p.label }, p.label))) }, group))) }) })] }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["\u2318 Enter to run \u00B7 Tables:", ' ', _jsx(Box, { as: "code", fontFamily: "mono", fontSize: 0, children: "spans" }), ', ', _jsx(Box, { as: "code", fontFamily: "mono", fontSize: 0, children: "metrics" }), ', ', _jsx(Box, { as: "code", fontFamily: "mono", fontSize: 0, children: "logs" })] })] }), _jsxs(Box, { display: "flex", flexDirection: "column", gap: 2, children: [_jsx(Textarea, { "aria-label": "SQL query", value: sql, onChange: e => setSql(e.target.value), onKeyDown: handleKeyDown, placeholder: "SELECT \u2026 FROM spans LIMIT 20", rows: 5, resize: "vertical", sx: { fontFamily: 'mono', fontSize: 1, width: '100%' } }), _jsxs(Box, { display: "flex", alignItems: "center", gap: 2, children: [_jsx(Button, { variant: "primary", onClick: handleRun, disabled: loading, size: "small", children: loading ? 'Running…' : 'Run' }), loading && _jsx(Spinner, { size: "small" })] })] }), error && (_jsx(Box, { p: 3, bg: "danger.subtle", borderRadius: 2, border: "1px solid", borderColor: "danger.muted", children: _jsx(Text, { sx: { color: 'danger.fg', fontFamily: 'mono', fontSize: 1 }, children: error }) })), !error && rows.length > 0 && (_jsxs(Box, { children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 1, mb: 1, children: [_jsx(TableIcon, { size: 14 }), _jsx(Text, { sx: { fontSize: 1, fontWeight: 'bold', color: 'fg.default' }, children: "Results" }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted', ml: 1 }, children: ["(", rows.length, " row", rows.length !== 1 ? 's' : '', ")"] }), _jsxs(Box, { as: "button", onClick: clear, title: "Clear results", ml: "auto", display: "flex", alignItems: "center", gap: 1, px: 2, py: "2px", border: "none", borderRadius: 2, bg: "transparent", color: "fg.muted", cursor: "pointer", fontSize: 0, hover: { color: 'danger.fg', bg: 'danger.subtle' }, children: [_jsx(XCircleIcon, { size: 12 }), _jsx(Text, { sx: { fontSize: 0 }, children: "Clear" })] }), _jsxs(Box, { as: "button", onClick: () => {
108
+ return (_jsxs(Box, { display: "flex", flexDirection: "column", flex: 1, minHeight: 0, height: "100%", gap: 3, p: 4, bg: "canvas.default", color: "fg.default", overflow: "auto", children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, flexWrap: "wrap", children: [_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Button, { size: "small", children: "Preset queries" }), _jsx(ActionMenu.Overlay, { width: "large", children: _jsx(ActionList, { children: PRESET_GROUPS.map(group => (_jsx(ActionList.Group, { title: group, children: PRESETS.filter(p => p.group === group).map(p => (_jsx(ActionList.Item, { onSelect: () => setSql(p.sql), children: p.label }, p.label))) }, group))) }) })] }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["\u2318 Enter to run \u00B7 Tables:", ' ', _jsx(Box, { as: "code", fontFamily: "mono", fontSize: 0, children: "spans" }), ', ', _jsx(Box, { as: "code", fontFamily: "mono", fontSize: 0, children: "metrics" }), ', ', _jsx(Box, { as: "code", fontFamily: "mono", fontSize: 0, children: "logs" })] })] }), _jsxs(Box, { display: "flex", flexDirection: "column", gap: 2, children: [_jsx(Textarea, { "aria-label": "SQL query", value: sql, onChange: e => setSql(e.target.value), onKeyDown: handleKeyDown, placeholder: "SELECT \u2026 FROM spans LIMIT 20", rows: 5, resize: "vertical", sx: { fontFamily: 'mono', fontSize: 1, width: '100%' } }), _jsxs(Box, { display: "flex", alignItems: "center", gap: 2, children: [_jsx(Button, { variant: "primary", onClick: handleRun, disabled: loading, size: "small", children: loading ? 'Running…' : 'Run' }), loading && _jsx(Spinner, { size: "small" })] })] }), error && (_jsx(Card, { border: true, shadow: "none", display: "block", p: 3, bg: "danger.subtle", borderColor: "danger.muted", children: _jsx(Text, { sx: { color: 'danger.fg', fontFamily: 'mono', fontSize: 1 }, children: error }) })), !error && rows.length > 0 && (_jsxs(Box, { children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 1, mb: 1, children: [_jsx(TableIcon, { size: 14 }), _jsx(Text, { sx: { fontSize: 1, fontWeight: 'bold', color: 'fg.default' }, children: "Results" }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted', ml: 1 }, children: ["(", rows.length, " row", rows.length !== 1 ? 's' : '', ")"] }), _jsxs(Box, { as: "button", onClick: clear, title: "Clear results", ml: "auto", display: "flex", alignItems: "center", gap: 1, px: 2, py: "2px", border: "none", borderRadius: 2, bg: "transparent", color: "fg.muted", cursor: "pointer", fontSize: 0, hover: { color: 'danger.fg', bg: 'danger.subtle' }, children: [_jsx(XCircleIcon, { size: 12 }), _jsx(Text, { sx: { fontSize: 0 }, children: "Clear" })] }), _jsxs(Box, { as: "button", onClick: () => {
109
109
  const header = columns.join(',');
110
110
  const body = rows
111
111
  .map(row => columns
@@ -128,12 +128,12 @@ export const OtelSqlView = ({ token, baseUrl = '', }) => {
128
128
  a.download = 'otel-query-result.csv';
129
129
  a.click();
130
130
  URL.revokeObjectURL(url);
131
- }, title: "Download as CSV", display: "flex", alignItems: "center", gap: 1, px: 2, py: "2px", border: "none", borderRadius: 2, bg: "transparent", color: "fg.muted", cursor: "pointer", fontSize: 0, hover: { color: 'accent.fg', bg: 'accent.subtle' }, children: [_jsx(DownloadIcon, { size: 12 }), _jsx(Text, { sx: { fontSize: 0 }, children: "CSV" })] })] }), _jsx(Box, { flex: 1, minHeight: 200, overflow: "auto", border: "1px solid", borderColor: "border.default", borderRadius: 2, children: _jsxs(Box, { as: "table", width: "100%", borderCollapse: "collapse", children: [_jsx(Box, { as: "thead", children: _jsx(Box, { as: "tr", bg: "canvas.subtle", children: columns.map(col => (_jsx(Box, { as: "th", px: 3, py: 2, textAlign: "left", fontWeight: "bold", fontSize: 0, color: "fg.muted", borderBottom: "1px solid", borderColor: "border.default", whiteSpace: "nowrap", children: col }, col))) }) }), _jsx(Box, { as: "tbody", children: rows.map((row, ri) => (_jsx(Box, { as: "tr", hover: { bg: 'canvas.subtle' }, sx: {
131
+ }, title: "Download as CSV", display: "flex", alignItems: "center", gap: 1, px: 2, py: "2px", border: "none", borderRadius: 2, bg: "transparent", color: "fg.muted", cursor: "pointer", fontSize: 0, hover: { color: 'accent.fg', bg: 'accent.subtle' }, children: [_jsx(DownloadIcon, { size: 12 }), _jsx(Text, { sx: { fontSize: 0 }, children: "CSV" })] })] }), _jsx(Card, { border: true, shadow: "none", display: "block", flex: 1, minHeight: 200, overflow: "auto", children: _jsxs(Box, { as: "table", width: "100%", borderCollapse: "collapse", children: [_jsx(Box, { as: "thead", children: _jsx(Box, { as: "tr", bg: "canvas.subtle", children: columns.map(col => (_jsx(Box, { as: "th", px: 3, py: 2, textAlign: "left", fontWeight: "bold", fontSize: 0, color: "fg.muted", borderBottom: "1px solid", borderColor: "border.default", whiteSpace: "nowrap", children: col }, col))) }) }), _jsx(Box, { as: "tbody", children: rows.map((row, ri) => (_jsx(Box, { as: "tr", hover: { bg: 'canvas.subtle' }, sx: {
132
132
  '&:not(:last-child) td': {
133
133
  borderBottom: '1px solid',
134
134
  borderColor: 'border.muted',
135
135
  },
136
- }, children: columns.map(col => (_jsx(Box, { as: "td", px: 3, py: 2, fontSize: 0, fontFamily: "mono", maxWidth: "300px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: String(row[col] ?? '') }, col))) }, ri))) })] }) })] })), !error && !loading && rows.length === 0 && (_jsx(Box, { flex: 1, display: "flex", alignItems: "center", justifyContent: "center", color: "fg.muted", children: _jsx(Text, { sx: { fontSize: 1 }, children: "Run a query to see results." }) })), history.length > 0 && (_jsxs(Box, { children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 1, mb: 1, children: [_jsx(HistoryIcon, { size: 12 }), _jsx(Text, { sx: { fontSize: 0, fontWeight: 'bold', color: 'fg.muted' }, children: "History" })] }), _jsx(Box, { display: "flex", flexDirection: "column", border: "1px solid", borderColor: "border.default", borderRadius: 2, overflow: "hidden", children: history.map((entry, i) => (_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, px: 3, py: "6px", cursor: "pointer", bg: i % 2 === 0 ? 'canvas.default' : 'canvas.subtle', borderTop: i > 0 ? '1px solid' : 'none', borderColor: "border.muted", hover: { bg: 'accent.subtle' }, onClick: () => setSql(entry), children: [_jsx(Text, { sx: {
136
+ }, children: columns.map(col => (_jsx(Box, { as: "td", px: 3, py: 2, fontSize: 0, fontFamily: "mono", maxWidth: "300px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: String(row[col] ?? '') }, col))) }, ri))) })] }) })] })), !error && !loading && rows.length === 0 && (_jsx(Box, { flex: 1, display: "flex", alignItems: "center", justifyContent: "center", color: "fg.muted", children: _jsx(Text, { sx: { fontSize: 1 }, children: "Run a query to see results." }) })), history.length > 0 && (_jsxs(Box, { children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 1, mb: 1, children: [_jsx(HistoryIcon, { size: 12 }), _jsx(Text, { sx: { fontSize: 0, fontWeight: 'bold', color: 'fg.muted' }, children: "History" })] }), _jsx(Card, { border: true, shadow: "none", display: "flex", flexDirection: "column", overflow: "hidden", children: history.map((entry, i) => (_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, px: 3, py: "6px", cursor: "pointer", bg: i % 2 === 0 ? 'canvas.default' : 'canvas.subtle', borderTop: i > 0 ? '1px solid' : 'none', borderColor: "border.muted", hover: { bg: 'accent.subtle' }, onClick: () => setSql(entry), children: [_jsx(Text, { sx: {
137
137
  fontFamily: 'mono',
138
138
  fontSize: 0,
139
139
  color: 'fg.default',
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Box } from '@datalayer/primer-addons';
2
+ import { Box, Card } from '@datalayer/primer-addons';
3
3
  import { Button, Spinner, Text } from '@primer/react';
4
4
  import { SyncIcon } from '@primer/octicons-react';
5
5
  import { useOtelSystem } from '../hooks/index.js';
@@ -41,5 +41,5 @@ const SystemViewContent = ({ data }) => {
41
41
  };
42
42
  export const OtelSystemView = ({ baseUrl = '', token, }) => {
43
43
  const { data, loading, error, refresh } = useOtelSystem({ baseUrl, token });
44
- return (_jsxs(Box, { display: "flex", flexDirection: "column", flex: 1, minHeight: 0, overflow: "auto", p: 3, children: [_jsxs(Box, { display: "flex", alignItems: "center", justifyContent: "space-between", mb: 3, flexShrink: 0, children: [_jsx(Text, { sx: { fontSize: 2, fontWeight: 'bold' }, children: "System Statistics" }), _jsx(Button, { size: "small", variant: "invisible", onClick: refresh, disabled: loading, leadingVisual: SyncIcon, children: "Refresh" })] }), loading && (_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, children: [_jsx(Spinner, { size: "small" }), _jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "Loading\u2026" })] })), !loading && error && (_jsx(Box, { bg: "danger.subtle", border: "1px solid", borderColor: "danger.muted", borderRadius: 2, p: 3, children: _jsx(Text, { sx: { color: 'danger.fg', fontSize: 1 }, children: error }) })), !loading && !error && data && _jsx(SystemViewContent, { data: data }), !loading && !error && !data && (_jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "No data available." }))] }));
44
+ return (_jsxs(Box, { display: "flex", flexDirection: "column", flex: 1, minHeight: 0, overflow: "auto", p: 3, children: [_jsxs(Box, { display: "flex", alignItems: "center", justifyContent: "space-between", mb: 3, flexShrink: 0, children: [_jsx(Text, { sx: { fontSize: 2, fontWeight: 'bold' }, children: "System Statistics" }), _jsx(Button, { size: "small", variant: "invisible", onClick: refresh, disabled: loading, leadingVisual: SyncIcon, children: "Refresh" })] }), loading && (_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, children: [_jsx(Spinner, { size: "small" }), _jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "Loading\u2026" })] })), !loading && error && (_jsx(Card, { border: true, shadow: "none", display: "block", bg: "danger.subtle", borderColor: "danger.muted", p: 3, children: _jsx(Text, { sx: { color: 'danger.fg', fontSize: 1 }, children: error }) })), !loading && !error && data && _jsx(SystemViewContent, { data: data }), !loading && !error && !data && (_jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "No data available." }))] }));
45
45
  };
@@ -17,7 +17,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
17
17
  * @module otel/OtelTimelineRangeSlider
18
18
  */
19
19
  import { useCallback, useRef, useState, useMemo } from 'react';
20
- import { Box } from '@datalayer/primer-addons';
20
+ import { Box, Card } from '@datalayer/primer-addons';
21
21
  import { Text } from '@primer/react';
22
22
  // ── Helpers ─────────────────────────────────────────────────────────
23
23
  function clamp(v, min, max) {
@@ -180,7 +180,7 @@ export const OtelTimelineRangeSlider = ({ timelineStart, timelineEnd, selectedSt
180
180
  const barWidth = 100 / (histogram.length || 1);
181
181
  const barH = (h.count / histMax) * 100;
182
182
  return (_jsx(Box, { position: "absolute", bottom: 0, left: `${pct}%`, width: `${barWidth}%`, height: `${barH}%`, bg: "accent.fg", opacity: 0.35, borderRadius: "1px 1px 0 0", pointerEvents: "none", zIndex: 4 }, i));
183
- }), _jsx(Box, { onPointerDown: e => handlePointerDown(e, 'start'), position: "absolute", top: 0, left: `${startPct}%`, width: HANDLE_WIDTH, height: HANDLE_HEIGHT, ml: `-${HANDLE_WIDTH / 2}px`, bg: "accent.fg", borderRadius: 1, cursor: "ew-resize", zIndex: 3, display: "flex", alignItems: "center", justifyContent: "center", transition: isDragging ? 'none' : 'left 0.05s ease', boxShadow: "shadow.medium", hover: { bg: 'accent.emphasis' }, children: _jsx(Box, { width: "2px", height: "40%", bg: "fg.onEmphasis", borderRadius: 1 }) }), _jsx(Box, { onPointerDown: e => handlePointerDown(e, 'end'), position: "absolute", top: 0, left: `${endPct}%`, width: HANDLE_WIDTH, height: HANDLE_HEIGHT, ml: `-${HANDLE_WIDTH / 2}px`, bg: "accent.fg", borderRadius: 1, cursor: "ew-resize", zIndex: 3, display: "flex", alignItems: "center", justifyContent: "center", transition: isDragging ? 'none' : 'left 0.05s ease', boxShadow: "shadow.medium", hover: { bg: 'accent.emphasis' }, children: _jsx(Box, { width: "2px", height: "40%", bg: "fg.onEmphasis", borderRadius: 1 }) }), hoverPct !== null && !isDragging && (_jsxs(_Fragment, { children: [_jsx(Box, { position: "absolute", top: 0, left: `${hoverPct}%`, width: "1px", height: RAIL_HEIGHT, borderLeft: "1px dashed", borderColor: "fg.default", opacity: 0.55, pointerEvents: "none", zIndex: 5 }), _jsx(Box, { position: "absolute", top: -36, left: `${hoverPct}%`, transform: "translateX(-50%)", bg: "canvas.overlay", border: "1px solid", borderColor: "border.default", borderRadius: 2, px: 2, py: "2px", pointerEvents: "none", zIndex: 10, boxShadow: "shadow.medium", whiteSpace: "nowrap", children: _jsx(Text, { sx: {
183
+ }), _jsx(Box, { onPointerDown: e => handlePointerDown(e, 'start'), position: "absolute", top: 0, left: `${startPct}%`, width: HANDLE_WIDTH, height: HANDLE_HEIGHT, ml: `-${HANDLE_WIDTH / 2}px`, bg: "accent.fg", borderRadius: 1, cursor: "ew-resize", zIndex: 3, display: "flex", alignItems: "center", justifyContent: "center", transition: isDragging ? 'none' : 'left 0.05s ease', boxShadow: "shadow.medium", hover: { bg: 'accent.emphasis' }, children: _jsx(Box, { width: "2px", height: "40%", bg: "fg.onEmphasis", borderRadius: 1 }) }), _jsx(Box, { onPointerDown: e => handlePointerDown(e, 'end'), position: "absolute", top: 0, left: `${endPct}%`, width: HANDLE_WIDTH, height: HANDLE_HEIGHT, ml: `-${HANDLE_WIDTH / 2}px`, bg: "accent.fg", borderRadius: 1, cursor: "ew-resize", zIndex: 3, display: "flex", alignItems: "center", justifyContent: "center", transition: isDragging ? 'none' : 'left 0.05s ease', boxShadow: "shadow.medium", hover: { bg: 'accent.emphasis' }, children: _jsx(Box, { width: "2px", height: "40%", bg: "fg.onEmphasis", borderRadius: 1 }) }), hoverPct !== null && !isDragging && (_jsxs(_Fragment, { children: [_jsx(Box, { position: "absolute", top: 0, left: `${hoverPct}%`, width: "1px", height: RAIL_HEIGHT, borderLeft: "1px dashed", borderColor: "fg.default", opacity: 0.55, pointerEvents: "none", zIndex: 5 }), _jsx(Card, { border: true, shadow: "none", display: "block", position: "absolute", top: -36, left: `${hoverPct}%`, transform: "translateX(-50%)", bg: "canvas.overlay", px: 2, py: "2px", pointerEvents: "none", zIndex: 10, boxShadow: "shadow.medium", whiteSpace: "nowrap", children: _jsx(Text, { sx: {
184
184
  fontSize: '11px',
185
185
  color: 'fg.default',
186
186
  fontFamily: 'mono',
@@ -14,7 +14,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
14
  * @module otel/OtelTracesList
15
15
  */
16
16
  import { useMemo, useCallback, useState } from 'react';
17
- import { Box } from '@datalayer/primer-addons';
17
+ import { Box, Card } from '@datalayer/primer-addons';
18
18
  import { Text, Label, Spinner } from '@primer/react';
19
19
  import { Blankslate } from '@primer/react/experimental';
20
20
  import { TelescopeIcon, ChevronRightIcon, ChevronDownIcon, } from '@primer/octicons-react';
@@ -123,7 +123,7 @@ export const OtelTracesList = ({ spans, loading, selectedSpanId, onSelectSpan, e
123
123
  const usage = getTokenUsage(span);
124
124
  if (!usage)
125
125
  return null;
126
- return (_jsxs(Box, { display: "inline-flex", alignItems: "center", gap: "2px", border: "1px solid", borderColor: "border.default", borderRadius: 2, px: 1, fontSize: "10px", fontFamily: "mono", color: "fg.muted", flexShrink: 0, lineHeight: "16px", children: [usage.input != null && (_jsxs(Box, { display: "inline-flex", alignItems: "center", gap: "1px", children: [_jsx(Text, { sx: { fontSize: '9px', color: 'fg.subtle' }, children: "\u2197" }), _jsx(Text, { children: usage.input })] })), usage.output != null && (_jsxs(Box, { display: "inline-flex", alignItems: "center", gap: "1px", children: [_jsx(Text, { sx: { fontSize: '9px', color: 'fg.subtle' }, children: "\u2199" }), _jsx(Text, { children: usage.output })] }))] }));
126
+ return (_jsxs(Card, { border: true, shadow: "none", display: "inline-flex", alignItems: "center", gap: "2px", px: 1, fontSize: "10px", fontFamily: "mono", color: "fg.muted", flexShrink: 0, lineHeight: "16px", children: [usage.input != null && (_jsxs(Box, { display: "inline-flex", alignItems: "center", gap: "1px", children: [_jsx(Text, { sx: { fontSize: '9px', color: 'fg.subtle' }, children: "\u2197" }), _jsx(Text, { children: usage.input })] })), usage.output != null && (_jsxs(Box, { display: "inline-flex", alignItems: "center", gap: "1px", children: [_jsx(Text, { sx: { fontSize: '9px', color: 'fg.subtle' }, children: "\u2199" }), _jsx(Text, { children: usage.output })] }))] }));
127
127
  })(), description && (_jsx(Text, { sx: {
128
128
  fontSize: 1,
129
129
  color: 'fg.muted',
@@ -5,6 +5,23 @@
5
5
  import { createStore } from 'zustand/vanilla';
6
6
  import { useStore } from 'zustand';
7
7
  import { registerSessionState } from '../sessionEnd.js';
8
+ /**
9
+ * Whether an update is about the entity the layout holds. An update naming
10
+ * another space or organization (by `uid`, else `id`) replaces the one held:
11
+ * merged over it, the fields the new one does not carry (an organization's
12
+ * handle, a team) stayed from the one left. An update naming none, or one
13
+ * naming the same, is merged onto what is held.
14
+ */
15
+ const entityUid = (entity) => {
16
+ const record = entity;
17
+ const uid = record?.uid ?? record?.id;
18
+ return typeof uid === 'string' && uid.trim() ? uid.trim() : undefined;
19
+ };
20
+ const sameEntity = (held, update) => {
21
+ const heldUid = entityUid(held);
22
+ const updateUid = entityUid(update);
23
+ return !heldUid || !updateUid || heldUid === updateUid;
24
+ };
8
25
  /**
9
26
  * Name of the cookie holding the space last selected by the user.
10
27
  *
@@ -25,7 +42,9 @@ const readSpaceContextCookie = () => {
25
42
  }
26
43
  try {
27
44
  const parsed = JSON.parse(decodeURIComponent(match[1]));
28
- if (!parsed || typeof parsed !== 'object' || typeof parsed.id !== 'string') {
45
+ if (!parsed ||
46
+ typeof parsed !== 'object' ||
47
+ typeof parsed.id !== 'string') {
29
48
  return undefined;
30
49
  }
31
50
  return {
@@ -102,7 +121,9 @@ export const layoutStore = createStore((set, get) => ({
102
121
  return {
103
122
  organization: organization
104
123
  ? {
105
- ...state.organization,
124
+ ...(sameEntity(state.organization, organization)
125
+ ? state.organization
126
+ : undefined),
106
127
  ...organization,
107
128
  }
108
129
  : undefined,
@@ -121,7 +142,7 @@ export const layoutStore = createStore((set, get) => ({
121
142
  updateLayoutSpace: (space) => set((state) => {
122
143
  const next = space
123
144
  ? {
124
- ...state.space,
145
+ ...(sameEntity(state.space, space) ? state.space : undefined),
125
146
  ...space,
126
147
  }
127
148
  : undefined;
@@ -15,7 +15,7 @@
15
15
  * `datalayer-research` organization, at `/research` (the public papers, read
16
16
  * signed in, at `/research/papers/…` beside them).
17
17
  */
18
- export type NavigationView = 'studio' | 'home' | 'agentify' | 'research' | 'admin' | 'apps';
18
+ export type NavigationView = 'studio' | 'home' | 'spaces' | 'agentify' | 'research' | 'admin' | 'apps';
19
19
  /** The shells, and where each one opens when it has not been visited. */
20
20
  export declare const NAVIGATION_VIEW_HOMES: Record<NavigationView, string>;
21
21
  /** Well-known view identifiers for {@link NavigationState.tabsByView}. */
@@ -77,13 +77,21 @@ export declare function isRememberableRoute(route: string): boolean;
77
77
  */
78
78
  export declare function isHostedAppRoute(route: string): boolean;
79
79
  /**
80
- * Whether a page belongs to no shell: the settings, the documentation and
81
- * the decks.
80
+ * Whether a page belongs to no shell: the settings, the documentation, the
81
+ * presentations and the agentspecs catalogue (since 8 November 2026: reached
82
+ * from an agent's picker on /agents/new, its env var's page swapped the
83
+ * Sessions sidebar for the Studio's).
82
84
  *
83
85
  * They are read from whichever shell the reader is in — its sidebar stays
84
86
  * beside them and the header's selector does not move — so they are never a
85
87
  * shell's own page: opening one does not change the shell, and no shell
86
88
  * returns to one.
89
+ *
90
+ * The presentations are `/decks/<collection>/<slug>` and their list,
91
+ * `/presentations`. Bare `/decks` is not one of them: it is the selected
92
+ * space's decks table, a page of the Spaces shell (the user, 12 November
93
+ * 2026). Neutral, it took the current shell whenever one was given, and the
94
+ * Sessions sidebar stayed beside it.
87
95
  */
88
96
  export declare function isShellNeutralRoute(route: string): boolean;
89
97
  /**