@mastra/playground-ui 29.0.0-alpha.2 → 29.0.0-alpha.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,72 @@
1
1
  # @mastra/playground-ui
2
2
 
3
+ ## 29.0.0-alpha.3
4
+
5
+ ### Minor Changes
6
+
7
+ - Removed `ButtonWithTooltip` from `@mastra/playground-ui`. Use `Button` with the `tooltip` prop instead. ([#16719](https://github.com/mastra-ai/mastra/pull/16719))
8
+
9
+ **Migration**
10
+
11
+ ```tsx
12
+ // before
13
+ import { ButtonWithTooltip } from '@mastra/playground-ui';
14
+
15
+ <ButtonWithTooltip tooltipContent="Search">
16
+ <Search />
17
+ </ButtonWithTooltip>;
18
+
19
+ // after
20
+ import { Button } from '@mastra/playground-ui';
21
+
22
+ <Button tooltip="Search">
23
+ <Search />
24
+ </Button>;
25
+ ```
26
+
27
+ `tooltip` supports the same values as `tooltipContent`. Icon-only buttons that pass a string `tooltip` now also get it as their `aria-label` automatically, matching how labelled controls have always behaved. Pass an explicit `aria-label` to override.
28
+
29
+ ### Patch Changes
30
+
31
+ - The Traces list now updates live via delta polling. Previously the list was refetched every 10 seconds, replacing the whole page with no signal about what changed; now new traces appear within a few seconds of being created, with a brief highlight to draw attention. Status changes on already-visible rows (running → success / error) also propagate without intervention, and returning to the tab after being idle re-syncs from a fresh cursor. ([#16727](https://github.com/mastra-ai/mastra/pull/16727))
32
+
33
+ **New `useTraces` return fields**
34
+ - `isRefetching` — true while any meaningful refetch is in flight. Use it to drive a heartbeat indicator.
35
+ - `autoRefetch` / `setAutoRefetch` — pause / resume all automatic polling so the consumer can render an opt-out toggle.
36
+ - `recentlyAddedKeys` — `Set<string>` of `traceId:spanId` for rows that just arrived via delta polling. Drives the temporary highlight in `TracesListView`.
37
+
38
+ **New polling config**
39
+
40
+ Every timing in the hook is tunable per-instance via a new `polling` option:
41
+
42
+ ```ts
43
+ import { useTraces, type TracesPollingConfig } from '@mastra/playground-ui';
44
+
45
+ useTraces({
46
+ filters,
47
+ listMode,
48
+ polling: {
49
+ deltaPollIntervalMs: 10_000,
50
+ idleGuardThresholdMs: 5 * 60_000,
51
+ },
52
+ });
53
+ ```
54
+
55
+ Omitted fields fall through to the defaults (delta poll every 5s, idle reset after 15 min, status refresh every 60s, etc).
56
+
57
+ **TracesListView**
58
+
59
+ New optional `recentlyAddedKeys?: Set<string>` prop. Rows whose `traceId:spanId` is in the set get the `animate-row-highlight` class — a brief fade-out to transparent, added to `index.css`.
60
+
61
+ **Compatibility**
62
+
63
+ Requires `@mastra/server` and `@mastra/client-js` at the versions that ship the observability delta-polling endpoints, and a store that opts into delta polling (`@mastra/clickhouse`, `@mastra/duckdb`, and the in-memory store today). When unavailable — older server or a store without delta capability — the hook silently falls back to page-mode interval refetching. No consumer changes required.
64
+
65
+ - Updated dependencies [[`5556cc1`](https://github.com/mastra-ai/mastra/commit/5556cc1befec71518d84f826b3bfe3a079a9daf7), [`5499303`](https://github.com/mastra-ai/mastra/commit/54993032c1ebc09642625b78d2014e0cf84a3cae), [`3498b49`](https://github.com/mastra-ai/mastra/commit/3498b4946be94f4313cd817733589680dcda5278), [`e47bca7`](https://github.com/mastra-ai/mastra/commit/e47bca7b72866d3abd173b9f530ac4318113a8ff), [`0031d0f`](https://github.com/mastra-ai/mastra/commit/0031d0f13831d7843ac5d498734a7d92862e2ce3), [`3498b49`](https://github.com/mastra-ai/mastra/commit/3498b4946be94f4313cd817733589680dcda5278), [`359439b`](https://github.com/mastra-ai/mastra/commit/359439bb8c635e048176306828195f8297f50021)]:
66
+ - @mastra/core@1.36.0-alpha.3
67
+ - @mastra/client-js@1.20.0-alpha.3
68
+ - @mastra/react@0.4.0-alpha.3
69
+
3
70
  ## 29.0.0-alpha.2
4
71
 
5
72
  ### Patch Changes
package/dist/index.cjs.js CHANGED
@@ -3664,12 +3664,10 @@ const buttonVariants = cva(
3664
3664
  {
3665
3665
  variants: {
3666
3666
  variant: {
3667
- default: "bg-surface3 border border-border1 hover:bg-surface5 hover:text-neutral6 active:bg-surface6 text-neutral6",
3668
- primary: "bg-surface4 border border-border2 hover:bg-surface5 hover:text-neutral6 active:bg-surface6 text-neutral6",
3669
- cta: "bg-accent1 border border-transparent hover:bg-accent1/90 hover:shadow-glow-accent1 disabled:hover:shadow-none text-surface1 font-medium",
3667
+ default: "bg-surface3 border border-border2 hover:bg-surface5 hover:text-neutral6 active:bg-surface6 text-neutral6",
3668
+ primary: "bg-neutral6 border border-transparent hover:bg-neutral6/90 active:bg-neutral6/80 text-surface1 font-medium",
3670
3669
  ghost: "bg-transparent border border-transparent hover:bg-neutral6/5 hover:text-neutral6 active:bg-neutral6/10 text-neutral4",
3671
- outline: "bg-transparent border border-border1 hover:bg-surface3 hover:text-neutral6 active:bg-surface4 text-neutral5",
3672
- link: "inline-flex justify-start rounded-none h-auto px-0 bg-transparent text-neutral3 hover:text-neutral4 gap-1 [&>svg]:mx-0 w-auto [&>svg]:opacity-70"
3670
+ outline: "bg-transparent border border-border1 hover:bg-surface3 hover:text-neutral6 active:bg-surface4 text-neutral5"
3673
3671
  },
3674
3672
  size: {
3675
3673
  sm: cn(`${formElementSizes.sm} text-ui-sm px-[.9em]`, TEXT_MODE_ADORNMENTS),
@@ -3742,16 +3740,6 @@ const Button = React.forwardRef(
3742
3740
  );
3743
3741
  Button.displayName = "Button";
3744
3742
 
3745
- const ButtonWithTooltip = React.forwardRef(
3746
- ({ tooltipContent, ...buttonProps }, ref) => {
3747
- return /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
3748
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { ref, ...buttonProps }) }),
3749
- tooltipContent && /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: tooltipContent })
3750
- ] });
3751
- }
3752
- );
3753
- ButtonWithTooltip.displayName = "ButtonWithTooltip";
3754
-
3755
3743
  function flattenSchemaToVariables(schema, maxDepth = 5) {
3756
3744
  if (!schema?.properties) {
3757
3745
  return [];
@@ -6938,7 +6926,7 @@ function HighlightedCode({ code, lang }) {
6938
6926
  }
6939
6927
 
6940
6928
  function DashboardCard({ children, className }) {
6941
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("border border-border1 rounded-lg p-6 bg-surface2", className), children });
6929
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("border border-border1 rounded-xl px-4 py-3 bg-surface-overlay-soft", className), children });
6942
6930
  }
6943
6931
 
6944
6932
  const DropdownMenuRoot = DropdownMenuPrimitive__namespace.Root;
@@ -10842,7 +10830,7 @@ const navItemClasses = ({ isActive, isCollapsed, isFeatured } = {}) => cn(
10842
10830
  "hover:bg-sidebar-nav-hover hover:text-neutral6 [&:hover_svg]:text-neutral5",
10843
10831
  "focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-accent1 focus-visible:shadow-focus-ring",
10844
10832
  !isCollapsed && "w-full gap-2.5 py-1 px-3 justify-start",
10845
- isCollapsed && "w-9 mx-auto p-0 justify-center",
10833
+ isCollapsed && "w-full p-0 justify-center",
10846
10834
  isActive && "text-neutral6 bg-sidebar-nav-active hover:bg-sidebar-nav-active hover:text-neutral6 [&_svg]:text-neutral6 [&:hover_svg]:text-neutral6",
10847
10835
  isCollapsed && !isActive && "[&_svg]:text-neutral3",
10848
10836
  isFeatured && "my-2 bg-accent1Dark hover:bg-accent1Darker text-accent1 hover:text-accent1 border border-accent1/30",
@@ -10879,11 +10867,7 @@ function MainSidebarNavLink({
10879
10867
  interactiveEl = /* @__PURE__ */ jsxRuntime.jsxs(Link, { href: link.url, ...linkParams, className: itemClassName, children: [
10880
10868
  link.icon,
10881
10869
  /* @__PURE__ */ jsxRuntime.jsx(MainSidebarNavLabel, { state, children: link.name }),
10882
- children,
10883
- link.isExperimental && !isCollapsed && !needsTooltip && /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
10884
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { render: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CircleAlertIcon, { className: "ml-auto stroke-accent5" }) }),
10885
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "right", align: "center", sideOffset: 16, children: "Experimental Feature" })
10886
- ] })
10870
+ children
10887
10871
  ] });
10888
10872
  }
10889
10873
  return /* @__PURE__ */ jsxRuntime.jsx("li", { ...props, className: cn("flex relative min-w-0", className), children: link && needsTooltip && React.isValidElement(interactiveEl) ? /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
@@ -11384,7 +11368,7 @@ function SectionCard({
11384
11368
  className: cn(
11385
11369
  "overflow-hidden rounded-2xl border",
11386
11370
  fillHeight && "flex h-full flex-col",
11387
- danger ? "border-accent2/25" : "border-section-card bg-section-card",
11371
+ danger ? "border-accent2/25" : "border-border1",
11388
11372
  className
11389
11373
  ),
11390
11374
  children: [
@@ -11393,7 +11377,7 @@ function SectionCard({
11393
11377
  {
11394
11378
  className: cn(
11395
11379
  "flex flex-col gap-3 px-7 pt-7 pb-6 sm:flex-row sm:items-start sm:justify-between sm:gap-6",
11396
- danger ? "bg-accent2/[0.08]" : "bg-section-card-header"
11380
+ danger ? "bg-accent2/8" : "bg-surface-overlay-soft"
11397
11381
  ),
11398
11382
  children: [
11399
11383
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx(CardHeading, { title, description, tone: danger ? "danger" : "default" }) }),
@@ -11407,7 +11391,7 @@ function SectionCard({
11407
11391
  className: cn(
11408
11392
  "min-w-0 px-7 pt-6 pb-7",
11409
11393
  fillHeight && "flex-1",
11410
- danger ? "bg-accent2/[0.04]" : null,
11394
+ danger ? "bg-accent2/4" : null,
11411
11395
  contentClassName
11412
11396
  ),
11413
11397
  children
@@ -14286,25 +14270,16 @@ function DataCodeSection({
14286
14270
  /* @__PURE__ */ jsxRuntime.jsxs(ButtonsGroup, { children: [
14287
14271
  /* @__PURE__ */ jsxRuntime.jsx(CopyButton, { content: codeStr || "No content", size: "sm" }),
14288
14272
  hasMultilineText && /* @__PURE__ */ jsxRuntime.jsx(
14289
- ButtonWithTooltip,
14273
+ Button,
14290
14274
  {
14291
14275
  size: "sm",
14292
14276
  "aria-label": showAsMultilineText ? "Show escaped newlines" : "Show multiline text",
14293
- tooltipContent: showAsMultilineText ? "Show escaped newlines" : "Show multiline text",
14277
+ tooltip: showAsMultilineText ? "Show escaped newlines" : "Show multiline text",
14294
14278
  onClick: () => setShowAsMultilineText((v) => !v),
14295
14279
  children: showAsMultilineText ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlignLeftIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlignJustifyIcon, {})
14296
14280
  }
14297
14281
  ),
14298
- /* @__PURE__ */ jsxRuntime.jsx(
14299
- ButtonWithTooltip,
14300
- {
14301
- size: "sm",
14302
- "aria-label": "Expand",
14303
- tooltipContent: "Expand",
14304
- onClick: () => setExpandedOpen(true),
14305
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExpandIcon, {})
14306
- }
14307
- )
14282
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "sm", "aria-label": "Expand", tooltip: "Expand", onClick: () => setExpandedOpen(true), children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExpandIcon, {}) })
14308
14283
  ] })
14309
14284
  ] })
14310
14285
  ] }),
@@ -14342,16 +14317,16 @@ function DataCodeSection({
14342
14317
  /* @__PURE__ */ jsxRuntime.jsxs(ButtonsGroup, { children: [
14343
14318
  /* @__PURE__ */ jsxRuntime.jsx(CopyButton, { content: codeStr || "No content", size: "sm" }),
14344
14319
  hasMultilineText && /* @__PURE__ */ jsxRuntime.jsx(
14345
- ButtonWithTooltip,
14320
+ Button,
14346
14321
  {
14347
14322
  size: "sm",
14348
14323
  "aria-label": expandedMultiline ? "Show escaped newlines" : "Show multiline text",
14349
- tooltipContent: expandedMultiline ? "Show escaped newlines" : "Show multiline text",
14324
+ tooltip: expandedMultiline ? "Show escaped newlines" : "Show multiline text",
14350
14325
  onClick: () => setExpandedMultiline((v) => !v),
14351
14326
  children: expandedMultiline ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlignLeftIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlignJustifyIcon, {})
14352
14327
  }
14353
14328
  ),
14354
- /* @__PURE__ */ jsxRuntime.jsx(DialogClose, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ButtonWithTooltip, { size: "sm", "aria-label": "Close", tooltipContent: "Close", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, {}) }) })
14329
+ /* @__PURE__ */ jsxRuntime.jsx(DialogClose, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "sm", "aria-label": "Close", tooltip: "Close", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, {}) }) })
14355
14330
  ] })
14356
14331
  ] })
14357
14332
  ] }),
@@ -14398,17 +14373,7 @@ function DataDetailsPanelCloseButton({
14398
14373
  tooltip = "Close panel",
14399
14374
  className
14400
14375
  }) {
14401
- return /* @__PURE__ */ jsxRuntime.jsx(
14402
- ButtonWithTooltip,
14403
- {
14404
- size: "md",
14405
- onClick,
14406
- "aria-label": "Close Panel",
14407
- tooltipContent: tooltip,
14408
- className,
14409
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, {})
14410
- }
14411
- );
14376
+ return /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "md", onClick, "aria-label": "Close Panel", tooltip, className, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, {}) });
14412
14377
  }
14413
14378
 
14414
14379
  function buildDarkTheme() {
@@ -14711,17 +14676,7 @@ const DataKeysAndValues = Object.assign(DataKeysAndValuesRoot, {
14711
14676
  });
14712
14677
 
14713
14678
  function DataPanelCloseButton({ onClick, tooltip = "Close panel", className }) {
14714
- return /* @__PURE__ */ jsxRuntime.jsx(
14715
- ButtonWithTooltip,
14716
- {
14717
- size: "md",
14718
- onClick,
14719
- "aria-label": "Close Panel",
14720
- tooltipContent: tooltip,
14721
- className,
14722
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, {})
14723
- }
14724
- );
14679
+ return /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "md", onClick, "aria-label": "Close Panel", tooltip, className, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, {}) });
14725
14680
  }
14726
14681
 
14727
14682
  function DataPanelContent({ children, ref }) {
@@ -14757,8 +14712,8 @@ function DataPanelNextPrevNav({
14757
14712
  nextLabel = "Next"
14758
14713
  }) {
14759
14714
  return /* @__PURE__ */ jsxRuntime.jsxs(ButtonsGroup, { spacing: "close", children: [
14760
- /* @__PURE__ */ jsxRuntime.jsx(ButtonWithTooltip, { size: "md", tooltipContent: previousLabel, onClick: onPrevious, disabled: !onPrevious, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUpIcon, {}) }),
14761
- /* @__PURE__ */ jsxRuntime.jsx(ButtonWithTooltip, { size: "md", tooltipContent: nextLabel, onClick: onNext, disabled: !onNext, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowDownIcon, {}) })
14715
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "md", tooltip: previousLabel, onClick: onPrevious, disabled: !onPrevious, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUpIcon, {}) }),
14716
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "md", tooltip: nextLabel, onClick: onNext, disabled: !onNext, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowDownIcon, {}) })
14762
14717
  ] });
14763
14718
  }
14764
14719
 
@@ -16808,13 +16763,13 @@ function StackedRunsBars({ data }) {
16808
16763
 
16809
16764
  function OpenInTracesButton({ href, LinkComponent = "a" }) {
16810
16765
  return /* @__PURE__ */ jsxRuntime.jsx(
16811
- ButtonWithTooltip,
16766
+ Button,
16812
16767
  {
16813
16768
  as: LinkComponent,
16814
16769
  href,
16815
16770
  variant: "ghost",
16816
16771
  size: "icon-md",
16817
- tooltipContent: "View in Traces",
16772
+ tooltip: "View in Traces",
16818
16773
  "aria-label": "View in Traces",
16819
16774
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.EyeIcon, {})
16820
16775
  }
@@ -16822,13 +16777,13 @@ function OpenInTracesButton({ href, LinkComponent = "a" }) {
16822
16777
  }
16823
16778
  function OpenErrorsInLogsButton({ href, LinkComponent = "a" }) {
16824
16779
  return /* @__PURE__ */ jsxRuntime.jsx(
16825
- ButtonWithTooltip,
16780
+ Button,
16826
16781
  {
16827
16782
  as: LinkComponent,
16828
16783
  href,
16829
16784
  variant: "ghost",
16830
16785
  size: "icon-md",
16831
- tooltipContent: "View errors in Logs",
16786
+ tooltip: "View errors in Logs",
16832
16787
  "aria-label": "View errors in Logs",
16833
16788
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.LogsIcon, {})
16834
16789
  }
@@ -19592,10 +19547,10 @@ function TraceDataPanelView({
19592
19547
  ] }),
19593
19548
  /* @__PURE__ */ jsxRuntime.jsxs(ButtonsGroup, { className: "ml-auto shrink-0", children: [
19594
19549
  onCollapsedChange && /* @__PURE__ */ jsxRuntime.jsx(
19595
- ButtonWithTooltip,
19550
+ Button,
19596
19551
  {
19597
19552
  size: "md",
19598
- tooltipContent: collapsed ? "Expand panel" : "Collapse panel",
19553
+ tooltip: collapsed ? "Expand panel" : "Collapse panel",
19599
19554
  onClick: () => setCollapsed(!collapsed),
19600
19555
  children: collapsed ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronsUpDownIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronsDownUpIcon, {})
19601
19556
  }
@@ -19610,12 +19565,12 @@ function TraceDataPanelView({
19610
19565
  }
19611
19566
  ),
19612
19567
  showOpenTracePageLink && /* @__PURE__ */ jsxRuntime.jsx(
19613
- ButtonWithTooltip,
19568
+ Button,
19614
19569
  {
19615
19570
  as: LinkComponent,
19616
19571
  href: traceHref,
19617
19572
  size: "md",
19618
- tooltipContent: "Open trace page",
19573
+ tooltip: "Open trace page",
19619
19574
  "aria-label": "Open trace page",
19620
19575
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Link2Icon, {})
19621
19576
  }
@@ -20126,6 +20081,7 @@ function TracesListView({
20126
20081
  featuredTraceId,
20127
20082
  featuredSpanId,
20128
20083
  isBranchesMode,
20084
+ recentlyAddedKeys,
20129
20085
  onTraceClick
20130
20086
  }) {
20131
20087
  const scrollRef = React.useRef(null);
@@ -20169,6 +20125,8 @@ function TracesListView({
20169
20125
  const trace = traces[vi.index];
20170
20126
  if (!trace) return null;
20171
20127
  const isFeatured = trace.traceId === featuredTraceId && (featuredSpanId == null || trace.spanId === featuredSpanId);
20128
+ const rowKey = `${trace.traceId}:${trace.spanId ?? ""}`;
20129
+ const isRecentlyAdded = recentlyAddedKeys?.has(rowKey) ?? false;
20172
20130
  const displayDate = trace.startedAt ?? trace.createdAt;
20173
20131
  const entityName = trace.entityName || trace.entityId || trace.attributes?.agentId || trace.attributes?.workflowId;
20174
20132
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -20177,7 +20135,7 @@ function TracesListView({
20177
20135
  ref: virtualizer.measureElement,
20178
20136
  "data-index": vi.index,
20179
20137
  onClick: () => onTraceClick(trace),
20180
- className: cn(isFeatured && "bg-surface4"),
20138
+ className: cn(isFeatured && "bg-surface4", isRecentlyAdded && "animate-row-highlight"),
20181
20139
  children: [
20182
20140
  /* @__PURE__ */ jsxRuntime.jsx(TracesDataList.DateCell, { timestamp: displayDate }),
20183
20141
  /* @__PURE__ */ jsxRuntime.jsx(TracesDataList.TimeCell, { timestamp: displayDate }),
@@ -20194,7 +20152,7 @@ function TracesListView({
20194
20152
  /* @__PURE__ */ jsxRuntime.jsx(TracesDataList.StatusCell, { status: trace.attributes?.status })
20195
20153
  ]
20196
20154
  },
20197
- `${trace.traceId}:${trace.spanId ?? ""}`
20155
+ rowKey
20198
20156
  );
20199
20157
  }),
20200
20158
  /* @__PURE__ */ jsxRuntime.jsx(TracesDataList.Spacer, { height: paddingBottom }),
@@ -20405,20 +20363,35 @@ function useTraceSpans(traceId) {
20405
20363
  });
20406
20364
  }
20407
20365
 
20366
+ const deltaSupportByClient = /* @__PURE__ */ new WeakMap();
20367
+ const DEFAULT_POLLING_CONFIG = {
20368
+ deltaPollIntervalMs: 5e3,
20369
+ deltaChaseIntervalMs: 100,
20370
+ deltaLimit: 100,
20371
+ pageModeRefetchIntervalMs: 1e4,
20372
+ page0StatusRefreshIntervalMs: 6e4,
20373
+ idleGuardThresholdMs: 15 * 6e4,
20374
+ minRefetchSpinMs: 400,
20375
+ deltaHighlightMs: 1e3
20376
+ };
20377
+ function shouldResetAfterIdle(lastSuccessAt, now, thresholdMs) {
20378
+ if (lastSuccessAt === 0) return false;
20379
+ return now - lastSuccessAt > thresholdMs;
20380
+ }
20381
+ function isHttp501(error) {
20382
+ return error?.status === 501;
20383
+ }
20408
20384
  const fetchTracesFn = async ({
20409
20385
  client,
20386
+ mode,
20410
20387
  page,
20411
20388
  perPage,
20389
+ after,
20390
+ limit,
20412
20391
  filters,
20413
20392
  listMode = "traces"
20414
20393
  }) => {
20415
- const params = {
20416
- pagination: {
20417
- page,
20418
- perPage
20419
- },
20420
- filters
20421
- };
20394
+ const params = mode === "delta" ? { mode: "delta", after, limit, filters } : { pagination: { page, perPage }, filters };
20422
20395
  if (listMode === "branches") {
20423
20396
  return client.listBranches(params);
20424
20397
  }
@@ -20443,13 +20416,90 @@ function selectUniqueTraces(data) {
20443
20416
  seen.add(key);
20444
20417
  return true;
20445
20418
  });
20446
- return { spans };
20419
+ return { spans, deltaCursor: data.pages[0]?.deltaCursor };
20420
+ }
20421
+ function refreshPage0Rows(old, refreshed, listMode) {
20422
+ if (!old || old.pages.length === 0) return old;
20423
+ const [firstPage, ...rest] = old.pages;
20424
+ const refreshedRows = listMode === "branches" && "branches" in refreshed ? refreshed.branches ?? [] : "spans" in refreshed ? refreshed.spans ?? [] : [];
20425
+ if (refreshedRows.length === 0) return old;
20426
+ const refreshedByKey = /* @__PURE__ */ new Map();
20427
+ for (const row of refreshedRows) {
20428
+ refreshedByKey.set(`${row.traceId}:${row.spanId}`, row);
20429
+ }
20430
+ let updatedFirst;
20431
+ if (listMode === "branches" && "branches" in firstPage) {
20432
+ const updated = (firstPage.branches ?? []).map((existing) => {
20433
+ const fresh = refreshedByKey.get(`${existing.traceId}:${existing.spanId}`);
20434
+ return fresh ?? existing;
20435
+ });
20436
+ updatedFirst = { ...firstPage, branches: updated };
20437
+ } else if ("spans" in firstPage) {
20438
+ const updated = (firstPage.spans ?? []).map((existing) => {
20439
+ const fresh = refreshedByKey.get(`${existing.traceId}:${existing.spanId}`);
20440
+ return fresh ?? existing;
20441
+ });
20442
+ updatedFirst = { ...firstPage, spans: updated };
20443
+ } else {
20444
+ return old;
20445
+ }
20446
+ return { ...old, pages: [updatedFirst, ...rest] };
20447
+ }
20448
+ function sortRowsByStartedAtDesc(rows) {
20449
+ return [...rows].sort((a, b) => {
20450
+ const aTime = a.startedAt ? new Date(a.startedAt).getTime() : 0;
20451
+ const bTime = b.startedAt ? new Date(b.startedAt).getTime() : 0;
20452
+ return bTime - aTime;
20453
+ });
20454
+ }
20455
+ function mergeDeltaIntoPage0(old, delta, listMode) {
20456
+ if (!old || old.pages.length === 0) return old;
20457
+ const [firstPage, ...rest] = old.pages;
20458
+ const nextCursor = delta.deltaCursor ?? firstPage.deltaCursor;
20459
+ let updatedFirst;
20460
+ if (listMode === "branches" && "branches" in firstPage) {
20461
+ const newRows = delta.branches ?? [];
20462
+ updatedFirst = {
20463
+ ...firstPage,
20464
+ branches: sortRowsByStartedAtDesc([...newRows, ...firstPage.branches ?? []]),
20465
+ deltaCursor: nextCursor
20466
+ };
20467
+ } else if ("spans" in firstPage) {
20468
+ const newRows = delta.spans ?? [];
20469
+ updatedFirst = {
20470
+ ...firstPage,
20471
+ spans: sortRowsByStartedAtDesc([...newRows, ...firstPage.spans ?? []]),
20472
+ deltaCursor: nextCursor
20473
+ };
20474
+ } else {
20475
+ return old;
20476
+ }
20477
+ return { ...old, pages: [updatedFirst, ...rest] };
20447
20478
  }
20448
- const useTraces = ({ filters, listMode = "traces" }) => {
20479
+ const useTraces = ({ filters, listMode = "traces", polling = {} }) => {
20480
+ const {
20481
+ deltaPollIntervalMs = DEFAULT_POLLING_CONFIG.deltaPollIntervalMs,
20482
+ deltaChaseIntervalMs = DEFAULT_POLLING_CONFIG.deltaChaseIntervalMs,
20483
+ deltaLimit = DEFAULT_POLLING_CONFIG.deltaLimit,
20484
+ pageModeRefetchIntervalMs = DEFAULT_POLLING_CONFIG.pageModeRefetchIntervalMs,
20485
+ page0StatusRefreshIntervalMs = DEFAULT_POLLING_CONFIG.page0StatusRefreshIntervalMs,
20486
+ idleGuardThresholdMs = DEFAULT_POLLING_CONFIG.idleGuardThresholdMs,
20487
+ minRefetchSpinMs = DEFAULT_POLLING_CONFIG.minRefetchSpinMs,
20488
+ deltaHighlightMs = DEFAULT_POLLING_CONFIG.deltaHighlightMs
20489
+ } = polling;
20449
20490
  const client = react.useMastraClient();
20491
+ const queryClient = reactQuery.useQueryClient();
20450
20492
  const { inView: isEndOfListInView, setRef: setEndOfListElement } = useInView();
20493
+ const [deltaSupport, setDeltaSupport] = React.useState(() => deltaSupportByClient.get(client) ?? "unknown");
20494
+ const deltaUnsupported = deltaSupport === "unsupported";
20495
+ const [autoRefetch, setAutoRefetch] = React.useState(true);
20496
+ const [recentlyAddedKeys, setRecentlyAddedKeys] = React.useState(() => /* @__PURE__ */ new Set());
20497
+ React.useEffect(() => {
20498
+ setRecentlyAddedKeys(/* @__PURE__ */ new Set());
20499
+ }, [listMode, filters]);
20500
+ const tracesQueryKey = ["traces", listMode, filters];
20451
20501
  const query = reactQuery.useInfiniteQuery({
20452
- queryKey: ["traces", listMode, filters],
20502
+ queryKey: tracesQueryKey,
20453
20503
  queryFn: ({ pageParam }) => fetchTracesFn({
20454
20504
  client,
20455
20505
  page: pageParam,
@@ -20461,16 +20511,150 @@ const useTraces = ({ filters, listMode = "traces" }) => {
20461
20511
  getNextPageParam: getTracesNextPageParam,
20462
20512
  select: selectUniqueTraces,
20463
20513
  retry: false,
20464
- // Disable polling on 403 to prevent flickering
20465
- refetchInterval: (query2) => is403ForbiddenError(query2.state.error) ? false : 1e4
20514
+ // Disable polling on 403 to prevent flickering.
20515
+ // Fall back to page-mode polling only when delta isn't running.
20516
+ refetchInterval: (q) => {
20517
+ if (is403ForbiddenError(q.state.error)) return false;
20518
+ if (!autoRefetch) return false;
20519
+ return deltaUnsupported ? pageModeRefetchIntervalMs : false;
20520
+ }
20466
20521
  });
20522
+ const cursor = query.data?.deltaCursor;
20523
+ React.useEffect(() => {
20524
+ if (query.isSuccess && cursor === void 0 && deltaSupport === "unknown") {
20525
+ deltaSupportByClient.set(client, "unsupported");
20526
+ setDeltaSupport("unsupported");
20527
+ }
20528
+ }, [query.isSuccess, cursor, deltaSupport, client]);
20529
+ const deltaQuery = reactQuery.useQuery({
20530
+ queryKey: ["traces-delta", listMode, filters],
20531
+ queryFn: () => {
20532
+ const current = queryClient.getQueryData(tracesQueryKey)?.pages[0]?.deltaCursor;
20533
+ if (!current) return null;
20534
+ return fetchTracesFn({
20535
+ client,
20536
+ mode: "delta",
20537
+ after: current,
20538
+ limit: deltaLimit,
20539
+ filters,
20540
+ listMode
20541
+ });
20542
+ },
20543
+ enabled: !!cursor && !deltaUnsupported && autoRefetch,
20544
+ retry: false,
20545
+ refetchInterval: (q) => {
20546
+ if (q.state.error) return false;
20547
+ const data = q.state.data;
20548
+ if (data?.delta?.hasMore) return deltaChaseIntervalMs;
20549
+ return deltaPollIntervalMs;
20550
+ }
20551
+ });
20552
+ React.useEffect(() => {
20553
+ const result = deltaQuery.data;
20554
+ if (!result) return;
20555
+ queryClient.setQueryData(
20556
+ tracesQueryKey,
20557
+ (old) => mergeDeltaIntoPage0(old, result, listMode)
20558
+ );
20559
+ const newRows = listMode === "branches" && "branches" in result ? result.branches ?? [] : "spans" in result ? result.spans ?? [] : [];
20560
+ if (newRows.length === 0) return;
20561
+ const keys = newRows.map((r) => `${r.traceId}:${r.spanId ?? ""}`);
20562
+ setRecentlyAddedKeys((prev) => {
20563
+ const next = new Set(prev);
20564
+ for (const k of keys) next.add(k);
20565
+ return next;
20566
+ });
20567
+ setTimeout(() => {
20568
+ setRecentlyAddedKeys((prev) => {
20569
+ const next = new Set(prev);
20570
+ for (const k of keys) next.delete(k);
20571
+ return next;
20572
+ });
20573
+ }, deltaHighlightMs);
20574
+ }, [deltaQuery.data, queryClient, listMode, filters, deltaHighlightMs]);
20575
+ React.useEffect(() => {
20576
+ if (isHttp501(deltaQuery.error)) {
20577
+ deltaSupportByClient.set(client, "unsupported");
20578
+ setDeltaSupport("unsupported");
20579
+ }
20580
+ }, [deltaQuery.error, client]);
20581
+ const statusRefreshQuery = reactQuery.useQuery({
20582
+ queryKey: ["traces-status-refresh", listMode, filters],
20583
+ queryFn: () => fetchTracesFn({
20584
+ client,
20585
+ page: 0,
20586
+ perPage: TRACES_PER_PAGE,
20587
+ filters,
20588
+ listMode
20589
+ }),
20590
+ enabled: !!cursor && !deltaUnsupported && autoRefetch,
20591
+ refetchInterval: page0StatusRefreshIntervalMs,
20592
+ refetchOnMount: false,
20593
+ retry: false
20594
+ });
20595
+ React.useEffect(() => {
20596
+ const result = statusRefreshQuery.data;
20597
+ if (!result) return;
20598
+ queryClient.setQueryData(
20599
+ tracesQueryKey,
20600
+ (old) => refreshPage0Rows(old, result, listMode)
20601
+ );
20602
+ }, [statusRefreshQuery.data, queryClient, listMode, filters]);
20603
+ const lastSuccessAtRef = React.useRef(0);
20604
+ React.useEffect(() => {
20605
+ const ts = Math.max(query.dataUpdatedAt, deltaQuery.dataUpdatedAt, statusRefreshQuery.dataUpdatedAt);
20606
+ if (ts > lastSuccessAtRef.current) {
20607
+ lastSuccessAtRef.current = ts;
20608
+ }
20609
+ }, [query.dataUpdatedAt, deltaQuery.dataUpdatedAt, statusRefreshQuery.dataUpdatedAt]);
20610
+ const autoRefetchRef = React.useRef(autoRefetch);
20611
+ React.useEffect(() => {
20612
+ autoRefetchRef.current = autoRefetch;
20613
+ }, [autoRefetch]);
20614
+ React.useEffect(() => {
20615
+ const handler = () => {
20616
+ if (document.visibilityState !== "visible") return;
20617
+ if (!autoRefetchRef.current) return;
20618
+ if (!shouldResetAfterIdle(lastSuccessAtRef.current, Date.now(), idleGuardThresholdMs)) return;
20619
+ void queryClient.resetQueries({ queryKey: ["traces", listMode, filters] });
20620
+ };
20621
+ document.addEventListener("visibilitychange", handler);
20622
+ return () => document.removeEventListener("visibilitychange", handler);
20623
+ }, [queryClient, listMode, filters, idleGuardThresholdMs]);
20467
20624
  const { hasNextPage, isFetchingNextPage, fetchNextPage } = query;
20468
20625
  React.useEffect(() => {
20469
20626
  if (isEndOfListInView && hasNextPage && !isFetchingNextPage) {
20470
20627
  void fetchNextPage();
20471
20628
  }
20472
20629
  }, [isEndOfListInView, hasNextPage, isFetchingNextPage, fetchNextPage]);
20473
- return { ...query, setEndOfListElement };
20630
+ const isFetchingAny = query.isFetching || statusRefreshQuery.isFetching || deltaQuery.isFetching;
20631
+ const lastActivityAt = Math.max(
20632
+ query.dataUpdatedAt,
20633
+ query.errorUpdatedAt,
20634
+ deltaQuery.dataUpdatedAt,
20635
+ deltaQuery.errorUpdatedAt,
20636
+ statusRefreshQuery.dataUpdatedAt,
20637
+ statusRefreshQuery.errorUpdatedAt
20638
+ );
20639
+ const [pulse, setPulse] = React.useState(false);
20640
+ const prevActivityAtRef = React.useRef(lastActivityAt);
20641
+ React.useEffect(() => {
20642
+ if (lastActivityAt > prevActivityAtRef.current) {
20643
+ prevActivityAtRef.current = lastActivityAt;
20644
+ setPulse(true);
20645
+ const t = setTimeout(() => setPulse(false), minRefetchSpinMs);
20646
+ return () => clearTimeout(t);
20647
+ }
20648
+ }, [lastActivityAt, minRefetchSpinMs]);
20649
+ const isRefetching = isFetchingAny || pulse;
20650
+ return {
20651
+ ...query,
20652
+ setEndOfListElement,
20653
+ isRefetching,
20654
+ autoRefetch,
20655
+ setAutoRefetch,
20656
+ recentlyAddedKeys
20657
+ };
20474
20658
  };
20475
20659
 
20476
20660
  const useTags = () => {
@@ -20939,17 +21123,17 @@ function LogDetailsView({
20939
21123
  ] }),
20940
21124
  /* @__PURE__ */ jsxRuntime.jsxs(ButtonsGroup, { className: "ml-auto shrink-0", children: [
20941
21125
  onCollapsedChange && /* @__PURE__ */ jsxRuntime.jsx(
20942
- ButtonWithTooltip,
21126
+ Button,
20943
21127
  {
20944
21128
  size: "md",
20945
- tooltipContent: collapsed ? "Expand panel" : "Collapse panel",
21129
+ tooltip: collapsed ? "Expand panel" : "Collapse panel",
20946
21130
  onClick: () => setCollapsed(!collapsed),
20947
21131
  children: collapsed ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronsUpDownIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronsDownUpIcon, {})
20948
21132
  }
20949
21133
  ),
20950
21134
  /* @__PURE__ */ jsxRuntime.jsxs(ButtonsGroup, { spacing: "close", children: [
20951
- /* @__PURE__ */ jsxRuntime.jsx(ButtonWithTooltip, { size: "md", tooltipContent: "Previous log", onClick: onPrevious, disabled: !onPrevious, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUpIcon, {}) }),
20952
- /* @__PURE__ */ jsxRuntime.jsx(ButtonWithTooltip, { size: "md", tooltipContent: "Next log", onClick: onNext, disabled: !onNext, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowDownIcon, {}) })
21135
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "md", tooltip: "Previous log", onClick: onPrevious, disabled: !onPrevious, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUpIcon, {}) }),
21136
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "md", tooltip: "Next log", onClick: onNext, disabled: !onNext, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowDownIcon, {}) })
20953
21137
  ] }),
20954
21138
  /* @__PURE__ */ jsxRuntime.jsx(DataDetailsPanel.CloseButton, { onClick: onClose })
20955
21139
  ] })
@@ -21638,7 +21822,6 @@ exports.BranchIcon = BranchIcon;
21638
21822
  exports.BrandLoader = BrandLoader;
21639
21823
  exports.Breadcrumb = Breadcrumb;
21640
21824
  exports.Button = Button;
21641
- exports.ButtonWithTooltip = ButtonWithTooltip;
21642
21825
  exports.ButtonsGroup = ButtonsGroup;
21643
21826
  exports.ButtonsGroupSeparator = ButtonsGroupSeparator;
21644
21827
  exports.ButtonsGroupText = ButtonsGroupText;