@tangle-network/ui 11.6.0 → 11.7.0
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 +6 -0
- package/README.md +43 -0
- package/dist/chat.d.ts +18 -8
- package/dist/chat.js +6 -5
- package/dist/{chunk-XROQW5J7.js → chunk-4ENFLPWQ.js} +1 -1
- package/dist/{chunk-XPYPZMYT.js → chunk-6RIX7M27.js} +1 -1
- package/dist/{chunk-R5GVQDL3.js → chunk-CVX53NCC.js} +7 -5
- package/dist/{chunk-ULDNFLIM.js → chunk-EIUE5GV2.js} +10 -14
- package/dist/chunk-L57WRIND.js +59 -0
- package/dist/chunk-LFVF2HFB.js +19 -0
- package/dist/{chunk-AZWDI2JG.js → chunk-UYTRNR7Z.js} +1 -1
- package/dist/{chunk-RKC3GXHE.js → chunk-VDCZHOOO.js} +99 -104
- package/dist/{chunk-J2G7KCYH.js → chunk-XT4JMYVB.js} +109 -67
- package/dist/hooks.js +6 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +15 -10
- package/dist/primitives.d.ts +15 -1
- package/dist/primitives.js +5 -1
- package/dist/run.d.ts +22 -17
- package/dist/run.js +8 -6
- package/dist/sdk-hooks.js +6 -5
- package/dist/utils.d.ts +6 -0
- package/dist/utils.js +1 -1
- package/package.json +1 -1
- package/src/chat/agent-timeline.stories.tsx +163 -10
- package/src/chat/agent-timeline.test.tsx +114 -7
- package/src/chat/agent-timeline.tsx +120 -158
- package/src/chat/chat-container.test.tsx +59 -0
- package/src/chat/user-message.test.tsx +33 -0
- package/src/chat/user-message.tsx +13 -8
- package/src/primitives/index.ts +3 -0
- package/src/primitives/text-shimmer.test.tsx +34 -0
- package/src/primitives/text-shimmer.tsx +82 -0
- package/src/run/inline-thinking-item.stories.tsx +2 -2
- package/src/run/inline-thinking-item.tsx +2 -2
- package/src/run/inline-tool-item.test.tsx +76 -0
- package/src/run/inline-tool-item.tsx +9 -7
- package/src/run/run-group.tsx +4 -3
- package/src/run/run-row-shell.test.tsx +74 -4
- package/src/run/run-row-shell.tsx +81 -53
- package/src/run/tool-call-step.tsx +9 -7
- package/src/utils/tool-display.test.ts +53 -0
- package/src/utils/tool-display.ts +16 -15
- package/dist/chunk-LQS34IGP.js +0 -0
- /package/dist/{chunk-EJ5NBW7V.js → chunk-LDLOJKPC.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @tangle-network/ui
|
|
2
2
|
|
|
3
|
+
## 11.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 0982a63: Quiet transcript rows: the agent transcript (`AgentTimeline`, `ChatContainer`'s `timeline` presentation) now reads as one column of prose with tool calls as quiet one-liners between the paragraphs. `RunRowShell` (and so `InlineToolItem` / `InlineThinkingItem`) rests with no card chrome — transparent border and fill, a bare 14px glyph, title and description at `text-sm` in `text-muted-foreground` that brighten on hover — and takes the bordered `--md3-surface-container` card only while open; the expand chevron replaces the glyph on hover and open, the success dot is gone (success is silence), the running spinner and live-ticking duration are gone (a running row shimmers its title through the new `TextShimmer` primitive, exported from `/primitives`), and the completed duration shows on hover only. `RunRowStatusDot` still exists and now draws only the error dot; `RunRowShellProps.startTime` is accepted and ignored (deprecated). `getToolDisplayMetadata` titles are the bare verb (`Read`, `Edit`, `Write`, `Shell`, `Search`, `Find`, `Web search`, `Fetch`, `Task`) and the path, command, pattern or query lives in `description` only, so a row no longer prints its subject twice; `targetPath` / `commandSnippet` are unchanged. `AgentTimeline` drops the connector spine, the uppercase "Tool activity" header above grouped calls (the group keeps that name as its `aria-label`), and the per-block timestamp on assistant prose; prose renders at 15px/1.5 with a muted streaming caret, and vertical rhythm now follows the pair of neighbours (`mt-4` at a user turn, `mt-1` inside tool sequences and between prose and tools). `UserMessage` loses its border and shows its timestamp beside the bubble on hover. Consumers that composed `RunRowShell` with a shimmer title of their own get the intended result now that the shell draws no spinner beside it.
|
|
8
|
+
|
|
3
9
|
## 11.6.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -26,6 +26,49 @@ export function App() {
|
|
|
26
26
|
}
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
## Styling
|
|
30
|
+
|
|
31
|
+
This package ships no CSS. Its components are Tailwind v4 class names in source, and the build emits JavaScript only, so two pieces of consumer setup carry real weight.
|
|
32
|
+
|
|
33
|
+
### Scan this package
|
|
34
|
+
|
|
35
|
+
Your Tailwind or UnoCSS build has to read this package's files. Neither scans `node_modules` on its own, so a class that only this package names is never generated. Nothing fails loudly when one is missing: the element still renders, still occupies space and still takes clicks, it just looks inert. A `Switch` with no track colour in either state, a `Tabs` where the selected tab is indistinguishable from the rest, and a `Textarea` collapsed to one row all come out of this.
|
|
36
|
+
|
|
37
|
+
Tailwind v4, in the stylesheet that imports `tailwindcss`. `@source` paths resolve relative to that stylesheet, so adjust the prefix to wherever yours lives:
|
|
38
|
+
|
|
39
|
+
```css
|
|
40
|
+
/* from src/styles/app.css, with node_modules two levels up */
|
|
41
|
+
@source "../../node_modules/@tangle-network/ui/src/**/*.tsx";
|
|
42
|
+
@source "../../node_modules/@tangle-network/ui/src/**/*.ts";
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`sandbox-ui` runs exactly these two globs in `src/styles/globals.css`, alongside `@import "tailwindcss" source(none)` so the scan is the whole of what the bundle compiles from.
|
|
46
|
+
|
|
47
|
+
UnoCSS, in `uno.config.ts`:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
content: {
|
|
51
|
+
filesystem: ["node_modules/@tangle-network/ui/dist/**/*.js"],
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Under pnpm that plain glob can match zero files. `node_modules/@tangle-network/ui` is a symlink into `node_modules/.pnpm`, and UnoCSS's globber does not follow it, which leaves you exactly where you started with no error. Pre-expand the glob with something that does follow symlinks, resolve each hit through `realpath`, and hand UnoCSS concrete paths. `tangle-network/blueprint-agent`'s `apps/web/uno.config.ts` carries a worked version that walks the import graph of the two entries it uses rather than the whole `dist`.
|
|
56
|
+
|
|
57
|
+
Two things to check after wiring this up, because the failure is silent either way. Read the generated CSS for a class this package alone names, rather than eyeballing the app. And assert the scan resolved a non-zero number of files: a scan that silently matches nothing looks identical to no scan at all.
|
|
58
|
+
|
|
59
|
+
One consequence worth knowing. This is a scan, not a manifest, so a consumer emits only the classes the version it installed actually names. Upgrading this package can introduce a class your build has never generated, and it will go missing the same silent way.
|
|
60
|
+
|
|
61
|
+
### Import the brand stylesheet
|
|
62
|
+
|
|
63
|
+
`@tangle-network/brand` is a peer dependency. It holds the tokens every colour here resolves through, and a few real classes that some components reference by name instead of composing out of utilities. `Badge` with `dot` is the one to know about: it emits `status-dot status-dot-running` and its siblings, which are defined in `@tangle-network/brand/styles/globals.css` and nowhere else. Without that import the dots have no size and no colour.
|
|
64
|
+
|
|
65
|
+
```css
|
|
66
|
+
@import "@tangle-network/brand/styles";
|
|
67
|
+
@import "tailwindcss";
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
See [`@tangle-network/brand`'s README](../brand/README.md) for the token-level breakdown, the finer-grained imports, and the fonts, which brand deliberately does not bundle.
|
|
71
|
+
|
|
29
72
|
## Subpaths
|
|
30
73
|
|
|
31
74
|
Sixteen named exports: `primitives`, `chat`, `run`, `openui`, `files`, `editor`, `markdown`, `auth`, `hooks`, `sdk-hooks`, `stores`, `types`, `utils`, `tool-previews`, `nav`, `redaction`.
|
package/dist/chat.d.ts
CHANGED
|
@@ -87,9 +87,11 @@ interface UserMessageProps {
|
|
|
87
87
|
actions?: ReactNode;
|
|
88
88
|
}
|
|
89
89
|
/**
|
|
90
|
-
* The single user message bubble — a quiet, right-aligned
|
|
91
|
-
*
|
|
92
|
-
*
|
|
90
|
+
* The single user message bubble — a quiet, right-aligned fill on the muted
|
|
91
|
+
* surface (no border, no loud fill, no uppercase label). The timestamp sits
|
|
92
|
+
* beside the bubble and appears on hover, so it costs no height. Used by both
|
|
93
|
+
* the run/message list (session-model `parts`) and AgentTimeline (direct
|
|
94
|
+
* `content`/`timestamp`).
|
|
93
95
|
*/
|
|
94
96
|
declare const UserMessage: React.MemoExoticComponent<({ message: _message, parts, content, timestamp, actions }: UserMessageProps) => react_jsx_runtime.JSX.Element | null>;
|
|
95
97
|
|
|
@@ -126,6 +128,8 @@ interface AgentTimelineMessageItem {
|
|
|
126
128
|
content: string;
|
|
127
129
|
toolCalls?: ReactNode;
|
|
128
130
|
isStreaming?: boolean;
|
|
131
|
+
/** Shown beside a user bubble on hover. Assistant prose carries no
|
|
132
|
+
* timestamp of its own — the turn's user message dates the exchange. */
|
|
129
133
|
timestamp?: Date;
|
|
130
134
|
after?: ReactNode;
|
|
131
135
|
}
|
|
@@ -140,6 +144,7 @@ interface AgentTimelineToolItem {
|
|
|
140
144
|
interface AgentTimelineToolGroupItem {
|
|
141
145
|
id: string;
|
|
142
146
|
kind: "tool_group";
|
|
147
|
+
/** Accessible name of the group. Not drawn — the rows are the label. */
|
|
143
148
|
title?: string;
|
|
144
149
|
calls: ToolCallData[];
|
|
145
150
|
/** Source tool parts, parallel to `calls`. */
|
|
@@ -166,6 +171,9 @@ interface AgentTimelineArtifactItem {
|
|
|
166
171
|
interface AgentTimelineCustomItem {
|
|
167
172
|
id: string;
|
|
168
173
|
kind: "custom";
|
|
174
|
+
/** Laid out as a run row: it takes the same horizontal bleed as tool rows,
|
|
175
|
+
* so a `RunRowShell`-based row (reasoning) aligns its glyph with the prose
|
|
176
|
+
* edge. */
|
|
169
177
|
content: ReactNode;
|
|
170
178
|
}
|
|
171
179
|
type AgentTimelineItem = AgentTimelineMessageItem | AgentTimelineToolItem | AgentTimelineToolGroupItem | AgentTimelineStatusItem | AgentTimelineArtifactItem | AgentTimelineCustomItem;
|
|
@@ -177,14 +185,16 @@ interface AgentTimelineProps {
|
|
|
177
185
|
/** Optional actions rendered beside each tool item (e.g. "open in artifacts").
|
|
178
186
|
* Receives the source tool part carried on the item. */
|
|
179
187
|
renderToolActions?: (part: ToolPart) => ReactNode;
|
|
180
|
-
/** When set, collapse the timeline to the first N
|
|
181
|
-
* "Show N more steps" toggle. Omit to always
|
|
188
|
+
/** When set, collapse the timeline to the first N step rows (every item
|
|
189
|
+
* except user messages) behind a "Show N more steps" toggle. Omit to always
|
|
190
|
+
* show every row. */
|
|
182
191
|
collapseAfter?: number;
|
|
183
192
|
}
|
|
184
193
|
/**
|
|
185
|
-
* AgentTimeline — unified mixed-content
|
|
186
|
-
* sessions. Renders messages, tool
|
|
187
|
-
*
|
|
194
|
+
* AgentTimeline — unified mixed-content transcript for agent-backed sandbox
|
|
195
|
+
* sessions. Renders messages, tool rows, status cards, and artifact handoffs
|
|
196
|
+
* as one plain column: prose at the reading size, tool calls as quiet
|
|
197
|
+
* one-liners between the paragraphs, no connector chrome.
|
|
188
198
|
*/
|
|
189
199
|
declare function AgentTimeline({ items, isThinking, emptyState, className, renderToolActions, collapseAfter, }: AgentTimelineProps): react_jsx_runtime.JSX.Element | null;
|
|
190
200
|
|
package/dist/chat.js
CHANGED
|
@@ -5,12 +5,13 @@ import {
|
|
|
5
5
|
MessageList,
|
|
6
6
|
ThinkingIndicator,
|
|
7
7
|
UserMessage
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-VDCZHOOO.js";
|
|
9
|
+
import "./chunk-UYTRNR7Z.js";
|
|
10
|
+
import "./chunk-CVX53NCC.js";
|
|
11
|
+
import "./chunk-XT4JMYVB.js";
|
|
12
|
+
import "./chunk-EIUE5GV2.js";
|
|
13
13
|
import "./chunk-AAUNOHVL.js";
|
|
14
|
+
import "./chunk-L57WRIND.js";
|
|
14
15
|
import "./chunk-3M7BPSSW.js";
|
|
15
16
|
import "./chunk-GYPQXTJU.js";
|
|
16
17
|
import "./chunk-MKTSMWVD.js";
|
|
@@ -2,11 +2,14 @@ import {
|
|
|
2
2
|
ExpandableContent,
|
|
3
3
|
InlineToolItem,
|
|
4
4
|
RunRowShell
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-XT4JMYVB.js";
|
|
6
6
|
import {
|
|
7
7
|
formatDuration,
|
|
8
8
|
truncateText
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-EIUE5GV2.js";
|
|
10
|
+
import {
|
|
11
|
+
TextShimmer
|
|
12
|
+
} from "./chunk-L57WRIND.js";
|
|
10
13
|
import {
|
|
11
14
|
OpenUIArtifactRenderer
|
|
12
15
|
} from "./chunk-3M7BPSSW.js";
|
|
@@ -54,10 +57,9 @@ var InlineThinkingItem = memo(
|
|
|
54
57
|
RunRowShell,
|
|
55
58
|
{
|
|
56
59
|
icon: /* @__PURE__ */ jsx(Brain, { className: "h-3.5 w-3.5" }),
|
|
57
|
-
title: isActive ? "Thinking\u2026" : "Reasoning",
|
|
60
|
+
title: isActive ? /* @__PURE__ */ jsx(TextShimmer, { children: "Thinking\u2026" }) : "Reasoning",
|
|
58
61
|
description: preview,
|
|
59
62
|
status: isActive ? "running" : "idle",
|
|
60
|
-
startTime,
|
|
61
63
|
durationMs,
|
|
62
64
|
open,
|
|
63
65
|
onOpenChange: setOpen,
|
|
@@ -107,7 +109,7 @@ function SpineRow({
|
|
|
107
109
|
"span",
|
|
108
110
|
{
|
|
109
111
|
className: cn(
|
|
110
|
-
"relative mt-
|
|
112
|
+
"relative mt-2.5 h-[var(--timeline-dot-size,0.5rem)] w-[var(--timeline-dot-size,0.5rem)] rounded-full ring-4 ring-[var(--bg-root)]",
|
|
111
113
|
accentClassName
|
|
112
114
|
)
|
|
113
115
|
}
|
|
@@ -18,10 +18,6 @@ function extractCommand(input) {
|
|
|
18
18
|
function extractFilePath(input) {
|
|
19
19
|
return extractString(input, "file_path") ?? extractString(input, "path") ?? extractString(input, "filePath") ?? extractString(input, "file");
|
|
20
20
|
}
|
|
21
|
-
function cleanPath(path) {
|
|
22
|
-
const parts = path.split("/");
|
|
23
|
-
return parts.length > 3 ? ".../" + parts.slice(-2).join("/") : path;
|
|
24
|
-
}
|
|
25
21
|
var TOOL_CATEGORY_ICONS = {
|
|
26
22
|
command: "terminal",
|
|
27
23
|
write: "file-plus",
|
|
@@ -87,7 +83,7 @@ function getToolDisplayMetadata(part) {
|
|
|
87
83
|
case "command":
|
|
88
84
|
case "execute":
|
|
89
85
|
return {
|
|
90
|
-
title: "
|
|
86
|
+
title: "Shell",
|
|
91
87
|
description: command ? truncateCommand(command) : void 0,
|
|
92
88
|
displayVariant: "command",
|
|
93
89
|
commandSnippet: command
|
|
@@ -96,7 +92,7 @@ function getToolDisplayMetadata(part) {
|
|
|
96
92
|
case "write_file":
|
|
97
93
|
case "create_file":
|
|
98
94
|
return {
|
|
99
|
-
title:
|
|
95
|
+
title: "Write",
|
|
100
96
|
description: filePath,
|
|
101
97
|
displayVariant: "write-file",
|
|
102
98
|
targetPath: filePath
|
|
@@ -104,7 +100,7 @@ function getToolDisplayMetadata(part) {
|
|
|
104
100
|
case "edit":
|
|
105
101
|
case "patch":
|
|
106
102
|
return {
|
|
107
|
-
title:
|
|
103
|
+
title: "Edit",
|
|
108
104
|
description: filePath,
|
|
109
105
|
hasDiffOutput: true,
|
|
110
106
|
diffFilePath: filePath,
|
|
@@ -115,7 +111,7 @@ function getToolDisplayMetadata(part) {
|
|
|
115
111
|
case "read_file":
|
|
116
112
|
case "cat":
|
|
117
113
|
return {
|
|
118
|
-
title:
|
|
114
|
+
title: "Read",
|
|
119
115
|
description: filePath,
|
|
120
116
|
displayVariant: "read-file",
|
|
121
117
|
targetPath: filePath
|
|
@@ -125,7 +121,7 @@ function getToolDisplayMetadata(part) {
|
|
|
125
121
|
case "rg": {
|
|
126
122
|
const pattern = extractString(input, "pattern");
|
|
127
123
|
return {
|
|
128
|
-
title:
|
|
124
|
+
title: "Search",
|
|
129
125
|
description: pattern,
|
|
130
126
|
displayVariant: "grep"
|
|
131
127
|
};
|
|
@@ -135,7 +131,7 @@ function getToolDisplayMetadata(part) {
|
|
|
135
131
|
case "ls": {
|
|
136
132
|
const pattern = extractString(input, "pattern");
|
|
137
133
|
return {
|
|
138
|
-
title:
|
|
134
|
+
title: "Find",
|
|
139
135
|
description: pattern,
|
|
140
136
|
displayVariant: "glob"
|
|
141
137
|
};
|
|
@@ -145,8 +141,8 @@ function getToolDisplayMetadata(part) {
|
|
|
145
141
|
case "fetch": {
|
|
146
142
|
const query = extractString(input, "query") ?? extractString(input, "url");
|
|
147
143
|
return {
|
|
148
|
-
title:
|
|
149
|
-
description: query,
|
|
144
|
+
title: name === "web_search" ? "Web search" : "Fetch",
|
|
145
|
+
description: query ? truncateCommand(query) : void 0,
|
|
150
146
|
displayVariant: "web-search"
|
|
151
147
|
};
|
|
152
148
|
}
|
|
@@ -155,8 +151,8 @@ function getToolDisplayMetadata(part) {
|
|
|
155
151
|
case "spawn": {
|
|
156
152
|
const desc = extractString(input, "description") ?? extractString(input, "prompt");
|
|
157
153
|
return {
|
|
158
|
-
title:
|
|
159
|
-
description: desc
|
|
154
|
+
title: "Task",
|
|
155
|
+
description: desc ? truncateCommand(desc) : void 0
|
|
160
156
|
};
|
|
161
157
|
}
|
|
162
158
|
default:
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cn
|
|
3
|
+
} from "./chunk-RQHJBTEU.js";
|
|
4
|
+
|
|
5
|
+
// src/primitives/text-shimmer.tsx
|
|
6
|
+
import { useEffect, useLayoutEffect } from "react";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
var CLASS_NAME = "tangle-text-shimmer";
|
|
9
|
+
var STYLE_ID = "tangle-text-shimmer-styles";
|
|
10
|
+
var SHIMMER_CSS = `
|
|
11
|
+
@keyframes ${CLASS_NAME} {
|
|
12
|
+
from { -webkit-mask-position: 150% center; mask-position: 150% center; }
|
|
13
|
+
to { -webkit-mask-position: -50% center; mask-position: -50% center; }
|
|
14
|
+
}
|
|
15
|
+
.${CLASS_NAME} {
|
|
16
|
+
-webkit-mask-image: linear-gradient(90deg, rgba(0,0,0,0.72) 0%, #000 45%, rgba(0,0,0,0.72) 60%);
|
|
17
|
+
mask-image: linear-gradient(90deg, rgba(0,0,0,0.72) 0%, #000 45%, rgba(0,0,0,0.72) 60%);
|
|
18
|
+
-webkit-mask-size: 250% 100%;
|
|
19
|
+
mask-size: 250% 100%;
|
|
20
|
+
animation: ${CLASS_NAME} 1.4s linear infinite;
|
|
21
|
+
}
|
|
22
|
+
@media (prefers-reduced-motion: reduce) {
|
|
23
|
+
.${CLASS_NAME} {
|
|
24
|
+
animation: none;
|
|
25
|
+
-webkit-mask-image: none;
|
|
26
|
+
mask-image: none;
|
|
27
|
+
text-decoration: underline dotted currentColor;
|
|
28
|
+
text-underline-offset: 3px;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
32
|
+
function ensureShimmerStyles() {
|
|
33
|
+
if (typeof document === "undefined") return;
|
|
34
|
+
if (document.getElementById(STYLE_ID)) return;
|
|
35
|
+
const style = document.createElement("style");
|
|
36
|
+
style.id = STYLE_ID;
|
|
37
|
+
style.textContent = SHIMMER_CSS;
|
|
38
|
+
document.head.appendChild(style);
|
|
39
|
+
}
|
|
40
|
+
var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
41
|
+
function TextShimmer({
|
|
42
|
+
children,
|
|
43
|
+
className,
|
|
44
|
+
durationMs = 1400
|
|
45
|
+
}) {
|
|
46
|
+
useIsomorphicLayoutEffect(ensureShimmerStyles, []);
|
|
47
|
+
return /* @__PURE__ */ jsx(
|
|
48
|
+
"span",
|
|
49
|
+
{
|
|
50
|
+
className: cn(CLASS_NAME, "text-foreground", className),
|
|
51
|
+
style: { animationDuration: `${durationMs}ms` },
|
|
52
|
+
children
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
TextShimmer
|
|
59
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatDuration
|
|
3
|
+
} from "./chunk-EIUE5GV2.js";
|
|
4
|
+
|
|
5
|
+
// src/run/run-item-primitives.tsx
|
|
6
|
+
import { useEffect, useState } from "react";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
function LiveDuration({ startTime }) {
|
|
9
|
+
const [elapsed, setElapsed] = useState(Date.now() - startTime);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const id = setInterval(() => setElapsed(Date.now() - startTime), 100);
|
|
12
|
+
return () => clearInterval(id);
|
|
13
|
+
}, [startTime]);
|
|
14
|
+
return /* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-neutral-400 dark:text-neutral-500 tabular-nums", children: formatDuration(elapsed) });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
LiveDuration
|
|
19
|
+
};
|