@mastra/playground-ui 47.0.0 → 48.0.0-alpha.1
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 +50 -0
- package/dist/components/Breadcrumb.cjs.js +10 -3
- package/dist/components/Breadcrumb.cjs.js.map +1 -1
- package/dist/components/Breadcrumb.es.js +10 -3
- package/dist/components/Breadcrumb.es.js.map +1 -1
- package/dist/domains/traces/components/traces-list-view.cjs.js +3 -3
- package/dist/domains/traces/components/traces-list-view.cjs.js.map +1 -1
- package/dist/domains/traces/components/traces-list-view.es.js +3 -3
- package/dist/domains/traces/components/traces-list-view.es.js.map +1 -1
- package/dist/domains/traces/hooks/use-traces.cjs.js +61 -19
- package/dist/domains/traces/hooks/use-traces.cjs.js.map +1 -1
- package/dist/domains/traces/hooks/use-traces.es.js +61 -20
- package/dist/domains/traces/hooks/use-traces.es.js.map +1 -1
- package/dist/src/domains/traces/components/traces-list-view.d.ts +2 -9
- package/dist/src/domains/traces/hooks/use-traces.d.ts +11 -7
- package/dist/src/ds/components/Breadcrumb/Breadcrumb.d.ts +1 -1
- package/dist/src/ds/components/Breadcrumb/breadcrumb.stories.d.ts +2 -0
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
# @mastra/playground-ui
|
|
2
2
|
|
|
3
|
+
## 48.0.0-alpha.1
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Removed the `attributes` field and the `TraceAttributes` type from `TracesListViewTrace`. ([#20677](https://github.com/mastra-ai/mastra/pull/20677))
|
|
8
|
+
|
|
9
|
+
The traces list used to fall back to `attributes.agentId`/`attributes.workflowId` for the entity column and to `attributes.status` for the status column. Rows now carry `entityName`/`entityId` and a computed `status` directly, and the lightweight rows the list fetches never carried `attributes` at all, so both fallbacks were dead code reading a field nothing produced.
|
|
10
|
+
|
|
11
|
+
Rows that relied on those fallbacks must set `status` and the entity fields directly:
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
// Before
|
|
15
|
+
<TracesListView traces={[{ traceId, name, createdAt, attributes: { status: 'completed' } }]} />
|
|
16
|
+
|
|
17
|
+
// After
|
|
18
|
+
<TracesListView traces={[{ traceId, name, createdAt, status: 'success', entityName: 'weatherAgent' }]} />
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`TraceAttributes` was reachable as `import type { TraceAttributes } from '@mastra/playground-ui/domains/traces/components/traces-list-view'` and no longer exists. There is no replacement — read `status`, `entityType`, `entityName` and `entityId` off the row instead.
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- Fix Crumb truncating labels without an ellipsis and clipping their descenders ([#20816](https://github.com/mastra-ai/mastra/pull/20816))
|
|
26
|
+
|
|
27
|
+
`truncate` sat on Crumb's flex root, where `text-overflow` cannot apply — a
|
|
28
|
+
clamped label was hard-cut mid-glyph instead of ellipsized, and the accompanying
|
|
29
|
+
`overflow-hidden` sheared the descenders off the line box. Text children now get
|
|
30
|
+
their own truncating box while icons stay flex siblings.
|
|
31
|
+
|
|
32
|
+
- Improved the Observability traces list, which was downloading every trace's full prompt and response just to render a 100-character preview. The list now transfers only what it renders, so it loads far less data before a trace is opened and stays fast as traces grow. Measured against ClickHouse with 400 agent-run traces, one 25-row page dropped from 506 KB to 10.6 KB. ([#20677](https://github.com/mastra-ai/mastra/pull/20677))
|
|
33
|
+
|
|
34
|
+
- Fixed the Observability traces list permanently falling back to the heavier trace endpoint after a passing server error. ([#20677](https://github.com/mastra-ai/mastra/pull/20677))
|
|
35
|
+
|
|
36
|
+
The list asks the server for lightweight trace rows and falls back to the full ones when a server is too old to serve them. A `500` was treated as that signal, so one transient fault — a database hiccup, a request timeout — pinned the tab to the heavier endpoint for the rest of the session, silently undoing the bandwidth savings until a reload. A `500` now falls back for that request only; the session switches for good once the errors have lasted more than ten seconds, which no longer looks like a hiccup. A `404` still switches immediately, since a server without the route will never grow one mid-session.
|
|
37
|
+
|
|
38
|
+
- Updated dependencies [[`e7109ee`](https://github.com/mastra-ai/mastra/commit/e7109ee6f731bacc79c885906f3c7dca8d8f013a), [`697d059`](https://github.com/mastra-ai/mastra/commit/697d05953049bf6d09e89646c137b76f8ed472ad), [`772c0c8`](https://github.com/mastra-ai/mastra/commit/772c0c897cec383258de2e6178147f8014767c7b), [`578bf2e`](https://github.com/mastra-ai/mastra/commit/578bf2e6a88e9d5b8bf502204e15a95dfbb679ae), [`3ae7303`](https://github.com/mastra-ai/mastra/commit/3ae7303508ee497c97d280208df68ffad4ffd368), [`06b2d87`](https://github.com/mastra-ai/mastra/commit/06b2d87e63bcdd0ed59215c6789692b9b12de376), [`ac01d63`](https://github.com/mastra-ai/mastra/commit/ac01d6355974aec73fdb8781449ed12bac582094), [`772c0c8`](https://github.com/mastra-ai/mastra/commit/772c0c897cec383258de2e6178147f8014767c7b), [`a810a05`](https://github.com/mastra-ai/mastra/commit/a810a058f62ad407cfc1701e0be36ae91145d7cf), [`f8da216`](https://github.com/mastra-ai/mastra/commit/f8da21633e7eb0e31c9ce0fc30567870d19416d3), [`a4f6806`](https://github.com/mastra-ai/mastra/commit/a4f68065d082af57770e7ee3d34996fdc914dbd1), [`6104347`](https://github.com/mastra-ai/mastra/commit/61043473ba6bfd0a25156824e853e13165562e6c), [`ffdfa25`](https://github.com/mastra-ai/mastra/commit/ffdfa25b5cdf3ecfd0c5a2e96363c3ba50995ce9), [`45bfb88`](https://github.com/mastra-ai/mastra/commit/45bfb88fd52f1dd3be20e2a38905777c96499c90), [`e3b9307`](https://github.com/mastra-ai/mastra/commit/e3b9307098daefbfae2a52ae2ef51bc9fc701190), [`d6834c5`](https://github.com/mastra-ai/mastra/commit/d6834c5a7866b16734d23900163c2414ed70d791), [`d382d57`](https://github.com/mastra-ai/mastra/commit/d382d57d072de142c4db113adc35b8b6d0bd49bf), [`c52d346`](https://github.com/mastra-ai/mastra/commit/c52d3462ec831a5d95926ecd3d3373f5928ad2e5), [`0023e79`](https://github.com/mastra-ai/mastra/commit/0023e7919431078280abd11c89d1edeae35fcc69), [`c2ad51e`](https://github.com/mastra-ai/mastra/commit/c2ad51e2467f901eecba8c9f4a45e22a50bd7c18), [`3dc97ea`](https://github.com/mastra-ai/mastra/commit/3dc97ea415fad353b48a13095fad1835933cc12a), [`3d01cd3`](https://github.com/mastra-ai/mastra/commit/3d01cd387321b6f9c5cac31d487c84bf51b19c78), [`7bf3086`](https://github.com/mastra-ai/mastra/commit/7bf308663f0115ca74ad20554ade740f06640859), [`a8dd139`](https://github.com/mastra-ai/mastra/commit/a8dd1391a9fe9a6632c25809ef236980afa9a020), [`e5786be`](https://github.com/mastra-ai/mastra/commit/e5786be02bb903073082bd9d6da880ebaacc343f), [`e5786be`](https://github.com/mastra-ai/mastra/commit/e5786be02bb903073082bd9d6da880ebaacc343f), [`2093fbd`](https://github.com/mastra-ai/mastra/commit/2093fbd53bb744bae19ec89f6d73db9a66fbe8a7), [`e7a5da4`](https://github.com/mastra-ai/mastra/commit/e7a5da4ef8e4dd452d2f232961b4e682a85ffe43), [`7b4393d`](https://github.com/mastra-ai/mastra/commit/7b4393d557411fdcf07b0e30e5acaf7cc85154ae)]:
|
|
39
|
+
- @mastra/core@1.58.0-alpha.1
|
|
40
|
+
- @mastra/client-js@1.39.0-alpha.1
|
|
41
|
+
- @mastra/react@1.4.2-alpha.1
|
|
42
|
+
- @mastra/memory@1.26.1-alpha.0
|
|
43
|
+
|
|
44
|
+
## 47.0.1-alpha.0
|
|
45
|
+
|
|
46
|
+
### Patch Changes
|
|
47
|
+
|
|
48
|
+
- Updated dependencies [[`45a9147`](https://github.com/mastra-ai/mastra/commit/45a914741f578754d79d8b7de7b4e4f304d8e14a), [`990611b`](https://github.com/mastra-ai/mastra/commit/990611ba76eb876d86c9c594371ae5f02f94b432), [`ed5d606`](https://github.com/mastra-ai/mastra/commit/ed5d606739c5e3fbdfa9f272df7809aa5ab43b1d)]:
|
|
49
|
+
- @mastra/core@1.58.0-alpha.0
|
|
50
|
+
- @mastra/client-js@1.38.1-alpha.0
|
|
51
|
+
- @mastra/react@1.4.2-alpha.0
|
|
52
|
+
|
|
3
53
|
## 47.0.0
|
|
4
54
|
|
|
5
55
|
### Patch Changes
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
4
4
|
|
|
5
5
|
const jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
const React = require('react');
|
|
6
7
|
const icons_Icon = require('../icons/Icon.cjs.js');
|
|
7
8
|
const icons_SlashIcon = require('../icons/SlashIcon.cjs.js');
|
|
8
9
|
const primitives_transitions = require('../primitives/transitions.cjs.js');
|
|
@@ -11,7 +12,12 @@ const utils_cn = require('../cn-vA5sWo9W.cjs');
|
|
|
11
12
|
const Breadcrumb = ({ children, label, className, listClassName }) => {
|
|
12
13
|
return /* @__PURE__ */ jsxRuntime.jsx("nav", { "aria-label": label, className, children: /* @__PURE__ */ jsxRuntime.jsx("ol", { className: utils_cn.cn("flex items-center gap-0.5", listClassName), children }) });
|
|
13
14
|
};
|
|
14
|
-
const
|
|
15
|
+
const crumbTextTruncateStyles = "min-w-0 flex-1 truncate";
|
|
16
|
+
const truncateTextChildren = (children) => React.Children.map(
|
|
17
|
+
children,
|
|
18
|
+
(child) => typeof child === "string" || typeof child === "number" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: crumbTextTruncateStyles, children: child }) : child
|
|
19
|
+
);
|
|
20
|
+
const Crumb = ({ className, as, isCurrent, action, children, ...props }) => {
|
|
15
21
|
const Root = as || "span";
|
|
16
22
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
17
23
|
/* @__PURE__ */ jsxRuntime.jsxs("li", { className: utils_cn.cn("flex h-full min-w-0 items-center gap-1", isCurrent ? "shrink" : "shrink-0"), children: [
|
|
@@ -20,12 +26,13 @@ const Crumb = ({ className, as, isCurrent, action, ...props }) => {
|
|
|
20
26
|
{
|
|
21
27
|
"aria-current": isCurrent ? "page" : void 0,
|
|
22
28
|
className: utils_cn.cn(
|
|
23
|
-
"flex min-w-0 items-center gap-2
|
|
29
|
+
"flex min-w-0 items-center gap-2 overflow-hidden rounded-md px-1 py-0.5 text-ui-md leading-ui-md",
|
|
24
30
|
primitives_transitions.transitions.colors,
|
|
25
31
|
isCurrent ? "font-medium text-neutral6" : "cursor-pointer text-neutral3 hover:bg-neutral6/5 hover:text-neutral5 active:bg-neutral6/10",
|
|
26
32
|
className
|
|
27
33
|
),
|
|
28
|
-
...props
|
|
34
|
+
...props,
|
|
35
|
+
children: truncateTextChildren(children)
|
|
29
36
|
}
|
|
30
37
|
),
|
|
31
38
|
action
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Breadcrumb.cjs.js","sources":["../../src/ds/components/Breadcrumb/Breadcrumb.tsx"],"sourcesContent":["import React from 'react';\n\nimport { Icon } from '../../icons/Icon';\nimport { SlashIcon } from '../../icons/SlashIcon';\nimport { transitions } from '@/ds/primitives/transitions';\nimport { cn } from '@/lib/utils';\n\nexport interface BreadcrumbProps {\n children?: React.ReactNode;\n label?: string;\n className?: string;\n listClassName?: string;\n}\n\nexport const Breadcrumb = ({ children, label, className, listClassName }: BreadcrumbProps) => {\n return (\n <nav aria-label={label} className={className}>\n <ol className={cn('flex items-center gap-0.5', listClassName)}>{children}</ol>\n </nav>\n );\n};\n\nexport interface CrumbProps {\n isCurrent?: boolean;\n as: React.ElementType;\n className?: string;\n to?: string;\n prefetch?: boolean | null;\n children: React.ReactNode;\n action?: React.ReactNode;\n 'data-testid'?: string;\n}\n\nexport const Crumb = ({ className, as, isCurrent, action, ...props }: CrumbProps) => {\n const Root = as || 'span';\n\n return (\n <>\n <li className={cn('flex h-full min-w-0 items-center gap-1', isCurrent ? 'shrink' : 'shrink-0')}>\n <Root\n aria-current={isCurrent ? 'page' : undefined}\n className={cn(\n 'flex min-w-0 items-center gap-2
|
|
1
|
+
{"version":3,"file":"Breadcrumb.cjs.js","sources":["../../src/ds/components/Breadcrumb/Breadcrumb.tsx"],"sourcesContent":["import React from 'react';\n\nimport { Icon } from '../../icons/Icon';\nimport { SlashIcon } from '../../icons/SlashIcon';\nimport { transitions } from '@/ds/primitives/transitions';\nimport { cn } from '@/lib/utils';\n\nexport interface BreadcrumbProps {\n children?: React.ReactNode;\n label?: string;\n className?: string;\n listClassName?: string;\n}\n\nexport const Breadcrumb = ({ children, label, className, listClassName }: BreadcrumbProps) => {\n return (\n <nav aria-label={label} className={className}>\n <ol className={cn('flex items-center gap-0.5', listClassName)}>{children}</ol>\n </nav>\n );\n};\n\nexport interface CrumbProps {\n isCurrent?: boolean;\n as: React.ElementType;\n className?: string;\n to?: string;\n prefetch?: boolean | null;\n children: React.ReactNode;\n action?: React.ReactNode;\n 'data-testid'?: string;\n}\n\n// `text-overflow` needs a block container, so the label truncates in its own\n// box rather than on the flex Root. Icons stay siblings to keep `gap-2`.\nconst crumbTextTruncateStyles = 'min-w-0 flex-1 truncate';\n\nconst truncateTextChildren = (children: React.ReactNode) =>\n React.Children.map(children, child =>\n typeof child === 'string' || typeof child === 'number' ? (\n <span className={crumbTextTruncateStyles}>{child}</span>\n ) : (\n child\n ),\n );\n\nexport const Crumb = ({ className, as, isCurrent, action, children, ...props }: CrumbProps) => {\n const Root = as || 'span';\n\n return (\n <>\n <li className={cn('flex h-full min-w-0 items-center gap-1', isCurrent ? 'shrink' : 'shrink-0')}>\n <Root\n aria-current={isCurrent ? 'page' : undefined}\n className={cn(\n 'flex min-w-0 items-center gap-2 overflow-hidden rounded-md px-1 py-0.5 text-ui-md leading-ui-md',\n transitions.colors,\n isCurrent\n ? 'font-medium text-neutral6'\n : 'cursor-pointer text-neutral3 hover:bg-neutral6/5 hover:text-neutral5 active:bg-neutral6/10',\n className,\n )}\n {...props}\n >\n {truncateTextChildren(children)}\n </Root>\n {action}\n </li>\n {!isCurrent && (\n <li role=\"separator\" className=\"flex h-full items-center\">\n <Icon className={cn('text-neutral2', transitions.colors)}>\n <SlashIcon />\n </Icon>\n </li>\n )}\n </>\n );\n};\n"],"names":["jsx","cn","jsxs","Fragment","transitions","Icon","SlashIcon"],"mappings":";;;;;;;;;;;AAcO,MAAM,aAAa,CAAC,EAAE,UAAU,KAAA,EAAO,SAAA,EAAW,eAAc,KAAuB;AAC5F,EAAA,uBACEA,cAAA,CAAC,KAAA,EAAA,EAAI,YAAA,EAAY,KAAA,EAAO,SAAA,EACtB,QAAA,kBAAAA,cAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAWC,WAAA,CAAG,2BAAA,EAA6B,aAAa,CAAA,EAAI,UAAS,CAAA,EAC3E,CAAA;AAEJ;AAeA,MAAM,uBAAA,GAA0B,yBAAA;AAEhC,MAAM,oBAAA,GAAuB,CAAC,QAAA,KAC5B,KAAA,CAAM,QAAA,CAAS,GAAA;AAAA,EAAI,QAAA;AAAA,EAAU,CAAA,KAAA,KAC3B,OAAO,KAAA,KAAU,QAAA,IAAY,OAAO,KAAA,KAAU,QAAA,mBAC5CD,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAW,uBAAA,EAA0B,QAAA,EAAA,KAAA,EAAM,CAAA,GAEjD;AAEJ,CAAA;AAEK,MAAM,KAAA,GAAQ,CAAC,EAAE,SAAA,EAAW,EAAA,EAAI,WAAW,MAAA,EAAQ,QAAA,EAAU,GAAG,KAAA,EAAM,KAAkB;AAC7F,EAAA,MAAM,OAAO,EAAA,IAAM,MAAA;AAEnB,EAAA,uBACEE,eAAA,CAAAC,mBAAA,EAAA,EACE,QAAA,EAAA;AAAA,oBAAAD,eAAA,CAAC,QAAG,SAAA,EAAWD,WAAA,CAAG,0CAA0C,SAAA,GAAY,QAAA,GAAW,UAAU,CAAA,EAC3F,QAAA,EAAA;AAAA,sBAAAD,cAAA;AAAA,QAAC,IAAA;AAAA,QAAA;AAAA,UACC,cAAA,EAAc,YAAY,MAAA,GAAS,MAAA;AAAA,UACnC,SAAA,EAAWC,WAAA;AAAA,YACT,iGAAA;AAAA,YACAG,kCAAA,CAAY,MAAA;AAAA,YACZ,YACI,2BAAA,GACA,4FAAA;AAAA,YACJ;AAAA,WACF;AAAA,UACC,GAAG,KAAA;AAAA,UAEH,+BAAqB,QAAQ;AAAA;AAAA,OAChC;AAAA,MACC;AAAA,KAAA,EACH,CAAA;AAAA,IACC,CAAC,SAAA,oBACAJ,cAAA,CAAC,QAAG,IAAA,EAAK,WAAA,EAAY,WAAU,0BAAA,EAC7B,QAAA,kBAAAA,cAAA,CAACK,mBAAK,SAAA,EAAWJ,WAAA,CAAG,iBAAiBG,kCAAA,CAAY,MAAM,GACrD,QAAA,kBAAAJ,cAAA,CAACM,yBAAA,EAAA,EAAU,GACb,CAAA,EACF;AAAA,GAAA,EAEJ,CAAA;AAEJ;;;;;"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
import React__default from 'react';
|
|
2
3
|
import { Icon } from '../icons/Icon.es.js';
|
|
3
4
|
import { SlashIcon } from '../icons/SlashIcon.es.js';
|
|
4
5
|
import { transitions } from '../primitives/transitions.es.js';
|
|
@@ -7,7 +8,12 @@ import { c as cn } from '../cn-BakJzhuv.js';
|
|
|
7
8
|
const Breadcrumb = ({ children, label, className, listClassName }) => {
|
|
8
9
|
return /* @__PURE__ */ jsx("nav", { "aria-label": label, className, children: /* @__PURE__ */ jsx("ol", { className: cn("flex items-center gap-0.5", listClassName), children }) });
|
|
9
10
|
};
|
|
10
|
-
const
|
|
11
|
+
const crumbTextTruncateStyles = "min-w-0 flex-1 truncate";
|
|
12
|
+
const truncateTextChildren = (children) => React__default.Children.map(
|
|
13
|
+
children,
|
|
14
|
+
(child) => typeof child === "string" || typeof child === "number" ? /* @__PURE__ */ jsx("span", { className: crumbTextTruncateStyles, children: child }) : child
|
|
15
|
+
);
|
|
16
|
+
const Crumb = ({ className, as, isCurrent, action, children, ...props }) => {
|
|
11
17
|
const Root = as || "span";
|
|
12
18
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
13
19
|
/* @__PURE__ */ jsxs("li", { className: cn("flex h-full min-w-0 items-center gap-1", isCurrent ? "shrink" : "shrink-0"), children: [
|
|
@@ -16,12 +22,13 @@ const Crumb = ({ className, as, isCurrent, action, ...props }) => {
|
|
|
16
22
|
{
|
|
17
23
|
"aria-current": isCurrent ? "page" : void 0,
|
|
18
24
|
className: cn(
|
|
19
|
-
"flex min-w-0 items-center gap-2
|
|
25
|
+
"flex min-w-0 items-center gap-2 overflow-hidden rounded-md px-1 py-0.5 text-ui-md leading-ui-md",
|
|
20
26
|
transitions.colors,
|
|
21
27
|
isCurrent ? "font-medium text-neutral6" : "cursor-pointer text-neutral3 hover:bg-neutral6/5 hover:text-neutral5 active:bg-neutral6/10",
|
|
22
28
|
className
|
|
23
29
|
),
|
|
24
|
-
...props
|
|
30
|
+
...props,
|
|
31
|
+
children: truncateTextChildren(children)
|
|
25
32
|
}
|
|
26
33
|
),
|
|
27
34
|
action
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Breadcrumb.es.js","sources":["../../src/ds/components/Breadcrumb/Breadcrumb.tsx"],"sourcesContent":["import React from 'react';\n\nimport { Icon } from '../../icons/Icon';\nimport { SlashIcon } from '../../icons/SlashIcon';\nimport { transitions } from '@/ds/primitives/transitions';\nimport { cn } from '@/lib/utils';\n\nexport interface BreadcrumbProps {\n children?: React.ReactNode;\n label?: string;\n className?: string;\n listClassName?: string;\n}\n\nexport const Breadcrumb = ({ children, label, className, listClassName }: BreadcrumbProps) => {\n return (\n <nav aria-label={label} className={className}>\n <ol className={cn('flex items-center gap-0.5', listClassName)}>{children}</ol>\n </nav>\n );\n};\n\nexport interface CrumbProps {\n isCurrent?: boolean;\n as: React.ElementType;\n className?: string;\n to?: string;\n prefetch?: boolean | null;\n children: React.ReactNode;\n action?: React.ReactNode;\n 'data-testid'?: string;\n}\n\nexport const Crumb = ({ className, as, isCurrent, action, ...props }: CrumbProps) => {\n const Root = as || 'span';\n\n return (\n <>\n <li className={cn('flex h-full min-w-0 items-center gap-1', isCurrent ? 'shrink' : 'shrink-0')}>\n <Root\n aria-current={isCurrent ? 'page' : undefined}\n className={cn(\n 'flex min-w-0 items-center gap-2
|
|
1
|
+
{"version":3,"file":"Breadcrumb.es.js","sources":["../../src/ds/components/Breadcrumb/Breadcrumb.tsx"],"sourcesContent":["import React from 'react';\n\nimport { Icon } from '../../icons/Icon';\nimport { SlashIcon } from '../../icons/SlashIcon';\nimport { transitions } from '@/ds/primitives/transitions';\nimport { cn } from '@/lib/utils';\n\nexport interface BreadcrumbProps {\n children?: React.ReactNode;\n label?: string;\n className?: string;\n listClassName?: string;\n}\n\nexport const Breadcrumb = ({ children, label, className, listClassName }: BreadcrumbProps) => {\n return (\n <nav aria-label={label} className={className}>\n <ol className={cn('flex items-center gap-0.5', listClassName)}>{children}</ol>\n </nav>\n );\n};\n\nexport interface CrumbProps {\n isCurrent?: boolean;\n as: React.ElementType;\n className?: string;\n to?: string;\n prefetch?: boolean | null;\n children: React.ReactNode;\n action?: React.ReactNode;\n 'data-testid'?: string;\n}\n\n// `text-overflow` needs a block container, so the label truncates in its own\n// box rather than on the flex Root. Icons stay siblings to keep `gap-2`.\nconst crumbTextTruncateStyles = 'min-w-0 flex-1 truncate';\n\nconst truncateTextChildren = (children: React.ReactNode) =>\n React.Children.map(children, child =>\n typeof child === 'string' || typeof child === 'number' ? (\n <span className={crumbTextTruncateStyles}>{child}</span>\n ) : (\n child\n ),\n );\n\nexport const Crumb = ({ className, as, isCurrent, action, children, ...props }: CrumbProps) => {\n const Root = as || 'span';\n\n return (\n <>\n <li className={cn('flex h-full min-w-0 items-center gap-1', isCurrent ? 'shrink' : 'shrink-0')}>\n <Root\n aria-current={isCurrent ? 'page' : undefined}\n className={cn(\n 'flex min-w-0 items-center gap-2 overflow-hidden rounded-md px-1 py-0.5 text-ui-md leading-ui-md',\n transitions.colors,\n isCurrent\n ? 'font-medium text-neutral6'\n : 'cursor-pointer text-neutral3 hover:bg-neutral6/5 hover:text-neutral5 active:bg-neutral6/10',\n className,\n )}\n {...props}\n >\n {truncateTextChildren(children)}\n </Root>\n {action}\n </li>\n {!isCurrent && (\n <li role=\"separator\" className=\"flex h-full items-center\">\n <Icon className={cn('text-neutral2', transitions.colors)}>\n <SlashIcon />\n </Icon>\n </li>\n )}\n </>\n );\n};\n"],"names":["React"],"mappings":";;;;;;;AAcO,MAAM,aAAa,CAAC,EAAE,UAAU,KAAA,EAAO,SAAA,EAAW,eAAc,KAAuB;AAC5F,EAAA,uBACE,GAAA,CAAC,KAAA,EAAA,EAAI,YAAA,EAAY,KAAA,EAAO,SAAA,EACtB,QAAA,kBAAA,GAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAW,EAAA,CAAG,2BAAA,EAA6B,aAAa,CAAA,EAAI,UAAS,CAAA,EAC3E,CAAA;AAEJ;AAeA,MAAM,uBAAA,GAA0B,yBAAA;AAEhC,MAAM,oBAAA,GAAuB,CAAC,QAAA,KAC5BA,cAAA,CAAM,QAAA,CAAS,GAAA;AAAA,EAAI,QAAA;AAAA,EAAU,CAAA,KAAA,KAC3B,OAAO,KAAA,KAAU,QAAA,IAAY,OAAO,KAAA,KAAU,QAAA,mBAC5C,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAW,uBAAA,EAA0B,QAAA,EAAA,KAAA,EAAM,CAAA,GAEjD;AAEJ,CAAA;AAEK,MAAM,KAAA,GAAQ,CAAC,EAAE,SAAA,EAAW,EAAA,EAAI,WAAW,MAAA,EAAQ,QAAA,EAAU,GAAG,KAAA,EAAM,KAAkB;AAC7F,EAAA,MAAM,OAAO,EAAA,IAAM,MAAA;AAEnB,EAAA,uBACE,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,oBAAA,IAAA,CAAC,QAAG,SAAA,EAAW,EAAA,CAAG,0CAA0C,SAAA,GAAY,QAAA,GAAW,UAAU,CAAA,EAC3F,QAAA,EAAA;AAAA,sBAAA,GAAA;AAAA,QAAC,IAAA;AAAA,QAAA;AAAA,UACC,cAAA,EAAc,YAAY,MAAA,GAAS,MAAA;AAAA,UACnC,SAAA,EAAW,EAAA;AAAA,YACT,iGAAA;AAAA,YACA,WAAA,CAAY,MAAA;AAAA,YACZ,YACI,2BAAA,GACA,4FAAA;AAAA,YACJ;AAAA,WACF;AAAA,UACC,GAAG,KAAA;AAAA,UAEH,+BAAqB,QAAQ;AAAA;AAAA,OAChC;AAAA,MACC;AAAA,KAAA,EACH,CAAA;AAAA,IACC,CAAC,SAAA,oBACA,GAAA,CAAC,QAAG,IAAA,EAAK,WAAA,EAAY,WAAU,0BAAA,EAC7B,QAAA,kBAAA,GAAA,CAAC,QAAK,SAAA,EAAW,EAAA,CAAG,iBAAiB,WAAA,CAAY,MAAM,GACrD,QAAA,kBAAA,GAAA,CAAC,SAAA,EAAA,EAAU,GACb,CAAA,EACF;AAAA,GAAA,EAEJ,CAAA;AAEJ;;;;"}
|
|
@@ -80,7 +80,7 @@ function TracesListView({
|
|
|
80
80
|
const rowKey = `${trace.traceId}:${trace.spanId ?? ""}`;
|
|
81
81
|
const isRecentlyAdded = recentlyAddedKeys?.has(rowKey) ?? false;
|
|
82
82
|
const displayDate = trace.startedAt ?? trace.createdAt;
|
|
83
|
-
const entityName = trace.entityName || trace.entityId
|
|
83
|
+
const entityName = trace.entityName || trace.entityId;
|
|
84
84
|
const usage = usageByTraceId?.get(trace.traceId);
|
|
85
85
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
86
86
|
tracesDataList.TracesDataList.RowButton,
|
|
@@ -101,9 +101,9 @@ function TracesListView({
|
|
|
101
101
|
showLevelTooltip: isBranchesMode
|
|
102
102
|
}
|
|
103
103
|
),
|
|
104
|
-
domains_traces_traceListColumns.hasTraceColumn(columnPreferences, "input") && /* @__PURE__ */ jsxRuntime.jsx(tracesDataList.TracesDataList.InputCell, { input: domains_traces_utils_spanUtils.getInputPreview(trace.input) }),
|
|
104
|
+
domains_traces_traceListColumns.hasTraceColumn(columnPreferences, "input") && /* @__PURE__ */ jsxRuntime.jsx(tracesDataList.TracesDataList.InputCell, { input: trace.inputPreview ?? domains_traces_utils_spanUtils.getInputPreview(trace.input) }),
|
|
105
105
|
domains_traces_traceListColumns.hasTraceColumn(columnPreferences, "entity") && /* @__PURE__ */ jsxRuntime.jsx(tracesDataList.TracesDataList.EntityCell, { entityType: trace.entityType, entityName }),
|
|
106
|
-
/* @__PURE__ */ jsxRuntime.jsx(tracesDataList.TracesDataList.StatusCell, { status: trace.status
|
|
106
|
+
/* @__PURE__ */ jsxRuntime.jsx(tracesDataList.TracesDataList.StatusCell, { status: trace.status }),
|
|
107
107
|
domains_traces_traceListColumns.hasTraceColumn(columnPreferences, "duration") && /* @__PURE__ */ jsxRuntime.jsx(dataList.DataList.NumberCell, { children: domains_traces_utils_spanUtils.formatSpanDuration(trace.startedAt, trace.endedAt) }),
|
|
108
108
|
domains_traces_traceListColumns.hasTraceColumn(columnPreferences, "inputTokens") && /* @__PURE__ */ jsxRuntime.jsx(dataList.DataList.NumberCell, { children: usage?.inputTokens === void 0 ? void 0 : domains_metrics_components_metricsUtils.formatCompact(usage.inputTokens) }),
|
|
109
109
|
domains_traces_traceListColumns.hasTraceColumn(columnPreferences, "outputTokens") && /* @__PURE__ */ jsxRuntime.jsx(dataList.DataList.NumberCell, { children: usage?.outputTokens === void 0 ? void 0 : domains_metrics_components_metricsUtils.formatCompact(usage.outputTokens) }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"traces-list-view.cjs.js","sources":["../../../../src/domains/traces/components/traces-list-view.tsx"],"sourcesContent":["import { useVirtualizer } from '@tanstack/react-virtual';\nimport { useEffect, useRef } from 'react';\nimport {\n DEFAULT_TRACE_COLUMN_PREFERENCES,\n buildTraceListColumns,\n formatTraceMetadataValue,\n hasTraceColumn,\n} from '../trace-list-columns';\nimport type { TraceColumnPreferences, TraceUsageSummary } from '../trace-list-columns';\nimport { formatSpanDuration, getInputPreview } from '../utils/span-utils';\nimport { formatCompact, formatCost } from '@/domains/metrics/components/metrics-utils';\nimport { DataList, DataListSkeleton, TracesDataList } from '@/ds/components/DataList';\nimport { cn } from '@/lib/utils';\n\n/** Span attributes fields the list view reads directly. Extra unknown keys are allowed so callers\n * can pass the full attributes record from @mastra/core/storage without mapping. */\nexport type TraceAttributes = {\n status?: string | null;\n agentId?: string | null;\n workflowId?: string | null;\n [key: string]: unknown;\n};\n\nexport type TracesListViewTrace = {\n traceId: string;\n /** Required for branch rows; absent on plain trace rows (which are root-rooted). */\n spanId?: string | null;\n /** `null`/missing → root span. Drives the Kind column's icon (top-level trace vs nested branch). */\n parentSpanId?: string | null;\n name: string;\n entityType?: string | null;\n entityId?: string | null;\n entityName?: string | null;\n status?: string | null;\n attributes?: TraceAttributes | null;\n input?: unknown;\n metadata?: Record<string, unknown> | null;\n startedAt?: Date | string | null;\n endedAt?: Date | string | null;\n createdAt: Date | string;\n};\n\nconst ROW_HEIGHT = 36;\nconst OVERSCAN = 8;\n\nexport type TracesListViewProps = {\n traces: TracesListViewTrace[];\n isLoading?: boolean;\n isFetchingNextPage?: boolean;\n hasNextPage?: boolean;\n setEndOfListElement?: (element: HTMLDivElement | null) => void;\n filtersApplied?: boolean;\n /** Currently featured/selected trace — its row gets the highlighted background. */\n featuredTraceId?: string | null;\n /**\n * Required in branches mode to disambiguate rows sharing a `traceId`. When set,\n * a row is featured only when both `traceId` and `spanId` match.\n */\n featuredSpanId?: string | null;\n /** Branches mode mixes root traces with subtraces — enables the Trace/Subtrace tooltip on the\n * level icon in the Name column, which is meaningless when every row is a root. */\n isBranchesMode?: boolean;\n /** Keys (`traceId:spanId`) of rows that just arrived via delta polling. Rows whose key is in\n * this set get a temporary tint to distinguish them from rows present since the last page-mode\n * fetch. Auto-expires upstream (in useTraces) after a short window. */\n recentlyAddedKeys?: Set<string>;\n /** Optional and metadata columns selected for this project. */\n columnPreferences?: TraceColumnPreferences;\n /** Token and estimated-cost totals, aggregated across model spans by trace ID. */\n usageByTraceId?: ReadonlyMap<string, TraceUsageSummary>;\n /** Called when a row is clicked. The current selection logic (toggle on same id) is the consumer's call. */\n onTraceClick: (trace: TracesListViewTrace) => void;\n};\n\n/**\n * Virtualized presentational list. Renders only the visible window via TanStack Virtual, and\n * uses DataList primitives for layout (CSS Grid with subgrid rows).\n */\nexport function TracesListView({\n traces,\n isLoading,\n isFetchingNextPage,\n hasNextPage,\n setEndOfListElement,\n filtersApplied,\n featuredTraceId,\n featuredSpanId,\n isBranchesMode,\n recentlyAddedKeys,\n columnPreferences = DEFAULT_TRACE_COLUMN_PREFERENCES,\n usageByTraceId,\n onTraceClick,\n}: TracesListViewProps) {\n const scrollRef = useRef<HTMLDivElement | null>(null);\n const columns = buildTraceListColumns(columnPreferences);\n\n const virtualizer = useVirtualizer({\n count: traces.length,\n getScrollElement: () => scrollRef.current,\n estimateSize: () => ROW_HEIGHT,\n overscan: OVERSCAN,\n });\n\n // Reset scroll to top whenever a fresh query resolves (filter / date range change).\n // `isLoading` only flips on initial fetches — `fetchNextPage` keeps it `false`, so this\n // effect doesn't fire during pagination.\n //\n // Why the manual scroll event: when the skeleton-vs-list branch swaps in the new scroll\n // container, it mounts at `scrollTop = 0`. The virtualizer rebinds its listener but\n // doesn't re-read `scrollTop`, so it keeps the stale `scrollOffset` from the previous\n // element. `scrollToOffset(0)` no-ops because the new element is already at 0 (no scroll\n // event fires). Dispatching a synthetic `scroll` forces the virtualizer's handler to\n // read the fresh `scrollTop` and recompute `virtualItems` with `paddingTop = 0`.\n const wasLoadingRef = useRef(isLoading);\n useEffect(() => {\n if (wasLoadingRef.current && !isLoading) {\n scrollRef.current?.dispatchEvent(new Event('scroll'));\n }\n wasLoadingRef.current = isLoading;\n }, [isLoading]);\n\n if (isLoading) {\n return <DataListSkeleton columns={columns} />;\n }\n\n const virtualItems = virtualizer.getVirtualItems();\n const totalSize = virtualizer.getTotalSize();\n const paddingTop = virtualItems[0]?.start ?? 0;\n const paddingBottom =\n virtualItems.length > 0 ? Math.max(0, totalSize - (virtualItems[virtualItems.length - 1]?.end ?? 0)) : 0;\n\n return (\n <TracesDataList columns={columns} variant=\"striped\" scrollRef={scrollRef} className=\"min-w-0\">\n <TracesDataList.Top>\n <TracesDataList.TopCell>Date</TracesDataList.TopCell>\n <TracesDataList.TopCell>Time</TracesDataList.TopCell>\n <TracesDataList.TopCell>Name</TracesDataList.TopCell>\n {hasTraceColumn(columnPreferences, 'input') && <TracesDataList.TopCell>Input</TracesDataList.TopCell>}\n {hasTraceColumn(columnPreferences, 'entity') && <TracesDataList.TopCell>Entity</TracesDataList.TopCell>}\n <TracesDataList.TopCell>Status</TracesDataList.TopCell>\n {hasTraceColumn(columnPreferences, 'duration') && (\n <TracesDataList.TopCell className=\"justify-end text-right\">Duration</TracesDataList.TopCell>\n )}\n {hasTraceColumn(columnPreferences, 'inputTokens') && (\n <TracesDataList.TopCell className=\"justify-end text-right\">Input tokens</TracesDataList.TopCell>\n )}\n {hasTraceColumn(columnPreferences, 'outputTokens') && (\n <TracesDataList.TopCell className=\"justify-end text-right\">Output tokens</TracesDataList.TopCell>\n )}\n {hasTraceColumn(columnPreferences, 'estimatedCost') && (\n <TracesDataList.TopCell className=\"justify-end text-right\">Est. cost</TracesDataList.TopCell>\n )}\n {columnPreferences.metadataKeys.map(key => (\n <TracesDataList.TopCellWithTooltip key={key} tooltip={key}>\n {key}\n </TracesDataList.TopCellWithTooltip>\n ))}\n </TracesDataList.Top>\n\n {traces.length === 0 ? (\n <TracesDataList.NoMatch\n message={filtersApplied ? 'No traces found for applied filters' : 'No traces found yet'}\n />\n ) : (\n <>\n <TracesDataList.Spacer height={paddingTop} />\n {virtualItems.map(vi => {\n const trace = traces[vi.index];\n if (!trace) return null;\n\n const isFeatured =\n trace.traceId === featuredTraceId && (featuredSpanId == null || trace.spanId === featuredSpanId);\n const rowKey = `${trace.traceId}:${trace.spanId ?? ''}`;\n const isRecentlyAdded = recentlyAddedKeys?.has(rowKey) ?? false;\n const displayDate = trace.startedAt ?? trace.createdAt;\n const entityName =\n trace.entityName || trace.entityId || trace.attributes?.agentId || trace.attributes?.workflowId;\n const usage = usageByTraceId?.get(trace.traceId);\n\n return (\n <TracesDataList.RowButton\n key={rowKey}\n ref={virtualizer.measureElement}\n data-index={vi.index}\n onClick={() => onTraceClick(trace)}\n featured={isFeatured}\n className={cn(isRecentlyAdded && 'animate-row-highlight')}\n >\n <TracesDataList.DateCell timestamp={displayDate} />\n <TracesDataList.TimeCell timestamp={displayDate} />\n <TracesDataList.NameCell\n name={trace.name}\n parentSpanId={trace.parentSpanId}\n showLevelTooltip={isBranchesMode}\n />\n {hasTraceColumn(columnPreferences, 'input') && (\n <TracesDataList.InputCell input={getInputPreview(trace.input)} />\n )}\n {hasTraceColumn(columnPreferences, 'entity') && (\n <TracesDataList.EntityCell entityType={trace.entityType} entityName={entityName} />\n )}\n <TracesDataList.StatusCell status={trace.status ?? trace.attributes?.status} />\n {hasTraceColumn(columnPreferences, 'duration') && (\n <DataList.NumberCell>{formatSpanDuration(trace.startedAt, trace.endedAt)}</DataList.NumberCell>\n )}\n {hasTraceColumn(columnPreferences, 'inputTokens') && (\n <DataList.NumberCell>\n {usage?.inputTokens === undefined ? undefined : formatCompact(usage.inputTokens)}\n </DataList.NumberCell>\n )}\n {hasTraceColumn(columnPreferences, 'outputTokens') && (\n <DataList.NumberCell>\n {usage?.outputTokens === undefined ? undefined : formatCompact(usage.outputTokens)}\n </DataList.NumberCell>\n )}\n {hasTraceColumn(columnPreferences, 'estimatedCost') && (\n <DataList.NumberCell>\n {usage?.estimatedCost === undefined ? undefined : formatCost(usage.estimatedCost, usage.costUnit)}\n </DataList.NumberCell>\n )}\n {columnPreferences.metadataKeys.map(key => {\n const value = formatTraceMetadataValue(trace.metadata, key);\n return <DataList.MonoCell key={key}>{value}</DataList.MonoCell>;\n })}\n </TracesDataList.RowButton>\n );\n })}\n <TracesDataList.Spacer height={paddingBottom} />\n <TracesDataList.NextPageLoading\n isLoading={isFetchingNextPage}\n hasMore={hasNextPage}\n setEndOfListElement={setEndOfListElement}\n />\n </>\n )}\n </TracesDataList>\n );\n}\n"],"names":["DEFAULT_TRACE_COLUMN_PREFERENCES","useRef","buildTraceListColumns","useVirtualizer","useEffect","jsx","DataListSkeleton","TracesDataList","jsxs","hasTraceColumn","Fragment","cn","getInputPreview","DataList","formatSpanDuration","formatCompact","formatCost","formatTraceMetadataValue"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"traces-list-view.cjs.js","sources":["../../../../src/domains/traces/components/traces-list-view.tsx"],"sourcesContent":["import { useVirtualizer } from '@tanstack/react-virtual';\nimport { useEffect, useRef } from 'react';\nimport {\n DEFAULT_TRACE_COLUMN_PREFERENCES,\n buildTraceListColumns,\n formatTraceMetadataValue,\n hasTraceColumn,\n} from '../trace-list-columns';\nimport type { TraceColumnPreferences, TraceUsageSummary } from '../trace-list-columns';\nimport { formatSpanDuration, getInputPreview } from '../utils/span-utils';\nimport { formatCompact, formatCost } from '@/domains/metrics/components/metrics-utils';\nimport { DataList, DataListSkeleton, TracesDataList } from '@/ds/components/DataList';\nimport { cn } from '@/lib/utils';\n\nexport type TracesListViewTrace = {\n traceId: string;\n /** Required for branch rows; absent on plain trace rows (which are root-rooted). */\n spanId?: string | null;\n /** `null`/missing → root span. Drives the Kind column's icon (top-level trace vs nested branch). */\n parentSpanId?: string | null;\n name: string;\n entityType?: string | null;\n entityId?: string | null;\n entityName?: string | null;\n status?: string | null;\n /** Server-rendered preview of `input`. Present on lightweight rows, which omit `input` itself. */\n inputPreview?: string | null;\n input?: unknown;\n metadata?: Record<string, unknown> | null;\n startedAt?: Date | string | null;\n endedAt?: Date | string | null;\n createdAt: Date | string;\n};\n\nconst ROW_HEIGHT = 36;\nconst OVERSCAN = 8;\n\nexport type TracesListViewProps = {\n traces: TracesListViewTrace[];\n isLoading?: boolean;\n isFetchingNextPage?: boolean;\n hasNextPage?: boolean;\n setEndOfListElement?: (element: HTMLDivElement | null) => void;\n filtersApplied?: boolean;\n /** Currently featured/selected trace — its row gets the highlighted background. */\n featuredTraceId?: string | null;\n /**\n * Required in branches mode to disambiguate rows sharing a `traceId`. When set,\n * a row is featured only when both `traceId` and `spanId` match.\n */\n featuredSpanId?: string | null;\n /** Branches mode mixes root traces with subtraces — enables the Trace/Subtrace tooltip on the\n * level icon in the Name column, which is meaningless when every row is a root. */\n isBranchesMode?: boolean;\n /** Keys (`traceId:spanId`) of rows that just arrived via delta polling. Rows whose key is in\n * this set get a temporary tint to distinguish them from rows present since the last page-mode\n * fetch. Auto-expires upstream (in useTraces) after a short window. */\n recentlyAddedKeys?: Set<string>;\n /** Optional and metadata columns selected for this project. */\n columnPreferences?: TraceColumnPreferences;\n /** Token and estimated-cost totals, aggregated across model spans by trace ID. */\n usageByTraceId?: ReadonlyMap<string, TraceUsageSummary>;\n /** Called when a row is clicked. The current selection logic (toggle on same id) is the consumer's call. */\n onTraceClick: (trace: TracesListViewTrace) => void;\n};\n\n/**\n * Virtualized presentational list. Renders only the visible window via TanStack Virtual, and\n * uses DataList primitives for layout (CSS Grid with subgrid rows).\n */\nexport function TracesListView({\n traces,\n isLoading,\n isFetchingNextPage,\n hasNextPage,\n setEndOfListElement,\n filtersApplied,\n featuredTraceId,\n featuredSpanId,\n isBranchesMode,\n recentlyAddedKeys,\n columnPreferences = DEFAULT_TRACE_COLUMN_PREFERENCES,\n usageByTraceId,\n onTraceClick,\n}: TracesListViewProps) {\n const scrollRef = useRef<HTMLDivElement | null>(null);\n const columns = buildTraceListColumns(columnPreferences);\n\n const virtualizer = useVirtualizer({\n count: traces.length,\n getScrollElement: () => scrollRef.current,\n estimateSize: () => ROW_HEIGHT,\n overscan: OVERSCAN,\n });\n\n // Reset scroll to top whenever a fresh query resolves (filter / date range change).\n // `isLoading` only flips on initial fetches — `fetchNextPage` keeps it `false`, so this\n // effect doesn't fire during pagination.\n //\n // Why the manual scroll event: when the skeleton-vs-list branch swaps in the new scroll\n // container, it mounts at `scrollTop = 0`. The virtualizer rebinds its listener but\n // doesn't re-read `scrollTop`, so it keeps the stale `scrollOffset` from the previous\n // element. `scrollToOffset(0)` no-ops because the new element is already at 0 (no scroll\n // event fires). Dispatching a synthetic `scroll` forces the virtualizer's handler to\n // read the fresh `scrollTop` and recompute `virtualItems` with `paddingTop = 0`.\n const wasLoadingRef = useRef(isLoading);\n useEffect(() => {\n if (wasLoadingRef.current && !isLoading) {\n scrollRef.current?.dispatchEvent(new Event('scroll'));\n }\n wasLoadingRef.current = isLoading;\n }, [isLoading]);\n\n if (isLoading) {\n return <DataListSkeleton columns={columns} />;\n }\n\n const virtualItems = virtualizer.getVirtualItems();\n const totalSize = virtualizer.getTotalSize();\n const paddingTop = virtualItems[0]?.start ?? 0;\n const paddingBottom =\n virtualItems.length > 0 ? Math.max(0, totalSize - (virtualItems[virtualItems.length - 1]?.end ?? 0)) : 0;\n\n return (\n <TracesDataList columns={columns} variant=\"striped\" scrollRef={scrollRef} className=\"min-w-0\">\n <TracesDataList.Top>\n <TracesDataList.TopCell>Date</TracesDataList.TopCell>\n <TracesDataList.TopCell>Time</TracesDataList.TopCell>\n <TracesDataList.TopCell>Name</TracesDataList.TopCell>\n {hasTraceColumn(columnPreferences, 'input') && <TracesDataList.TopCell>Input</TracesDataList.TopCell>}\n {hasTraceColumn(columnPreferences, 'entity') && <TracesDataList.TopCell>Entity</TracesDataList.TopCell>}\n <TracesDataList.TopCell>Status</TracesDataList.TopCell>\n {hasTraceColumn(columnPreferences, 'duration') && (\n <TracesDataList.TopCell className=\"justify-end text-right\">Duration</TracesDataList.TopCell>\n )}\n {hasTraceColumn(columnPreferences, 'inputTokens') && (\n <TracesDataList.TopCell className=\"justify-end text-right\">Input tokens</TracesDataList.TopCell>\n )}\n {hasTraceColumn(columnPreferences, 'outputTokens') && (\n <TracesDataList.TopCell className=\"justify-end text-right\">Output tokens</TracesDataList.TopCell>\n )}\n {hasTraceColumn(columnPreferences, 'estimatedCost') && (\n <TracesDataList.TopCell className=\"justify-end text-right\">Est. cost</TracesDataList.TopCell>\n )}\n {columnPreferences.metadataKeys.map(key => (\n <TracesDataList.TopCellWithTooltip key={key} tooltip={key}>\n {key}\n </TracesDataList.TopCellWithTooltip>\n ))}\n </TracesDataList.Top>\n\n {traces.length === 0 ? (\n <TracesDataList.NoMatch\n message={filtersApplied ? 'No traces found for applied filters' : 'No traces found yet'}\n />\n ) : (\n <>\n <TracesDataList.Spacer height={paddingTop} />\n {virtualItems.map(vi => {\n const trace = traces[vi.index];\n if (!trace) return null;\n\n const isFeatured =\n trace.traceId === featuredTraceId && (featuredSpanId == null || trace.spanId === featuredSpanId);\n const rowKey = `${trace.traceId}:${trace.spanId ?? ''}`;\n const isRecentlyAdded = recentlyAddedKeys?.has(rowKey) ?? false;\n const displayDate = trace.startedAt ?? trace.createdAt;\n const entityName = trace.entityName || trace.entityId;\n const usage = usageByTraceId?.get(trace.traceId);\n\n return (\n <TracesDataList.RowButton\n key={rowKey}\n ref={virtualizer.measureElement}\n data-index={vi.index}\n onClick={() => onTraceClick(trace)}\n featured={isFeatured}\n className={cn(isRecentlyAdded && 'animate-row-highlight')}\n >\n <TracesDataList.DateCell timestamp={displayDate} />\n <TracesDataList.TimeCell timestamp={displayDate} />\n <TracesDataList.NameCell\n name={trace.name}\n parentSpanId={trace.parentSpanId}\n showLevelTooltip={isBranchesMode}\n />\n {hasTraceColumn(columnPreferences, 'input') && (\n <TracesDataList.InputCell input={trace.inputPreview ?? getInputPreview(trace.input)} />\n )}\n {hasTraceColumn(columnPreferences, 'entity') && (\n <TracesDataList.EntityCell entityType={trace.entityType} entityName={entityName} />\n )}\n <TracesDataList.StatusCell status={trace.status} />\n {hasTraceColumn(columnPreferences, 'duration') && (\n <DataList.NumberCell>{formatSpanDuration(trace.startedAt, trace.endedAt)}</DataList.NumberCell>\n )}\n {hasTraceColumn(columnPreferences, 'inputTokens') && (\n <DataList.NumberCell>\n {usage?.inputTokens === undefined ? undefined : formatCompact(usage.inputTokens)}\n </DataList.NumberCell>\n )}\n {hasTraceColumn(columnPreferences, 'outputTokens') && (\n <DataList.NumberCell>\n {usage?.outputTokens === undefined ? undefined : formatCompact(usage.outputTokens)}\n </DataList.NumberCell>\n )}\n {hasTraceColumn(columnPreferences, 'estimatedCost') && (\n <DataList.NumberCell>\n {usage?.estimatedCost === undefined ? undefined : formatCost(usage.estimatedCost, usage.costUnit)}\n </DataList.NumberCell>\n )}\n {columnPreferences.metadataKeys.map(key => {\n const value = formatTraceMetadataValue(trace.metadata, key);\n return <DataList.MonoCell key={key}>{value}</DataList.MonoCell>;\n })}\n </TracesDataList.RowButton>\n );\n })}\n <TracesDataList.Spacer height={paddingBottom} />\n <TracesDataList.NextPageLoading\n isLoading={isFetchingNextPage}\n hasMore={hasNextPage}\n setEndOfListElement={setEndOfListElement}\n />\n </>\n )}\n </TracesDataList>\n );\n}\n"],"names":["DEFAULT_TRACE_COLUMN_PREFERENCES","useRef","buildTraceListColumns","useVirtualizer","useEffect","jsx","DataListSkeleton","TracesDataList","jsxs","hasTraceColumn","Fragment","cn","getInputPreview","DataList","formatSpanDuration","formatCompact","formatCost","formatTraceMetadataValue"],"mappings":"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"}
|
|
@@ -76,7 +76,7 @@ function TracesListView({
|
|
|
76
76
|
const rowKey = `${trace.traceId}:${trace.spanId ?? ""}`;
|
|
77
77
|
const isRecentlyAdded = recentlyAddedKeys?.has(rowKey) ?? false;
|
|
78
78
|
const displayDate = trace.startedAt ?? trace.createdAt;
|
|
79
|
-
const entityName = trace.entityName || trace.entityId
|
|
79
|
+
const entityName = trace.entityName || trace.entityId;
|
|
80
80
|
const usage = usageByTraceId?.get(trace.traceId);
|
|
81
81
|
return /* @__PURE__ */ jsxs(
|
|
82
82
|
TracesDataList.RowButton,
|
|
@@ -97,9 +97,9 @@ function TracesListView({
|
|
|
97
97
|
showLevelTooltip: isBranchesMode
|
|
98
98
|
}
|
|
99
99
|
),
|
|
100
|
-
hasTraceColumn(columnPreferences, "input") && /* @__PURE__ */ jsx(TracesDataList.InputCell, { input: getInputPreview(trace.input) }),
|
|
100
|
+
hasTraceColumn(columnPreferences, "input") && /* @__PURE__ */ jsx(TracesDataList.InputCell, { input: trace.inputPreview ?? getInputPreview(trace.input) }),
|
|
101
101
|
hasTraceColumn(columnPreferences, "entity") && /* @__PURE__ */ jsx(TracesDataList.EntityCell, { entityType: trace.entityType, entityName }),
|
|
102
|
-
/* @__PURE__ */ jsx(TracesDataList.StatusCell, { status: trace.status
|
|
102
|
+
/* @__PURE__ */ jsx(TracesDataList.StatusCell, { status: trace.status }),
|
|
103
103
|
hasTraceColumn(columnPreferences, "duration") && /* @__PURE__ */ jsx(DataList.NumberCell, { children: formatSpanDuration(trace.startedAt, trace.endedAt) }),
|
|
104
104
|
hasTraceColumn(columnPreferences, "inputTokens") && /* @__PURE__ */ jsx(DataList.NumberCell, { children: usage?.inputTokens === void 0 ? void 0 : formatCompact(usage.inputTokens) }),
|
|
105
105
|
hasTraceColumn(columnPreferences, "outputTokens") && /* @__PURE__ */ jsx(DataList.NumberCell, { children: usage?.outputTokens === void 0 ? void 0 : formatCompact(usage.outputTokens) }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"traces-list-view.es.js","sources":["../../../../src/domains/traces/components/traces-list-view.tsx"],"sourcesContent":["import { useVirtualizer } from '@tanstack/react-virtual';\nimport { useEffect, useRef } from 'react';\nimport {\n DEFAULT_TRACE_COLUMN_PREFERENCES,\n buildTraceListColumns,\n formatTraceMetadataValue,\n hasTraceColumn,\n} from '../trace-list-columns';\nimport type { TraceColumnPreferences, TraceUsageSummary } from '../trace-list-columns';\nimport { formatSpanDuration, getInputPreview } from '../utils/span-utils';\nimport { formatCompact, formatCost } from '@/domains/metrics/components/metrics-utils';\nimport { DataList, DataListSkeleton, TracesDataList } from '@/ds/components/DataList';\nimport { cn } from '@/lib/utils';\n\n/** Span attributes fields the list view reads directly. Extra unknown keys are allowed so callers\n * can pass the full attributes record from @mastra/core/storage without mapping. */\nexport type TraceAttributes = {\n status?: string | null;\n agentId?: string | null;\n workflowId?: string | null;\n [key: string]: unknown;\n};\n\nexport type TracesListViewTrace = {\n traceId: string;\n /** Required for branch rows; absent on plain trace rows (which are root-rooted). */\n spanId?: string | null;\n /** `null`/missing → root span. Drives the Kind column's icon (top-level trace vs nested branch). */\n parentSpanId?: string | null;\n name: string;\n entityType?: string | null;\n entityId?: string | null;\n entityName?: string | null;\n status?: string | null;\n attributes?: TraceAttributes | null;\n input?: unknown;\n metadata?: Record<string, unknown> | null;\n startedAt?: Date | string | null;\n endedAt?: Date | string | null;\n createdAt: Date | string;\n};\n\nconst ROW_HEIGHT = 36;\nconst OVERSCAN = 8;\n\nexport type TracesListViewProps = {\n traces: TracesListViewTrace[];\n isLoading?: boolean;\n isFetchingNextPage?: boolean;\n hasNextPage?: boolean;\n setEndOfListElement?: (element: HTMLDivElement | null) => void;\n filtersApplied?: boolean;\n /** Currently featured/selected trace — its row gets the highlighted background. */\n featuredTraceId?: string | null;\n /**\n * Required in branches mode to disambiguate rows sharing a `traceId`. When set,\n * a row is featured only when both `traceId` and `spanId` match.\n */\n featuredSpanId?: string | null;\n /** Branches mode mixes root traces with subtraces — enables the Trace/Subtrace tooltip on the\n * level icon in the Name column, which is meaningless when every row is a root. */\n isBranchesMode?: boolean;\n /** Keys (`traceId:spanId`) of rows that just arrived via delta polling. Rows whose key is in\n * this set get a temporary tint to distinguish them from rows present since the last page-mode\n * fetch. Auto-expires upstream (in useTraces) after a short window. */\n recentlyAddedKeys?: Set<string>;\n /** Optional and metadata columns selected for this project. */\n columnPreferences?: TraceColumnPreferences;\n /** Token and estimated-cost totals, aggregated across model spans by trace ID. */\n usageByTraceId?: ReadonlyMap<string, TraceUsageSummary>;\n /** Called when a row is clicked. The current selection logic (toggle on same id) is the consumer's call. */\n onTraceClick: (trace: TracesListViewTrace) => void;\n};\n\n/**\n * Virtualized presentational list. Renders only the visible window via TanStack Virtual, and\n * uses DataList primitives for layout (CSS Grid with subgrid rows).\n */\nexport function TracesListView({\n traces,\n isLoading,\n isFetchingNextPage,\n hasNextPage,\n setEndOfListElement,\n filtersApplied,\n featuredTraceId,\n featuredSpanId,\n isBranchesMode,\n recentlyAddedKeys,\n columnPreferences = DEFAULT_TRACE_COLUMN_PREFERENCES,\n usageByTraceId,\n onTraceClick,\n}: TracesListViewProps) {\n const scrollRef = useRef<HTMLDivElement | null>(null);\n const columns = buildTraceListColumns(columnPreferences);\n\n const virtualizer = useVirtualizer({\n count: traces.length,\n getScrollElement: () => scrollRef.current,\n estimateSize: () => ROW_HEIGHT,\n overscan: OVERSCAN,\n });\n\n // Reset scroll to top whenever a fresh query resolves (filter / date range change).\n // `isLoading` only flips on initial fetches — `fetchNextPage` keeps it `false`, so this\n // effect doesn't fire during pagination.\n //\n // Why the manual scroll event: when the skeleton-vs-list branch swaps in the new scroll\n // container, it mounts at `scrollTop = 0`. The virtualizer rebinds its listener but\n // doesn't re-read `scrollTop`, so it keeps the stale `scrollOffset` from the previous\n // element. `scrollToOffset(0)` no-ops because the new element is already at 0 (no scroll\n // event fires). Dispatching a synthetic `scroll` forces the virtualizer's handler to\n // read the fresh `scrollTop` and recompute `virtualItems` with `paddingTop = 0`.\n const wasLoadingRef = useRef(isLoading);\n useEffect(() => {\n if (wasLoadingRef.current && !isLoading) {\n scrollRef.current?.dispatchEvent(new Event('scroll'));\n }\n wasLoadingRef.current = isLoading;\n }, [isLoading]);\n\n if (isLoading) {\n return <DataListSkeleton columns={columns} />;\n }\n\n const virtualItems = virtualizer.getVirtualItems();\n const totalSize = virtualizer.getTotalSize();\n const paddingTop = virtualItems[0]?.start ?? 0;\n const paddingBottom =\n virtualItems.length > 0 ? Math.max(0, totalSize - (virtualItems[virtualItems.length - 1]?.end ?? 0)) : 0;\n\n return (\n <TracesDataList columns={columns} variant=\"striped\" scrollRef={scrollRef} className=\"min-w-0\">\n <TracesDataList.Top>\n <TracesDataList.TopCell>Date</TracesDataList.TopCell>\n <TracesDataList.TopCell>Time</TracesDataList.TopCell>\n <TracesDataList.TopCell>Name</TracesDataList.TopCell>\n {hasTraceColumn(columnPreferences, 'input') && <TracesDataList.TopCell>Input</TracesDataList.TopCell>}\n {hasTraceColumn(columnPreferences, 'entity') && <TracesDataList.TopCell>Entity</TracesDataList.TopCell>}\n <TracesDataList.TopCell>Status</TracesDataList.TopCell>\n {hasTraceColumn(columnPreferences, 'duration') && (\n <TracesDataList.TopCell className=\"justify-end text-right\">Duration</TracesDataList.TopCell>\n )}\n {hasTraceColumn(columnPreferences, 'inputTokens') && (\n <TracesDataList.TopCell className=\"justify-end text-right\">Input tokens</TracesDataList.TopCell>\n )}\n {hasTraceColumn(columnPreferences, 'outputTokens') && (\n <TracesDataList.TopCell className=\"justify-end text-right\">Output tokens</TracesDataList.TopCell>\n )}\n {hasTraceColumn(columnPreferences, 'estimatedCost') && (\n <TracesDataList.TopCell className=\"justify-end text-right\">Est. cost</TracesDataList.TopCell>\n )}\n {columnPreferences.metadataKeys.map(key => (\n <TracesDataList.TopCellWithTooltip key={key} tooltip={key}>\n {key}\n </TracesDataList.TopCellWithTooltip>\n ))}\n </TracesDataList.Top>\n\n {traces.length === 0 ? (\n <TracesDataList.NoMatch\n message={filtersApplied ? 'No traces found for applied filters' : 'No traces found yet'}\n />\n ) : (\n <>\n <TracesDataList.Spacer height={paddingTop} />\n {virtualItems.map(vi => {\n const trace = traces[vi.index];\n if (!trace) return null;\n\n const isFeatured =\n trace.traceId === featuredTraceId && (featuredSpanId == null || trace.spanId === featuredSpanId);\n const rowKey = `${trace.traceId}:${trace.spanId ?? ''}`;\n const isRecentlyAdded = recentlyAddedKeys?.has(rowKey) ?? false;\n const displayDate = trace.startedAt ?? trace.createdAt;\n const entityName =\n trace.entityName || trace.entityId || trace.attributes?.agentId || trace.attributes?.workflowId;\n const usage = usageByTraceId?.get(trace.traceId);\n\n return (\n <TracesDataList.RowButton\n key={rowKey}\n ref={virtualizer.measureElement}\n data-index={vi.index}\n onClick={() => onTraceClick(trace)}\n featured={isFeatured}\n className={cn(isRecentlyAdded && 'animate-row-highlight')}\n >\n <TracesDataList.DateCell timestamp={displayDate} />\n <TracesDataList.TimeCell timestamp={displayDate} />\n <TracesDataList.NameCell\n name={trace.name}\n parentSpanId={trace.parentSpanId}\n showLevelTooltip={isBranchesMode}\n />\n {hasTraceColumn(columnPreferences, 'input') && (\n <TracesDataList.InputCell input={getInputPreview(trace.input)} />\n )}\n {hasTraceColumn(columnPreferences, 'entity') && (\n <TracesDataList.EntityCell entityType={trace.entityType} entityName={entityName} />\n )}\n <TracesDataList.StatusCell status={trace.status ?? trace.attributes?.status} />\n {hasTraceColumn(columnPreferences, 'duration') && (\n <DataList.NumberCell>{formatSpanDuration(trace.startedAt, trace.endedAt)}</DataList.NumberCell>\n )}\n {hasTraceColumn(columnPreferences, 'inputTokens') && (\n <DataList.NumberCell>\n {usage?.inputTokens === undefined ? undefined : formatCompact(usage.inputTokens)}\n </DataList.NumberCell>\n )}\n {hasTraceColumn(columnPreferences, 'outputTokens') && (\n <DataList.NumberCell>\n {usage?.outputTokens === undefined ? undefined : formatCompact(usage.outputTokens)}\n </DataList.NumberCell>\n )}\n {hasTraceColumn(columnPreferences, 'estimatedCost') && (\n <DataList.NumberCell>\n {usage?.estimatedCost === undefined ? undefined : formatCost(usage.estimatedCost, usage.costUnit)}\n </DataList.NumberCell>\n )}\n {columnPreferences.metadataKeys.map(key => {\n const value = formatTraceMetadataValue(trace.metadata, key);\n return <DataList.MonoCell key={key}>{value}</DataList.MonoCell>;\n })}\n </TracesDataList.RowButton>\n );\n })}\n <TracesDataList.Spacer height={paddingBottom} />\n <TracesDataList.NextPageLoading\n isLoading={isFetchingNextPage}\n hasMore={hasNextPage}\n setEndOfListElement={setEndOfListElement}\n />\n </>\n )}\n </TracesDataList>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"traces-list-view.es.js","sources":["../../../../src/domains/traces/components/traces-list-view.tsx"],"sourcesContent":["import { useVirtualizer } from '@tanstack/react-virtual';\nimport { useEffect, useRef } from 'react';\nimport {\n DEFAULT_TRACE_COLUMN_PREFERENCES,\n buildTraceListColumns,\n formatTraceMetadataValue,\n hasTraceColumn,\n} from '../trace-list-columns';\nimport type { TraceColumnPreferences, TraceUsageSummary } from '../trace-list-columns';\nimport { formatSpanDuration, getInputPreview } from '../utils/span-utils';\nimport { formatCompact, formatCost } from '@/domains/metrics/components/metrics-utils';\nimport { DataList, DataListSkeleton, TracesDataList } from '@/ds/components/DataList';\nimport { cn } from '@/lib/utils';\n\nexport type TracesListViewTrace = {\n traceId: string;\n /** Required for branch rows; absent on plain trace rows (which are root-rooted). */\n spanId?: string | null;\n /** `null`/missing → root span. Drives the Kind column's icon (top-level trace vs nested branch). */\n parentSpanId?: string | null;\n name: string;\n entityType?: string | null;\n entityId?: string | null;\n entityName?: string | null;\n status?: string | null;\n /** Server-rendered preview of `input`. Present on lightweight rows, which omit `input` itself. */\n inputPreview?: string | null;\n input?: unknown;\n metadata?: Record<string, unknown> | null;\n startedAt?: Date | string | null;\n endedAt?: Date | string | null;\n createdAt: Date | string;\n};\n\nconst ROW_HEIGHT = 36;\nconst OVERSCAN = 8;\n\nexport type TracesListViewProps = {\n traces: TracesListViewTrace[];\n isLoading?: boolean;\n isFetchingNextPage?: boolean;\n hasNextPage?: boolean;\n setEndOfListElement?: (element: HTMLDivElement | null) => void;\n filtersApplied?: boolean;\n /** Currently featured/selected trace — its row gets the highlighted background. */\n featuredTraceId?: string | null;\n /**\n * Required in branches mode to disambiguate rows sharing a `traceId`. When set,\n * a row is featured only when both `traceId` and `spanId` match.\n */\n featuredSpanId?: string | null;\n /** Branches mode mixes root traces with subtraces — enables the Trace/Subtrace tooltip on the\n * level icon in the Name column, which is meaningless when every row is a root. */\n isBranchesMode?: boolean;\n /** Keys (`traceId:spanId`) of rows that just arrived via delta polling. Rows whose key is in\n * this set get a temporary tint to distinguish them from rows present since the last page-mode\n * fetch. Auto-expires upstream (in useTraces) after a short window. */\n recentlyAddedKeys?: Set<string>;\n /** Optional and metadata columns selected for this project. */\n columnPreferences?: TraceColumnPreferences;\n /** Token and estimated-cost totals, aggregated across model spans by trace ID. */\n usageByTraceId?: ReadonlyMap<string, TraceUsageSummary>;\n /** Called when a row is clicked. The current selection logic (toggle on same id) is the consumer's call. */\n onTraceClick: (trace: TracesListViewTrace) => void;\n};\n\n/**\n * Virtualized presentational list. Renders only the visible window via TanStack Virtual, and\n * uses DataList primitives for layout (CSS Grid with subgrid rows).\n */\nexport function TracesListView({\n traces,\n isLoading,\n isFetchingNextPage,\n hasNextPage,\n setEndOfListElement,\n filtersApplied,\n featuredTraceId,\n featuredSpanId,\n isBranchesMode,\n recentlyAddedKeys,\n columnPreferences = DEFAULT_TRACE_COLUMN_PREFERENCES,\n usageByTraceId,\n onTraceClick,\n}: TracesListViewProps) {\n const scrollRef = useRef<HTMLDivElement | null>(null);\n const columns = buildTraceListColumns(columnPreferences);\n\n const virtualizer = useVirtualizer({\n count: traces.length,\n getScrollElement: () => scrollRef.current,\n estimateSize: () => ROW_HEIGHT,\n overscan: OVERSCAN,\n });\n\n // Reset scroll to top whenever a fresh query resolves (filter / date range change).\n // `isLoading` only flips on initial fetches — `fetchNextPage` keeps it `false`, so this\n // effect doesn't fire during pagination.\n //\n // Why the manual scroll event: when the skeleton-vs-list branch swaps in the new scroll\n // container, it mounts at `scrollTop = 0`. The virtualizer rebinds its listener but\n // doesn't re-read `scrollTop`, so it keeps the stale `scrollOffset` from the previous\n // element. `scrollToOffset(0)` no-ops because the new element is already at 0 (no scroll\n // event fires). Dispatching a synthetic `scroll` forces the virtualizer's handler to\n // read the fresh `scrollTop` and recompute `virtualItems` with `paddingTop = 0`.\n const wasLoadingRef = useRef(isLoading);\n useEffect(() => {\n if (wasLoadingRef.current && !isLoading) {\n scrollRef.current?.dispatchEvent(new Event('scroll'));\n }\n wasLoadingRef.current = isLoading;\n }, [isLoading]);\n\n if (isLoading) {\n return <DataListSkeleton columns={columns} />;\n }\n\n const virtualItems = virtualizer.getVirtualItems();\n const totalSize = virtualizer.getTotalSize();\n const paddingTop = virtualItems[0]?.start ?? 0;\n const paddingBottom =\n virtualItems.length > 0 ? Math.max(0, totalSize - (virtualItems[virtualItems.length - 1]?.end ?? 0)) : 0;\n\n return (\n <TracesDataList columns={columns} variant=\"striped\" scrollRef={scrollRef} className=\"min-w-0\">\n <TracesDataList.Top>\n <TracesDataList.TopCell>Date</TracesDataList.TopCell>\n <TracesDataList.TopCell>Time</TracesDataList.TopCell>\n <TracesDataList.TopCell>Name</TracesDataList.TopCell>\n {hasTraceColumn(columnPreferences, 'input') && <TracesDataList.TopCell>Input</TracesDataList.TopCell>}\n {hasTraceColumn(columnPreferences, 'entity') && <TracesDataList.TopCell>Entity</TracesDataList.TopCell>}\n <TracesDataList.TopCell>Status</TracesDataList.TopCell>\n {hasTraceColumn(columnPreferences, 'duration') && (\n <TracesDataList.TopCell className=\"justify-end text-right\">Duration</TracesDataList.TopCell>\n )}\n {hasTraceColumn(columnPreferences, 'inputTokens') && (\n <TracesDataList.TopCell className=\"justify-end text-right\">Input tokens</TracesDataList.TopCell>\n )}\n {hasTraceColumn(columnPreferences, 'outputTokens') && (\n <TracesDataList.TopCell className=\"justify-end text-right\">Output tokens</TracesDataList.TopCell>\n )}\n {hasTraceColumn(columnPreferences, 'estimatedCost') && (\n <TracesDataList.TopCell className=\"justify-end text-right\">Est. cost</TracesDataList.TopCell>\n )}\n {columnPreferences.metadataKeys.map(key => (\n <TracesDataList.TopCellWithTooltip key={key} tooltip={key}>\n {key}\n </TracesDataList.TopCellWithTooltip>\n ))}\n </TracesDataList.Top>\n\n {traces.length === 0 ? (\n <TracesDataList.NoMatch\n message={filtersApplied ? 'No traces found for applied filters' : 'No traces found yet'}\n />\n ) : (\n <>\n <TracesDataList.Spacer height={paddingTop} />\n {virtualItems.map(vi => {\n const trace = traces[vi.index];\n if (!trace) return null;\n\n const isFeatured =\n trace.traceId === featuredTraceId && (featuredSpanId == null || trace.spanId === featuredSpanId);\n const rowKey = `${trace.traceId}:${trace.spanId ?? ''}`;\n const isRecentlyAdded = recentlyAddedKeys?.has(rowKey) ?? false;\n const displayDate = trace.startedAt ?? trace.createdAt;\n const entityName = trace.entityName || trace.entityId;\n const usage = usageByTraceId?.get(trace.traceId);\n\n return (\n <TracesDataList.RowButton\n key={rowKey}\n ref={virtualizer.measureElement}\n data-index={vi.index}\n onClick={() => onTraceClick(trace)}\n featured={isFeatured}\n className={cn(isRecentlyAdded && 'animate-row-highlight')}\n >\n <TracesDataList.DateCell timestamp={displayDate} />\n <TracesDataList.TimeCell timestamp={displayDate} />\n <TracesDataList.NameCell\n name={trace.name}\n parentSpanId={trace.parentSpanId}\n showLevelTooltip={isBranchesMode}\n />\n {hasTraceColumn(columnPreferences, 'input') && (\n <TracesDataList.InputCell input={trace.inputPreview ?? getInputPreview(trace.input)} />\n )}\n {hasTraceColumn(columnPreferences, 'entity') && (\n <TracesDataList.EntityCell entityType={trace.entityType} entityName={entityName} />\n )}\n <TracesDataList.StatusCell status={trace.status} />\n {hasTraceColumn(columnPreferences, 'duration') && (\n <DataList.NumberCell>{formatSpanDuration(trace.startedAt, trace.endedAt)}</DataList.NumberCell>\n )}\n {hasTraceColumn(columnPreferences, 'inputTokens') && (\n <DataList.NumberCell>\n {usage?.inputTokens === undefined ? undefined : formatCompact(usage.inputTokens)}\n </DataList.NumberCell>\n )}\n {hasTraceColumn(columnPreferences, 'outputTokens') && (\n <DataList.NumberCell>\n {usage?.outputTokens === undefined ? undefined : formatCompact(usage.outputTokens)}\n </DataList.NumberCell>\n )}\n {hasTraceColumn(columnPreferences, 'estimatedCost') && (\n <DataList.NumberCell>\n {usage?.estimatedCost === undefined ? undefined : formatCost(usage.estimatedCost, usage.costUnit)}\n </DataList.NumberCell>\n )}\n {columnPreferences.metadataKeys.map(key => {\n const value = formatTraceMetadataValue(trace.metadata, key);\n return <DataList.MonoCell key={key}>{value}</DataList.MonoCell>;\n })}\n </TracesDataList.RowButton>\n );\n })}\n <TracesDataList.Spacer height={paddingBottom} />\n <TracesDataList.NextPageLoading\n isLoading={isFetchingNextPage}\n hasMore={hasNextPage}\n setEndOfListElement={setEndOfListElement}\n />\n </>\n )}\n </TracesDataList>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,9 @@ const hooks_useInView = require('../../../hooks/use-in-view.cjs.js');
|
|
|
9
9
|
const utils_queryUtils = require('../../../utils/query-utils.cjs.js');
|
|
10
10
|
|
|
11
11
|
const deltaSupportByClient = /* @__PURE__ */ new WeakMap();
|
|
12
|
+
const lightListSupportByClient = /* @__PURE__ */ new WeakMap();
|
|
13
|
+
const lightListFirstServerErrorAtByClient = /* @__PURE__ */ new WeakMap();
|
|
14
|
+
const LIGHT_LIST_SERVER_ERROR_PIN_AFTER_MS = 1e4;
|
|
12
15
|
const DEFAULT_POLLING_CONFIG = {
|
|
13
16
|
deltaPollIntervalMs: 5e3,
|
|
14
17
|
deltaChaseIntervalMs: 100,
|
|
@@ -26,13 +29,49 @@ function shouldResetAfterIdle(lastSuccessAt, now, thresholdMs) {
|
|
|
26
29
|
function isHttp501(error) {
|
|
27
30
|
return error?.status === 501;
|
|
28
31
|
}
|
|
32
|
+
function lightListFallbackStatus(error) {
|
|
33
|
+
const status = error?.status;
|
|
34
|
+
return status === 404 || status === 500 ? status : void 0;
|
|
35
|
+
}
|
|
36
|
+
function shouldPinLightList(status, firstServerErrorAt, now) {
|
|
37
|
+
if (status === 404) return true;
|
|
38
|
+
if (status !== 500) return false;
|
|
39
|
+
if (firstServerErrorAt === void 0) return false;
|
|
40
|
+
return now - firstServerErrorAt >= LIGHT_LIST_SERVER_ERROR_PIN_AFTER_MS;
|
|
41
|
+
}
|
|
29
42
|
const fetchTracesFn = async (args) => {
|
|
30
43
|
const { client, filters, listMode = "traces" } = args;
|
|
31
44
|
const params = args.mode === "delta" ? { mode: "delta", after: args.after, limit: args.limit, filters } : { pagination: { page: args.page, perPage: args.perPage }, filters };
|
|
32
45
|
if (listMode === "branches") {
|
|
33
46
|
return client.listBranches(params);
|
|
34
47
|
}
|
|
35
|
-
|
|
48
|
+
if (lightListSupportByClient.get(client) === "unsupported") {
|
|
49
|
+
return client.listTraces(params);
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const response = await client.listTracesLight(params);
|
|
53
|
+
if (args.mode !== "delta") {
|
|
54
|
+
const spans = response.spans ?? [];
|
|
55
|
+
if (spans.length > 0 && spans.every((span) => span.status === void 0)) {
|
|
56
|
+
lightListSupportByClient.set(client, "unsupported");
|
|
57
|
+
return client.listTraces(params);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
lightListFirstServerErrorAtByClient.delete(client);
|
|
61
|
+
return response;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
const status = lightListFallbackStatus(error);
|
|
64
|
+
if (status === void 0) throw error;
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
const firstServerErrorAt = lightListFirstServerErrorAtByClient.get(client);
|
|
67
|
+
if (status === 500 && firstServerErrorAt === void 0) {
|
|
68
|
+
lightListFirstServerErrorAtByClient.set(client, now);
|
|
69
|
+
}
|
|
70
|
+
if (shouldPinLightList(status, firstServerErrorAt, now)) {
|
|
71
|
+
lightListSupportByClient.set(client, "unsupported");
|
|
72
|
+
}
|
|
73
|
+
return client.listTraces(params);
|
|
74
|
+
}
|
|
36
75
|
};
|
|
37
76
|
const TRACES_PER_PAGE = 25;
|
|
38
77
|
function getTracesNextPageParam(lastPage, _allPages, lastPageParam) {
|
|
@@ -55,29 +94,31 @@ function selectUniqueTraces(data) {
|
|
|
55
94
|
});
|
|
56
95
|
return { spans, deltaCursor: data.pages[0]?.deltaCursor };
|
|
57
96
|
}
|
|
97
|
+
function rowKey(row) {
|
|
98
|
+
return `${row.traceId}:${row.spanId}`;
|
|
99
|
+
}
|
|
100
|
+
function indexRowsByKey(rows) {
|
|
101
|
+
return new Map(rows.map((row) => [rowKey(row), row]));
|
|
102
|
+
}
|
|
58
103
|
function refreshPage0Rows(old, refreshed, listMode) {
|
|
59
104
|
if (!old || old.pages.length === 0) return old;
|
|
60
105
|
const [firstPage, ...rest] = old.pages;
|
|
61
106
|
if (!firstPage) return old;
|
|
62
|
-
const refreshedRows = listMode === "branches" && "branches" in refreshed ? refreshed.branches ?? [] : "spans" in refreshed ? refreshed.spans ?? [] : [];
|
|
63
|
-
if (refreshedRows.length === 0) return old;
|
|
64
|
-
const refreshedByKey = /* @__PURE__ */ new Map();
|
|
65
|
-
for (const row of refreshedRows) {
|
|
66
|
-
refreshedByKey.set(`${row.traceId}:${row.spanId}`, row);
|
|
67
|
-
}
|
|
68
107
|
let updatedFirst;
|
|
69
|
-
if (listMode === "branches" && "branches" in firstPage) {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
108
|
+
if (listMode === "branches" && "branches" in firstPage && "branches" in refreshed) {
|
|
109
|
+
const refreshedByKey = indexRowsByKey(refreshed.branches ?? []);
|
|
110
|
+
if (refreshedByKey.size === 0) return old;
|
|
111
|
+
updatedFirst = {
|
|
112
|
+
...firstPage,
|
|
113
|
+
branches: (firstPage.branches ?? []).map((existing) => refreshedByKey.get(rowKey(existing)) ?? existing)
|
|
114
|
+
};
|
|
115
|
+
} else if ("spans" in firstPage && "spans" in refreshed) {
|
|
116
|
+
const refreshedByKey = indexRowsByKey(refreshed.spans ?? []);
|
|
117
|
+
if (refreshedByKey.size === 0) return old;
|
|
118
|
+
updatedFirst = {
|
|
119
|
+
...firstPage,
|
|
120
|
+
spans: (firstPage.spans ?? []).map((existing) => refreshedByKey.get(rowKey(existing)) ?? existing)
|
|
121
|
+
};
|
|
81
122
|
} else {
|
|
82
123
|
return old;
|
|
83
124
|
}
|
|
@@ -305,6 +346,7 @@ exports.getTracesNextPageParam = getTracesNextPageParam;
|
|
|
305
346
|
exports.mergeDeltaIntoPage0 = mergeDeltaIntoPage0;
|
|
306
347
|
exports.refreshPage0Rows = refreshPage0Rows;
|
|
307
348
|
exports.selectUniqueTraces = selectUniqueTraces;
|
|
349
|
+
exports.shouldPinLightList = shouldPinLightList;
|
|
308
350
|
exports.shouldResetAfterIdle = shouldResetAfterIdle;
|
|
309
351
|
exports.useTraces = useTraces;
|
|
310
352
|
//# sourceMappingURL=use-traces.cjs.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-traces.cjs.js","sources":["../../../../src/domains/traces/hooks/use-traces.tsx"],"sourcesContent":["import type {\n ListBranchesArgs,\n ListBranchesResponse,\n ListTracesArgs,\n ListTracesResponse,\n TraceSpan,\n} from '@mastra/core/storage';\nimport { useMastraClient } from '@mastra/react';\nimport type { InfiniteData } from '@tanstack/react-query';\nimport { useInfiniteQuery, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { useEffect, useRef, useState } from 'react';\nimport type { TraceListMode } from '../trace-filters';\nimport { useInView } from '@/hooks/use-in-view';\nimport { is403ForbiddenError } from '@/lib/query-utils';\n\n/**\n * Per-MastraClient delta-polling support cache. Sticks once we observe a 501\n * or a page-0 response without `deltaCursor` (legacy server or store without\n * delta capability), so we don't re-probe on every mount.\n */\ntype DeltaSupport = 'unknown' | 'unsupported';\nconst deltaSupportByClient = new WeakMap<ReturnType<typeof useMastraClient>, DeltaSupport>();\n\n/** Tunables for live-tail polling. All fields optional — defaults below.\n * Platform consumers override individual fields to throttle traffic or\n * reshape the freshness/visibility trade-offs. */\nexport interface TracesPollingConfig {\n /** Interval between delta polls. Default 5_000ms. */\n deltaPollIntervalMs?: number;\n /** Refetch delay when the server signals `delta.hasMore` (chase remaining updates immediately). Default 100ms. */\n deltaChaseIntervalMs?: number;\n /** Max rows per delta poll. Server caps at 100. Default 100. */\n deltaLimit?: number;\n /** Page-mode refetch interval used when the configured store does not support delta polling. Default 10_000ms. */\n pageModeRefetchIntervalMs?: number;\n /** Periodic page-0 refetch interval while delta is active. Delta only delivers new rows, so this\n * is how we surface status flips (running → success/error) on rows already visible. Default 60_000ms. */\n page0StatusRefreshIntervalMs?: number;\n /** When the tab returns from being hidden and the last successful poll was older than this, the\n * cursor is treated as stale (ClickHouse delta tables TTL after 2 days; other stores may be more\n * aggressive). The infinite query is reset so page 0 refetches and delta resumes from a fresh\n * cursor. Default 900_000ms (15 min). */\n idleGuardThresholdMs?: number;\n /** Minimum visible duration for `isRefetching` after any query completes. On localhost a delta\n * poll can resolve in tens of ms — too brief for the spinner's 1s rotation to register visually,\n * and quick enough that React may batch the start/finish transitions. Pulsing for this duration\n * on completion (~144° of rotation at 400ms) makes each poll perceptible as a heartbeat.\n * Default 400ms. */\n minRefetchSpinMs?: number;\n /** Window during which a delta-arrived row's key stays in `recentlyAddedKeys` so the list view\n * can apply its `animate-row-highlight` class. Should match the CSS animation duration\n * (currently 1_000ms — see `index.css`). Default 1_000ms. */\n deltaHighlightMs?: number;\n}\n\nconst DEFAULT_POLLING_CONFIG: Required<TracesPollingConfig> = {\n deltaPollIntervalMs: 5_000,\n deltaChaseIntervalMs: 100,\n deltaLimit: 100,\n pageModeRefetchIntervalMs: 10_000,\n page0StatusRefreshIntervalMs: 60_000,\n idleGuardThresholdMs: 15 * 60_000,\n minRefetchSpinMs: 400,\n deltaHighlightMs: 1_000,\n};\n\n/** Returns true when the cursor should be considered stale and re-seeded.\n * Exported for testing — pure function of timestamps. */\nexport function shouldResetAfterIdle(lastSuccessAt: number, now: number, thresholdMs: number): boolean {\n if (lastSuccessAt === 0) return false;\n return now - lastSuccessAt > thresholdMs;\n}\n\nfunction isHttp501(error: unknown): boolean {\n return (error as { status?: number } | null)?.status === 501;\n}\n\ntype FetchTracesFnArgs = TracesFilters & {\n client: ReturnType<typeof useMastraClient>;\n} & ({ mode: 'delta'; after?: string; limit?: number } | { mode?: 'page'; page: number; perPage: number });\n\nconst fetchTracesFn = async (args: FetchTracesFnArgs) => {\n const { client, filters, listMode = 'traces' } = args;\n const params =\n args.mode === 'delta'\n ? { mode: 'delta' as const, after: args.after, limit: args.limit, filters }\n : { pagination: { page: args.page, perPage: args.perPage }, filters };\n\n if (listMode === 'branches') {\n return client.listBranches(params as ListBranchesArgs);\n }\n\n return client.listTraces(params as ListTracesArgs);\n};\n\nexport const TRACES_PER_PAGE = 25;\n\nexport interface TracesFilters {\n filters?: ListTracesArgs['filters'] | ListBranchesArgs['filters'];\n listMode?: TraceListMode;\n}\n\n/** Returns the next page number if the server indicates more pages are available. */\nexport function getTracesNextPageParam(\n lastPage: ListTracesResponse | ListBranchesResponse | undefined,\n _allPages: unknown,\n lastPageParam: number,\n) {\n if (lastPage?.pagination?.hasMore) {\n return lastPageParam + 1;\n }\n return undefined;\n}\n\ntype TracesPageResponse = ListTracesResponse | ListBranchesResponse;\n\nfunction getPageSpans(page: TracesPageResponse) {\n if ('branches' in page) return page.branches ?? [];\n return page.spans ?? [];\n}\n\n/** Deduplicates trace/branch rows by traceId + spanId across all loaded pages.\n * Also surfaces page 0's deltaCursor so the live-tail query can read it reactively. */\nexport function selectUniqueTraces(data: { pages: TracesPageResponse[] }): {\n spans: TraceSpan[];\n deltaCursor: string | undefined;\n} {\n const seen = new Set<string>();\n const spans = data.pages\n .flatMap(page => getPageSpans(page))\n .filter(span => {\n const key = `${span.traceId}:${span.spanId}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n\n return { spans, deltaCursor: data.pages[0]?.deltaCursor };\n}\n\n/** Replaces existing page-0 rows in place (keyed by traceId:spanId) with\n * refreshed copies from the server. Rows the server doesn't return are kept\n * as-is so delta-accumulated rows that have aged off the server's page 0\n * aren't dropped from the UI. New rows aren't added here — delta polling\n * delivers those. */\nexport function refreshPage0Rows(\n old: InfiniteData<TracesPageResponse> | undefined,\n refreshed: ListTracesResponse | ListBranchesResponse,\n listMode: TraceListMode,\n): InfiniteData<TracesPageResponse> | undefined {\n if (!old || old.pages.length === 0) return old;\n const [firstPage, ...rest] = old.pages;\n if (!firstPage) return old;\n\n const refreshedRows =\n listMode === 'branches' && 'branches' in refreshed\n ? (refreshed.branches ?? [])\n : 'spans' in refreshed\n ? (refreshed.spans ?? [])\n : [];\n\n if (refreshedRows.length === 0) return old;\n\n const refreshedByKey = new Map<string, (typeof refreshedRows)[number]>();\n for (const row of refreshedRows) {\n refreshedByKey.set(`${row.traceId}:${row.spanId}`, row);\n }\n\n let updatedFirst: TracesPageResponse;\n if (listMode === 'branches' && 'branches' in firstPage) {\n const updated = (firstPage.branches ?? []).map(existing => {\n const fresh = refreshedByKey.get(`${existing.traceId}:${existing.spanId}`);\n return fresh ?? existing;\n });\n updatedFirst = { ...firstPage, branches: updated };\n } else if ('spans' in firstPage) {\n const updated = (firstPage.spans ?? []).map(existing => {\n const fresh = refreshedByKey.get(`${existing.traceId}:${existing.spanId}`);\n return fresh ?? existing;\n });\n updatedFirst = { ...firstPage, spans: updated };\n } else {\n return old;\n }\n\n return { ...old, pages: [updatedFirst, ...rest] };\n}\n\n/** Stable `startedAt DESC` sort matching the server's default page-mode\n * ordering for both list endpoints. Rows missing `startedAt` sink to the\n * bottom (compared as time 0). */\nfunction sortRowsByStartedAtDesc<T extends { startedAt?: unknown }>(rows: T[]): T[] {\n return [...rows].sort((a, b) => {\n const aTime = a.startedAt ? new Date(a.startedAt as string | number | Date).getTime() : 0;\n const bTime = b.startedAt ? new Date(b.startedAt as string | number | Date).getTime() : 0;\n return bTime - aTime;\n });\n}\n\n/** Prepends delta-mode rows into page 0 and advances its deltaCursor.\n * Delta responses come back in server cursor order (insertion order into\n * the per-store delta index), not the `startedAt DESC` order page-mode\n * uses — so we re-sort the merged page 0 to match what the user sees on\n * a fresh page-mode load. Cross-page duplication is handled later by\n * selectUniqueTraces. */\nexport function mergeDeltaIntoPage0(\n old: InfiniteData<TracesPageResponse> | undefined,\n delta: ListTracesResponse | ListBranchesResponse,\n listMode: TraceListMode,\n): InfiniteData<TracesPageResponse> | undefined {\n if (!old || old.pages.length === 0) return old;\n const [firstPage, ...rest] = old.pages;\n if (!firstPage) return old;\n const nextCursor = delta.deltaCursor ?? firstPage.deltaCursor;\n\n let updatedFirst: TracesPageResponse;\n if (listMode === 'branches' && 'branches' in firstPage) {\n const newRows = (delta as ListBranchesResponse).branches ?? [];\n updatedFirst = {\n ...firstPage,\n branches: sortRowsByStartedAtDesc([...newRows, ...(firstPage.branches ?? [])]),\n deltaCursor: nextCursor,\n };\n } else if ('spans' in firstPage) {\n const newRows = (delta as ListTracesResponse).spans ?? [];\n updatedFirst = {\n ...firstPage,\n spans: sortRowsByStartedAtDesc([...newRows, ...(firstPage.spans ?? [])]),\n deltaCursor: nextCursor,\n };\n } else {\n return old;\n }\n\n return { ...old, pages: [updatedFirst, ...rest] };\n}\n\nexport interface UseTracesArgs extends TracesFilters {\n /** Optional overrides for the live-tail polling tunables. Any omitted fields fall back to the\n * built-in defaults; pass only what you want to change. */\n polling?: TracesPollingConfig;\n}\n\ninterface UseTracesReturn {\n data: { spans: TraceSpan[]; deltaCursor: string | undefined } | undefined;\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n fetchNextPage: () => void;\n isLoading: boolean;\n isError: boolean;\n error: Error | null;\n setEndOfListElement: (node: HTMLDivElement | null) => void;\n isRefetching: boolean;\n autoRefetch: boolean;\n setAutoRefetch: (v: boolean) => void;\n recentlyAddedKeys: Set<string>;\n dataUpdatedAt: number;\n errorUpdatedAt: number;\n isFetching: boolean;\n isSuccess: boolean;\n fetchStatus: 'idle' | 'fetching' | 'paused';\n refetch: () => void;\n}\n\nexport const useTraces: (args: UseTracesArgs) => UseTracesReturn = ({\n filters,\n listMode = 'traces',\n polling = {},\n}: UseTracesArgs) => {\n const {\n deltaPollIntervalMs = DEFAULT_POLLING_CONFIG.deltaPollIntervalMs,\n deltaChaseIntervalMs = DEFAULT_POLLING_CONFIG.deltaChaseIntervalMs,\n deltaLimit = DEFAULT_POLLING_CONFIG.deltaLimit,\n pageModeRefetchIntervalMs = DEFAULT_POLLING_CONFIG.pageModeRefetchIntervalMs,\n page0StatusRefreshIntervalMs = DEFAULT_POLLING_CONFIG.page0StatusRefreshIntervalMs,\n idleGuardThresholdMs = DEFAULT_POLLING_CONFIG.idleGuardThresholdMs,\n minRefetchSpinMs = DEFAULT_POLLING_CONFIG.minRefetchSpinMs,\n deltaHighlightMs = DEFAULT_POLLING_CONFIG.deltaHighlightMs,\n } = polling;\n const client = useMastraClient();\n const queryClient = useQueryClient();\n const { inView: isEndOfListInView, setRef: setEndOfListElement } = useInView();\n\n const [deltaSupport, setDeltaSupport] = useState<DeltaSupport>(() => deltaSupportByClient.get(client) ?? 'unknown');\n const deltaUnsupported = deltaSupport === 'unsupported';\n\n // When false, all automatic polling stops: delta polls, status refresh,\n // idle-guard resets, and the page-mode fallback interval. Manual `resync`\n // still works. Session-scoped (no persistence across mounts).\n const [autoRefetch, setAutoRefetch] = useState(true);\n\n // Keys (`traceId:spanId`) of rows that arrived via the most recent delta\n // poll(s). The list view reads this to apply a temporary highlight class\n // for deltaHighlightMs, then they auto-expire. Resets on filter/listMode\n // change since a new query key spawns a fresh list.\n const [recentlyAddedKeys, setRecentlyAddedKeys] = useState<Set<string>>(() => new Set());\n useEffect(() => {\n setRecentlyAddedKeys(new Set());\n }, [listMode, filters]);\n\n const tracesQueryKey = ['traces', listMode, filters] as const;\n\n const query = useInfiniteQuery({\n queryKey: tracesQueryKey,\n queryFn: ({ pageParam }) =>\n fetchTracesFn({\n client,\n page: pageParam,\n perPage: TRACES_PER_PAGE,\n filters,\n listMode,\n }),\n initialPageParam: 0,\n getNextPageParam: getTracesNextPageParam,\n select: selectUniqueTraces,\n retry: false,\n // Disable polling on 403 to prevent flickering.\n // Fall back to page-mode polling only when delta isn't running.\n refetchInterval: q => {\n if (is403ForbiddenError(q.state.error)) return false;\n if (!autoRefetch) return false;\n return deltaUnsupported ? pageModeRefetchIntervalMs : false;\n },\n });\n\n const cursor = query.data?.deltaCursor;\n\n // If page 0 came back without a deltaCursor, the server/store doesn't\n // support delta polling. Pin the client to 'unsupported' so the infinite\n // query resumes page-mode polling and we stop probing.\n useEffect(() => {\n if (query.isSuccess && cursor === undefined && deltaSupport === 'unknown') {\n deltaSupportByClient.set(client, 'unsupported');\n setDeltaSupport('unsupported');\n }\n }, [query.isSuccess, cursor, deltaSupport, client]);\n\n const deltaQuery = useQuery({\n queryKey: ['traces-delta', listMode, filters] as const,\n queryFn: () => {\n const current = queryClient.getQueryData<InfiniteData<TracesPageResponse>>(tracesQueryKey)?.pages[0]?.deltaCursor;\n if (!current) return null;\n return fetchTracesFn({\n client,\n mode: 'delta',\n after: current,\n limit: deltaLimit,\n filters,\n listMode,\n });\n },\n enabled: !!cursor && !deltaUnsupported && autoRefetch,\n retry: false,\n refetchInterval: q => {\n if (q.state.error) return false;\n const data = q.state.data as ListTracesResponse | ListBranchesResponse | null | undefined;\n if (data?.delta?.hasMore) return deltaChaseIntervalMs;\n return deltaPollIntervalMs;\n },\n });\n\n // Merge new delta rows into the infinite-query cache. Also captures the\n // arrived keys into `recentlyAddedKeys` for deltaHighlightMs so the UI\n // can briefly distinguish them. The cleanup timer is intentionally NOT\n // tied to the effect lifecycle — if a newer delta arrives first, we still\n // want each prior batch to expire on its own schedule.\n useEffect(() => {\n const result = deltaQuery.data;\n if (!result) return;\n queryClient.setQueryData<InfiniteData<TracesPageResponse>>(tracesQueryKey, old =>\n mergeDeltaIntoPage0(old, result, listMode),\n );\n\n const newRows =\n listMode === 'branches' && 'branches' in result\n ? (result.branches ?? [])\n : 'spans' in result\n ? (result.spans ?? [])\n : [];\n if (newRows.length === 0) return;\n const keys = newRows.map(r => `${r.traceId}:${r.spanId ?? ''}`);\n setRecentlyAddedKeys(prev => {\n const next = new Set(prev);\n for (const k of keys) next.add(k);\n return next;\n });\n setTimeout(() => {\n setRecentlyAddedKeys(prev => {\n const next = new Set(prev);\n for (const k of keys) next.delete(k);\n return next;\n });\n }, deltaHighlightMs);\n // tracesQueryKey is a new array each render but its contents drive cache keying;\n // depending on its members (listMode/filters) is enough.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [deltaQuery.data, queryClient, listMode, filters, deltaHighlightMs]);\n\n // A 501 means the server is too old (or store doesn't support delta) — pin\n // 'unsupported' so the delta query disables and page-mode polling resumes.\n useEffect(() => {\n if (isHttp501(deltaQuery.error)) {\n deltaSupportByClient.set(client, 'unsupported');\n setDeltaSupport('unsupported');\n }\n }, [deltaQuery.error, client]);\n\n // Periodic page-0 refresh to surface status updates on visible rows\n // (running → success/error). Only runs while delta is the primary polling\n // mechanism; otherwise the infinite query's own refetchInterval covers it.\n const statusRefreshQuery = useQuery({\n queryKey: ['traces-status-refresh', listMode, filters] as const,\n queryFn: () =>\n fetchTracesFn({\n client,\n page: 0,\n perPage: TRACES_PER_PAGE,\n filters,\n listMode,\n }),\n enabled: !!cursor && !deltaUnsupported && autoRefetch,\n refetchInterval: page0StatusRefreshIntervalMs,\n refetchOnMount: false,\n retry: false,\n });\n\n useEffect(() => {\n const result = statusRefreshQuery.data;\n if (!result) return;\n queryClient.setQueryData<InfiniteData<TracesPageResponse>>(tracesQueryKey, old =>\n refreshPage0Rows(old, result, listMode),\n );\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [statusRefreshQuery.data, queryClient, listMode, filters]);\n\n // Idle guard: when the tab returns from being hidden, check how long it's\n // been since the last successful poll. If past the threshold, reset the\n // infinite query so page 0 refetches and the cursor is re-seeded.\n const lastSuccessAtRef = useRef(0);\n useEffect(() => {\n const ts = Math.max(query.dataUpdatedAt, deltaQuery.dataUpdatedAt, statusRefreshQuery.dataUpdatedAt);\n if (ts > lastSuccessAtRef.current) {\n lastSuccessAtRef.current = ts;\n }\n }, [query.dataUpdatedAt, deltaQuery.dataUpdatedAt, statusRefreshQuery.dataUpdatedAt]);\n\n // Track autoRefetch in a ref so the visibility listener (attached once)\n // reads the current value without needing to re-attach.\n const autoRefetchRef = useRef(autoRefetch);\n useEffect(() => {\n autoRefetchRef.current = autoRefetch;\n }, [autoRefetch]);\n\n useEffect(() => {\n const handler = () => {\n if (document.visibilityState !== 'visible') return;\n if (!autoRefetchRef.current) return;\n if (!shouldResetAfterIdle(lastSuccessAtRef.current, Date.now(), idleGuardThresholdMs)) return;\n void queryClient.resetQueries({ queryKey: ['traces', listMode, filters] });\n };\n document.addEventListener('visibilitychange', handler);\n return () => document.removeEventListener('visibilitychange', handler);\n }, [queryClient, listMode, filters, idleGuardThresholdMs]);\n\n const { hasNextPage, isFetchingNextPage, fetchNextPage } = query;\n\n useEffect(() => {\n if (isEndOfListInView && hasNextPage && !isFetchingNextPage) {\n void fetchNextPage();\n }\n }, [isEndOfListInView, hasNextPage, isFetchingNextPage, fetchNextPage]);\n\n // `isRefetching` aggregates every active fetch — page-mode (initial, next\n // page, manual resync, idle reset), the 60s page-0 status refresh, AND the\n // delta polls — so a spinner bound to it acts as a heartbeat indicator.\n //\n // Short polls (e.g. delta on localhost) can resolve in tens of ms — too\n // brief for `animate-spin` (1s/rotation) to register visually, and quick\n // enough that React may batch the start/finish transitions so we never\n // even observe `isFetching: true`. So we also watch every query's\n // data/errorUpdatedAt; any advance pulses `isRefetching` true for at least\n // minRefetchSpinMs, which is ~144° of rotation — clearly perceptible.\n const isFetchingAny = query.isFetching || statusRefreshQuery.isFetching || deltaQuery.isFetching;\n const lastActivityAt = Math.max(\n query.dataUpdatedAt,\n query.errorUpdatedAt,\n deltaQuery.dataUpdatedAt,\n deltaQuery.errorUpdatedAt,\n statusRefreshQuery.dataUpdatedAt,\n statusRefreshQuery.errorUpdatedAt,\n );\n const [pulse, setPulse] = useState(false);\n const prevActivityAtRef = useRef(lastActivityAt);\n useEffect(() => {\n if (lastActivityAt > prevActivityAtRef.current) {\n prevActivityAtRef.current = lastActivityAt;\n setPulse(true);\n const t = setTimeout(() => setPulse(false), minRefetchSpinMs);\n return () => clearTimeout(t);\n }\n }, [lastActivityAt, minRefetchSpinMs]);\n const isRefetching = isFetchingAny || pulse;\n\n return {\n ...query,\n setEndOfListElement,\n isRefetching,\n autoRefetch,\n setAutoRefetch,\n recentlyAddedKeys,\n };\n};\n"],"names":["useMastraClient","useQueryClient","useInView","useState","useEffect","useInfiniteQuery","is403ForbiddenError","useQuery","useRef"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-traces.cjs.js","sources":["../../../../src/domains/traces/hooks/use-traces.tsx"],"sourcesContent":["import type {\n LightSpanRecord,\n ListBranchesArgs,\n ListBranchesResponse,\n ListTracesArgs,\n ListTracesLightResponse,\n} from '@mastra/core/storage';\nimport { useMastraClient } from '@mastra/react';\nimport type { InfiniteData } from '@tanstack/react-query';\nimport { useInfiniteQuery, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { useEffect, useRef, useState } from 'react';\nimport type { TraceListMode } from '../trace-filters';\nimport { useInView } from '@/hooks/use-in-view';\nimport { is403ForbiddenError } from '@/lib/query-utils';\n\n/**\n * Per-MastraClient delta-polling support cache. Sticks once we observe a 501\n * or a page-0 response without `deltaCursor` (legacy server or store without\n * delta capability), so we don't re-probe on every mount.\n */\ntype DeltaSupport = 'unknown' | 'unsupported';\nconst deltaSupportByClient = new WeakMap<ReturnType<typeof useMastraClient>, DeltaSupport>();\n\n/**\n * Per-MastraClient light-list support cache. Sticks once the light endpoint is\n * shown not to be served, so every subsequent page fetch, delta poll and periodic\n * refresh uses the full list endpoint instead. Other failures (auth, rate limits,\n * outages) propagate without pinning — they'd hit the full endpoint too.\n */\ntype LightListSupport = 'unknown' | 'unsupported';\nconst lightListSupportByClient = new WeakMap<ReturnType<typeof useMastraClient>, LightListSupport>();\n\n/** When the current run of light-endpoint 500s started, per client. Cleared by any light success. */\nconst lightListFirstServerErrorAtByClient = new WeakMap<ReturnType<typeof useMastraClient>, number>();\n\n/** A 500 is ambiguous — an old core's store throwing, or a transient fault (DB hiccup, timeout).\n * Serve the request from the full endpoint either way, but only pin the session once the 500s\n * have outlasted a blip; a hiccup would otherwise degrade the list until the tab is reloaded.\n * Elapsed time, not a request count: the page refetch, the delta poll and the status refresh can\n * fail in the same instant, and a fallback response still looks like a success to the delta query,\n * so chase mode keeps re-polling every `deltaChaseIntervalMs` throughout the outage. */\nconst LIGHT_LIST_SERVER_ERROR_PIN_AFTER_MS = 10_000;\n\n/** Tunables for live-tail polling. All fields optional — defaults below.\n * Platform consumers override individual fields to throttle traffic or\n * reshape the freshness/visibility trade-offs. */\nexport interface TracesPollingConfig {\n /** Interval between delta polls. Default 5_000ms. */\n deltaPollIntervalMs?: number;\n /** Refetch delay when the server signals `delta.hasMore` (chase remaining updates immediately). Default 100ms. */\n deltaChaseIntervalMs?: number;\n /** Max rows per delta poll. Server caps at 100. Default 100. */\n deltaLimit?: number;\n /** Page-mode refetch interval used when the configured store does not support delta polling. Default 10_000ms. */\n pageModeRefetchIntervalMs?: number;\n /** Periodic page-0 refetch interval while delta is active. Delta only delivers new rows, so this\n * is how we surface status flips (running → success/error) on rows already visible. Default 60_000ms. */\n page0StatusRefreshIntervalMs?: number;\n /** When the tab returns from being hidden and the last successful poll was older than this, the\n * cursor is treated as stale (ClickHouse delta tables TTL after 2 days; other stores may be more\n * aggressive). The infinite query is reset so page 0 refetches and delta resumes from a fresh\n * cursor. Default 900_000ms (15 min). */\n idleGuardThresholdMs?: number;\n /** Minimum visible duration for `isRefetching` after any query completes. On localhost a delta\n * poll can resolve in tens of ms — too brief for the spinner's 1s rotation to register visually,\n * and quick enough that React may batch the start/finish transitions. Pulsing for this duration\n * on completion (~144° of rotation at 400ms) makes each poll perceptible as a heartbeat.\n * Default 400ms. */\n minRefetchSpinMs?: number;\n /** Window during which a delta-arrived row's key stays in `recentlyAddedKeys` so the list view\n * can apply its `animate-row-highlight` class. Should match the CSS animation duration\n * (currently 1_000ms — see `index.css`). Default 1_000ms. */\n deltaHighlightMs?: number;\n}\n\nconst DEFAULT_POLLING_CONFIG: Required<TracesPollingConfig> = {\n deltaPollIntervalMs: 5_000,\n deltaChaseIntervalMs: 100,\n deltaLimit: 100,\n pageModeRefetchIntervalMs: 10_000,\n page0StatusRefreshIntervalMs: 60_000,\n idleGuardThresholdMs: 15 * 60_000,\n minRefetchSpinMs: 400,\n deltaHighlightMs: 1_000,\n};\n\n/** Returns true when the cursor should be considered stale and re-seeded.\n * Exported for testing — pure function of timestamps. */\nexport function shouldResetAfterIdle(lastSuccessAt: number, now: number, thresholdMs: number): boolean {\n if (lastSuccessAt === 0) return false;\n return now - lastSuccessAt > thresholdMs;\n}\n\nfunction isHttp501(error: unknown): boolean {\n return (error as { status?: number } | null)?.status === 501;\n}\n\n/** Only 404 and 500 can mean the light endpoint isn't served: 404 from servers without\n * the route, 500 from cores whose store throws instead of serving the projection.\n * Anything else (401, 403, 429, other 5xx, the 501 disabled-domain signal) is a\n * condition the full endpoint would hit too, so it propagates instead of falling\n * back — mirroring how the delta-support cache matches exactly 501. */\nfunction lightListFallbackStatus(error: unknown): 404 | 500 | undefined {\n const status = (error as { status?: number } | null)?.status;\n return status === 404 || status === 500 ? status : undefined;\n}\n\n/** Whether a failed light-list request should pin the session to the full endpoint.\n * `firstServerErrorAt` is undefined when this 500 opens a new run of them, so a single\n * fault never pins. Exported for testing — pure function of the status and timestamps. */\nexport function shouldPinLightList(\n status: number | undefined,\n firstServerErrorAt: number | undefined,\n now: number,\n): boolean {\n if (status === 404) return true;\n if (status !== 500) return false;\n if (firstServerErrorAt === undefined) return false;\n return now - firstServerErrorAt >= LIGHT_LIST_SERVER_ERROR_PIN_AFTER_MS;\n}\n\ntype FetchTracesFnArgs = TracesFilters & {\n client: ReturnType<typeof useMastraClient>;\n} & ({ mode: 'delta'; after?: string; limit?: number } | { mode?: 'page'; page: number; perPage: number });\n\nconst fetchTracesFn = async (args: FetchTracesFnArgs) => {\n const { client, filters, listMode = 'traces' } = args;\n const params =\n args.mode === 'delta'\n ? { mode: 'delta' as const, after: args.after, limit: args.limit, filters }\n : { pagination: { page: args.page, perPage: args.perPage }, filters };\n\n if (listMode === 'branches') {\n return client.listBranches(params as ListBranchesArgs);\n }\n\n // The list only renders identity, timing, status and a short input preview, so ask\n // for the lightweight projection. Selecting a row fetches the full record separately.\n // Clients pinned to full mode keep using listTraces (page and delta alike), exactly\n // as before the light endpoint existed; full rows are a superset of the light ones.\n if (lightListSupportByClient.get(client) === 'unsupported') {\n return client.listTraces(params as ListTracesArgs);\n }\n try {\n const response = await client.listTracesLight(params as ListTracesArgs);\n // A 200 can still be a legacy projection: servers whose store implements the\n // pre-preview light list answer with rows carrying none of the new fields.\n // Current servers always set `status` on every row, so a non-empty page where\n // every row lacks it is legacy-shaped — pin and refetch the full rows. Empty\n // pages never pin (indistinguishable, and they render identically either way;\n // the next non-empty page decides).\n if (args.mode !== 'delta') {\n const spans = response.spans ?? [];\n if (spans.length > 0 && spans.every(span => span.status === undefined)) {\n lightListSupportByClient.set(client, 'unsupported');\n return client.listTraces(params as ListTracesArgs);\n }\n }\n lightListFirstServerErrorAtByClient.delete(client);\n return response;\n } catch (error) {\n const status = lightListFallbackStatus(error);\n if (status === undefined) throw error;\n\n const now = Date.now();\n const firstServerErrorAt = lightListFirstServerErrorAtByClient.get(client);\n if (status === 500 && firstServerErrorAt === undefined) {\n lightListFirstServerErrorAtByClient.set(client, now);\n }\n if (shouldPinLightList(status, firstServerErrorAt, now)) {\n lightListSupportByClient.set(client, 'unsupported');\n }\n return client.listTraces(params as ListTracesArgs);\n }\n};\n\nexport const TRACES_PER_PAGE = 25;\n\nexport interface TracesFilters {\n filters?: ListTracesArgs['filters'] | ListBranchesArgs['filters'];\n listMode?: TraceListMode;\n}\n\n/** Returns the next page number if the server indicates more pages are available. */\nexport function getTracesNextPageParam(\n lastPage: ListTracesLightResponse | ListBranchesResponse | undefined,\n _allPages: unknown,\n lastPageParam: number,\n) {\n if (lastPage?.pagination?.hasMore) {\n return lastPageParam + 1;\n }\n return undefined;\n}\n\ntype TracesPageResponse = ListTracesLightResponse | ListBranchesResponse;\n\n/** Branch rows are full spans and trace rows are lightweight; the list only reads the\n * fields they share, so both are surfaced as `LightSpanRecord`. */\nfunction getPageSpans(page: TracesPageResponse): LightSpanRecord[] {\n if ('branches' in page) return page.branches ?? [];\n return page.spans ?? [];\n}\n\n/** Deduplicates trace/branch rows by traceId + spanId across all loaded pages.\n * Also surfaces page 0's deltaCursor so the live-tail query can read it reactively. */\nexport function selectUniqueTraces(data: { pages: TracesPageResponse[] }): {\n spans: LightSpanRecord[];\n deltaCursor: string | undefined;\n} {\n const seen = new Set<string>();\n const spans = data.pages\n .flatMap(page => getPageSpans(page))\n .filter(span => {\n const key = `${span.traceId}:${span.spanId}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n\n return { spans, deltaCursor: data.pages[0]?.deltaCursor };\n}\n\ntype RowIdentity = { traceId: string; spanId?: string | null };\n\nfunction rowKey(row: RowIdentity): string {\n return `${row.traceId}:${row.spanId}`;\n}\n\nfunction indexRowsByKey<TRow extends RowIdentity>(rows: TRow[]): Map<string, TRow> {\n return new Map(rows.map(row => [rowKey(row), row]));\n}\n\n/** Replaces existing page-0 rows in place (keyed by traceId:spanId) with\n * refreshed copies from the server. Rows the server doesn't return are kept\n * as-is so delta-accumulated rows that have aged off the server's page 0\n * aren't dropped from the UI. New rows aren't added here — delta polling\n * delivers those. */\nexport function refreshPage0Rows(\n old: InfiniteData<TracesPageResponse> | undefined,\n refreshed: ListTracesLightResponse | ListBranchesResponse,\n listMode: TraceListMode,\n): InfiniteData<TracesPageResponse> | undefined {\n if (!old || old.pages.length === 0) return old;\n const [firstPage, ...rest] = old.pages;\n if (!firstPage) return old;\n\n // Branch and trace rows have different row shapes, so each mode keys its own map.\n let updatedFirst: TracesPageResponse;\n if (listMode === 'branches' && 'branches' in firstPage && 'branches' in refreshed) {\n const refreshedByKey = indexRowsByKey(refreshed.branches ?? []);\n if (refreshedByKey.size === 0) return old;\n updatedFirst = {\n ...firstPage,\n branches: (firstPage.branches ?? []).map(existing => refreshedByKey.get(rowKey(existing)) ?? existing),\n };\n } else if ('spans' in firstPage && 'spans' in refreshed) {\n const refreshedByKey = indexRowsByKey(refreshed.spans ?? []);\n if (refreshedByKey.size === 0) return old;\n updatedFirst = {\n ...firstPage,\n spans: (firstPage.spans ?? []).map(existing => refreshedByKey.get(rowKey(existing)) ?? existing),\n };\n } else {\n return old;\n }\n\n return { ...old, pages: [updatedFirst, ...rest] };\n}\n\n/** Stable `startedAt DESC` sort matching the server's default page-mode\n * ordering for both list endpoints. Rows missing `startedAt` sink to the\n * bottom (compared as time 0). */\nfunction sortRowsByStartedAtDesc<T extends { startedAt?: unknown }>(rows: T[]): T[] {\n return [...rows].sort((a, b) => {\n const aTime = a.startedAt ? new Date(a.startedAt as string | number | Date).getTime() : 0;\n const bTime = b.startedAt ? new Date(b.startedAt as string | number | Date).getTime() : 0;\n return bTime - aTime;\n });\n}\n\n/** Prepends delta-mode rows into page 0 and advances its deltaCursor.\n * Delta responses come back in server cursor order (insertion order into\n * the per-store delta index), not the `startedAt DESC` order page-mode\n * uses — so we re-sort the merged page 0 to match what the user sees on\n * a fresh page-mode load. Cross-page duplication is handled later by\n * selectUniqueTraces. */\nexport function mergeDeltaIntoPage0(\n old: InfiniteData<TracesPageResponse> | undefined,\n delta: ListTracesLightResponse | ListBranchesResponse,\n listMode: TraceListMode,\n): InfiniteData<TracesPageResponse> | undefined {\n if (!old || old.pages.length === 0) return old;\n const [firstPage, ...rest] = old.pages;\n if (!firstPage) return old;\n const nextCursor = delta.deltaCursor ?? firstPage.deltaCursor;\n\n let updatedFirst: TracesPageResponse;\n if (listMode === 'branches' && 'branches' in firstPage) {\n const newRows = (delta as ListBranchesResponse).branches ?? [];\n updatedFirst = {\n ...firstPage,\n branches: sortRowsByStartedAtDesc([...newRows, ...(firstPage.branches ?? [])]),\n deltaCursor: nextCursor,\n };\n } else if ('spans' in firstPage) {\n const newRows = (delta as ListTracesLightResponse).spans ?? [];\n updatedFirst = {\n ...firstPage,\n spans: sortRowsByStartedAtDesc([...newRows, ...(firstPage.spans ?? [])]),\n deltaCursor: nextCursor,\n };\n } else {\n return old;\n }\n\n return { ...old, pages: [updatedFirst, ...rest] };\n}\n\nexport interface UseTracesArgs extends TracesFilters {\n /** Optional overrides for the live-tail polling tunables. Any omitted fields fall back to the\n * built-in defaults; pass only what you want to change. */\n polling?: TracesPollingConfig;\n}\n\ninterface UseTracesReturn {\n data: { spans: LightSpanRecord[]; deltaCursor: string | undefined } | undefined;\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n fetchNextPage: () => void;\n isLoading: boolean;\n isError: boolean;\n error: Error | null;\n setEndOfListElement: (node: HTMLDivElement | null) => void;\n isRefetching: boolean;\n autoRefetch: boolean;\n setAutoRefetch: (v: boolean) => void;\n recentlyAddedKeys: Set<string>;\n dataUpdatedAt: number;\n errorUpdatedAt: number;\n isFetching: boolean;\n isSuccess: boolean;\n fetchStatus: 'idle' | 'fetching' | 'paused';\n refetch: () => void;\n}\n\nexport const useTraces: (args: UseTracesArgs) => UseTracesReturn = ({\n filters,\n listMode = 'traces',\n polling = {},\n}: UseTracesArgs) => {\n const {\n deltaPollIntervalMs = DEFAULT_POLLING_CONFIG.deltaPollIntervalMs,\n deltaChaseIntervalMs = DEFAULT_POLLING_CONFIG.deltaChaseIntervalMs,\n deltaLimit = DEFAULT_POLLING_CONFIG.deltaLimit,\n pageModeRefetchIntervalMs = DEFAULT_POLLING_CONFIG.pageModeRefetchIntervalMs,\n page0StatusRefreshIntervalMs = DEFAULT_POLLING_CONFIG.page0StatusRefreshIntervalMs,\n idleGuardThresholdMs = DEFAULT_POLLING_CONFIG.idleGuardThresholdMs,\n minRefetchSpinMs = DEFAULT_POLLING_CONFIG.minRefetchSpinMs,\n deltaHighlightMs = DEFAULT_POLLING_CONFIG.deltaHighlightMs,\n } = polling;\n const client = useMastraClient();\n const queryClient = useQueryClient();\n const { inView: isEndOfListInView, setRef: setEndOfListElement } = useInView();\n\n const [deltaSupport, setDeltaSupport] = useState<DeltaSupport>(() => deltaSupportByClient.get(client) ?? 'unknown');\n const deltaUnsupported = deltaSupport === 'unsupported';\n\n // When false, all automatic polling stops: delta polls, status refresh,\n // idle-guard resets, and the page-mode fallback interval. Manual `resync`\n // still works. Session-scoped (no persistence across mounts).\n const [autoRefetch, setAutoRefetch] = useState(true);\n\n // Keys (`traceId:spanId`) of rows that arrived via the most recent delta\n // poll(s). The list view reads this to apply a temporary highlight class\n // for deltaHighlightMs, then they auto-expire. Resets on filter/listMode\n // change since a new query key spawns a fresh list.\n const [recentlyAddedKeys, setRecentlyAddedKeys] = useState<Set<string>>(() => new Set());\n useEffect(() => {\n setRecentlyAddedKeys(new Set());\n }, [listMode, filters]);\n\n const tracesQueryKey = ['traces', listMode, filters] as const;\n\n const query = useInfiniteQuery({\n queryKey: tracesQueryKey,\n queryFn: ({ pageParam }) =>\n fetchTracesFn({\n client,\n page: pageParam,\n perPage: TRACES_PER_PAGE,\n filters,\n listMode,\n }),\n initialPageParam: 0,\n getNextPageParam: getTracesNextPageParam,\n select: selectUniqueTraces,\n retry: false,\n // Disable polling on 403 to prevent flickering.\n // Fall back to page-mode polling only when delta isn't running.\n refetchInterval: q => {\n if (is403ForbiddenError(q.state.error)) return false;\n if (!autoRefetch) return false;\n return deltaUnsupported ? pageModeRefetchIntervalMs : false;\n },\n });\n\n const cursor = query.data?.deltaCursor;\n\n // If page 0 came back without a deltaCursor, the server/store doesn't\n // support delta polling. Pin the client to 'unsupported' so the infinite\n // query resumes page-mode polling and we stop probing.\n useEffect(() => {\n if (query.isSuccess && cursor === undefined && deltaSupport === 'unknown') {\n deltaSupportByClient.set(client, 'unsupported');\n setDeltaSupport('unsupported');\n }\n }, [query.isSuccess, cursor, deltaSupport, client]);\n\n const deltaQuery = useQuery({\n queryKey: ['traces-delta', listMode, filters] as const,\n queryFn: () => {\n const current = queryClient.getQueryData<InfiniteData<TracesPageResponse>>(tracesQueryKey)?.pages[0]?.deltaCursor;\n if (!current) return null;\n return fetchTracesFn({\n client,\n mode: 'delta',\n after: current,\n limit: deltaLimit,\n filters,\n listMode,\n });\n },\n enabled: !!cursor && !deltaUnsupported && autoRefetch,\n retry: false,\n refetchInterval: q => {\n if (q.state.error) return false;\n const data = q.state.data as ListTracesLightResponse | ListBranchesResponse | null | undefined;\n if (data?.delta?.hasMore) return deltaChaseIntervalMs;\n return deltaPollIntervalMs;\n },\n });\n\n // Merge new delta rows into the infinite-query cache. Also captures the\n // arrived keys into `recentlyAddedKeys` for deltaHighlightMs so the UI\n // can briefly distinguish them. The cleanup timer is intentionally NOT\n // tied to the effect lifecycle — if a newer delta arrives first, we still\n // want each prior batch to expire on its own schedule.\n useEffect(() => {\n const result = deltaQuery.data;\n if (!result) return;\n queryClient.setQueryData<InfiniteData<TracesPageResponse>>(tracesQueryKey, old =>\n mergeDeltaIntoPage0(old, result, listMode),\n );\n\n const newRows: RowIdentity[] =\n listMode === 'branches' && 'branches' in result\n ? (result.branches ?? [])\n : 'spans' in result\n ? (result.spans ?? [])\n : [];\n if (newRows.length === 0) return;\n const keys = newRows.map(r => `${r.traceId}:${r.spanId ?? ''}`);\n setRecentlyAddedKeys(prev => {\n const next = new Set(prev);\n for (const k of keys) next.add(k);\n return next;\n });\n setTimeout(() => {\n setRecentlyAddedKeys(prev => {\n const next = new Set(prev);\n for (const k of keys) next.delete(k);\n return next;\n });\n }, deltaHighlightMs);\n // tracesQueryKey is a new array each render but its contents drive cache keying;\n // depending on its members (listMode/filters) is enough.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [deltaQuery.data, queryClient, listMode, filters, deltaHighlightMs]);\n\n // A 501 means the server is too old (or store doesn't support delta) — pin\n // 'unsupported' so the delta query disables and page-mode polling resumes.\n useEffect(() => {\n if (isHttp501(deltaQuery.error)) {\n deltaSupportByClient.set(client, 'unsupported');\n setDeltaSupport('unsupported');\n }\n }, [deltaQuery.error, client]);\n\n // Periodic page-0 refresh to surface status updates on visible rows\n // (running → success/error). Only runs while delta is the primary polling\n // mechanism; otherwise the infinite query's own refetchInterval covers it.\n const statusRefreshQuery = useQuery({\n queryKey: ['traces-status-refresh', listMode, filters] as const,\n queryFn: () =>\n fetchTracesFn({\n client,\n page: 0,\n perPage: TRACES_PER_PAGE,\n filters,\n listMode,\n }),\n enabled: !!cursor && !deltaUnsupported && autoRefetch,\n refetchInterval: page0StatusRefreshIntervalMs,\n refetchOnMount: false,\n retry: false,\n });\n\n useEffect(() => {\n const result = statusRefreshQuery.data;\n if (!result) return;\n queryClient.setQueryData<InfiniteData<TracesPageResponse>>(tracesQueryKey, old =>\n refreshPage0Rows(old, result, listMode),\n );\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [statusRefreshQuery.data, queryClient, listMode, filters]);\n\n // Idle guard: when the tab returns from being hidden, check how long it's\n // been since the last successful poll. If past the threshold, reset the\n // infinite query so page 0 refetches and the cursor is re-seeded.\n const lastSuccessAtRef = useRef(0);\n useEffect(() => {\n const ts = Math.max(query.dataUpdatedAt, deltaQuery.dataUpdatedAt, statusRefreshQuery.dataUpdatedAt);\n if (ts > lastSuccessAtRef.current) {\n lastSuccessAtRef.current = ts;\n }\n }, [query.dataUpdatedAt, deltaQuery.dataUpdatedAt, statusRefreshQuery.dataUpdatedAt]);\n\n // Track autoRefetch in a ref so the visibility listener (attached once)\n // reads the current value without needing to re-attach.\n const autoRefetchRef = useRef(autoRefetch);\n useEffect(() => {\n autoRefetchRef.current = autoRefetch;\n }, [autoRefetch]);\n\n useEffect(() => {\n const handler = () => {\n if (document.visibilityState !== 'visible') return;\n if (!autoRefetchRef.current) return;\n if (!shouldResetAfterIdle(lastSuccessAtRef.current, Date.now(), idleGuardThresholdMs)) return;\n void queryClient.resetQueries({ queryKey: ['traces', listMode, filters] });\n };\n document.addEventListener('visibilitychange', handler);\n return () => document.removeEventListener('visibilitychange', handler);\n }, [queryClient, listMode, filters, idleGuardThresholdMs]);\n\n const { hasNextPage, isFetchingNextPage, fetchNextPage } = query;\n\n useEffect(() => {\n if (isEndOfListInView && hasNextPage && !isFetchingNextPage) {\n void fetchNextPage();\n }\n }, [isEndOfListInView, hasNextPage, isFetchingNextPage, fetchNextPage]);\n\n // `isRefetching` aggregates every active fetch — page-mode (initial, next\n // page, manual resync, idle reset), the 60s page-0 status refresh, AND the\n // delta polls — so a spinner bound to it acts as a heartbeat indicator.\n //\n // Short polls (e.g. delta on localhost) can resolve in tens of ms — too\n // brief for `animate-spin` (1s/rotation) to register visually, and quick\n // enough that React may batch the start/finish transitions so we never\n // even observe `isFetching: true`. So we also watch every query's\n // data/errorUpdatedAt; any advance pulses `isRefetching` true for at least\n // minRefetchSpinMs, which is ~144° of rotation — clearly perceptible.\n const isFetchingAny = query.isFetching || statusRefreshQuery.isFetching || deltaQuery.isFetching;\n const lastActivityAt = Math.max(\n query.dataUpdatedAt,\n query.errorUpdatedAt,\n deltaQuery.dataUpdatedAt,\n deltaQuery.errorUpdatedAt,\n statusRefreshQuery.dataUpdatedAt,\n statusRefreshQuery.errorUpdatedAt,\n );\n const [pulse, setPulse] = useState(false);\n const prevActivityAtRef = useRef(lastActivityAt);\n useEffect(() => {\n if (lastActivityAt > prevActivityAtRef.current) {\n prevActivityAtRef.current = lastActivityAt;\n setPulse(true);\n const t = setTimeout(() => setPulse(false), minRefetchSpinMs);\n return () => clearTimeout(t);\n }\n }, [lastActivityAt, minRefetchSpinMs]);\n const isRefetching = isFetchingAny || pulse;\n\n return {\n ...query,\n setEndOfListElement,\n isRefetching,\n autoRefetch,\n setAutoRefetch,\n recentlyAddedKeys,\n };\n};\n"],"names":["useMastraClient","useQueryClient","useInView","useState","useEffect","useInfiniteQuery","is403ForbiddenError","useQuery","useRef"],"mappings":"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"}
|
|
@@ -5,6 +5,9 @@ import { useInView } from '../../../hooks/use-in-view.es.js';
|
|
|
5
5
|
import { is403ForbiddenError } from '../../../utils/query-utils.es.js';
|
|
6
6
|
|
|
7
7
|
const deltaSupportByClient = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
const lightListSupportByClient = /* @__PURE__ */ new WeakMap();
|
|
9
|
+
const lightListFirstServerErrorAtByClient = /* @__PURE__ */ new WeakMap();
|
|
10
|
+
const LIGHT_LIST_SERVER_ERROR_PIN_AFTER_MS = 1e4;
|
|
8
11
|
const DEFAULT_POLLING_CONFIG = {
|
|
9
12
|
deltaPollIntervalMs: 5e3,
|
|
10
13
|
deltaChaseIntervalMs: 100,
|
|
@@ -22,13 +25,49 @@ function shouldResetAfterIdle(lastSuccessAt, now, thresholdMs) {
|
|
|
22
25
|
function isHttp501(error) {
|
|
23
26
|
return error?.status === 501;
|
|
24
27
|
}
|
|
28
|
+
function lightListFallbackStatus(error) {
|
|
29
|
+
const status = error?.status;
|
|
30
|
+
return status === 404 || status === 500 ? status : void 0;
|
|
31
|
+
}
|
|
32
|
+
function shouldPinLightList(status, firstServerErrorAt, now) {
|
|
33
|
+
if (status === 404) return true;
|
|
34
|
+
if (status !== 500) return false;
|
|
35
|
+
if (firstServerErrorAt === void 0) return false;
|
|
36
|
+
return now - firstServerErrorAt >= LIGHT_LIST_SERVER_ERROR_PIN_AFTER_MS;
|
|
37
|
+
}
|
|
25
38
|
const fetchTracesFn = async (args) => {
|
|
26
39
|
const { client, filters, listMode = "traces" } = args;
|
|
27
40
|
const params = args.mode === "delta" ? { mode: "delta", after: args.after, limit: args.limit, filters } : { pagination: { page: args.page, perPage: args.perPage }, filters };
|
|
28
41
|
if (listMode === "branches") {
|
|
29
42
|
return client.listBranches(params);
|
|
30
43
|
}
|
|
31
|
-
|
|
44
|
+
if (lightListSupportByClient.get(client) === "unsupported") {
|
|
45
|
+
return client.listTraces(params);
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const response = await client.listTracesLight(params);
|
|
49
|
+
if (args.mode !== "delta") {
|
|
50
|
+
const spans = response.spans ?? [];
|
|
51
|
+
if (spans.length > 0 && spans.every((span) => span.status === void 0)) {
|
|
52
|
+
lightListSupportByClient.set(client, "unsupported");
|
|
53
|
+
return client.listTraces(params);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
lightListFirstServerErrorAtByClient.delete(client);
|
|
57
|
+
return response;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
const status = lightListFallbackStatus(error);
|
|
60
|
+
if (status === void 0) throw error;
|
|
61
|
+
const now = Date.now();
|
|
62
|
+
const firstServerErrorAt = lightListFirstServerErrorAtByClient.get(client);
|
|
63
|
+
if (status === 500 && firstServerErrorAt === void 0) {
|
|
64
|
+
lightListFirstServerErrorAtByClient.set(client, now);
|
|
65
|
+
}
|
|
66
|
+
if (shouldPinLightList(status, firstServerErrorAt, now)) {
|
|
67
|
+
lightListSupportByClient.set(client, "unsupported");
|
|
68
|
+
}
|
|
69
|
+
return client.listTraces(params);
|
|
70
|
+
}
|
|
32
71
|
};
|
|
33
72
|
const TRACES_PER_PAGE = 25;
|
|
34
73
|
function getTracesNextPageParam(lastPage, _allPages, lastPageParam) {
|
|
@@ -51,29 +90,31 @@ function selectUniqueTraces(data) {
|
|
|
51
90
|
});
|
|
52
91
|
return { spans, deltaCursor: data.pages[0]?.deltaCursor };
|
|
53
92
|
}
|
|
93
|
+
function rowKey(row) {
|
|
94
|
+
return `${row.traceId}:${row.spanId}`;
|
|
95
|
+
}
|
|
96
|
+
function indexRowsByKey(rows) {
|
|
97
|
+
return new Map(rows.map((row) => [rowKey(row), row]));
|
|
98
|
+
}
|
|
54
99
|
function refreshPage0Rows(old, refreshed, listMode) {
|
|
55
100
|
if (!old || old.pages.length === 0) return old;
|
|
56
101
|
const [firstPage, ...rest] = old.pages;
|
|
57
102
|
if (!firstPage) return old;
|
|
58
|
-
const refreshedRows = listMode === "branches" && "branches" in refreshed ? refreshed.branches ?? [] : "spans" in refreshed ? refreshed.spans ?? [] : [];
|
|
59
|
-
if (refreshedRows.length === 0) return old;
|
|
60
|
-
const refreshedByKey = /* @__PURE__ */ new Map();
|
|
61
|
-
for (const row of refreshedRows) {
|
|
62
|
-
refreshedByKey.set(`${row.traceId}:${row.spanId}`, row);
|
|
63
|
-
}
|
|
64
103
|
let updatedFirst;
|
|
65
|
-
if (listMode === "branches" && "branches" in firstPage) {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
104
|
+
if (listMode === "branches" && "branches" in firstPage && "branches" in refreshed) {
|
|
105
|
+
const refreshedByKey = indexRowsByKey(refreshed.branches ?? []);
|
|
106
|
+
if (refreshedByKey.size === 0) return old;
|
|
107
|
+
updatedFirst = {
|
|
108
|
+
...firstPage,
|
|
109
|
+
branches: (firstPage.branches ?? []).map((existing) => refreshedByKey.get(rowKey(existing)) ?? existing)
|
|
110
|
+
};
|
|
111
|
+
} else if ("spans" in firstPage && "spans" in refreshed) {
|
|
112
|
+
const refreshedByKey = indexRowsByKey(refreshed.spans ?? []);
|
|
113
|
+
if (refreshedByKey.size === 0) return old;
|
|
114
|
+
updatedFirst = {
|
|
115
|
+
...firstPage,
|
|
116
|
+
spans: (firstPage.spans ?? []).map((existing) => refreshedByKey.get(rowKey(existing)) ?? existing)
|
|
117
|
+
};
|
|
77
118
|
} else {
|
|
78
119
|
return old;
|
|
79
120
|
}
|
|
@@ -296,5 +337,5 @@ const useTraces = ({
|
|
|
296
337
|
};
|
|
297
338
|
};
|
|
298
339
|
|
|
299
|
-
export { TRACES_PER_PAGE, getTracesNextPageParam, mergeDeltaIntoPage0, refreshPage0Rows, selectUniqueTraces, shouldResetAfterIdle, useTraces };
|
|
340
|
+
export { TRACES_PER_PAGE, getTracesNextPageParam, mergeDeltaIntoPage0, refreshPage0Rows, selectUniqueTraces, shouldPinLightList, shouldResetAfterIdle, useTraces };
|
|
300
341
|
//# sourceMappingURL=use-traces.es.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-traces.es.js","sources":["../../../../src/domains/traces/hooks/use-traces.tsx"],"sourcesContent":["import type {\n ListBranchesArgs,\n ListBranchesResponse,\n ListTracesArgs,\n ListTracesResponse,\n TraceSpan,\n} from '@mastra/core/storage';\nimport { useMastraClient } from '@mastra/react';\nimport type { InfiniteData } from '@tanstack/react-query';\nimport { useInfiniteQuery, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { useEffect, useRef, useState } from 'react';\nimport type { TraceListMode } from '../trace-filters';\nimport { useInView } from '@/hooks/use-in-view';\nimport { is403ForbiddenError } from '@/lib/query-utils';\n\n/**\n * Per-MastraClient delta-polling support cache. Sticks once we observe a 501\n * or a page-0 response without `deltaCursor` (legacy server or store without\n * delta capability), so we don't re-probe on every mount.\n */\ntype DeltaSupport = 'unknown' | 'unsupported';\nconst deltaSupportByClient = new WeakMap<ReturnType<typeof useMastraClient>, DeltaSupport>();\n\n/** Tunables for live-tail polling. All fields optional — defaults below.\n * Platform consumers override individual fields to throttle traffic or\n * reshape the freshness/visibility trade-offs. */\nexport interface TracesPollingConfig {\n /** Interval between delta polls. Default 5_000ms. */\n deltaPollIntervalMs?: number;\n /** Refetch delay when the server signals `delta.hasMore` (chase remaining updates immediately). Default 100ms. */\n deltaChaseIntervalMs?: number;\n /** Max rows per delta poll. Server caps at 100. Default 100. */\n deltaLimit?: number;\n /** Page-mode refetch interval used when the configured store does not support delta polling. Default 10_000ms. */\n pageModeRefetchIntervalMs?: number;\n /** Periodic page-0 refetch interval while delta is active. Delta only delivers new rows, so this\n * is how we surface status flips (running → success/error) on rows already visible. Default 60_000ms. */\n page0StatusRefreshIntervalMs?: number;\n /** When the tab returns from being hidden and the last successful poll was older than this, the\n * cursor is treated as stale (ClickHouse delta tables TTL after 2 days; other stores may be more\n * aggressive). The infinite query is reset so page 0 refetches and delta resumes from a fresh\n * cursor. Default 900_000ms (15 min). */\n idleGuardThresholdMs?: number;\n /** Minimum visible duration for `isRefetching` after any query completes. On localhost a delta\n * poll can resolve in tens of ms — too brief for the spinner's 1s rotation to register visually,\n * and quick enough that React may batch the start/finish transitions. Pulsing for this duration\n * on completion (~144° of rotation at 400ms) makes each poll perceptible as a heartbeat.\n * Default 400ms. */\n minRefetchSpinMs?: number;\n /** Window during which a delta-arrived row's key stays in `recentlyAddedKeys` so the list view\n * can apply its `animate-row-highlight` class. Should match the CSS animation duration\n * (currently 1_000ms — see `index.css`). Default 1_000ms. */\n deltaHighlightMs?: number;\n}\n\nconst DEFAULT_POLLING_CONFIG: Required<TracesPollingConfig> = {\n deltaPollIntervalMs: 5_000,\n deltaChaseIntervalMs: 100,\n deltaLimit: 100,\n pageModeRefetchIntervalMs: 10_000,\n page0StatusRefreshIntervalMs: 60_000,\n idleGuardThresholdMs: 15 * 60_000,\n minRefetchSpinMs: 400,\n deltaHighlightMs: 1_000,\n};\n\n/** Returns true when the cursor should be considered stale and re-seeded.\n * Exported for testing — pure function of timestamps. */\nexport function shouldResetAfterIdle(lastSuccessAt: number, now: number, thresholdMs: number): boolean {\n if (lastSuccessAt === 0) return false;\n return now - lastSuccessAt > thresholdMs;\n}\n\nfunction isHttp501(error: unknown): boolean {\n return (error as { status?: number } | null)?.status === 501;\n}\n\ntype FetchTracesFnArgs = TracesFilters & {\n client: ReturnType<typeof useMastraClient>;\n} & ({ mode: 'delta'; after?: string; limit?: number } | { mode?: 'page'; page: number; perPage: number });\n\nconst fetchTracesFn = async (args: FetchTracesFnArgs) => {\n const { client, filters, listMode = 'traces' } = args;\n const params =\n args.mode === 'delta'\n ? { mode: 'delta' as const, after: args.after, limit: args.limit, filters }\n : { pagination: { page: args.page, perPage: args.perPage }, filters };\n\n if (listMode === 'branches') {\n return client.listBranches(params as ListBranchesArgs);\n }\n\n return client.listTraces(params as ListTracesArgs);\n};\n\nexport const TRACES_PER_PAGE = 25;\n\nexport interface TracesFilters {\n filters?: ListTracesArgs['filters'] | ListBranchesArgs['filters'];\n listMode?: TraceListMode;\n}\n\n/** Returns the next page number if the server indicates more pages are available. */\nexport function getTracesNextPageParam(\n lastPage: ListTracesResponse | ListBranchesResponse | undefined,\n _allPages: unknown,\n lastPageParam: number,\n) {\n if (lastPage?.pagination?.hasMore) {\n return lastPageParam + 1;\n }\n return undefined;\n}\n\ntype TracesPageResponse = ListTracesResponse | ListBranchesResponse;\n\nfunction getPageSpans(page: TracesPageResponse) {\n if ('branches' in page) return page.branches ?? [];\n return page.spans ?? [];\n}\n\n/** Deduplicates trace/branch rows by traceId + spanId across all loaded pages.\n * Also surfaces page 0's deltaCursor so the live-tail query can read it reactively. */\nexport function selectUniqueTraces(data: { pages: TracesPageResponse[] }): {\n spans: TraceSpan[];\n deltaCursor: string | undefined;\n} {\n const seen = new Set<string>();\n const spans = data.pages\n .flatMap(page => getPageSpans(page))\n .filter(span => {\n const key = `${span.traceId}:${span.spanId}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n\n return { spans, deltaCursor: data.pages[0]?.deltaCursor };\n}\n\n/** Replaces existing page-0 rows in place (keyed by traceId:spanId) with\n * refreshed copies from the server. Rows the server doesn't return are kept\n * as-is so delta-accumulated rows that have aged off the server's page 0\n * aren't dropped from the UI. New rows aren't added here — delta polling\n * delivers those. */\nexport function refreshPage0Rows(\n old: InfiniteData<TracesPageResponse> | undefined,\n refreshed: ListTracesResponse | ListBranchesResponse,\n listMode: TraceListMode,\n): InfiniteData<TracesPageResponse> | undefined {\n if (!old || old.pages.length === 0) return old;\n const [firstPage, ...rest] = old.pages;\n if (!firstPage) return old;\n\n const refreshedRows =\n listMode === 'branches' && 'branches' in refreshed\n ? (refreshed.branches ?? [])\n : 'spans' in refreshed\n ? (refreshed.spans ?? [])\n : [];\n\n if (refreshedRows.length === 0) return old;\n\n const refreshedByKey = new Map<string, (typeof refreshedRows)[number]>();\n for (const row of refreshedRows) {\n refreshedByKey.set(`${row.traceId}:${row.spanId}`, row);\n }\n\n let updatedFirst: TracesPageResponse;\n if (listMode === 'branches' && 'branches' in firstPage) {\n const updated = (firstPage.branches ?? []).map(existing => {\n const fresh = refreshedByKey.get(`${existing.traceId}:${existing.spanId}`);\n return fresh ?? existing;\n });\n updatedFirst = { ...firstPage, branches: updated };\n } else if ('spans' in firstPage) {\n const updated = (firstPage.spans ?? []).map(existing => {\n const fresh = refreshedByKey.get(`${existing.traceId}:${existing.spanId}`);\n return fresh ?? existing;\n });\n updatedFirst = { ...firstPage, spans: updated };\n } else {\n return old;\n }\n\n return { ...old, pages: [updatedFirst, ...rest] };\n}\n\n/** Stable `startedAt DESC` sort matching the server's default page-mode\n * ordering for both list endpoints. Rows missing `startedAt` sink to the\n * bottom (compared as time 0). */\nfunction sortRowsByStartedAtDesc<T extends { startedAt?: unknown }>(rows: T[]): T[] {\n return [...rows].sort((a, b) => {\n const aTime = a.startedAt ? new Date(a.startedAt as string | number | Date).getTime() : 0;\n const bTime = b.startedAt ? new Date(b.startedAt as string | number | Date).getTime() : 0;\n return bTime - aTime;\n });\n}\n\n/** Prepends delta-mode rows into page 0 and advances its deltaCursor.\n * Delta responses come back in server cursor order (insertion order into\n * the per-store delta index), not the `startedAt DESC` order page-mode\n * uses — so we re-sort the merged page 0 to match what the user sees on\n * a fresh page-mode load. Cross-page duplication is handled later by\n * selectUniqueTraces. */\nexport function mergeDeltaIntoPage0(\n old: InfiniteData<TracesPageResponse> | undefined,\n delta: ListTracesResponse | ListBranchesResponse,\n listMode: TraceListMode,\n): InfiniteData<TracesPageResponse> | undefined {\n if (!old || old.pages.length === 0) return old;\n const [firstPage, ...rest] = old.pages;\n if (!firstPage) return old;\n const nextCursor = delta.deltaCursor ?? firstPage.deltaCursor;\n\n let updatedFirst: TracesPageResponse;\n if (listMode === 'branches' && 'branches' in firstPage) {\n const newRows = (delta as ListBranchesResponse).branches ?? [];\n updatedFirst = {\n ...firstPage,\n branches: sortRowsByStartedAtDesc([...newRows, ...(firstPage.branches ?? [])]),\n deltaCursor: nextCursor,\n };\n } else if ('spans' in firstPage) {\n const newRows = (delta as ListTracesResponse).spans ?? [];\n updatedFirst = {\n ...firstPage,\n spans: sortRowsByStartedAtDesc([...newRows, ...(firstPage.spans ?? [])]),\n deltaCursor: nextCursor,\n };\n } else {\n return old;\n }\n\n return { ...old, pages: [updatedFirst, ...rest] };\n}\n\nexport interface UseTracesArgs extends TracesFilters {\n /** Optional overrides for the live-tail polling tunables. Any omitted fields fall back to the\n * built-in defaults; pass only what you want to change. */\n polling?: TracesPollingConfig;\n}\n\ninterface UseTracesReturn {\n data: { spans: TraceSpan[]; deltaCursor: string | undefined } | undefined;\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n fetchNextPage: () => void;\n isLoading: boolean;\n isError: boolean;\n error: Error | null;\n setEndOfListElement: (node: HTMLDivElement | null) => void;\n isRefetching: boolean;\n autoRefetch: boolean;\n setAutoRefetch: (v: boolean) => void;\n recentlyAddedKeys: Set<string>;\n dataUpdatedAt: number;\n errorUpdatedAt: number;\n isFetching: boolean;\n isSuccess: boolean;\n fetchStatus: 'idle' | 'fetching' | 'paused';\n refetch: () => void;\n}\n\nexport const useTraces: (args: UseTracesArgs) => UseTracesReturn = ({\n filters,\n listMode = 'traces',\n polling = {},\n}: UseTracesArgs) => {\n const {\n deltaPollIntervalMs = DEFAULT_POLLING_CONFIG.deltaPollIntervalMs,\n deltaChaseIntervalMs = DEFAULT_POLLING_CONFIG.deltaChaseIntervalMs,\n deltaLimit = DEFAULT_POLLING_CONFIG.deltaLimit,\n pageModeRefetchIntervalMs = DEFAULT_POLLING_CONFIG.pageModeRefetchIntervalMs,\n page0StatusRefreshIntervalMs = DEFAULT_POLLING_CONFIG.page0StatusRefreshIntervalMs,\n idleGuardThresholdMs = DEFAULT_POLLING_CONFIG.idleGuardThresholdMs,\n minRefetchSpinMs = DEFAULT_POLLING_CONFIG.minRefetchSpinMs,\n deltaHighlightMs = DEFAULT_POLLING_CONFIG.deltaHighlightMs,\n } = polling;\n const client = useMastraClient();\n const queryClient = useQueryClient();\n const { inView: isEndOfListInView, setRef: setEndOfListElement } = useInView();\n\n const [deltaSupport, setDeltaSupport] = useState<DeltaSupport>(() => deltaSupportByClient.get(client) ?? 'unknown');\n const deltaUnsupported = deltaSupport === 'unsupported';\n\n // When false, all automatic polling stops: delta polls, status refresh,\n // idle-guard resets, and the page-mode fallback interval. Manual `resync`\n // still works. Session-scoped (no persistence across mounts).\n const [autoRefetch, setAutoRefetch] = useState(true);\n\n // Keys (`traceId:spanId`) of rows that arrived via the most recent delta\n // poll(s). The list view reads this to apply a temporary highlight class\n // for deltaHighlightMs, then they auto-expire. Resets on filter/listMode\n // change since a new query key spawns a fresh list.\n const [recentlyAddedKeys, setRecentlyAddedKeys] = useState<Set<string>>(() => new Set());\n useEffect(() => {\n setRecentlyAddedKeys(new Set());\n }, [listMode, filters]);\n\n const tracesQueryKey = ['traces', listMode, filters] as const;\n\n const query = useInfiniteQuery({\n queryKey: tracesQueryKey,\n queryFn: ({ pageParam }) =>\n fetchTracesFn({\n client,\n page: pageParam,\n perPage: TRACES_PER_PAGE,\n filters,\n listMode,\n }),\n initialPageParam: 0,\n getNextPageParam: getTracesNextPageParam,\n select: selectUniqueTraces,\n retry: false,\n // Disable polling on 403 to prevent flickering.\n // Fall back to page-mode polling only when delta isn't running.\n refetchInterval: q => {\n if (is403ForbiddenError(q.state.error)) return false;\n if (!autoRefetch) return false;\n return deltaUnsupported ? pageModeRefetchIntervalMs : false;\n },\n });\n\n const cursor = query.data?.deltaCursor;\n\n // If page 0 came back without a deltaCursor, the server/store doesn't\n // support delta polling. Pin the client to 'unsupported' so the infinite\n // query resumes page-mode polling and we stop probing.\n useEffect(() => {\n if (query.isSuccess && cursor === undefined && deltaSupport === 'unknown') {\n deltaSupportByClient.set(client, 'unsupported');\n setDeltaSupport('unsupported');\n }\n }, [query.isSuccess, cursor, deltaSupport, client]);\n\n const deltaQuery = useQuery({\n queryKey: ['traces-delta', listMode, filters] as const,\n queryFn: () => {\n const current = queryClient.getQueryData<InfiniteData<TracesPageResponse>>(tracesQueryKey)?.pages[0]?.deltaCursor;\n if (!current) return null;\n return fetchTracesFn({\n client,\n mode: 'delta',\n after: current,\n limit: deltaLimit,\n filters,\n listMode,\n });\n },\n enabled: !!cursor && !deltaUnsupported && autoRefetch,\n retry: false,\n refetchInterval: q => {\n if (q.state.error) return false;\n const data = q.state.data as ListTracesResponse | ListBranchesResponse | null | undefined;\n if (data?.delta?.hasMore) return deltaChaseIntervalMs;\n return deltaPollIntervalMs;\n },\n });\n\n // Merge new delta rows into the infinite-query cache. Also captures the\n // arrived keys into `recentlyAddedKeys` for deltaHighlightMs so the UI\n // can briefly distinguish them. The cleanup timer is intentionally NOT\n // tied to the effect lifecycle — if a newer delta arrives first, we still\n // want each prior batch to expire on its own schedule.\n useEffect(() => {\n const result = deltaQuery.data;\n if (!result) return;\n queryClient.setQueryData<InfiniteData<TracesPageResponse>>(tracesQueryKey, old =>\n mergeDeltaIntoPage0(old, result, listMode),\n );\n\n const newRows =\n listMode === 'branches' && 'branches' in result\n ? (result.branches ?? [])\n : 'spans' in result\n ? (result.spans ?? [])\n : [];\n if (newRows.length === 0) return;\n const keys = newRows.map(r => `${r.traceId}:${r.spanId ?? ''}`);\n setRecentlyAddedKeys(prev => {\n const next = new Set(prev);\n for (const k of keys) next.add(k);\n return next;\n });\n setTimeout(() => {\n setRecentlyAddedKeys(prev => {\n const next = new Set(prev);\n for (const k of keys) next.delete(k);\n return next;\n });\n }, deltaHighlightMs);\n // tracesQueryKey is a new array each render but its contents drive cache keying;\n // depending on its members (listMode/filters) is enough.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [deltaQuery.data, queryClient, listMode, filters, deltaHighlightMs]);\n\n // A 501 means the server is too old (or store doesn't support delta) — pin\n // 'unsupported' so the delta query disables and page-mode polling resumes.\n useEffect(() => {\n if (isHttp501(deltaQuery.error)) {\n deltaSupportByClient.set(client, 'unsupported');\n setDeltaSupport('unsupported');\n }\n }, [deltaQuery.error, client]);\n\n // Periodic page-0 refresh to surface status updates on visible rows\n // (running → success/error). Only runs while delta is the primary polling\n // mechanism; otherwise the infinite query's own refetchInterval covers it.\n const statusRefreshQuery = useQuery({\n queryKey: ['traces-status-refresh', listMode, filters] as const,\n queryFn: () =>\n fetchTracesFn({\n client,\n page: 0,\n perPage: TRACES_PER_PAGE,\n filters,\n listMode,\n }),\n enabled: !!cursor && !deltaUnsupported && autoRefetch,\n refetchInterval: page0StatusRefreshIntervalMs,\n refetchOnMount: false,\n retry: false,\n });\n\n useEffect(() => {\n const result = statusRefreshQuery.data;\n if (!result) return;\n queryClient.setQueryData<InfiniteData<TracesPageResponse>>(tracesQueryKey, old =>\n refreshPage0Rows(old, result, listMode),\n );\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [statusRefreshQuery.data, queryClient, listMode, filters]);\n\n // Idle guard: when the tab returns from being hidden, check how long it's\n // been since the last successful poll. If past the threshold, reset the\n // infinite query so page 0 refetches and the cursor is re-seeded.\n const lastSuccessAtRef = useRef(0);\n useEffect(() => {\n const ts = Math.max(query.dataUpdatedAt, deltaQuery.dataUpdatedAt, statusRefreshQuery.dataUpdatedAt);\n if (ts > lastSuccessAtRef.current) {\n lastSuccessAtRef.current = ts;\n }\n }, [query.dataUpdatedAt, deltaQuery.dataUpdatedAt, statusRefreshQuery.dataUpdatedAt]);\n\n // Track autoRefetch in a ref so the visibility listener (attached once)\n // reads the current value without needing to re-attach.\n const autoRefetchRef = useRef(autoRefetch);\n useEffect(() => {\n autoRefetchRef.current = autoRefetch;\n }, [autoRefetch]);\n\n useEffect(() => {\n const handler = () => {\n if (document.visibilityState !== 'visible') return;\n if (!autoRefetchRef.current) return;\n if (!shouldResetAfterIdle(lastSuccessAtRef.current, Date.now(), idleGuardThresholdMs)) return;\n void queryClient.resetQueries({ queryKey: ['traces', listMode, filters] });\n };\n document.addEventListener('visibilitychange', handler);\n return () => document.removeEventListener('visibilitychange', handler);\n }, [queryClient, listMode, filters, idleGuardThresholdMs]);\n\n const { hasNextPage, isFetchingNextPage, fetchNextPage } = query;\n\n useEffect(() => {\n if (isEndOfListInView && hasNextPage && !isFetchingNextPage) {\n void fetchNextPage();\n }\n }, [isEndOfListInView, hasNextPage, isFetchingNextPage, fetchNextPage]);\n\n // `isRefetching` aggregates every active fetch — page-mode (initial, next\n // page, manual resync, idle reset), the 60s page-0 status refresh, AND the\n // delta polls — so a spinner bound to it acts as a heartbeat indicator.\n //\n // Short polls (e.g. delta on localhost) can resolve in tens of ms — too\n // brief for `animate-spin` (1s/rotation) to register visually, and quick\n // enough that React may batch the start/finish transitions so we never\n // even observe `isFetching: true`. So we also watch every query's\n // data/errorUpdatedAt; any advance pulses `isRefetching` true for at least\n // minRefetchSpinMs, which is ~144° of rotation — clearly perceptible.\n const isFetchingAny = query.isFetching || statusRefreshQuery.isFetching || deltaQuery.isFetching;\n const lastActivityAt = Math.max(\n query.dataUpdatedAt,\n query.errorUpdatedAt,\n deltaQuery.dataUpdatedAt,\n deltaQuery.errorUpdatedAt,\n statusRefreshQuery.dataUpdatedAt,\n statusRefreshQuery.errorUpdatedAt,\n );\n const [pulse, setPulse] = useState(false);\n const prevActivityAtRef = useRef(lastActivityAt);\n useEffect(() => {\n if (lastActivityAt > prevActivityAtRef.current) {\n prevActivityAtRef.current = lastActivityAt;\n setPulse(true);\n const t = setTimeout(() => setPulse(false), minRefetchSpinMs);\n return () => clearTimeout(t);\n }\n }, [lastActivityAt, minRefetchSpinMs]);\n const isRefetching = isFetchingAny || pulse;\n\n return {\n ...query,\n setEndOfListElement,\n isRefetching,\n autoRefetch,\n setAutoRefetch,\n recentlyAddedKeys,\n };\n};\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-traces.es.js","sources":["../../../../src/domains/traces/hooks/use-traces.tsx"],"sourcesContent":["import type {\n LightSpanRecord,\n ListBranchesArgs,\n ListBranchesResponse,\n ListTracesArgs,\n ListTracesLightResponse,\n} from '@mastra/core/storage';\nimport { useMastraClient } from '@mastra/react';\nimport type { InfiniteData } from '@tanstack/react-query';\nimport { useInfiniteQuery, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { useEffect, useRef, useState } from 'react';\nimport type { TraceListMode } from '../trace-filters';\nimport { useInView } from '@/hooks/use-in-view';\nimport { is403ForbiddenError } from '@/lib/query-utils';\n\n/**\n * Per-MastraClient delta-polling support cache. Sticks once we observe a 501\n * or a page-0 response without `deltaCursor` (legacy server or store without\n * delta capability), so we don't re-probe on every mount.\n */\ntype DeltaSupport = 'unknown' | 'unsupported';\nconst deltaSupportByClient = new WeakMap<ReturnType<typeof useMastraClient>, DeltaSupport>();\n\n/**\n * Per-MastraClient light-list support cache. Sticks once the light endpoint is\n * shown not to be served, so every subsequent page fetch, delta poll and periodic\n * refresh uses the full list endpoint instead. Other failures (auth, rate limits,\n * outages) propagate without pinning — they'd hit the full endpoint too.\n */\ntype LightListSupport = 'unknown' | 'unsupported';\nconst lightListSupportByClient = new WeakMap<ReturnType<typeof useMastraClient>, LightListSupport>();\n\n/** When the current run of light-endpoint 500s started, per client. Cleared by any light success. */\nconst lightListFirstServerErrorAtByClient = new WeakMap<ReturnType<typeof useMastraClient>, number>();\n\n/** A 500 is ambiguous — an old core's store throwing, or a transient fault (DB hiccup, timeout).\n * Serve the request from the full endpoint either way, but only pin the session once the 500s\n * have outlasted a blip; a hiccup would otherwise degrade the list until the tab is reloaded.\n * Elapsed time, not a request count: the page refetch, the delta poll and the status refresh can\n * fail in the same instant, and a fallback response still looks like a success to the delta query,\n * so chase mode keeps re-polling every `deltaChaseIntervalMs` throughout the outage. */\nconst LIGHT_LIST_SERVER_ERROR_PIN_AFTER_MS = 10_000;\n\n/** Tunables for live-tail polling. All fields optional — defaults below.\n * Platform consumers override individual fields to throttle traffic or\n * reshape the freshness/visibility trade-offs. */\nexport interface TracesPollingConfig {\n /** Interval between delta polls. Default 5_000ms. */\n deltaPollIntervalMs?: number;\n /** Refetch delay when the server signals `delta.hasMore` (chase remaining updates immediately). Default 100ms. */\n deltaChaseIntervalMs?: number;\n /** Max rows per delta poll. Server caps at 100. Default 100. */\n deltaLimit?: number;\n /** Page-mode refetch interval used when the configured store does not support delta polling. Default 10_000ms. */\n pageModeRefetchIntervalMs?: number;\n /** Periodic page-0 refetch interval while delta is active. Delta only delivers new rows, so this\n * is how we surface status flips (running → success/error) on rows already visible. Default 60_000ms. */\n page0StatusRefreshIntervalMs?: number;\n /** When the tab returns from being hidden and the last successful poll was older than this, the\n * cursor is treated as stale (ClickHouse delta tables TTL after 2 days; other stores may be more\n * aggressive). The infinite query is reset so page 0 refetches and delta resumes from a fresh\n * cursor. Default 900_000ms (15 min). */\n idleGuardThresholdMs?: number;\n /** Minimum visible duration for `isRefetching` after any query completes. On localhost a delta\n * poll can resolve in tens of ms — too brief for the spinner's 1s rotation to register visually,\n * and quick enough that React may batch the start/finish transitions. Pulsing for this duration\n * on completion (~144° of rotation at 400ms) makes each poll perceptible as a heartbeat.\n * Default 400ms. */\n minRefetchSpinMs?: number;\n /** Window during which a delta-arrived row's key stays in `recentlyAddedKeys` so the list view\n * can apply its `animate-row-highlight` class. Should match the CSS animation duration\n * (currently 1_000ms — see `index.css`). Default 1_000ms. */\n deltaHighlightMs?: number;\n}\n\nconst DEFAULT_POLLING_CONFIG: Required<TracesPollingConfig> = {\n deltaPollIntervalMs: 5_000,\n deltaChaseIntervalMs: 100,\n deltaLimit: 100,\n pageModeRefetchIntervalMs: 10_000,\n page0StatusRefreshIntervalMs: 60_000,\n idleGuardThresholdMs: 15 * 60_000,\n minRefetchSpinMs: 400,\n deltaHighlightMs: 1_000,\n};\n\n/** Returns true when the cursor should be considered stale and re-seeded.\n * Exported for testing — pure function of timestamps. */\nexport function shouldResetAfterIdle(lastSuccessAt: number, now: number, thresholdMs: number): boolean {\n if (lastSuccessAt === 0) return false;\n return now - lastSuccessAt > thresholdMs;\n}\n\nfunction isHttp501(error: unknown): boolean {\n return (error as { status?: number } | null)?.status === 501;\n}\n\n/** Only 404 and 500 can mean the light endpoint isn't served: 404 from servers without\n * the route, 500 from cores whose store throws instead of serving the projection.\n * Anything else (401, 403, 429, other 5xx, the 501 disabled-domain signal) is a\n * condition the full endpoint would hit too, so it propagates instead of falling\n * back — mirroring how the delta-support cache matches exactly 501. */\nfunction lightListFallbackStatus(error: unknown): 404 | 500 | undefined {\n const status = (error as { status?: number } | null)?.status;\n return status === 404 || status === 500 ? status : undefined;\n}\n\n/** Whether a failed light-list request should pin the session to the full endpoint.\n * `firstServerErrorAt` is undefined when this 500 opens a new run of them, so a single\n * fault never pins. Exported for testing — pure function of the status and timestamps. */\nexport function shouldPinLightList(\n status: number | undefined,\n firstServerErrorAt: number | undefined,\n now: number,\n): boolean {\n if (status === 404) return true;\n if (status !== 500) return false;\n if (firstServerErrorAt === undefined) return false;\n return now - firstServerErrorAt >= LIGHT_LIST_SERVER_ERROR_PIN_AFTER_MS;\n}\n\ntype FetchTracesFnArgs = TracesFilters & {\n client: ReturnType<typeof useMastraClient>;\n} & ({ mode: 'delta'; after?: string; limit?: number } | { mode?: 'page'; page: number; perPage: number });\n\nconst fetchTracesFn = async (args: FetchTracesFnArgs) => {\n const { client, filters, listMode = 'traces' } = args;\n const params =\n args.mode === 'delta'\n ? { mode: 'delta' as const, after: args.after, limit: args.limit, filters }\n : { pagination: { page: args.page, perPage: args.perPage }, filters };\n\n if (listMode === 'branches') {\n return client.listBranches(params as ListBranchesArgs);\n }\n\n // The list only renders identity, timing, status and a short input preview, so ask\n // for the lightweight projection. Selecting a row fetches the full record separately.\n // Clients pinned to full mode keep using listTraces (page and delta alike), exactly\n // as before the light endpoint existed; full rows are a superset of the light ones.\n if (lightListSupportByClient.get(client) === 'unsupported') {\n return client.listTraces(params as ListTracesArgs);\n }\n try {\n const response = await client.listTracesLight(params as ListTracesArgs);\n // A 200 can still be a legacy projection: servers whose store implements the\n // pre-preview light list answer with rows carrying none of the new fields.\n // Current servers always set `status` on every row, so a non-empty page where\n // every row lacks it is legacy-shaped — pin and refetch the full rows. Empty\n // pages never pin (indistinguishable, and they render identically either way;\n // the next non-empty page decides).\n if (args.mode !== 'delta') {\n const spans = response.spans ?? [];\n if (spans.length > 0 && spans.every(span => span.status === undefined)) {\n lightListSupportByClient.set(client, 'unsupported');\n return client.listTraces(params as ListTracesArgs);\n }\n }\n lightListFirstServerErrorAtByClient.delete(client);\n return response;\n } catch (error) {\n const status = lightListFallbackStatus(error);\n if (status === undefined) throw error;\n\n const now = Date.now();\n const firstServerErrorAt = lightListFirstServerErrorAtByClient.get(client);\n if (status === 500 && firstServerErrorAt === undefined) {\n lightListFirstServerErrorAtByClient.set(client, now);\n }\n if (shouldPinLightList(status, firstServerErrorAt, now)) {\n lightListSupportByClient.set(client, 'unsupported');\n }\n return client.listTraces(params as ListTracesArgs);\n }\n};\n\nexport const TRACES_PER_PAGE = 25;\n\nexport interface TracesFilters {\n filters?: ListTracesArgs['filters'] | ListBranchesArgs['filters'];\n listMode?: TraceListMode;\n}\n\n/** Returns the next page number if the server indicates more pages are available. */\nexport function getTracesNextPageParam(\n lastPage: ListTracesLightResponse | ListBranchesResponse | undefined,\n _allPages: unknown,\n lastPageParam: number,\n) {\n if (lastPage?.pagination?.hasMore) {\n return lastPageParam + 1;\n }\n return undefined;\n}\n\ntype TracesPageResponse = ListTracesLightResponse | ListBranchesResponse;\n\n/** Branch rows are full spans and trace rows are lightweight; the list only reads the\n * fields they share, so both are surfaced as `LightSpanRecord`. */\nfunction getPageSpans(page: TracesPageResponse): LightSpanRecord[] {\n if ('branches' in page) return page.branches ?? [];\n return page.spans ?? [];\n}\n\n/** Deduplicates trace/branch rows by traceId + spanId across all loaded pages.\n * Also surfaces page 0's deltaCursor so the live-tail query can read it reactively. */\nexport function selectUniqueTraces(data: { pages: TracesPageResponse[] }): {\n spans: LightSpanRecord[];\n deltaCursor: string | undefined;\n} {\n const seen = new Set<string>();\n const spans = data.pages\n .flatMap(page => getPageSpans(page))\n .filter(span => {\n const key = `${span.traceId}:${span.spanId}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n\n return { spans, deltaCursor: data.pages[0]?.deltaCursor };\n}\n\ntype RowIdentity = { traceId: string; spanId?: string | null };\n\nfunction rowKey(row: RowIdentity): string {\n return `${row.traceId}:${row.spanId}`;\n}\n\nfunction indexRowsByKey<TRow extends RowIdentity>(rows: TRow[]): Map<string, TRow> {\n return new Map(rows.map(row => [rowKey(row), row]));\n}\n\n/** Replaces existing page-0 rows in place (keyed by traceId:spanId) with\n * refreshed copies from the server. Rows the server doesn't return are kept\n * as-is so delta-accumulated rows that have aged off the server's page 0\n * aren't dropped from the UI. New rows aren't added here — delta polling\n * delivers those. */\nexport function refreshPage0Rows(\n old: InfiniteData<TracesPageResponse> | undefined,\n refreshed: ListTracesLightResponse | ListBranchesResponse,\n listMode: TraceListMode,\n): InfiniteData<TracesPageResponse> | undefined {\n if (!old || old.pages.length === 0) return old;\n const [firstPage, ...rest] = old.pages;\n if (!firstPage) return old;\n\n // Branch and trace rows have different row shapes, so each mode keys its own map.\n let updatedFirst: TracesPageResponse;\n if (listMode === 'branches' && 'branches' in firstPage && 'branches' in refreshed) {\n const refreshedByKey = indexRowsByKey(refreshed.branches ?? []);\n if (refreshedByKey.size === 0) return old;\n updatedFirst = {\n ...firstPage,\n branches: (firstPage.branches ?? []).map(existing => refreshedByKey.get(rowKey(existing)) ?? existing),\n };\n } else if ('spans' in firstPage && 'spans' in refreshed) {\n const refreshedByKey = indexRowsByKey(refreshed.spans ?? []);\n if (refreshedByKey.size === 0) return old;\n updatedFirst = {\n ...firstPage,\n spans: (firstPage.spans ?? []).map(existing => refreshedByKey.get(rowKey(existing)) ?? existing),\n };\n } else {\n return old;\n }\n\n return { ...old, pages: [updatedFirst, ...rest] };\n}\n\n/** Stable `startedAt DESC` sort matching the server's default page-mode\n * ordering for both list endpoints. Rows missing `startedAt` sink to the\n * bottom (compared as time 0). */\nfunction sortRowsByStartedAtDesc<T extends { startedAt?: unknown }>(rows: T[]): T[] {\n return [...rows].sort((a, b) => {\n const aTime = a.startedAt ? new Date(a.startedAt as string | number | Date).getTime() : 0;\n const bTime = b.startedAt ? new Date(b.startedAt as string | number | Date).getTime() : 0;\n return bTime - aTime;\n });\n}\n\n/** Prepends delta-mode rows into page 0 and advances its deltaCursor.\n * Delta responses come back in server cursor order (insertion order into\n * the per-store delta index), not the `startedAt DESC` order page-mode\n * uses — so we re-sort the merged page 0 to match what the user sees on\n * a fresh page-mode load. Cross-page duplication is handled later by\n * selectUniqueTraces. */\nexport function mergeDeltaIntoPage0(\n old: InfiniteData<TracesPageResponse> | undefined,\n delta: ListTracesLightResponse | ListBranchesResponse,\n listMode: TraceListMode,\n): InfiniteData<TracesPageResponse> | undefined {\n if (!old || old.pages.length === 0) return old;\n const [firstPage, ...rest] = old.pages;\n if (!firstPage) return old;\n const nextCursor = delta.deltaCursor ?? firstPage.deltaCursor;\n\n let updatedFirst: TracesPageResponse;\n if (listMode === 'branches' && 'branches' in firstPage) {\n const newRows = (delta as ListBranchesResponse).branches ?? [];\n updatedFirst = {\n ...firstPage,\n branches: sortRowsByStartedAtDesc([...newRows, ...(firstPage.branches ?? [])]),\n deltaCursor: nextCursor,\n };\n } else if ('spans' in firstPage) {\n const newRows = (delta as ListTracesLightResponse).spans ?? [];\n updatedFirst = {\n ...firstPage,\n spans: sortRowsByStartedAtDesc([...newRows, ...(firstPage.spans ?? [])]),\n deltaCursor: nextCursor,\n };\n } else {\n return old;\n }\n\n return { ...old, pages: [updatedFirst, ...rest] };\n}\n\nexport interface UseTracesArgs extends TracesFilters {\n /** Optional overrides for the live-tail polling tunables. Any omitted fields fall back to the\n * built-in defaults; pass only what you want to change. */\n polling?: TracesPollingConfig;\n}\n\ninterface UseTracesReturn {\n data: { spans: LightSpanRecord[]; deltaCursor: string | undefined } | undefined;\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n fetchNextPage: () => void;\n isLoading: boolean;\n isError: boolean;\n error: Error | null;\n setEndOfListElement: (node: HTMLDivElement | null) => void;\n isRefetching: boolean;\n autoRefetch: boolean;\n setAutoRefetch: (v: boolean) => void;\n recentlyAddedKeys: Set<string>;\n dataUpdatedAt: number;\n errorUpdatedAt: number;\n isFetching: boolean;\n isSuccess: boolean;\n fetchStatus: 'idle' | 'fetching' | 'paused';\n refetch: () => void;\n}\n\nexport const useTraces: (args: UseTracesArgs) => UseTracesReturn = ({\n filters,\n listMode = 'traces',\n polling = {},\n}: UseTracesArgs) => {\n const {\n deltaPollIntervalMs = DEFAULT_POLLING_CONFIG.deltaPollIntervalMs,\n deltaChaseIntervalMs = DEFAULT_POLLING_CONFIG.deltaChaseIntervalMs,\n deltaLimit = DEFAULT_POLLING_CONFIG.deltaLimit,\n pageModeRefetchIntervalMs = DEFAULT_POLLING_CONFIG.pageModeRefetchIntervalMs,\n page0StatusRefreshIntervalMs = DEFAULT_POLLING_CONFIG.page0StatusRefreshIntervalMs,\n idleGuardThresholdMs = DEFAULT_POLLING_CONFIG.idleGuardThresholdMs,\n minRefetchSpinMs = DEFAULT_POLLING_CONFIG.minRefetchSpinMs,\n deltaHighlightMs = DEFAULT_POLLING_CONFIG.deltaHighlightMs,\n } = polling;\n const client = useMastraClient();\n const queryClient = useQueryClient();\n const { inView: isEndOfListInView, setRef: setEndOfListElement } = useInView();\n\n const [deltaSupport, setDeltaSupport] = useState<DeltaSupport>(() => deltaSupportByClient.get(client) ?? 'unknown');\n const deltaUnsupported = deltaSupport === 'unsupported';\n\n // When false, all automatic polling stops: delta polls, status refresh,\n // idle-guard resets, and the page-mode fallback interval. Manual `resync`\n // still works. Session-scoped (no persistence across mounts).\n const [autoRefetch, setAutoRefetch] = useState(true);\n\n // Keys (`traceId:spanId`) of rows that arrived via the most recent delta\n // poll(s). The list view reads this to apply a temporary highlight class\n // for deltaHighlightMs, then they auto-expire. Resets on filter/listMode\n // change since a new query key spawns a fresh list.\n const [recentlyAddedKeys, setRecentlyAddedKeys] = useState<Set<string>>(() => new Set());\n useEffect(() => {\n setRecentlyAddedKeys(new Set());\n }, [listMode, filters]);\n\n const tracesQueryKey = ['traces', listMode, filters] as const;\n\n const query = useInfiniteQuery({\n queryKey: tracesQueryKey,\n queryFn: ({ pageParam }) =>\n fetchTracesFn({\n client,\n page: pageParam,\n perPage: TRACES_PER_PAGE,\n filters,\n listMode,\n }),\n initialPageParam: 0,\n getNextPageParam: getTracesNextPageParam,\n select: selectUniqueTraces,\n retry: false,\n // Disable polling on 403 to prevent flickering.\n // Fall back to page-mode polling only when delta isn't running.\n refetchInterval: q => {\n if (is403ForbiddenError(q.state.error)) return false;\n if (!autoRefetch) return false;\n return deltaUnsupported ? pageModeRefetchIntervalMs : false;\n },\n });\n\n const cursor = query.data?.deltaCursor;\n\n // If page 0 came back without a deltaCursor, the server/store doesn't\n // support delta polling. Pin the client to 'unsupported' so the infinite\n // query resumes page-mode polling and we stop probing.\n useEffect(() => {\n if (query.isSuccess && cursor === undefined && deltaSupport === 'unknown') {\n deltaSupportByClient.set(client, 'unsupported');\n setDeltaSupport('unsupported');\n }\n }, [query.isSuccess, cursor, deltaSupport, client]);\n\n const deltaQuery = useQuery({\n queryKey: ['traces-delta', listMode, filters] as const,\n queryFn: () => {\n const current = queryClient.getQueryData<InfiniteData<TracesPageResponse>>(tracesQueryKey)?.pages[0]?.deltaCursor;\n if (!current) return null;\n return fetchTracesFn({\n client,\n mode: 'delta',\n after: current,\n limit: deltaLimit,\n filters,\n listMode,\n });\n },\n enabled: !!cursor && !deltaUnsupported && autoRefetch,\n retry: false,\n refetchInterval: q => {\n if (q.state.error) return false;\n const data = q.state.data as ListTracesLightResponse | ListBranchesResponse | null | undefined;\n if (data?.delta?.hasMore) return deltaChaseIntervalMs;\n return deltaPollIntervalMs;\n },\n });\n\n // Merge new delta rows into the infinite-query cache. Also captures the\n // arrived keys into `recentlyAddedKeys` for deltaHighlightMs so the UI\n // can briefly distinguish them. The cleanup timer is intentionally NOT\n // tied to the effect lifecycle — if a newer delta arrives first, we still\n // want each prior batch to expire on its own schedule.\n useEffect(() => {\n const result = deltaQuery.data;\n if (!result) return;\n queryClient.setQueryData<InfiniteData<TracesPageResponse>>(tracesQueryKey, old =>\n mergeDeltaIntoPage0(old, result, listMode),\n );\n\n const newRows: RowIdentity[] =\n listMode === 'branches' && 'branches' in result\n ? (result.branches ?? [])\n : 'spans' in result\n ? (result.spans ?? [])\n : [];\n if (newRows.length === 0) return;\n const keys = newRows.map(r => `${r.traceId}:${r.spanId ?? ''}`);\n setRecentlyAddedKeys(prev => {\n const next = new Set(prev);\n for (const k of keys) next.add(k);\n return next;\n });\n setTimeout(() => {\n setRecentlyAddedKeys(prev => {\n const next = new Set(prev);\n for (const k of keys) next.delete(k);\n return next;\n });\n }, deltaHighlightMs);\n // tracesQueryKey is a new array each render but its contents drive cache keying;\n // depending on its members (listMode/filters) is enough.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [deltaQuery.data, queryClient, listMode, filters, deltaHighlightMs]);\n\n // A 501 means the server is too old (or store doesn't support delta) — pin\n // 'unsupported' so the delta query disables and page-mode polling resumes.\n useEffect(() => {\n if (isHttp501(deltaQuery.error)) {\n deltaSupportByClient.set(client, 'unsupported');\n setDeltaSupport('unsupported');\n }\n }, [deltaQuery.error, client]);\n\n // Periodic page-0 refresh to surface status updates on visible rows\n // (running → success/error). Only runs while delta is the primary polling\n // mechanism; otherwise the infinite query's own refetchInterval covers it.\n const statusRefreshQuery = useQuery({\n queryKey: ['traces-status-refresh', listMode, filters] as const,\n queryFn: () =>\n fetchTracesFn({\n client,\n page: 0,\n perPage: TRACES_PER_PAGE,\n filters,\n listMode,\n }),\n enabled: !!cursor && !deltaUnsupported && autoRefetch,\n refetchInterval: page0StatusRefreshIntervalMs,\n refetchOnMount: false,\n retry: false,\n });\n\n useEffect(() => {\n const result = statusRefreshQuery.data;\n if (!result) return;\n queryClient.setQueryData<InfiniteData<TracesPageResponse>>(tracesQueryKey, old =>\n refreshPage0Rows(old, result, listMode),\n );\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [statusRefreshQuery.data, queryClient, listMode, filters]);\n\n // Idle guard: when the tab returns from being hidden, check how long it's\n // been since the last successful poll. If past the threshold, reset the\n // infinite query so page 0 refetches and the cursor is re-seeded.\n const lastSuccessAtRef = useRef(0);\n useEffect(() => {\n const ts = Math.max(query.dataUpdatedAt, deltaQuery.dataUpdatedAt, statusRefreshQuery.dataUpdatedAt);\n if (ts > lastSuccessAtRef.current) {\n lastSuccessAtRef.current = ts;\n }\n }, [query.dataUpdatedAt, deltaQuery.dataUpdatedAt, statusRefreshQuery.dataUpdatedAt]);\n\n // Track autoRefetch in a ref so the visibility listener (attached once)\n // reads the current value without needing to re-attach.\n const autoRefetchRef = useRef(autoRefetch);\n useEffect(() => {\n autoRefetchRef.current = autoRefetch;\n }, [autoRefetch]);\n\n useEffect(() => {\n const handler = () => {\n if (document.visibilityState !== 'visible') return;\n if (!autoRefetchRef.current) return;\n if (!shouldResetAfterIdle(lastSuccessAtRef.current, Date.now(), idleGuardThresholdMs)) return;\n void queryClient.resetQueries({ queryKey: ['traces', listMode, filters] });\n };\n document.addEventListener('visibilitychange', handler);\n return () => document.removeEventListener('visibilitychange', handler);\n }, [queryClient, listMode, filters, idleGuardThresholdMs]);\n\n const { hasNextPage, isFetchingNextPage, fetchNextPage } = query;\n\n useEffect(() => {\n if (isEndOfListInView && hasNextPage && !isFetchingNextPage) {\n void fetchNextPage();\n }\n }, [isEndOfListInView, hasNextPage, isFetchingNextPage, fetchNextPage]);\n\n // `isRefetching` aggregates every active fetch — page-mode (initial, next\n // page, manual resync, idle reset), the 60s page-0 status refresh, AND the\n // delta polls — so a spinner bound to it acts as a heartbeat indicator.\n //\n // Short polls (e.g. delta on localhost) can resolve in tens of ms — too\n // brief for `animate-spin` (1s/rotation) to register visually, and quick\n // enough that React may batch the start/finish transitions so we never\n // even observe `isFetching: true`. So we also watch every query's\n // data/errorUpdatedAt; any advance pulses `isRefetching` true for at least\n // minRefetchSpinMs, which is ~144° of rotation — clearly perceptible.\n const isFetchingAny = query.isFetching || statusRefreshQuery.isFetching || deltaQuery.isFetching;\n const lastActivityAt = Math.max(\n query.dataUpdatedAt,\n query.errorUpdatedAt,\n deltaQuery.dataUpdatedAt,\n deltaQuery.errorUpdatedAt,\n statusRefreshQuery.dataUpdatedAt,\n statusRefreshQuery.errorUpdatedAt,\n );\n const [pulse, setPulse] = useState(false);\n const prevActivityAtRef = useRef(lastActivityAt);\n useEffect(() => {\n if (lastActivityAt > prevActivityAtRef.current) {\n prevActivityAtRef.current = lastActivityAt;\n setPulse(true);\n const t = setTimeout(() => setPulse(false), minRefetchSpinMs);\n return () => clearTimeout(t);\n }\n }, [lastActivityAt, minRefetchSpinMs]);\n const isRefetching = isFetchingAny || pulse;\n\n return {\n ...query,\n setEndOfListElement,\n isRefetching,\n autoRefetch,\n setAutoRefetch,\n recentlyAddedKeys,\n };\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
import { TraceColumnPreferences, TraceUsageSummary } from '../trace-list-columns';
|
|
2
|
-
/** Span attributes fields the list view reads directly. Extra unknown keys are allowed so callers
|
|
3
|
-
* can pass the full attributes record from @mastra/core/storage without mapping. */
|
|
4
|
-
export type TraceAttributes = {
|
|
5
|
-
status?: string | null;
|
|
6
|
-
agentId?: string | null;
|
|
7
|
-
workflowId?: string | null;
|
|
8
|
-
[key: string]: unknown;
|
|
9
|
-
};
|
|
10
2
|
export type TracesListViewTrace = {
|
|
11
3
|
traceId: string;
|
|
12
4
|
/** Required for branch rows; absent on plain trace rows (which are root-rooted). */
|
|
@@ -18,7 +10,8 @@ export type TracesListViewTrace = {
|
|
|
18
10
|
entityId?: string | null;
|
|
19
11
|
entityName?: string | null;
|
|
20
12
|
status?: string | null;
|
|
21
|
-
|
|
13
|
+
/** Server-rendered preview of `input`. Present on lightweight rows, which omit `input` itself. */
|
|
14
|
+
inputPreview?: string | null;
|
|
22
15
|
input?: unknown;
|
|
23
16
|
metadata?: Record<string, unknown> | null;
|
|
24
17
|
startedAt?: Date | string | null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ListBranchesArgs, ListBranchesResponse, ListTracesArgs,
|
|
1
|
+
import { LightSpanRecord, ListBranchesArgs, ListBranchesResponse, ListTracesArgs, ListTracesLightResponse } from '@mastra/core/storage';
|
|
2
2
|
import { InfiniteData } from '@tanstack/react-query';
|
|
3
3
|
import { TraceListMode } from '../trace-filters';
|
|
4
4
|
/** Tunables for live-tail polling. All fields optional — defaults below.
|
|
@@ -35,20 +35,24 @@ export interface TracesPollingConfig {
|
|
|
35
35
|
/** Returns true when the cursor should be considered stale and re-seeded.
|
|
36
36
|
* Exported for testing — pure function of timestamps. */
|
|
37
37
|
export declare function shouldResetAfterIdle(lastSuccessAt: number, now: number, thresholdMs: number): boolean;
|
|
38
|
+
/** Whether a failed light-list request should pin the session to the full endpoint.
|
|
39
|
+
* `firstServerErrorAt` is undefined when this 500 opens a new run of them, so a single
|
|
40
|
+
* fault never pins. Exported for testing — pure function of the status and timestamps. */
|
|
41
|
+
export declare function shouldPinLightList(status: number | undefined, firstServerErrorAt: number | undefined, now: number): boolean;
|
|
38
42
|
export declare const TRACES_PER_PAGE = 25;
|
|
39
43
|
export interface TracesFilters {
|
|
40
44
|
filters?: ListTracesArgs['filters'] | ListBranchesArgs['filters'];
|
|
41
45
|
listMode?: TraceListMode;
|
|
42
46
|
}
|
|
43
47
|
/** Returns the next page number if the server indicates more pages are available. */
|
|
44
|
-
export declare function getTracesNextPageParam(lastPage:
|
|
45
|
-
type TracesPageResponse =
|
|
48
|
+
export declare function getTracesNextPageParam(lastPage: ListTracesLightResponse | ListBranchesResponse | undefined, _allPages: unknown, lastPageParam: number): number | undefined;
|
|
49
|
+
type TracesPageResponse = ListTracesLightResponse | ListBranchesResponse;
|
|
46
50
|
/** Deduplicates trace/branch rows by traceId + spanId across all loaded pages.
|
|
47
51
|
* Also surfaces page 0's deltaCursor so the live-tail query can read it reactively. */
|
|
48
52
|
export declare function selectUniqueTraces(data: {
|
|
49
53
|
pages: TracesPageResponse[];
|
|
50
54
|
}): {
|
|
51
|
-
spans:
|
|
55
|
+
spans: LightSpanRecord[];
|
|
52
56
|
deltaCursor: string | undefined;
|
|
53
57
|
};
|
|
54
58
|
/** Replaces existing page-0 rows in place (keyed by traceId:spanId) with
|
|
@@ -56,14 +60,14 @@ export declare function selectUniqueTraces(data: {
|
|
|
56
60
|
* as-is so delta-accumulated rows that have aged off the server's page 0
|
|
57
61
|
* aren't dropped from the UI. New rows aren't added here — delta polling
|
|
58
62
|
* delivers those. */
|
|
59
|
-
export declare function refreshPage0Rows(old: InfiniteData<TracesPageResponse> | undefined, refreshed:
|
|
63
|
+
export declare function refreshPage0Rows(old: InfiniteData<TracesPageResponse> | undefined, refreshed: ListTracesLightResponse | ListBranchesResponse, listMode: TraceListMode): InfiniteData<TracesPageResponse> | undefined;
|
|
60
64
|
/** Prepends delta-mode rows into page 0 and advances its deltaCursor.
|
|
61
65
|
* Delta responses come back in server cursor order (insertion order into
|
|
62
66
|
* the per-store delta index), not the `startedAt DESC` order page-mode
|
|
63
67
|
* uses — so we re-sort the merged page 0 to match what the user sees on
|
|
64
68
|
* a fresh page-mode load. Cross-page duplication is handled later by
|
|
65
69
|
* selectUniqueTraces. */
|
|
66
|
-
export declare function mergeDeltaIntoPage0(old: InfiniteData<TracesPageResponse> | undefined, delta:
|
|
70
|
+
export declare function mergeDeltaIntoPage0(old: InfiniteData<TracesPageResponse> | undefined, delta: ListTracesLightResponse | ListBranchesResponse, listMode: TraceListMode): InfiniteData<TracesPageResponse> | undefined;
|
|
67
71
|
export interface UseTracesArgs extends TracesFilters {
|
|
68
72
|
/** Optional overrides for the live-tail polling tunables. Any omitted fields fall back to the
|
|
69
73
|
* built-in defaults; pass only what you want to change. */
|
|
@@ -71,7 +75,7 @@ export interface UseTracesArgs extends TracesFilters {
|
|
|
71
75
|
}
|
|
72
76
|
interface UseTracesReturn {
|
|
73
77
|
data: {
|
|
74
|
-
spans:
|
|
78
|
+
spans: LightSpanRecord[];
|
|
75
79
|
deltaCursor: string | undefined;
|
|
76
80
|
} | undefined;
|
|
77
81
|
hasNextPage: boolean;
|
|
@@ -16,4 +16,4 @@ export interface CrumbProps {
|
|
|
16
16
|
action?: React.ReactNode;
|
|
17
17
|
'data-testid'?: string;
|
|
18
18
|
}
|
|
19
|
-
export declare const Crumb: ({ className, as, isCurrent, action, ...props }: CrumbProps) => React.JSX.Element;
|
|
19
|
+
export declare const Crumb: ({ className, as, isCurrent, action, children, ...props }: CrumbProps) => React.JSX.Element;
|
|
@@ -8,3 +8,5 @@ export declare const TwoLevels: Story;
|
|
|
8
8
|
export declare const ManyLevels: Story;
|
|
9
9
|
export declare const WithAction: Story;
|
|
10
10
|
export declare const SingleItem: Story;
|
|
11
|
+
/** A clamped label must ellipsize, not cut mid-glyph, and keep its descenders. */
|
|
12
|
+
export declare const TruncatedLabel: Story;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/playground-ui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "48.0.0-alpha.1",
|
|
5
5
|
"description": "Mastra Playground components",
|
|
6
6
|
"sideEffects": [
|
|
7
7
|
"**/*.css"
|
|
@@ -150,8 +150,8 @@
|
|
|
150
150
|
"react": ">=19.0.0",
|
|
151
151
|
"react-dom": ">=19.0.0",
|
|
152
152
|
"tailwindcss": "^4.0.0",
|
|
153
|
-
"@mastra/client-js": "^1.
|
|
154
|
-
"@mastra/react": "1.4.1"
|
|
153
|
+
"@mastra/client-js": "^1.39.0-alpha.1",
|
|
154
|
+
"@mastra/react": "1.4.2-alpha.1"
|
|
155
155
|
},
|
|
156
156
|
"devDependencies": {
|
|
157
157
|
"@storybook/addon-a11y": "^10.4.1",
|
|
@@ -189,11 +189,11 @@
|
|
|
189
189
|
"vite-plugin-dts": "^4.5.4",
|
|
190
190
|
"vite-plugin-lib-inject-css": "^2.2.2",
|
|
191
191
|
"vitest": "4.1.10",
|
|
192
|
-
"@mastra/
|
|
193
|
-
"@mastra/
|
|
194
|
-
"@
|
|
195
|
-
"@mastra/
|
|
196
|
-
"@
|
|
192
|
+
"@mastra/memory": "1.26.1-alpha.0",
|
|
193
|
+
"@mastra/react": "1.4.2-alpha.1",
|
|
194
|
+
"@internal/lint": "0.0.121",
|
|
195
|
+
"@mastra/client-js": "^1.39.0-alpha.1",
|
|
196
|
+
"@mastra/core": "1.58.0-alpha.1"
|
|
197
197
|
},
|
|
198
198
|
"homepage": "https://mastra.ai",
|
|
199
199
|
"repository": {
|