@theoremjs/react 0.3.4-next.608.gd007c52 → 0.3.4-next.612.g21d312d

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theoremjs/react",
3
- "version": "0.3.4-next.608.gd007c52",
3
+ "version": "0.3.4-next.612.g21d312d",
4
4
  "description": "React bindings for Theorem: headless hooks + transport, an Astryx chat UI, and a Web-standard server handler.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -60,8 +60,8 @@
60
60
  "@stylexjs/stylex": "0.19.0",
61
61
  "react": "^19.0.0",
62
62
  "react-dom": "^19.0.0",
63
- "@theoremjs/agents": "^0.3.4-next.608.gd007c52",
64
- "@theoremjs/web": "^0.3.4-next.608.gd007c52"
63
+ "@theoremjs/agents": "^0.3.4-next.612.g21d312d",
64
+ "@theoremjs/web": "^0.3.4-next.612.g21d312d"
65
65
  },
66
66
  "peerDependenciesMeta": {
67
67
  "@astryxdesign/core": {
package/ui/CardWall.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { VStack } from '@astryxdesign/core/VStack';
3
3
  import { Children, isValidElement, useLayoutEffect, useRef, useState } from 'react';
4
+ import { flushSync } from 'react-dom';
4
5
  import { columnCount, wallPlaces } from "./wall-columns.js";
5
6
  function sameWall(a, b) {
6
7
  return (a.width === b.width &&
@@ -22,6 +23,21 @@ export function CardWall({ minWidth, children }) {
22
23
  const wallRef = useRef(null);
23
24
  const slots = useRef([]);
24
25
  const observer = useRef(null);
26
+ const slotRefs = useRef([]);
27
+ // why: One callback per place, so a re-render does not detach and watch every slot again.
28
+ const slotRef = (at) => {
29
+ slotRefs.current[at] ??= (slot) => {
30
+ slots.current[at] = slot;
31
+ // why: A card that mounts later is measured once it is watched.
32
+ if (slot)
33
+ observer.current?.observe(slot);
34
+ return () => {
35
+ if (slot)
36
+ observer.current?.unobserve(slot);
37
+ };
38
+ };
39
+ return slotRefs.current[at];
40
+ };
25
41
  const [wall, setWall] = useState({ width: 0, gap: 0, heights: [] });
26
42
  const count = columnCount(wall.width, minWidth, wall.gap);
27
43
  const columnWidth = (wall.width - wall.gap * (count - 1)) / count;
@@ -33,16 +49,26 @@ export function CardWall({ minWidth, children }) {
33
49
  const node = wallRef.current;
34
50
  if (!node)
35
51
  return;
36
- const measure = () => {
52
+ const sizes = new WeakMap();
53
+ let width = 0;
54
+ // why: Sizes come only from the observer, after the browser's own layout: reading them here
55
+ // would force a layout, and inside a closed disclosure lay out what is hidden.
56
+ const watch = new ResizeObserver((entries) => {
57
+ for (const entry of entries) {
58
+ const box = entry.borderBoxSize[0];
59
+ if (entry.target === node)
60
+ width = entry.contentBoxSize[0]?.inlineSize ?? width;
61
+ else if (box)
62
+ sizes.set(entry.target, box.blockSize);
63
+ }
37
64
  const next = {
38
- width: node.clientWidth,
65
+ width,
39
66
  gap: Number.parseFloat(getComputedStyle(node).rowGap) || 0,
40
- heights: slots.current.map((slot) => slot?.offsetHeight),
67
+ heights: slots.current.map((slot) => (slot ? sizes.get(slot) : undefined)),
41
68
  };
42
- setWall((shown) => (sameWall(shown, next) ? shown : next));
43
- };
44
- measure();
45
- const watch = new ResizeObserver(measure);
69
+ // why: Placed in the same frame it was measured, so no frame shows the cards unplaced.
70
+ flushSync(() => setWall((shown) => (sameWall(shown, next) ? shown : next)));
71
+ });
46
72
  watch.observe(node);
47
73
  for (const slot of slots.current)
48
74
  if (slot)
@@ -55,16 +81,7 @@ export function CardWall({ minWidth, children }) {
55
81
  }, []);
56
82
  return (_jsx(VStack, { ref: wallRef, gap: 3, style: isPlaced ? { position: 'relative', height } : undefined, children: cards.map((card, at) => {
57
83
  const place = places[at];
58
- return (_jsx("div", { ref: (slot) => {
59
- slots.current[at] = slot;
60
- // why: A card that mounts later is measured once it is watched.
61
- if (slot)
62
- observer.current?.observe(slot);
63
- return () => {
64
- if (slot)
65
- observer.current?.unobserve(slot);
66
- };
67
- }, style: isPlaced && place
84
+ return (_jsx("div", { ref: slotRef(at), style: isPlaced && place
68
85
  ? {
69
86
  position: 'absolute',
70
87
  top: place.top,
@@ -6,6 +6,7 @@ import { HStack } from '@astryxdesign/core/HStack';
6
6
  import { useLocale } from '@astryxdesign/core/i18n';
7
7
  import { Layout, LayoutContent } from '@astryxdesign/core/Layout';
8
8
  import { useResizable } from '@astryxdesign/core/Resizable';
9
+ import { percent, pixel } from '@astryxdesign/core/Resizable/utils';
9
10
  import { SegmentedControl, SegmentedControlItem } from '@astryxdesign/core/SegmentedControl';
10
11
  import { Text } from '@astryxdesign/core/Text';
11
12
  import { TextArea } from '@astryxdesign/core/TextArea';
@@ -79,7 +80,8 @@ function ConsoleColumns({ requestLabel = 'Request', layoutRef, children, }) {
79
80
  const requestPanel = useResizable({
80
81
  defaultSize: basisWidth > 0 ? Math.round(basisWidth * 0.4) : '40%',
81
82
  minSize: 280,
82
- maxSize: '70%',
83
+ // why: In a narrow pair 70% is below the minimum; the maximum never is.
84
+ maxSize: percent(70, { min: pixel(280) }),
83
85
  containerRef: pairRef,
84
86
  });
85
87
  const [request, response] = Children.toArray(children);
package/ui/SidePanel.js CHANGED
@@ -4,6 +4,7 @@ import { HStack } from '@astryxdesign/core/HStack';
4
4
  import { IconButton } from '@astryxdesign/core/IconButton';
5
5
  import { Layout, LayoutContent, LayoutHeader, LayoutPanel } from '@astryxdesign/core/Layout';
6
6
  import { ResizeHandle, useResizable, } from '@astryxdesign/core/Resizable';
7
+ import { percent, pixel } from '@astryxdesign/core/Resizable/utils';
7
8
  import { ScrollableArea } from '@astryxdesign/core/ScrollableArea';
8
9
  import { StackItem } from '@astryxdesign/core/Stack';
9
10
  import { Text } from '@astryxdesign/core/Text';
@@ -13,12 +14,12 @@ import { useCallback, useId, useState } from 'react';
13
14
  /**
14
15
  * `useResizable` sizing for side panels: a third of the layout (pass the
15
16
  * Layout as `containerRef` so the percentages resolve), dragged between a
16
- * readable minimum and half.
17
+ * readable minimum and half, or the minimum when half is less.
17
18
  */
18
19
  const SIDE_PANEL_SIZING = {
19
20
  defaultSize: '33%',
20
21
  minSize: 280,
21
- maxSize: '50%',
22
+ maxSize: percent(50, { min: pixel(280) }),
22
23
  };
23
24
  /** Whether a side panel is wide enough for two readable columns: twice its minimum. */
24
25
  export function isSidePanelWide(size) {
package/ui/ToolResult.js CHANGED
@@ -32,7 +32,7 @@ function ResultLead({ layout, parts = [], }) {
32
32
  if (!hasLead(layout, media))
33
33
  return null;
34
34
  const { images, audio } = media;
35
- return (_jsxs(VStack, { gap: 3, children: [_jsxs(CardWall, { minWidth: CARD_PX, children: [layout.figures.map((figure) => (_jsx(Arrive, { children: _jsx(ChartCard, { title: figure.label, value: withUnit(decimal.format(figure.value), figure.unit) }) }, `figure:${figure.key}`))), layout.charts.map((chart) => (_jsx(Arrive, { children: _jsx(Suspense, { fallback: _jsx(Skeleton, { height: CHART_CARD_PX }), children: _jsx(ToolResultChart, { chart: chart }) }) }, `chart:${chart.key}`))), images.map((image, index) => (_jsx(Arrive, { children: _jsx(ClickableCard, { label: image.alt || 'Open image', padding: 0, onClick: () => setOpen(index), children: _jsx(AspectRatio, { ratio: 4 / 3, fit: "cover", children: _jsx("img", { src: image.src, alt: image.alt, loading: "lazy" }) }) }) }, `image:${image.src}`)))] }), images.length > 0 ? (_jsx(Lightbox, { isOpen: open !== null, onOpenChange: (isOpen) => {
35
+ return (_jsxs(VStack, { gap: 3, children: [_jsxs(CardWall, { minWidth: CARD_PX, children: [layout.figures.map((figure) => (_jsx(Arrive, { children: _jsx(ChartCard, { title: figure.label, value: withUnit(decimal.format(figure.value), figure.unit) }) }, `figure:${figure.key}`))), layout.charts.map((chart) => (_jsx(Suspense, { fallback: _jsx(Arrive, { children: _jsx(Skeleton, { height: CHART_CARD_PX }) }), children: _jsx(Arrive, { children: _jsx(ToolResultChart, { chart: chart }) }) }, `chart:${chart.key}`))), images.map((image, index) => (_jsx(Arrive, { children: _jsx(ClickableCard, { label: image.alt || 'Open image', padding: 0, onClick: () => setOpen(index), children: _jsx(AspectRatio, { ratio: 4 / 3, fit: "cover", children: _jsx("img", { src: image.src, alt: image.alt, loading: "lazy" }) }) }) }, `image:${image.src}`)))] }), images.length > 0 ? (_jsx(Lightbox, { isOpen: open !== null, onOpenChange: (isOpen) => {
36
36
  if (!isOpen)
37
37
  setOpen(null);
38
38
  }, media: images.map((image) => ({ src: image.src, alt: image.alt })), index: open ?? 0, onIndexChange: setOpen })) : null, keyedByContent(audio, (clip) => clip.src).map(({ item: clip, key }) => (_jsx(Arrive, { children: _jsx(VoiceNote, { src: clip.src, mimeType: clip.mimeType }) }, key)))] }));
@@ -1,24 +1,13 @@
1
- import { type TraceLatency, type TraceTimeSplit, type TraceTurn } from '../client/trace-story.ts';
2
- import { type TraceNode, type TraceTotals } from '../client/trace-view.ts';
3
- /**
4
- * A turn's figures, then where its time, tokens, and slowest steps went, in
5
- * one wall. A bar opens its span.
6
- */
7
- export declare function TraceCharts({ root, split, totals, tools, latency, onSelect, }: {
1
+ import { type TraceTimeSplit, type TraceTurn } from '../client/trace-story.ts';
2
+ import { type TraceNode } from '../client/trace-view.ts';
3
+ /** Where a turn's time, tokens, and slowest steps went. A bar opens its span. */
4
+ export declare function TraceCharts({ root, split, onSelect, }: {
8
5
  root: TraceNode;
9
6
  split: TraceTimeSplit;
10
- totals: TraceTotals;
11
- tools: number;
12
- latency?: TraceLatency;
13
7
  onSelect: (node: TraceNode) => void;
14
8
  }): import("react").JSX.Element;
15
- /**
16
- * The whole conversation: its figures, then each turn's time and tokens, in
17
- * one wall. A column opens its turn.
18
- */
19
- export declare function ConversationCharts({ turns, totals, tools, onPick, }: {
9
+ /** The whole conversation: each turn's time and tokens. A column opens its turn. */
10
+ export declare function ConversationCharts({ turns, onPick, }: {
20
11
  turns: readonly TraceTurn[];
21
- totals: TraceTotals;
22
- tools: number;
23
12
  onPick: (index: number) => void;
24
13
  }): import("react").JSX.Element;
package/ui/TraceCharts.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Grid } from '@astryxdesign/core/Grid';
2
3
  import { HStack } from '@astryxdesign/core/HStack';
3
4
  import { Text } from '@astryxdesign/core/Text';
4
5
  import { VStack } from '@astryxdesign/core/VStack';
@@ -6,8 +7,7 @@ import { useMemo } from 'react';
6
7
  import { Bar, BarChart, Cell, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts';
7
8
  import { isCallTrace, traceActor, } from "../client/trace-story.js";
8
9
  import { traceSpans } from "../client/trace-view.js";
9
- import { CardWall } from "./CardWall.js";
10
- import { ChartFrame, ChartTip, FIRST_WIDTH_PX, TRACE_CARD_PX, turnStats, } from "./TraceOverview.js";
10
+ import { ChartFrame, ChartTip, FIRST_WIDTH_PX } from "./TraceOverview.js";
11
11
  import { useTraceFormat } from "./TraceValues.js";
12
12
  import { ACTOR_FILL, ChartCard, TRACE_GAP, TRACE_RADIUS, TraceSwatch, traceFill, } from "./trace-patterns.js";
13
13
  /** Height of each column chart's plot, in pixels. */
@@ -206,14 +206,11 @@ function SlowestCard({ root, onSelect }) {
206
206
  formatter: (value) => format.duration(Number(value)),
207
207
  }, children: rows.map((row) => (_jsx(Cell, { fill: traceFill(ACTOR_FILL[traceActor(row.node)]) }, row.id))) })] }) }) }) }));
208
208
  }
209
- /**
210
- * A turn's figures, then where its time, tokens, and slowest steps went, in
211
- * one wall. A bar opens its span.
212
- */
213
- export function TraceCharts({ root, split, totals, tools, latency, onSelect, }) {
214
- const format = useTraceFormat();
209
+ /** Where a turn's time, tokens, and slowest steps went. A bar opens its span. */
210
+ export function TraceCharts({ root, split, onSelect, }) {
211
+ const { t } = useTraceFormat();
215
212
  const callRows = useCallRows(root, onSelect);
216
- return (_jsxs(CardWall, { minWidth: TRACE_CARD_PX, children: [turnStats(format, { totals, tools, latency }), _jsx(TimeCard, { split: split }, "time"), _jsx(TokensCard, { title: format.t('@theorem.panel.trace.charts.calls'), rows: callRows }, "call-tokens"), _jsx(SlowestCard, { root: root, onSelect: onSelect }, "slowest")] }));
213
+ return (_jsxs(Grid, { columns: { minWidth: 260, repeat: 'fit' }, gap: 3, children: [_jsx(TimeCard, { split: split }), _jsx(TokensCard, { title: t('@theorem.panel.trace.charts.calls'), rows: callRows }), _jsx(SlowestCard, { root: root, onSelect: onSelect })] }));
217
214
  }
218
215
  function TurnTimeTip({ active, payload }) {
219
216
  const format = useTraceFormat();
@@ -240,13 +237,9 @@ function TurnTimeCard({ rows, isCalls }) {
240
237
  })
241
238
  : undefined, children: [_jsx(Columns, { rows: rows, keys: SPLIT_KEYS, fills: SPLIT_FILL, tip: (props) => _jsx(TurnTimeTip, { ...props }) }), _jsx(SplitLegend, { split: sums, total: total })] }));
242
239
  }
243
- /**
244
- * The whole conversation: its figures, then each turn's time and tokens, in
245
- * one wall. A column opens its turn.
246
- */
247
- export function ConversationCharts({ turns, totals, tools, onPick, }) {
248
- const format = useTraceFormat();
249
- const { t } = format;
240
+ /** The whole conversation: each turn's time and tokens. A column opens its turn. */
241
+ export function ConversationCharts({ turns, onPick, }) {
242
+ const { t } = useTraceFormat();
250
243
  const isCalls = isCallTrace(turns);
251
244
  const column = isCalls
252
245
  ? '@theorem.panel.trace.charts.hostCall'
@@ -269,5 +262,5 @@ export function ConversationCharts({ turns, totals, tools, onPick, }) {
269
262
  }),
270
263
  open: () => onPick(position),
271
264
  })), [turns, onPick, t, column]);
272
- return (_jsxs(CardWall, { minWidth: TRACE_CARD_PX, children: [turnStats(format, { totals, tools }), _jsx(TurnTimeCard, { rows: time, isCalls: isCalls }, "turn-time"), _jsx(TokensCard, { title: t('@theorem.panel.trace.charts.turnTokens'), rows: tokens }, "turn-tokens")] }));
265
+ return (_jsxs(Grid, { columns: { minWidth: 260, repeat: 'fit' }, gap: 3, children: [_jsx(TurnTimeCard, { rows: time, isCalls: isCalls }), _jsx(TokensCard, { title: t('@theorem.panel.trace.charts.turnTokens'), rows: tokens })] }));
273
266
  }
@@ -13,11 +13,10 @@ import { isCallTrace, traceGuardrails, traceLatency, traceStory, traceTurnRow, t
13
13
  import { TRACE_SEARCH_OPERATORS, TRACE_TEXT_FIELD, traceMatchIds, traceSearchFields, traceSpans, traceTotals, traceTree, } from "../client/trace-view.js";
14
14
  import { useTraceRecords } from "../hooks/use-trace-records.js";
15
15
  import { Stream, useArrive } from "./arrive.js";
16
- import { CardWall } from "./CardWall.js";
17
16
  import { PaneLayout } from "./SidePanel.js";
18
17
  import { ConversationCharts, TraceCharts } from "./TraceCharts.js";
19
18
  import { TraceGuardrails } from "./TraceGuardrails.js";
20
- import { GuardrailStat, TRACE_CARD_PX, TurnPicker } from "./TraceOverview.js";
19
+ import { GuardrailStats, TurnPicker, TurnStats } from "./TraceOverview.js";
21
20
  import { TraceSpanDetail } from "./TraceSpanDetail.js";
22
21
  import { ActorMark, TraceStory } from "./TraceStory.js";
23
22
  import { useTraceFormat } from "./TraceValues.js";
@@ -125,7 +124,7 @@ function ConversationOverview({ turns, onPick, }) {
125
124
  const isCalls = isCallTrace(turns);
126
125
  return (_jsxs(VStack, { gap: 5, padding: 4, children: [_jsx(Text, { weight: "semibold", children: t(isCalls ? '@theorem.panel.trace.calls.count' : '@theorem.panel.trace.conversation', {
127
126
  count: turns.length,
128
- }) }), _jsx(ConversationCharts, { turns: turns, totals: totals, tools: tools, onPick: onPick }), _jsxs(VStack, { gap: 2, children: [_jsx(SectionTitle, { title: t(isCalls ? '@theorem.panel.trace.calls' : '@theorem.panel.trace.turns') }), _jsx(Stream, { children: _jsx(VStack, { gap: 0, children: turns.map((turn, index) => (_jsx(TurnRow, { turn: turn, index: index, isCall: isCalls, onOpen: () => onPick(index) }, turn.node.id))) }) })] })] }));
127
+ }) }), _jsxs(VStack, { gap: 3, children: [_jsx(TurnStats, { totals: totals, tools: tools }), _jsx(ConversationCharts, { turns: turns, onPick: onPick })] }), _jsxs(VStack, { gap: 2, children: [_jsx(SectionTitle, { title: t(isCalls ? '@theorem.panel.trace.calls' : '@theorem.panel.trace.turns') }), _jsx(Stream, { children: _jsx(VStack, { gap: 0, children: turns.map((turn, index) => (_jsx(TurnRow, { turn: turn, index: index, isCall: isCalls, onOpen: () => onPick(index) }, turn.node.id))) }) })] })] }));
129
128
  }
130
129
  /** One turn at a glance, then its story, then its timeline under the search. */
131
130
  function TurnOverview({ turns, index, onPick, onBack, selectedId, onSelect, }) {
@@ -141,7 +140,7 @@ function TurnOverview({ turns, index, onPick, onBack, selectedId, onSelect, }) {
141
140
  if (!turn || !root)
142
141
  return null;
143
142
  const tools = traceSpans([root]).filter((node) => node.meta.type === 'tool').length;
144
- return (_jsxs(VStack, { gap: 5, padding: 4, children: [_jsx(TurnPicker, { turns: turns, index: index, onChange: onPick, onBack: onBack }), _jsx(TraceCharts, { root: root, split: turn.split, totals: turn.totals, tools: tools, latency: latency, onSelect: onSelect }), _jsxs(VStack, { gap: 2, children: [_jsx(SectionTitle, { title: t('@theorem.panel.trace.timeline') }), _jsx(PowerSearch, { config: config, filters: filters, onChange: setFilters, label: t('@theorem.panel.trace.search'), isLabelHidden: true, placeholder: t('@theorem.panel.trace.search.placeholder'), resultCount: matches?.size, size: "sm" }), _jsx(TraceWaterfall, { root: root, selectedId: selectedId, matches: matches, onSelect: onSelect }, root.id)] }), story.length > 0 ? (_jsxs(VStack, { gap: 2, children: [_jsx(SectionTitle, { title: t('@theorem.panel.trace.story') }), _jsx(TraceStory, { steps: story, selectedId: selectedId, matches: matches, onSelect: onSelect })] })) : null, guardrails.length > 0 ? (_jsxs(VStack, { gap: 2, children: [_jsx(SectionTitle, { title: t('@theorem.panel.trace.guardrails') }), _jsx(TraceGuardrails, { checks: guardrails, onSelect: onSelect })] })) : null] }));
143
+ return (_jsxs(VStack, { gap: 5, padding: 4, children: [_jsx(TurnPicker, { turns: turns, index: index, onChange: onPick, onBack: onBack }), _jsxs(VStack, { gap: 3, children: [_jsx(TurnStats, { totals: turn.totals, tools: tools, latency: latency }), _jsx(TraceCharts, { root: root, split: turn.split, onSelect: onSelect })] }), _jsxs(VStack, { gap: 2, children: [_jsx(SectionTitle, { title: t('@theorem.panel.trace.timeline') }), _jsx(PowerSearch, { config: config, filters: filters, onChange: setFilters, label: t('@theorem.panel.trace.search'), isLabelHidden: true, placeholder: t('@theorem.panel.trace.search.placeholder'), resultCount: matches?.size, size: "sm" }), _jsx(TraceWaterfall, { root: root, selectedId: selectedId, matches: matches, onSelect: onSelect }, root.id)] }), story.length > 0 ? (_jsxs(VStack, { gap: 2, children: [_jsx(SectionTitle, { title: t('@theorem.panel.trace.story') }), _jsx(TraceStory, { steps: story, selectedId: selectedId, matches: matches, onSelect: onSelect })] })) : null, guardrails.length > 0 ? (_jsxs(VStack, { gap: 2, children: [_jsx(SectionTitle, { title: t('@theorem.panel.trace.guardrails') }), _jsx(TraceGuardrails, { checks: guardrails, onSelect: onSelect })] })) : null] }));
145
144
  }
146
145
  function EmptyTrace() {
147
146
  const { t } = useTraceFormat();
@@ -203,8 +202,5 @@ export function TraceGuardrailsBody({ records, head, }) {
203
202
  const { t } = useTraceFormat();
204
203
  const roots = useMemo(() => traceTurns(traceTree(records)).map((turn) => turn.node), [records]);
205
204
  const checks = useMemo(() => roots.flatMap(traceGuardrails), [roots]);
206
- const timed = useMemo(() => roots
207
- .map((root) => ({ id: root.id, latency: traceLatency(root) }))
208
- .filter(({ latency }) => latency.guardrails), [roots]);
209
- return (_jsxs(VStack, { gap: 5, padding: 4, children: [head, timed.length > 0 ? (_jsx(CardWall, { minWidth: TRACE_CARD_PX, children: timed.map(({ id, latency }) => (_jsx(GuardrailStat, { latency: latency }, id))) })) : null, checks.length > 0 ? (_jsxs(VStack, { gap: 2, children: [_jsx(SectionTitle, { title: t('@theorem.panel.trace.guardrails') }), _jsx(TraceGuardrails, { checks: checks })] })) : null] }));
205
+ return (_jsxs(VStack, { gap: 5, padding: 4, children: [head, roots.map((root) => (_jsx(GuardrailStats, { latency: traceLatency(root) }, root.id))), checks.length > 0 ? (_jsxs(VStack, { gap: 2, children: [_jsx(SectionTitle, { title: t('@theorem.panel.trace.guardrails') }), _jsx(TraceGuardrails, { checks: checks })] })) : null] }));
210
206
  }
@@ -1,7 +1,6 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import { type TraceLatency, type TraceTurn } from '../client/trace-story.ts';
3
3
  import type { TraceTotals } from '../client/trace-view.ts';
4
- import { useTraceFormat } from './TraceValues.tsx';
5
4
  /** Width a chart draws at before it has measured its column: the panel's narrowest. */
6
5
  export declare const FIRST_WIDTH_PX = 280;
7
6
  /**
@@ -24,20 +23,16 @@ export declare function TurnPicker({ turns, index, onChange, onBack, }: {
24
23
  /** Leaves the turn for the whole conversation; absent when there is only the one turn. */
25
24
  onBack?: (() => void) | undefined;
26
25
  }): import("react").JSX.Element | null;
27
- export type TraceFormat = ReturnType<typeof useTraceFormat>;
28
- /** Narrowest a card in a trace overview's wall gets: wide enough for a chart's columns. */
29
- export declare const TRACE_CARD_PX = 260;
30
- /** A turn's guardrail figure alone, the card {@link turnStats} gives it. */
31
- export declare function GuardrailStat({ latency }: {
26
+ /** A turn's guardrail figures alone, as {@link TurnStats} lays them out. */
27
+ export declare function GuardrailStats({ latency }: {
32
28
  latency: TraceLatency;
33
- }): import("react").JSX.Element;
29
+ }): import("react").JSX.Element | null;
34
30
  /**
35
- * Duration, first text, cost, tokens, steps, and guardrails of the open turn,
36
- * as cards for its overview's wall; a total the trace did not record renders
37
- * nothing.
31
+ * Duration, first text, cost, tokens, steps, and guardrails of the open turn;
32
+ * a total the trace did not record is left out.
38
33
  */
39
- export declare function turnStats(format: TraceFormat, { totals, tools, latency }: {
34
+ export declare function TurnStats({ totals, tools, latency, }: {
40
35
  totals: TraceTotals;
41
36
  tools: number;
42
37
  latency?: TraceLatency;
43
- }): ReactNode[];
38
+ }): import("react").JSX.Element;
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Button } from '@astryxdesign/core/Button';
3
3
  import { Card } from '@astryxdesign/core/Card';
4
+ import { Grid } from '@astryxdesign/core/Grid';
4
5
  import { HStack } from '@astryxdesign/core/HStack';
5
6
  import { Icon } from '@astryxdesign/core/Icon';
6
7
  import { IconButton } from '@astryxdesign/core/IconButton';
@@ -60,7 +61,7 @@ function Stat({ label, value, detail, }) {
60
61
  .filter(Boolean)
61
62
  .map((line) => (_jsx(Text, { type: "supporting", color: "secondary", hasTabularNumbers: true, children: line }, String(line)))) })] }) }));
62
63
  }
63
- function FirstTextStat({ latency, format, }) {
64
+ function FirstTextStat({ latency, format }) {
64
65
  if (latency?.firstTextMs === undefined)
65
66
  return null;
66
67
  const { t } = format;
@@ -108,25 +109,18 @@ function GuardrailsStat({ latency, format, }) {
108
109
  flagged: guardrails.flagged,
109
110
  }) }));
110
111
  }
111
- /** Narrowest a card in a trace overview's wall gets: wide enough for a chart's columns. */
112
- export const TRACE_CARD_PX = 260;
113
- /** A turn's guardrail figure alone, the card {@link turnStats} gives it. */
114
- export function GuardrailStat({ latency }) {
112
+ /** A turn's guardrail figures alone, as {@link TurnStats} lays them out. */
113
+ export function GuardrailStats({ latency }) {
115
114
  const format = useTraceFormat();
116
- return _jsx(GuardrailsStat, { latency: latency, format: format });
115
+ if (!latency.guardrails)
116
+ return null;
117
+ return (_jsx(Grid, { columns: { minWidth: 120, repeat: 'fit' }, gap: 2, children: _jsx(GuardrailsStat, { latency: latency, format: format }) }));
117
118
  }
118
119
  /**
119
- * Duration, first text, cost, tokens, steps, and guardrails of the open turn,
120
- * as cards for its overview's wall; a total the trace did not record renders
121
- * nothing.
120
+ * Duration, first text, cost, tokens, steps, and guardrails of the open turn;
121
+ * a total the trace did not record is left out.
122
122
  */
123
- export function turnStats(format, { totals, tools, latency }) {
124
- return [
125
- _jsx(Stat, { label: TRACE_FIELDS.duration.label, value: format.duration(totals.durationMs) }, "duration"),
126
- _jsx(FirstTextStat, { latency: latency, format: format }, "first-text"),
127
- _jsx(CostStat, { totals: totals, format: format }, "cost"),
128
- _jsx(TokensStat, { totals: totals, format: format }, "tokens"),
129
- _jsx(StepsStat, { totals: totals, tools: tools, format: format }, "steps"),
130
- _jsx(GuardrailsStat, { latency: latency, format: format }, "guardrails"),
131
- ];
123
+ export function TurnStats({ totals, tools, latency, }) {
124
+ const format = useTraceFormat();
125
+ return (_jsxs(Grid, { columns: { minWidth: 120, repeat: 'fit' }, gap: 2, children: [_jsx(Stat, { label: TRACE_FIELDS.duration.label, value: format.duration(totals.durationMs) }), _jsx(FirstTextStat, { latency: latency, format: format }), _jsx(CostStat, { totals: totals, format: format }), _jsx(TokensStat, { totals: totals, format: format }), _jsx(StepsStat, { totals: totals, tools: tools, format: format }), _jsx(GuardrailsStat, { latency: latency, format: format })] }));
132
126
  }