@tangle-network/ui 11.51.3 → 11.51.5
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 +8 -0
- package/dist/chat/user-message.js +1 -4
- package/dist/hooks/use-live-time.d.ts +3 -4
- package/dist/hooks/use-live-time.js +39 -16
- package/dist/hooks/use-sse-stream.js +3 -8
- package/dist/hooks.d.ts +2 -2
- package/dist/hooks.js +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/run/run-item-primitives.js +2 -6
- package/dist/run/status-meta.d.ts +2 -0
- package/dist/run/status-meta.js +12 -1
- package/dist/run.d.ts +2 -2
- package/dist/run.js +2 -2
- package/package.json +2 -2
- package/src/chat/user-message.tsx +1 -1
- package/src/hooks/use-live-time.ts +32 -24
- package/src/hooks/use-sse-stream.ts +3 -10
- package/src/run/index.ts +1 -0
- package/src/run/run-item-primitives.tsx +2 -7
- package/src/run/status-meta.tsx +7 -4
- package/src/chat/agent-timeline.test.tsx +0 -342
- package/src/chat/user-message.test.tsx +0 -39
- package/src/hooks/use-live-time.test.tsx +0 -40
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @tangle-network/ui
|
|
2
2
|
|
|
3
|
+
## 11.51.5
|
|
4
|
+
|
|
5
|
+
- Render user-message times explicitly in UTC so server and browser hydration agree across timezones. Share one clock across run duration, stream age and graph readers; keep replay timestamps fixed and export the canonical open-state predicate.
|
|
6
|
+
|
|
7
|
+
## 11.51.4
|
|
8
|
+
|
|
9
|
+
- Normalize retained record lifecycle aliases through the shared status vocabulary; stopped and absent terminal states remain distinct from success.
|
|
10
|
+
|
|
3
11
|
## 11.51.3
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
|
@@ -5,10 +5,7 @@ import { memo, useLayoutEffect, useRef, useState } from "react";
|
|
|
5
5
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
// src/chat/user-message.tsx
|
|
7
7
|
function formatTime(date) {
|
|
8
|
-
return date.
|
|
9
|
-
hour: "numeric",
|
|
10
|
-
minute: "2-digit"
|
|
11
|
-
});
|
|
8
|
+
return Number.isFinite(date.valueOf()) ? date.toISOString().slice(11, 16) + " UTC" : "Invalid Date";
|
|
12
9
|
}
|
|
13
10
|
function MessageActions({ children, align = "end" }) {
|
|
14
11
|
return /* @__PURE__ */ jsx("div", {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*/
|
|
1
|
+
/** One run clock; retained playback observes only its supplied timestamp. */
|
|
2
|
+
export declare const useRunNow: (fixed?: number) => number;
|
|
3
|
+
/** Existing cadence API shares the same clock and scheduler, including its 100ms floor. */
|
|
5
4
|
export declare function useLiveTime(intervalMs?: number): number;
|
|
@@ -1,21 +1,44 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import
|
|
2
|
+
import { useCallback, useRef, useSyncExternalStore } from "react";
|
|
3
3
|
// src/hooks/use-live-time.ts
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
4
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
5
|
+
let now = Date.now();
|
|
6
|
+
let timer;
|
|
7
|
+
function schedule() {
|
|
8
|
+
clearTimeout(timer);
|
|
9
|
+
if (!subscribers.size) return;
|
|
10
|
+
timer = setTimeout(() => {
|
|
11
|
+
now = Date.now();
|
|
12
|
+
for (const subscriber of subscribers) if (now >= subscriber.next) {
|
|
13
|
+
subscriber.next = now + subscriber.period;
|
|
14
|
+
subscriber.notify();
|
|
15
|
+
}
|
|
16
|
+
schedule();
|
|
17
|
+
}, Math.max(0, Math.min(...[...subscribers].map((subscriber) => subscriber.next)) - Date.now()));
|
|
18
|
+
}
|
|
19
|
+
function useClock(period, fixed, initial) {
|
|
20
|
+
const subscribe = useCallback((notify) => {
|
|
21
|
+
if (fixed !== void 0) return () => {};
|
|
22
|
+
if (!subscribers.size) now = Date.now();
|
|
23
|
+
const subscriber = {
|
|
24
|
+
period,
|
|
25
|
+
next: Math.floor(now / period + 1) * period,
|
|
26
|
+
notify
|
|
27
|
+
};
|
|
28
|
+
subscribers.add(subscriber);
|
|
29
|
+
schedule();
|
|
15
30
|
return () => {
|
|
16
|
-
|
|
31
|
+
subscribers.delete(subscriber);
|
|
32
|
+
schedule();
|
|
17
33
|
};
|
|
18
|
-
}, [
|
|
19
|
-
return now;
|
|
34
|
+
}, [period, fixed]);
|
|
35
|
+
return useSyncExternalStore(subscribe, () => fixed ?? Math.max(now, initial), () => fixed ?? initial);
|
|
36
|
+
}
|
|
37
|
+
/** One run clock; retained playback observes only its supplied timestamp. */
|
|
38
|
+
const useRunNow = (fixed) => useClock(1e3, fixed, 0);
|
|
39
|
+
/** Existing cadence API shares the same clock and scheduler, including its 100ms floor. */
|
|
40
|
+
function useLiveTime(intervalMs = 1e3) {
|
|
41
|
+
const initial = useRef(Date.now()).current;
|
|
42
|
+
return useClock(Number.isFinite(intervalMs) && intervalMs > 0 ? Math.max(intervalMs, 100) : 1e3, void 0, initial);
|
|
20
43
|
}
|
|
21
|
-
export { useLiveTime };
|
|
44
|
+
export { useLiveTime, useRunNow };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { useRunNow } from "./use-live-time.js";
|
|
2
3
|
import { openNativeStream } from "./use-event-stream.js";
|
|
3
4
|
import * as React from "react";
|
|
4
5
|
// src/hooks/use-sse-stream.ts
|
|
@@ -31,18 +32,12 @@ function useSSEStream(options) {
|
|
|
31
32
|
const [lastEvent, setLastEvent] = React.useState(null);
|
|
32
33
|
const [error, setError] = React.useState(null);
|
|
33
34
|
const [lastEventTime, setLastEventTime] = React.useState(Date.now());
|
|
34
|
-
const
|
|
35
|
+
const timeSinceLastEvent = Math.max(0, useRunNow() - lastEventTime);
|
|
35
36
|
const retryCountRef = React.useRef(0);
|
|
36
37
|
const eventSourceRef = React.useRef(null);
|
|
37
38
|
const abortControllerRef = React.useRef(null);
|
|
38
39
|
const reconnectTimeoutRef = React.useRef(null);
|
|
39
40
|
const lastEventIdRef = React.useRef(void 0);
|
|
40
|
-
React.useEffect(() => {
|
|
41
|
-
const interval = setInterval(() => {
|
|
42
|
-
setTimeSinceLastEvent(Date.now() - lastEventTime);
|
|
43
|
-
}, 1e3);
|
|
44
|
-
return () => clearInterval(interval);
|
|
45
|
-
}, [lastEventTime]);
|
|
46
41
|
React.useEffect(() => {
|
|
47
42
|
onStateChange?.(state);
|
|
48
43
|
}, [state, onStateChange]);
|
|
@@ -54,7 +49,7 @@ function useSSEStream(options) {
|
|
|
54
49
|
timestamp: Date.now()
|
|
55
50
|
};
|
|
56
51
|
if (id) lastEventIdRef.current = id;
|
|
57
|
-
setLastEventTime(
|
|
52
|
+
setLastEventTime(event.timestamp);
|
|
58
53
|
setLastEvent(event);
|
|
59
54
|
setEvents((prev) => {
|
|
60
55
|
const next = [...prev, event];
|
package/dist/hooks.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useAutoScroll } from "./hooks/use-auto-scroll.js";
|
|
2
2
|
import { useDropdownMenu } from "./hooks/use-dropdown-menu.js";
|
|
3
|
-
import { useLiveTime } from "./hooks/use-live-time.js";
|
|
3
|
+
import { useLiveTime, useRunNow } from "./hooks/use-live-time.js";
|
|
4
4
|
import { useRunCollapseState } from "./hooks/use-run-collapse-state.js";
|
|
5
5
|
import { UseRunGroupsOptions, useRunGroups } from "./hooks/use-run-groups.js";
|
|
6
6
|
import { UseToolCallStreamReturn, useToolCallStream } from "./hooks/use-tool-call-stream.js";
|
|
@@ -10,4 +10,4 @@ import { RealtimeSessionOptions, RealtimeSessionRegistry, RealtimeSessionRegistr
|
|
|
10
10
|
import { AgentStreamEvent, AutomationStreamEvent, BotStreamEvent, ConnectionState, SSEEvent, TaskStreamEvent, TerminalStreamEvent, UseSSEStreamOptions, UseSSEStreamResult, useSSEStream } from "./hooks/use-sse-stream.js";
|
|
11
11
|
import { EventStreamHandlers, EventStreamOpener, EventStreamSource, useEventStream } from "./hooks/use-event-stream.js";
|
|
12
12
|
import { useFollowTail } from "./hooks/use-follow-tail.js";
|
|
13
|
-
export { AgentStreamEvent, AppendUserMessageOptions, ApplySdkEventOptions, AuthUser, AutomationStreamEvent, BeginAssistantMessageOptions, BotStreamEvent, CompleteAssistantMessageOptions, ConnectionState, type EventStreamHandlers, type EventStreamOpener, type EventStreamSource, RealtimeSessionOptions, RealtimeSessionRegistry, RealtimeSessionRegistryProps, RealtimeSessionState, RealtimeSessionTarget, SSEEvent, SdkSessionAttachment, SdkSessionEvent, SdkSessionSeed, TaskStreamEvent, TerminalStreamEvent, UseAuthOptions, UseAuthResult, UseRunGroupsOptions, UseSSEStreamOptions, UseSSEStreamResult, UseSdkSessionOptions, UseSdkSessionReturn, UseToolCallStreamReturn, createAuthFetcher, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useEventStream, useFollowTail, useLiveTime, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useToolCallStream };
|
|
13
|
+
export { AgentStreamEvent, AppendUserMessageOptions, ApplySdkEventOptions, AuthUser, AutomationStreamEvent, BeginAssistantMessageOptions, BotStreamEvent, CompleteAssistantMessageOptions, ConnectionState, type EventStreamHandlers, type EventStreamOpener, type EventStreamSource, RealtimeSessionOptions, RealtimeSessionRegistry, RealtimeSessionRegistryProps, RealtimeSessionState, RealtimeSessionTarget, SSEEvent, SdkSessionAttachment, SdkSessionEvent, SdkSessionSeed, TaskStreamEvent, TerminalStreamEvent, UseAuthOptions, UseAuthResult, UseRunGroupsOptions, UseSSEStreamOptions, UseSSEStreamResult, UseSdkSessionOptions, UseSdkSessionReturn, UseToolCallStreamReturn, createAuthFetcher, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useEventStream, useFollowTail, useLiveTime, useRealtimeSession, useRunCollapseState, useRunGroups, useRunNow, useSSEStream, useSdkSession, useToolCallStream };
|
package/dist/hooks.js
CHANGED
|
@@ -2,12 +2,12 @@ import { useRunGroups } from "./hooks/use-run-groups.js";
|
|
|
2
2
|
import { useRunCollapseState } from "./hooks/use-run-collapse-state.js";
|
|
3
3
|
import { useFollowTail } from "./hooks/use-follow-tail.js";
|
|
4
4
|
import { useAutoScroll } from "./hooks/use-auto-scroll.js";
|
|
5
|
+
import { useLiveTime, useRunNow } from "./hooks/use-live-time.js";
|
|
5
6
|
import { useDropdownMenu } from "./hooks/use-dropdown-menu.js";
|
|
6
|
-
import { useLiveTime } from "./hooks/use-live-time.js";
|
|
7
7
|
import { useToolCallStream } from "./hooks/use-tool-call-stream.js";
|
|
8
8
|
import { createAuthFetcher, useApiKey, useAuth } from "./hooks/use-auth.js";
|
|
9
9
|
import { RealtimeSessionRegistry, useRealtimeSession } from "./hooks/use-realtime-session.js";
|
|
10
10
|
import { useSdkSession } from "./hooks/use-sdk-session.js";
|
|
11
11
|
import { useEventStream } from "./hooks/use-event-stream.js";
|
|
12
12
|
import { useSSEStream } from "./hooks/use-sse-stream.js";
|
|
13
|
-
export { RealtimeSessionRegistry, createAuthFetcher, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useEventStream, useFollowTail, useLiveTime, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useToolCallStream };
|
|
13
|
+
export { RealtimeSessionRegistry, createAuthFetcher, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useEventStream, useFollowTail, useLiveTime, useRealtimeSession, useRunCollapseState, useRunGroups, useRunNow, useSSEStream, useSdkSession, useToolCallStream };
|
package/dist/index.d.ts
CHANGED
|
@@ -40,7 +40,7 @@ import { FileFormat, FilePreviewKind, detectFileFormat, fileExtension, getCodeLa
|
|
|
40
40
|
import "./files.js";
|
|
41
41
|
import { useAutoScroll } from "./hooks/use-auto-scroll.js";
|
|
42
42
|
import { useDropdownMenu } from "./hooks/use-dropdown-menu.js";
|
|
43
|
-
import { useLiveTime } from "./hooks/use-live-time.js";
|
|
43
|
+
import { useLiveTime, useRunNow } from "./hooks/use-live-time.js";
|
|
44
44
|
import { useRunCollapseState } from "./hooks/use-run-collapse-state.js";
|
|
45
45
|
import { UseRunGroupsOptions, useRunGroups } from "./hooks/use-run-groups.js";
|
|
46
46
|
import { UseToolCallStreamReturn, useToolCallStream } from "./hooks/use-tool-call-stream.js";
|
|
@@ -120,7 +120,7 @@ import { LiveDuration } from "./run/run-item-primitives.js";
|
|
|
120
120
|
import { TaskList, TaskListItem, TaskListProps, TaskStatus, showsTaskPriority, taskProgress, taskStatus } from "./run/task-list.js";
|
|
121
121
|
import { ApprovalCard, ApprovalCardProps, ApprovalDiffFile, ApprovalDiffSummary, ApprovalPlanStep } from "./run/approval-card.js";
|
|
122
122
|
import { RunPhase, RunPhaseList } from "./run/run-phase-list.js";
|
|
123
|
-
import { RunStatus, RunStatusIcon, RunStatusIconProps, StatusMeta, runStatus, statusMeta } from "./run/status-meta.js";
|
|
123
|
+
import { RunStatus, RunStatusIcon, RunStatusIconProps, StatusMeta, isOpen, runStatus, statusMeta } from "./run/status-meta.js";
|
|
124
124
|
import "./run.js";
|
|
125
125
|
import { OPENUI_NODE_JSON_SCHEMA, OPENUI_NODE_TYPES } from "./openui/schema-data.js";
|
|
126
126
|
import { OpenUISchemaIssue, OpenUISchemaValidation, findUnsupportedOpenUINode, validateOpenUIJsonArtifact, validateOpenUIJsonNode } from "./openui-schema.js";
|
|
@@ -147,4 +147,4 @@ import { QuestionPreview, QuestionPreviewProps } from "./tool-previews/question-
|
|
|
147
147
|
import { DiffPreview, DiffPreviewProps } from "./tool-previews/diff-preview.js";
|
|
148
148
|
import { RedactedDocSegment, RedactedDocument, RedactedDocumentData, RedactedDocumentProps, RevealResult } from "./redaction/redacted-document.js";
|
|
149
149
|
import "./redaction.js";
|
|
150
|
-
export { ActiveProjectActivity, ActiveSessionActivityOptions, ActiveSessionConnectionOptions, ActiveSessionConnectionState, ActiveSessionReconnectState, ActiveSessionRecord, ActiveSessionStatus, ActiveSessionTransportMode, ActiveSessionsState, AgentBranding, AgentStreamEvent, AgentTimeline, type AgentTimelineArtifactItem, AgentTimelineColumn, type AgentTimelineCustomItem, type AgentTimelineFoldItem, type AgentTimelineItem, type AgentTimelineMessageItem, type AgentTimelineProps, type AgentTimelineStatusItem, AgentTimelineStep, type AgentTimelineStepKind, type AgentTimelineStepProps, type AgentTimelineTone, type AgentTimelineToolGroupItem, type AgentTimelineToolItem, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, type AlertProps, AlertTitle, type AlertTone, ApiSchema, type ApiSchemaProps, AppendUserMessageOptions, ApplySdkEventOptions, ApprovalCard, type ApprovalCardProps, type ApprovalDiffFile, ApprovalDiffSummary, type ApprovalPlanStep, ArtifactPane, type ArtifactPaneProps, AuthHeader, type AuthHeaderProps, AuthUser, AutomationStreamEvent, Avatar, AvatarFallback, AvatarImage, type AvatarProps, Badge, type BadgeProps, BeginAssistantMessageOptions, BotStreamEvent, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, CATEGORY_TONES, CSV_PREVIEW_ROW_LIMIT, Calendar, type CalendarDate, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, type CardTitleProps, type CategoryTone, type ChartReferenceLine, ChatAuthor, ChatContainer, type ChatContainerProps, ChatMessage, type ChatMessageProps, Checkbox, Chip, type ChipProps, CodeBlock, type CodeBlockProps, Collapsible, CollapsibleContent, CollapsibleTrigger, CommandPreview, type CommandPreviewProps, CompleteAssistantMessageOptions, ComplianceBadge, type ComplianceBadgeProps, type ComplianceProduct, type ComplianceStatus, ConnectionState, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, type CopyButtonProps, CustomToolRenderer, DatePicker, type DatePickerProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DiffPreview, type DiffPreviewProps, DisplayVariant, DropZone, type DropZoneProps, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, type EventStreamHandlers, type EventStreamOpener, type EventStreamSource, ExpandedToolDetail, type ExpandedToolDetailProps, type FeedSegment, Field, type FieldProps, FileArtifactPane, type FileArtifactPaneProps, FileCard, type FileCardProps, type FileDetails, type FileFormat, FileList, type FileListItem, type FileListProps, type FileNode, FilePreview, type FilePreviewKind, type FilePreviewProps, type FileTabData, FileTabs, type FileTabsProps, FileTree, type FileTreeProps, type FileTreeVisibilityOptions, type FileUploader, FilterField, type FilterFieldProps, FinalTextPart, GitHubLoginButton, type GitHubLoginButtonProps, GlobResultsPreview, type GlobResultsPreviewProps, GrepResultsPreview, type GrepResultsPreviewProps, GroupedMessage, Heading, type HeadingProps, type HeadingVariant, HelpText, type HelpTextProps, HoverCard, HoverCardContent, HoverCardDescription, HoverCardHeader, HoverCardTitle, HoverCardTrigger, IconTile, type IconTileProps, InlineCode, type InlineCodeProps, InlineThinkingItem, type InlineThinkingItemProps, InlineToolItem, type InlineToolItemProps, Input, type InputProps, Label, LiveDuration, LoginLayout, type LoginLayoutProps, Logo, type LogoProps, Markdown, type MarkdownLinkChip, type MarkdownLinkChipResolver, type MarkdownProps, MessageAuthor, type MessageAuthorProps, MessageList, type MessageListProps, type MessageRole, MessageRun, MessageUser, Metric, MetricChart, type MetricChartProps, type MetricProps, MetricStrip, type MetricStripProps, OPENUI_NODE_JSON_SCHEMA, OPENUI_NODE_TYPES, type OpenUIAction, type OpenUIActionsNode, OpenUIArtifactRenderer, type OpenUIArtifactRendererProps, type OpenUIBadgeNode, type OpenUICardNode, type OpenUICodeNode, type OpenUIComponentNode, type OpenUIGridNode, type OpenUIHeadingNode, type OpenUIKeyValueNode, type OpenUIMarkdownNode, type OpenUIPrimitive, type OpenUISchemaIssue, type OpenUISchemaValidation, type OpenUISeparatorNode, type OpenUIStackNode, type OpenUIStatNode, type OpenUITableNode, type OpenUITextNode, PageHeader, type PageHeaderProps, PageShell, type PageShellProps, Pagination, type PaginationProps, PdfViewer, type PdfViewerConfig, type PdfViewerProps, type PeriodDelta, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, QuestionPreview, type QuestionPreviewProps, RadioGroup, RadioGroupItem, RealtimeSessionOptions, RealtimeSessionRegistry, RealtimeSessionRegistryProps, RealtimeSessionState, RealtimeSessionTarget, ReasoningPart, type RedactedDocSegment, RedactedDocument, type RedactedDocumentData, type RedactedDocumentProps, RegisterActiveSessionOptions, type RevealResult, RichFileTree, type RichFileTreeGitEntry, type RichFileTreeGitStatus, type RichFileTreeProps, type RichFileTreeThemeVars, Run, RunGroup, type RunGroupProps, type RunPhase, RunPhaseList, RunRowShell, type RunRowShellProps, type RunRowStatus, RunRowStatusDot, RunStats, type RunStatus, RunStatusIcon, type RunStatusIconProps, SSEEvent, SdkSessionAttachment, SdkSessionEvent, SdkSessionSeed, SegmentedControl, type SegmentedControlOption, type SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeriesExtent, type SeriesPoint, type SeriesPointInput, type SeriesScale, SeriesSparkline, type SeriesSparklineProps, SessionMessage, SessionPart, SessionProjectKey, type SessionUser, SettingsList, type SettingsListAction, type SettingsListColumns, SettingsListItem, type SettingsListItemProps, type SettingsListMenuItem, type SettingsListProps, SettingsNote, type SettingsNoteProps, SettingsRow, type SettingsRowLayout, type SettingsRowProps, SettingsSection, type SettingsSectionProps, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, type SidebarDropZoneProps, Skeleton, SkeletonCard, SkeletonTable, Slider, type Speaker, SpeakerHeader, type SpeakerHeaderProps, type SpeakerPart, type SpeakerSection, type SpeakerState, StatCard, type StatCardProps, type StatusMeta, StatusPill, type StatusPillProps, type StatusTone, Switch, TONE_CLASSES, TOOL_CATEGORY_ICONS, TRANSCRIPT_TEXT, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, type TableProps, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, type TagProps, TangleKnot, TaskList, type TaskListItem, type TaskListProps, type TaskStatus, TaskStreamEvent, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TerminalStreamEvent, TextPart, TextShimmer, type TextShimmerProps, Textarea, type TextareaProps, ThemeToggle, ThinkingIndicator, type ThinkingIndicatorProps, TimeSelect, type TimeSelectProps, type Toast, ToastContainer, ToastProvider, type Tone, type ToneClasses, type ToolCallData, ToolCallFeed, type ToolCallFeedProps, type ToolCallStatus, type ToolCallType, ToolCategory, ToolDisplayMetadata, ToolPart, ToolState, ToolStatus, ToolTime, Toolbar, type ToolbarProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UploadFile, UploadProgress, type UploadProgressProps, UseAuthOptions, UseAuthResult, UseRunGroupsOptions, UseSSEStreamOptions, UseSSEStreamResult, UseSdkSessionOptions, UseSdkSessionReturn, UseToolCallStreamReturn, UserMenu, type UserMenuProps, UserMessage, type UserMessageProps, WebSearchPreview, type WebSearchPreviewProps, Wizard, type WizardProps, WizardStep, type WizardStepProps, WriteFilePreview, type WriteFilePreviewProps, activeSessionsAtom, addMessage, addParts, agentTimelineStepKind, badgeVariants, bumpActiveSessionActivity, buttonVariants, clearChat, cn, codeBlockGrammar, configurePdfViewer, copyText, createAuthFetcher, detectFileFormat, fenceInfo, fieldMessageId, fileExtension, filterFileTree, findUnsupportedOpenUINode, focusField, focusFieldInvalid, focusFieldWithin, focusRing, focusRingInset, foldSummary, formatBytes, formatCalendarDate, formatDuration, formatUptime, getActiveSession, getAllActiveSessions, getAllProjectActivity, getCodeLanguage, getFormatLabel, getSanitizedMarkdownHeadingIdFromRawFragment, getSessionsByActivity, getSessionsForNavbar, getSessionsForProject, getSyntaxLanguage, getToolCategory, getToolDisplayMetadata, getToolErrorText, getTotalRunningSessionCount, hasBackgroundRunningSessions, initialsOf, isStreamingAtom, isViewerMessage, ixControl, ixControlAfter, ixControlSoft, ixField, ixRow, ixSelected, ixTabUnderline, localToday, messagesAtom, normalizeSeries, numericColumns, parseCalendarDate, parseCsv, parseToolEvent, partMapAtom, partitionBySpeaker, periodDelta, plainTextFromMarkdown, registerActiveSession, resetActiveSessions, resolveFilePreviewKind, runStatus, seriesAreaPath, seriesExtent, seriesLinePath, seriesMean, seriesScale, setActiveSessionAttention, setActiveSessionConnection, setActiveSessionError, setActiveSessionRunning, setForegroundActiveSession, showsTaskPriority, statusMeta, tableCsv, taskProgress, taskStatus, timeAgo, toneFor, truncateText, unregisterActiveSession, updateActiveSessionMeta, updatePart, useActiveSession, useActiveSessions, useActiveSessionsState, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useEventStream, useFollowTail, useHasBackgroundRunningSessions, useLiveTime, useNavbarSessions, useProjectActivity, useProjectSessions, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useSessionsByActivity, useTheme, useToast, useToolCallStream, useTotalRunningSessions, validateOpenUIJsonArtifact, validateOpenUIJsonNode };
|
|
150
|
+
export { ActiveProjectActivity, ActiveSessionActivityOptions, ActiveSessionConnectionOptions, ActiveSessionConnectionState, ActiveSessionReconnectState, ActiveSessionRecord, ActiveSessionStatus, ActiveSessionTransportMode, ActiveSessionsState, AgentBranding, AgentStreamEvent, AgentTimeline, type AgentTimelineArtifactItem, AgentTimelineColumn, type AgentTimelineCustomItem, type AgentTimelineFoldItem, type AgentTimelineItem, type AgentTimelineMessageItem, type AgentTimelineProps, type AgentTimelineStatusItem, AgentTimelineStep, type AgentTimelineStepKind, type AgentTimelineStepProps, type AgentTimelineTone, type AgentTimelineToolGroupItem, type AgentTimelineToolItem, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, type AlertProps, AlertTitle, type AlertTone, ApiSchema, type ApiSchemaProps, AppendUserMessageOptions, ApplySdkEventOptions, ApprovalCard, type ApprovalCardProps, type ApprovalDiffFile, ApprovalDiffSummary, type ApprovalPlanStep, ArtifactPane, type ArtifactPaneProps, AuthHeader, type AuthHeaderProps, AuthUser, AutomationStreamEvent, Avatar, AvatarFallback, AvatarImage, type AvatarProps, Badge, type BadgeProps, BeginAssistantMessageOptions, BotStreamEvent, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, CATEGORY_TONES, CSV_PREVIEW_ROW_LIMIT, Calendar, type CalendarDate, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, type CardTitleProps, type CategoryTone, type ChartReferenceLine, ChatAuthor, ChatContainer, type ChatContainerProps, ChatMessage, type ChatMessageProps, Checkbox, Chip, type ChipProps, CodeBlock, type CodeBlockProps, Collapsible, CollapsibleContent, CollapsibleTrigger, CommandPreview, type CommandPreviewProps, CompleteAssistantMessageOptions, ComplianceBadge, type ComplianceBadgeProps, type ComplianceProduct, type ComplianceStatus, ConnectionState, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, type CopyButtonProps, CustomToolRenderer, DatePicker, type DatePickerProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DiffPreview, type DiffPreviewProps, DisplayVariant, DropZone, type DropZoneProps, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, type EventStreamHandlers, type EventStreamOpener, type EventStreamSource, ExpandedToolDetail, type ExpandedToolDetailProps, type FeedSegment, Field, type FieldProps, FileArtifactPane, type FileArtifactPaneProps, FileCard, type FileCardProps, type FileDetails, type FileFormat, FileList, type FileListItem, type FileListProps, type FileNode, FilePreview, type FilePreviewKind, type FilePreviewProps, type FileTabData, FileTabs, type FileTabsProps, FileTree, type FileTreeProps, type FileTreeVisibilityOptions, type FileUploader, FilterField, type FilterFieldProps, FinalTextPart, GitHubLoginButton, type GitHubLoginButtonProps, GlobResultsPreview, type GlobResultsPreviewProps, GrepResultsPreview, type GrepResultsPreviewProps, GroupedMessage, Heading, type HeadingProps, type HeadingVariant, HelpText, type HelpTextProps, HoverCard, HoverCardContent, HoverCardDescription, HoverCardHeader, HoverCardTitle, HoverCardTrigger, IconTile, type IconTileProps, InlineCode, type InlineCodeProps, InlineThinkingItem, type InlineThinkingItemProps, InlineToolItem, type InlineToolItemProps, Input, type InputProps, Label, LiveDuration, LoginLayout, type LoginLayoutProps, Logo, type LogoProps, Markdown, type MarkdownLinkChip, type MarkdownLinkChipResolver, type MarkdownProps, MessageAuthor, type MessageAuthorProps, MessageList, type MessageListProps, type MessageRole, MessageRun, MessageUser, Metric, MetricChart, type MetricChartProps, type MetricProps, MetricStrip, type MetricStripProps, OPENUI_NODE_JSON_SCHEMA, OPENUI_NODE_TYPES, type OpenUIAction, type OpenUIActionsNode, OpenUIArtifactRenderer, type OpenUIArtifactRendererProps, type OpenUIBadgeNode, type OpenUICardNode, type OpenUICodeNode, type OpenUIComponentNode, type OpenUIGridNode, type OpenUIHeadingNode, type OpenUIKeyValueNode, type OpenUIMarkdownNode, type OpenUIPrimitive, type OpenUISchemaIssue, type OpenUISchemaValidation, type OpenUISeparatorNode, type OpenUIStackNode, type OpenUIStatNode, type OpenUITableNode, type OpenUITextNode, PageHeader, type PageHeaderProps, PageShell, type PageShellProps, Pagination, type PaginationProps, PdfViewer, type PdfViewerConfig, type PdfViewerProps, type PeriodDelta, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, QuestionPreview, type QuestionPreviewProps, RadioGroup, RadioGroupItem, RealtimeSessionOptions, RealtimeSessionRegistry, RealtimeSessionRegistryProps, RealtimeSessionState, RealtimeSessionTarget, ReasoningPart, type RedactedDocSegment, RedactedDocument, type RedactedDocumentData, type RedactedDocumentProps, RegisterActiveSessionOptions, type RevealResult, RichFileTree, type RichFileTreeGitEntry, type RichFileTreeGitStatus, type RichFileTreeProps, type RichFileTreeThemeVars, Run, RunGroup, type RunGroupProps, type RunPhase, RunPhaseList, RunRowShell, type RunRowShellProps, type RunRowStatus, RunRowStatusDot, RunStats, type RunStatus, RunStatusIcon, type RunStatusIconProps, SSEEvent, SdkSessionAttachment, SdkSessionEvent, SdkSessionSeed, SegmentedControl, type SegmentedControlOption, type SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeriesExtent, type SeriesPoint, type SeriesPointInput, type SeriesScale, SeriesSparkline, type SeriesSparklineProps, SessionMessage, SessionPart, SessionProjectKey, type SessionUser, SettingsList, type SettingsListAction, type SettingsListColumns, SettingsListItem, type SettingsListItemProps, type SettingsListMenuItem, type SettingsListProps, SettingsNote, type SettingsNoteProps, SettingsRow, type SettingsRowLayout, type SettingsRowProps, SettingsSection, type SettingsSectionProps, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, type SidebarDropZoneProps, Skeleton, SkeletonCard, SkeletonTable, Slider, type Speaker, SpeakerHeader, type SpeakerHeaderProps, type SpeakerPart, type SpeakerSection, type SpeakerState, StatCard, type StatCardProps, type StatusMeta, StatusPill, type StatusPillProps, type StatusTone, Switch, TONE_CLASSES, TOOL_CATEGORY_ICONS, TRANSCRIPT_TEXT, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, type TableProps, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, type TagProps, TangleKnot, TaskList, type TaskListItem, type TaskListProps, type TaskStatus, TaskStreamEvent, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TerminalStreamEvent, TextPart, TextShimmer, type TextShimmerProps, Textarea, type TextareaProps, ThemeToggle, ThinkingIndicator, type ThinkingIndicatorProps, TimeSelect, type TimeSelectProps, type Toast, ToastContainer, ToastProvider, type Tone, type ToneClasses, type ToolCallData, ToolCallFeed, type ToolCallFeedProps, type ToolCallStatus, type ToolCallType, ToolCategory, ToolDisplayMetadata, ToolPart, ToolState, ToolStatus, ToolTime, Toolbar, type ToolbarProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UploadFile, UploadProgress, type UploadProgressProps, UseAuthOptions, UseAuthResult, UseRunGroupsOptions, UseSSEStreamOptions, UseSSEStreamResult, UseSdkSessionOptions, UseSdkSessionReturn, UseToolCallStreamReturn, UserMenu, type UserMenuProps, UserMessage, type UserMessageProps, WebSearchPreview, type WebSearchPreviewProps, Wizard, type WizardProps, WizardStep, type WizardStepProps, WriteFilePreview, type WriteFilePreviewProps, activeSessionsAtom, addMessage, addParts, agentTimelineStepKind, badgeVariants, bumpActiveSessionActivity, buttonVariants, clearChat, cn, codeBlockGrammar, configurePdfViewer, copyText, createAuthFetcher, detectFileFormat, fenceInfo, fieldMessageId, fileExtension, filterFileTree, findUnsupportedOpenUINode, focusField, focusFieldInvalid, focusFieldWithin, focusRing, focusRingInset, foldSummary, formatBytes, formatCalendarDate, formatDuration, formatUptime, getActiveSession, getAllActiveSessions, getAllProjectActivity, getCodeLanguage, getFormatLabel, getSanitizedMarkdownHeadingIdFromRawFragment, getSessionsByActivity, getSessionsForNavbar, getSessionsForProject, getSyntaxLanguage, getToolCategory, getToolDisplayMetadata, getToolErrorText, getTotalRunningSessionCount, hasBackgroundRunningSessions, initialsOf, isOpen, isStreamingAtom, isViewerMessage, ixControl, ixControlAfter, ixControlSoft, ixField, ixRow, ixSelected, ixTabUnderline, localToday, messagesAtom, normalizeSeries, numericColumns, parseCalendarDate, parseCsv, parseToolEvent, partMapAtom, partitionBySpeaker, periodDelta, plainTextFromMarkdown, registerActiveSession, resetActiveSessions, resolveFilePreviewKind, runStatus, seriesAreaPath, seriesExtent, seriesLinePath, seriesMean, seriesScale, setActiveSessionAttention, setActiveSessionConnection, setActiveSessionError, setActiveSessionRunning, setForegroundActiveSession, showsTaskPriority, statusMeta, tableCsv, taskProgress, taskStatus, timeAgo, toneFor, truncateText, unregisterActiveSession, updateActiveSessionMeta, updatePart, useActiveSession, useActiveSessions, useActiveSessionsState, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useEventStream, useFollowTail, useHasBackgroundRunningSessions, useLiveTime, useNavbarSessions, useProjectActivity, useProjectSessions, useRealtimeSession, useRunCollapseState, useRunGroups, useRunNow, useSSEStream, useSdkSession, useSessionsByActivity, useTheme, useToast, useToolCallStream, useTotalRunningSessions, validateOpenUIJsonArtifact, validateOpenUIJsonNode };
|
package/dist/index.js
CHANGED
|
@@ -80,7 +80,7 @@ import { GlobResultsPreview } from "./tool-previews/glob-results-preview.js";
|
|
|
80
80
|
import { WebSearchPreview } from "./tool-previews/web-search-preview.js";
|
|
81
81
|
import { QuestionPreview } from "./tool-previews/question-preview.js";
|
|
82
82
|
import { DiffPreview } from "./tool-previews/diff-preview.js";
|
|
83
|
-
import { RunStatusIcon, runStatus, statusMeta } from "./run/status-meta.js";
|
|
83
|
+
import { RunStatusIcon, isOpen, runStatus, statusMeta } from "./run/status-meta.js";
|
|
84
84
|
import { ExpandedToolDetail } from "./run/expanded-tool-detail.js";
|
|
85
85
|
import { RunRowShell, RunRowStatusDot } from "./run/run-row-shell.js";
|
|
86
86
|
import { InlineToolItem } from "./run/inline-tool-item.js";
|
|
@@ -101,6 +101,7 @@ import { AgentTimeline, AgentTimelineColumn, AgentTimelineStep, agentTimelineSte
|
|
|
101
101
|
import { ChatContainer } from "./chat/chat-container.js";
|
|
102
102
|
import { ChatMessage } from "./chat/chat-message.js";
|
|
103
103
|
import { SpeakerHeader, partitionBySpeaker } from "./chat/speaker.js";
|
|
104
|
+
import { useLiveTime, useRunNow } from "./hooks/use-live-time.js";
|
|
104
105
|
import { LiveDuration } from "./run/run-item-primitives.js";
|
|
105
106
|
import { ToolCallFeed, parseToolEvent } from "./run/tool-call-feed.js";
|
|
106
107
|
import { TaskList, showsTaskPriority, taskProgress, taskStatus } from "./run/task-list.js";
|
|
@@ -119,7 +120,6 @@ import { ApiSchema } from "./markdown/api-schema.js";
|
|
|
119
120
|
import { AuthHeader, GitHubLoginButton, UserMenu } from "./auth/auth.js";
|
|
120
121
|
import { LoginLayout } from "./auth/login-layout.js";
|
|
121
122
|
import { useDropdownMenu } from "./hooks/use-dropdown-menu.js";
|
|
122
|
-
import { useLiveTime } from "./hooks/use-live-time.js";
|
|
123
123
|
import { useToolCallStream } from "./hooks/use-tool-call-stream.js";
|
|
124
124
|
import { createAuthFetcher, useApiKey, useAuth } from "./hooks/use-auth.js";
|
|
125
125
|
import { activeSessionsAtom, bumpActiveSessionActivity, getActiveSession, getAllActiveSessions, getAllProjectActivity, getSessionsByActivity, getSessionsForNavbar, getSessionsForProject, getTotalRunningSessionCount, hasBackgroundRunningSessions, registerActiveSession, resetActiveSessions, setActiveSessionAttention, setActiveSessionConnection, setActiveSessionError, setActiveSessionRunning, setForegroundActiveSession, unregisterActiveSession, updateActiveSessionMeta, useActiveSession, useActiveSessions, useActiveSessionsState, useHasBackgroundRunningSessions, useNavbarSessions, useProjectActivity, useProjectSessions, useSessionsByActivity, useTotalRunningSessions } from "./stores/active-sessions-store.js";
|
|
@@ -131,4 +131,4 @@ import { addMessage, addParts, clearChat, isStreamingAtom, messagesAtom, partMap
|
|
|
131
131
|
import { copyText } from "./utils/copy-text.js";
|
|
132
132
|
import { timeAgo } from "./utils/time-ago.js";
|
|
133
133
|
import { RedactedDocument } from "./redaction/redacted-document.js";
|
|
134
|
-
export { AgentTimeline, AgentTimelineColumn, AgentTimelineStep, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, ApiSchema, ApprovalCard, ApprovalDiffSummary, ArtifactPane, AuthHeader, Avatar, AvatarFallback, AvatarImage, Badge, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, CATEGORY_TONES, CSV_PREVIEW_ROW_LIMIT, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ChatContainer, ChatMessage, Checkbox, Chip, CodeBlock, Collapsible, CollapsibleContent, CollapsibleTrigger, CommandPreview, ComplianceBadge, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, DatePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DiffPreview, DropZone, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, ExpandedToolDetail, Field, FileArtifactPane, FileCard, FileList, FilePreview, FileTabs, FileTree, FilterField, GitHubLoginButton, GlobResultsPreview, GrepResultsPreview, Heading, HelpText, HoverCard, HoverCardContent, HoverCardDescription, HoverCardHeader, HoverCardTitle, HoverCardTrigger, IconTile, InlineCode, InlineThinkingItem, InlineToolItem, Input, Label, LiveDuration, LoginLayout, Logo, Markdown, MessageAuthor, MessageList, Metric, MetricChart, MetricStrip, OPENUI_NODE_JSON_SCHEMA, OPENUI_NODE_TYPES, OpenUIArtifactRenderer, PageHeader, PageShell, Pagination, PdfViewer, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, QuestionPreview, RadioGroup, RadioGroupItem, RealtimeSessionRegistry, RedactedDocument, RichFileTree, RunGroup, RunPhaseList, RunRowShell, RunRowStatusDot, RunStatusIcon, SegmentedControl, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, SeriesSparkline, SettingsList, SettingsListItem, SettingsNote, SettingsRow, SettingsSection, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, Skeleton, SkeletonCard, SkeletonTable, Slider, SpeakerHeader, StatCard, StatusPill, Switch, TONE_CLASSES, TOOL_CATEGORY_ICONS, TRANSCRIPT_TEXT, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, TangleKnot, TaskList, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, Textarea, ThemeToggle, ThinkingIndicator, TimeSelect, ToastContainer, ToastProvider, ToolCallFeed, Toolbar, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UploadProgress, UserMenu, UserMessage, WebSearchPreview, Wizard, WizardStep, WriteFilePreview, activeSessionsAtom, addMessage, addParts, agentTimelineStepKind, badgeVariants, bumpActiveSessionActivity, buttonVariants, clearChat, cn, codeBlockGrammar, configurePdfViewer, copyText, createAuthFetcher, detectFileFormat, fenceInfo, fieldMessageId, fileExtension, filterFileTree, findUnsupportedOpenUINode, focusField, focusFieldInvalid, focusFieldWithin, focusRing, focusRingInset, foldSummary, formatBytes, formatCalendarDate, formatDuration, formatUptime, getActiveSession, getAllActiveSessions, getAllProjectActivity, getCodeLanguage, getFormatLabel, getSanitizedMarkdownHeadingIdFromRawFragment, getSessionsByActivity, getSessionsForNavbar, getSessionsForProject, getSyntaxLanguage, getToolCategory, getToolDisplayMetadata, getToolErrorText, getTotalRunningSessionCount, hasBackgroundRunningSessions, initialsOf, isStreamingAtom, isViewerMessage, ixControl, ixControlAfter, ixControlSoft, ixField, ixRow, ixSelected, ixTabUnderline, localToday, messagesAtom, normalizeSeries, numericColumns, parseCalendarDate, parseCsv, parseToolEvent, partMapAtom, partitionBySpeaker, periodDelta, plainTextFromMarkdown, registerActiveSession, resetActiveSessions, resolveFilePreviewKind, runStatus, seriesAreaPath, seriesExtent, seriesLinePath, seriesMean, seriesScale, setActiveSessionAttention, setActiveSessionConnection, setActiveSessionError, setActiveSessionRunning, setForegroundActiveSession, showsTaskPriority, statusMeta, tableCsv, taskProgress, taskStatus, timeAgo, toneFor, truncateText, unregisterActiveSession, updateActiveSessionMeta, updatePart, useActiveSession, useActiveSessions, useActiveSessionsState, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useEventStream, useFollowTail, useHasBackgroundRunningSessions, useLiveTime, useNavbarSessions, useProjectActivity, useProjectSessions, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useSessionsByActivity, useTheme, useToast, useToolCallStream, useTotalRunningSessions, validateOpenUIJsonArtifact, validateOpenUIJsonNode };
|
|
134
|
+
export { AgentTimeline, AgentTimelineColumn, AgentTimelineStep, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, ApiSchema, ApprovalCard, ApprovalDiffSummary, ArtifactPane, AuthHeader, Avatar, AvatarFallback, AvatarImage, Badge, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, CATEGORY_TONES, CSV_PREVIEW_ROW_LIMIT, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ChatContainer, ChatMessage, Checkbox, Chip, CodeBlock, Collapsible, CollapsibleContent, CollapsibleTrigger, CommandPreview, ComplianceBadge, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, DatePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DiffPreview, DropZone, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, ExpandedToolDetail, Field, FileArtifactPane, FileCard, FileList, FilePreview, FileTabs, FileTree, FilterField, GitHubLoginButton, GlobResultsPreview, GrepResultsPreview, Heading, HelpText, HoverCard, HoverCardContent, HoverCardDescription, HoverCardHeader, HoverCardTitle, HoverCardTrigger, IconTile, InlineCode, InlineThinkingItem, InlineToolItem, Input, Label, LiveDuration, LoginLayout, Logo, Markdown, MessageAuthor, MessageList, Metric, MetricChart, MetricStrip, OPENUI_NODE_JSON_SCHEMA, OPENUI_NODE_TYPES, OpenUIArtifactRenderer, PageHeader, PageShell, Pagination, PdfViewer, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, QuestionPreview, RadioGroup, RadioGroupItem, RealtimeSessionRegistry, RedactedDocument, RichFileTree, RunGroup, RunPhaseList, RunRowShell, RunRowStatusDot, RunStatusIcon, SegmentedControl, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, SeriesSparkline, SettingsList, SettingsListItem, SettingsNote, SettingsRow, SettingsSection, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, Skeleton, SkeletonCard, SkeletonTable, Slider, SpeakerHeader, StatCard, StatusPill, Switch, TONE_CLASSES, TOOL_CATEGORY_ICONS, TRANSCRIPT_TEXT, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, TangleKnot, TaskList, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, Textarea, ThemeToggle, ThinkingIndicator, TimeSelect, ToastContainer, ToastProvider, ToolCallFeed, Toolbar, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UploadProgress, UserMenu, UserMessage, WebSearchPreview, Wizard, WizardStep, WriteFilePreview, activeSessionsAtom, addMessage, addParts, agentTimelineStepKind, badgeVariants, bumpActiveSessionActivity, buttonVariants, clearChat, cn, codeBlockGrammar, configurePdfViewer, copyText, createAuthFetcher, detectFileFormat, fenceInfo, fieldMessageId, fileExtension, filterFileTree, findUnsupportedOpenUINode, focusField, focusFieldInvalid, focusFieldWithin, focusRing, focusRingInset, foldSummary, formatBytes, formatCalendarDate, formatDuration, formatUptime, getActiveSession, getAllActiveSessions, getAllProjectActivity, getCodeLanguage, getFormatLabel, getSanitizedMarkdownHeadingIdFromRawFragment, getSessionsByActivity, getSessionsForNavbar, getSessionsForProject, getSyntaxLanguage, getToolCategory, getToolDisplayMetadata, getToolErrorText, getTotalRunningSessionCount, hasBackgroundRunningSessions, initialsOf, isOpen, isStreamingAtom, isViewerMessage, ixControl, ixControlAfter, ixControlSoft, ixField, ixRow, ixSelected, ixTabUnderline, localToday, messagesAtom, normalizeSeries, numericColumns, parseCalendarDate, parseCsv, parseToolEvent, partMapAtom, partitionBySpeaker, periodDelta, plainTextFromMarkdown, registerActiveSession, resetActiveSessions, resolveFilePreviewKind, runStatus, seriesAreaPath, seriesExtent, seriesLinePath, seriesMean, seriesScale, setActiveSessionAttention, setActiveSessionConnection, setActiveSessionError, setActiveSessionRunning, setForegroundActiveSession, showsTaskPriority, statusMeta, tableCsv, taskProgress, taskStatus, timeAgo, toneFor, truncateText, unregisterActiveSession, updateActiveSessionMeta, updatePart, useActiveSession, useActiveSessions, useActiveSessionsState, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useEventStream, useFollowTail, useHasBackgroundRunningSessions, useLiveTime, useNavbarSessions, useProjectActivity, useProjectSessions, useRealtimeSession, useRunCollapseState, useRunGroups, useRunNow, useSSEStream, useSdkSession, useSessionsByActivity, useTheme, useToast, useToolCallStream, useTotalRunningSessions, validateOpenUIJsonArtifact, validateOpenUIJsonNode };
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { formatDuration } from "../utils/format.js";
|
|
2
|
-
import {
|
|
2
|
+
import { useLiveTime } from "../hooks/use-live-time.js";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
// src/run/run-item-primitives.tsx
|
|
5
5
|
function LiveDuration({ startTime }) {
|
|
6
|
-
const
|
|
7
|
-
useEffect(() => {
|
|
8
|
-
const id = setInterval(() => setElapsed(Date.now() - startTime), 100);
|
|
9
|
-
return () => clearInterval(id);
|
|
10
|
-
}, [startTime]);
|
|
6
|
+
const elapsed = useLiveTime(100) - startTime;
|
|
11
7
|
return /* @__PURE__ */ jsx("span", {
|
|
12
8
|
className: "text-xs font-mono text-neutral-400 dark:text-neutral-500 tabular-nums",
|
|
13
9
|
children: formatDuration(elapsed)
|
|
@@ -11,6 +11,8 @@ export interface StatusMeta {
|
|
|
11
11
|
}
|
|
12
12
|
/** Any status string as a `RunStatus`; a value nothing names reads as Unknown, never as a guess. */
|
|
13
13
|
export declare function runStatus(status: string): RunStatus;
|
|
14
|
+
/** Whether a canonical run state can still advance. */
|
|
15
|
+
export declare const isOpen: (status: RunStatus | (string & {})) => boolean;
|
|
14
16
|
/** The label, glyph and colour token for a status, in every surface. */
|
|
15
17
|
export declare function statusMeta(status: RunStatus | (string & {})): StatusMeta;
|
|
16
18
|
export interface RunStatusIconProps extends Omit<LucideProps, "ref"> {
|
package/dist/run/status-meta.js
CHANGED
|
@@ -46,6 +46,7 @@ const META = {
|
|
|
46
46
|
};
|
|
47
47
|
const ALIASES = {
|
|
48
48
|
active: "running",
|
|
49
|
+
working: "running",
|
|
49
50
|
in_progress: "running",
|
|
50
51
|
"in-progress": "running",
|
|
51
52
|
streaming: "running",
|
|
@@ -56,6 +57,7 @@ const ALIASES = {
|
|
|
56
57
|
needs_you: "needs-you",
|
|
57
58
|
needs_input: "needs-you",
|
|
58
59
|
awaiting_input: "needs-you",
|
|
60
|
+
winner: "done",
|
|
59
61
|
completed: "done",
|
|
60
62
|
complete: "done",
|
|
61
63
|
success: "done",
|
|
@@ -63,6 +65,9 @@ const ALIASES = {
|
|
|
63
65
|
shipped: "done",
|
|
64
66
|
error: "failed",
|
|
65
67
|
errored: "failed",
|
|
68
|
+
"driver-failed": "failed",
|
|
69
|
+
down: "stopped",
|
|
70
|
+
"no-winner": "stopped",
|
|
66
71
|
cancelled: "stopped",
|
|
67
72
|
canceled: "stopped",
|
|
68
73
|
aborted: "stopped"
|
|
@@ -73,6 +78,12 @@ function runStatus(status) {
|
|
|
73
78
|
if (Object.hasOwn(META, key)) return key;
|
|
74
79
|
return Object.hasOwn(ALIASES, key) ? ALIASES[key] : "unknown";
|
|
75
80
|
}
|
|
81
|
+
/** Whether a canonical run state can still advance. */
|
|
82
|
+
const isOpen = (status) => [
|
|
83
|
+
"running",
|
|
84
|
+
"waiting",
|
|
85
|
+
"needs-you"
|
|
86
|
+
].includes(runStatus(status));
|
|
76
87
|
/** The label, glyph and colour token for a status, in every surface. */
|
|
77
88
|
function statusMeta(status) {
|
|
78
89
|
return META[runStatus(status)];
|
|
@@ -102,4 +113,4 @@ function RunStatusIcon({ status, className, style, ...props }) {
|
|
|
102
113
|
...props
|
|
103
114
|
});
|
|
104
115
|
}
|
|
105
|
-
export { RunStatusIcon, runStatus, statusMeta };
|
|
116
|
+
export { RunStatusIcon, isOpen, runStatus, statusMeta };
|
package/dist/run.d.ts
CHANGED
|
@@ -9,5 +9,5 @@ import { LiveDuration } from "./run/run-item-primitives.js";
|
|
|
9
9
|
import { TaskList, TaskListItem, TaskListProps, TaskStatus, showsTaskPriority, taskProgress, taskStatus } from "./run/task-list.js";
|
|
10
10
|
import { ApprovalCard, ApprovalCardProps, ApprovalDiffFile, ApprovalDiffSummary, ApprovalPlanStep } from "./run/approval-card.js";
|
|
11
11
|
import { RunPhase, RunPhaseList } from "./run/run-phase-list.js";
|
|
12
|
-
import { RunStatus, RunStatusIcon, RunStatusIconProps, StatusMeta, runStatus, statusMeta } from "./run/status-meta.js";
|
|
13
|
-
export { ApprovalCard, type ApprovalCardProps, type ApprovalDiffFile, ApprovalDiffSummary, type ApprovalPlanStep, ExpandedToolDetail, type ExpandedToolDetailProps, type FeedSegment, InlineThinkingItem, type InlineThinkingItemProps, InlineToolItem, type InlineToolItemProps, LiveDuration, RunGroup, type RunGroupProps, type RunPhase, RunPhaseList, RunRowShell, type RunRowShellProps, type RunRowStatus, RunRowStatusDot, type RunStatus, RunStatusIcon, type RunStatusIconProps, type StatusMeta, TaskList, type TaskListItem, type TaskListProps, type TaskStatus, type ToolCallData, ToolCallFeed, type ToolCallFeedProps, type ToolCallStatus, type ToolCallType, parseToolEvent, runStatus, showsTaskPriority, statusMeta, taskProgress, taskStatus };
|
|
12
|
+
import { RunStatus, RunStatusIcon, RunStatusIconProps, StatusMeta, isOpen, runStatus, statusMeta } from "./run/status-meta.js";
|
|
13
|
+
export { ApprovalCard, type ApprovalCardProps, type ApprovalDiffFile, ApprovalDiffSummary, type ApprovalPlanStep, ExpandedToolDetail, type ExpandedToolDetailProps, type FeedSegment, InlineThinkingItem, type InlineThinkingItemProps, InlineToolItem, type InlineToolItemProps, LiveDuration, RunGroup, type RunGroupProps, type RunPhase, RunPhaseList, RunRowShell, type RunRowShellProps, type RunRowStatus, RunRowStatusDot, type RunStatus, RunStatusIcon, type RunStatusIconProps, type StatusMeta, TaskList, type TaskListItem, type TaskListProps, type TaskStatus, type ToolCallData, ToolCallFeed, type ToolCallFeedProps, type ToolCallStatus, type ToolCallType, isOpen, parseToolEvent, runStatus, showsTaskPriority, statusMeta, taskProgress, taskStatus };
|
package/dist/run.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RunStatusIcon, runStatus, statusMeta } from "./run/status-meta.js";
|
|
1
|
+
import { RunStatusIcon, isOpen, runStatus, statusMeta } from "./run/status-meta.js";
|
|
2
2
|
import { ExpandedToolDetail } from "./run/expanded-tool-detail.js";
|
|
3
3
|
import { RunRowShell, RunRowStatusDot } from "./run/run-row-shell.js";
|
|
4
4
|
import { InlineToolItem } from "./run/inline-tool-item.js";
|
|
@@ -9,4 +9,4 @@ import { ToolCallFeed, parseToolEvent } from "./run/tool-call-feed.js";
|
|
|
9
9
|
import { TaskList, showsTaskPriority, taskProgress, taskStatus } from "./run/task-list.js";
|
|
10
10
|
import { ApprovalCard, ApprovalDiffSummary } from "./run/approval-card.js";
|
|
11
11
|
import { RunPhaseList } from "./run/run-phase-list.js";
|
|
12
|
-
export { ApprovalCard, ApprovalDiffSummary, ExpandedToolDetail, InlineThinkingItem, InlineToolItem, LiveDuration, RunGroup, RunPhaseList, RunRowShell, RunRowStatusDot, RunStatusIcon, TaskList, ToolCallFeed, parseToolEvent, runStatus, showsTaskPriority, statusMeta, taskProgress, taskStatus };
|
|
12
|
+
export { ApprovalCard, ApprovalDiffSummary, ExpandedToolDetail, InlineThinkingItem, InlineToolItem, LiveDuration, RunGroup, RunPhaseList, RunRowShell, RunRowStatusDot, RunStatusIcon, TaskList, ToolCallFeed, isOpen, parseToolEvent, runStatus, showsTaskPriority, statusMeta, taskProgress, taskStatus };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/ui",
|
|
3
|
-
"version": "11.51.
|
|
3
|
+
"version": "11.51.5",
|
|
4
4
|
"description": "Generic React UI components for Tangle products — primitives, chat, run, files, editor, markdown.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -167,7 +167,7 @@
|
|
|
167
167
|
"peerDependencies": {
|
|
168
168
|
"@hocuspocus/provider": ">=2.15.0 <5.0.0",
|
|
169
169
|
"@nanostores/react": ">=0.8.4 <3.0.0",
|
|
170
|
-
"@tangle-network/brand": "^1.31.
|
|
170
|
+
"@tangle-network/brand": "^1.31.3",
|
|
171
171
|
"@tiptap/core": ">=3.20.4 <4.0.0",
|
|
172
172
|
"@tiptap/extension-collaboration": ">=3.20.4 <4.0.0",
|
|
173
173
|
"@tiptap/extension-collaboration-caret": ">=3.7.0 <4.0.0",
|
|
@@ -31,7 +31,7 @@ export interface UserMessageProps {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
function formatTime(date: Date): string {
|
|
34
|
-
return date.
|
|
34
|
+
return Number.isFinite(date.valueOf()) ? date.toISOString().slice(11, 16) + " UTC" : "Invalid Date";
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function MessageActions({ children, align = "end" }: { children: ReactNode; align?: "start" | "end" }) {
|
|
@@ -1,27 +1,35 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { useCallback, useRef, useSyncExternalStore } from "react";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return now;
|
|
4
|
+
type Subscriber = { period: number; next: number; notify: () => void };
|
|
5
|
+
const subscribers = new Set<Subscriber>();
|
|
6
|
+
let now = Date.now(), timer: ReturnType<typeof setTimeout> | undefined;
|
|
7
|
+
function schedule() {
|
|
8
|
+
clearTimeout(timer);
|
|
9
|
+
if (!subscribers.size) return;
|
|
10
|
+
timer = setTimeout(() => {
|
|
11
|
+
now = Date.now();
|
|
12
|
+
for (const subscriber of subscribers) if (now >= subscriber.next) {
|
|
13
|
+
subscriber.next = now + subscriber.period;
|
|
14
|
+
subscriber.notify();
|
|
15
|
+
}
|
|
16
|
+
schedule();
|
|
17
|
+
}, Math.max(0, Math.min(...[...subscribers].map(subscriber => subscriber.next)) - Date.now()));
|
|
18
|
+
}
|
|
19
|
+
function useClock(period: number, fixed: number | undefined, initial: number) {
|
|
20
|
+
const subscribe = useCallback((notify: () => void) => {
|
|
21
|
+
if (fixed !== undefined) return () => {};
|
|
22
|
+
if (!subscribers.size) now = Date.now();
|
|
23
|
+
const subscriber = { period, next: Math.floor(now / period + 1) * period, notify };
|
|
24
|
+
subscribers.add(subscriber); schedule();
|
|
25
|
+
return () => { subscribers.delete(subscriber); schedule(); };
|
|
26
|
+
}, [period, fixed]);
|
|
27
|
+
return useSyncExternalStore(subscribe, () => fixed ?? Math.max(now, initial), () => fixed ?? initial);
|
|
28
|
+
}
|
|
29
|
+
/** One run clock; retained playback observes only its supplied timestamp. */
|
|
30
|
+
export const useRunNow = (fixed?: number): number => useClock(1000, fixed, 0);
|
|
31
|
+
/** Existing cadence API shares the same clock and scheduler, including its 100ms floor. */
|
|
32
|
+
export function useLiveTime(intervalMs = 1000): number {
|
|
33
|
+
const initial = useRef(Date.now()).current;
|
|
34
|
+
return useClock(Number.isFinite(intervalMs) && intervalMs > 0 ? Math.max(intervalMs, 100) : 1000, undefined, initial);
|
|
27
35
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { useRunNow } from "./use-live-time";
|
|
2
3
|
|
|
3
4
|
import * as React from "react";
|
|
4
5
|
import { openNativeStream } from "./use-event-stream";
|
|
@@ -119,7 +120,7 @@ export function useSSEStream<T = unknown>(
|
|
|
119
120
|
const [lastEvent, setLastEvent] = React.useState<SSEEvent<T> | null>(null);
|
|
120
121
|
const [error, setError] = React.useState<Error | null>(null);
|
|
121
122
|
const [lastEventTime, setLastEventTime] = React.useState<number>(Date.now());
|
|
122
|
-
const
|
|
123
|
+
const timeSinceLastEvent = Math.max(0, useRunNow() - lastEventTime);
|
|
123
124
|
|
|
124
125
|
const retryCountRef = React.useRef(0);
|
|
125
126
|
const eventSourceRef = React.useRef<{ close(): void } | null>(null);
|
|
@@ -129,14 +130,6 @@ export function useSSEStream<T = unknown>(
|
|
|
129
130
|
> | null>(null);
|
|
130
131
|
const lastEventIdRef = React.useRef<string | undefined>(undefined);
|
|
131
132
|
|
|
132
|
-
// Update time since last event
|
|
133
|
-
React.useEffect(() => {
|
|
134
|
-
const interval = setInterval(() => {
|
|
135
|
-
setTimeSinceLastEvent(Date.now() - lastEventTime);
|
|
136
|
-
}, 1000);
|
|
137
|
-
return () => clearInterval(interval);
|
|
138
|
-
}, [lastEventTime]);
|
|
139
|
-
|
|
140
133
|
// Notify state changes
|
|
141
134
|
React.useEffect(() => {
|
|
142
135
|
onStateChange?.(state);
|
|
@@ -155,7 +148,7 @@ export function useSSEStream<T = unknown>(
|
|
|
155
148
|
lastEventIdRef.current = id;
|
|
156
149
|
}
|
|
157
150
|
|
|
158
|
-
setLastEventTime(
|
|
151
|
+
setLastEventTime(event.timestamp);
|
|
159
152
|
setLastEvent(event);
|
|
160
153
|
setEvents((prev) => {
|
|
161
154
|
const next = [...prev, event];
|
package/src/run/index.ts
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useLiveTime } from "../hooks/use-live-time";
|
|
2
2
|
import { formatDuration } from "../utils/format";
|
|
3
3
|
|
|
4
4
|
export function LiveDuration({ startTime }: { startTime: number }) {
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
useEffect(() => {
|
|
8
|
-
const id = setInterval(() => setElapsed(Date.now() - startTime), 100);
|
|
9
|
-
return () => clearInterval(id);
|
|
10
|
-
}, [startTime]);
|
|
5
|
+
const elapsed = useLiveTime(100) - startTime;
|
|
11
6
|
|
|
12
7
|
return (
|
|
13
8
|
<span className="text-xs font-mono text-neutral-400 dark:text-neutral-500 tabular-nums">
|
package/src/run/status-meta.tsx
CHANGED
|
@@ -42,12 +42,12 @@ const META: Record<RunStatus, StatusMeta> = {
|
|
|
42
42
|
|
|
43
43
|
// The vocabularies ui and its products already speak, so each maps here once.
|
|
44
44
|
const ALIASES: Record<string, RunStatus> = {
|
|
45
|
-
active: "running", in_progress: "running", "in-progress": "running", streaming: "running",
|
|
45
|
+
active: "running", working: "running", in_progress: "running", "in-progress": "running", streaming: "running",
|
|
46
46
|
pending: "waiting", queued: "waiting", blocked: "waiting",
|
|
47
47
|
"attention-needed": "needs-you", needs_you: "needs-you", needs_input: "needs-you", awaiting_input: "needs-you",
|
|
48
|
-
completed: "done", complete: "done", success: "done", succeeded: "done", shipped: "done",
|
|
49
|
-
error: "failed", errored: "failed",
|
|
50
|
-
cancelled: "stopped", canceled: "stopped", aborted: "stopped",
|
|
48
|
+
winner: "done", completed: "done", complete: "done", success: "done", succeeded: "done", shipped: "done",
|
|
49
|
+
error: "failed", errored: "failed", "driver-failed": "failed",
|
|
50
|
+
down: "stopped", "no-winner": "stopped", cancelled: "stopped", canceled: "stopped", aborted: "stopped",
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
/** Any status string as a `RunStatus`; a value nothing names reads as Unknown, never as a guess. */
|
|
@@ -57,6 +57,9 @@ export function runStatus(status: string): RunStatus {
|
|
|
57
57
|
return Object.hasOwn(ALIASES, key) ? ALIASES[key] : "unknown";
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/** Whether a canonical run state can still advance. */
|
|
61
|
+
export const isOpen = (status: RunStatus | (string & {})): boolean => ["running", "waiting", "needs-you"].includes(runStatus(status));
|
|
62
|
+
|
|
60
63
|
/** The label, glyph and colour token for a status, in every surface. */
|
|
61
64
|
export function statusMeta(status: RunStatus | (string & {})): StatusMeta {
|
|
62
65
|
return META[runStatus(status)];
|
|
@@ -1,342 +0,0 @@
|
|
|
1
|
-
import { render, screen } from "@testing-library/react"
|
|
2
|
-
import userEvent from "@testing-library/user-event"
|
|
3
|
-
import { describe, expect, it, vi } from "vitest"
|
|
4
|
-
import type { ToolPart } from "../types/parts"
|
|
5
|
-
import { AgentTimeline, type AgentTimelineItem } from "./agent-timeline"
|
|
6
|
-
import { TRANSCRIPT_TEXT } from "./transcript-text"
|
|
7
|
-
|
|
8
|
-
const editPart: ToolPart = {
|
|
9
|
-
type: "tool",
|
|
10
|
-
id: "tool-1",
|
|
11
|
-
tool: "edit",
|
|
12
|
-
state: {
|
|
13
|
-
status: "completed",
|
|
14
|
-
input: { path: "src/batch-writer.ts" },
|
|
15
|
-
output: "Applied 1 edit",
|
|
16
|
-
},
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const readPart: ToolPart = {
|
|
20
|
-
type: "tool",
|
|
21
|
-
id: "tool-2",
|
|
22
|
-
tool: "read",
|
|
23
|
-
state: {
|
|
24
|
-
status: "completed",
|
|
25
|
-
input: { path: "src/lib/jitter.ts" },
|
|
26
|
-
output: "export function jitter() {}",
|
|
27
|
-
},
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function toolItem(part: ToolPart): AgentTimelineItem {
|
|
31
|
-
return {
|
|
32
|
-
id: "item-1",
|
|
33
|
-
kind: "tool",
|
|
34
|
-
call: {
|
|
35
|
-
id: part.id,
|
|
36
|
-
type: "edit",
|
|
37
|
-
label: "Edit",
|
|
38
|
-
status: "success",
|
|
39
|
-
detail: "src/batch-writer.ts",
|
|
40
|
-
output: "Applied 1 edit",
|
|
41
|
-
},
|
|
42
|
-
part,
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
describe("AgentTimeline tool actions", () => {
|
|
47
|
-
it("renders a tool call's verb and its path, once each", () => {
|
|
48
|
-
render(<AgentTimeline items={[toolItem(editPart)]} />)
|
|
49
|
-
expect(screen.getByText("Edit")).toBeInTheDocument()
|
|
50
|
-
expect(screen.getAllByText("src/batch-writer.ts")).toHaveLength(1)
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it("renders renderToolActions beside the tool item, called with the source part", () => {
|
|
54
|
-
const renderToolActions = vi.fn((part: ToolPart) => (
|
|
55
|
-
<button type="button">Open {part.id}</button>
|
|
56
|
-
))
|
|
57
|
-
|
|
58
|
-
render(
|
|
59
|
-
<AgentTimeline
|
|
60
|
-
items={[toolItem(editPart)]}
|
|
61
|
-
renderToolActions={renderToolActions}
|
|
62
|
-
/>,
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
expect(renderToolActions).toHaveBeenCalledWith(editPart)
|
|
66
|
-
expect(
|
|
67
|
-
screen.getByRole("button", { name: /open tool-1/i }),
|
|
68
|
-
).toBeInTheDocument()
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it("renders no action when the item carries no source part", () => {
|
|
72
|
-
const renderToolActions = vi.fn(() => <button type="button">Open</button>)
|
|
73
|
-
const item = { ...toolItem(editPart), part: undefined }
|
|
74
|
-
|
|
75
|
-
render(
|
|
76
|
-
<AgentTimeline items={[item]} renderToolActions={renderToolActions} />,
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
expect(renderToolActions).not.toHaveBeenCalled()
|
|
80
|
-
expect(screen.queryByRole("button", { name: /open/i })).not.toBeInTheDocument()
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
it("renders tool items unchanged when no renderToolActions is provided", () => {
|
|
84
|
-
render(<AgentTimeline items={[toolItem(editPart)]} />)
|
|
85
|
-
expect(screen.getByText("Edit")).toBeInTheDocument()
|
|
86
|
-
expect(screen.queryByRole("button", { name: /open/i })).not.toBeInTheDocument()
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
it("renders renderToolActions for each call in a tool group, with its part", () => {
|
|
90
|
-
const renderToolActions = vi.fn((part: ToolPart) => (
|
|
91
|
-
<button type="button">Open {part.id}</button>
|
|
92
|
-
))
|
|
93
|
-
const groupItem: AgentTimelineItem = {
|
|
94
|
-
id: "group-1",
|
|
95
|
-
kind: "tool_group",
|
|
96
|
-
title: "Tool activity",
|
|
97
|
-
calls: [
|
|
98
|
-
{
|
|
99
|
-
id: "tool-1",
|
|
100
|
-
type: "edit",
|
|
101
|
-
label: "Edit",
|
|
102
|
-
status: "success",
|
|
103
|
-
detail: "src/batch-writer.ts",
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
id: "tool-2",
|
|
107
|
-
type: "read",
|
|
108
|
-
label: "Read",
|
|
109
|
-
status: "success",
|
|
110
|
-
detail: "src/lib/jitter.ts",
|
|
111
|
-
},
|
|
112
|
-
],
|
|
113
|
-
parts: [editPart, readPart],
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
render(
|
|
117
|
-
<AgentTimeline items={[groupItem]} renderToolActions={renderToolActions} />,
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
expect(renderToolActions).toHaveBeenCalledWith(editPart)
|
|
121
|
-
expect(renderToolActions).toHaveBeenCalledWith(readPart)
|
|
122
|
-
expect(
|
|
123
|
-
screen.getByRole("button", { name: /open tool-1/i }),
|
|
124
|
-
).toBeInTheDocument()
|
|
125
|
-
expect(
|
|
126
|
-
screen.getByRole("button", { name: /open tool-2/i }),
|
|
127
|
-
).toBeInTheDocument()
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
it("renders every row and no toggle when collapseAfter is unset", () => {
|
|
131
|
-
const items: AgentTimelineItem[] = Array.from({ length: 5 }, (_, i) => ({
|
|
132
|
-
id: `s-${i}`,
|
|
133
|
-
kind: "status",
|
|
134
|
-
label: `Step ${i}`,
|
|
135
|
-
}))
|
|
136
|
-
render(<AgentTimeline items={items} />)
|
|
137
|
-
expect(screen.getByText("Step 4")).toBeInTheDocument()
|
|
138
|
-
expect(
|
|
139
|
-
screen.queryByRole("button", { name: /more step/i }),
|
|
140
|
-
).not.toBeInTheDocument()
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
it("collapses to the first N spine rows behind a toggle and reveals the rest", async () => {
|
|
144
|
-
const user = userEvent.setup()
|
|
145
|
-
const items: AgentTimelineItem[] = Array.from({ length: 5 }, (_, i) => ({
|
|
146
|
-
id: `s-${i}`,
|
|
147
|
-
kind: "status",
|
|
148
|
-
label: `Step ${i}`,
|
|
149
|
-
}))
|
|
150
|
-
render(<AgentTimeline items={items} collapseAfter={2} />)
|
|
151
|
-
|
|
152
|
-
// First 2 visible, the other 3 hidden behind the toggle
|
|
153
|
-
expect(screen.getByText("Step 0")).toBeInTheDocument()
|
|
154
|
-
expect(screen.getByText("Step 1")).toBeInTheDocument()
|
|
155
|
-
expect(screen.queryByText("Step 4")).not.toBeInTheDocument()
|
|
156
|
-
|
|
157
|
-
const toggle = screen.getByRole("button", { name: /show 3 more steps/i })
|
|
158
|
-
await user.click(toggle)
|
|
159
|
-
|
|
160
|
-
expect(screen.getByText("Step 4")).toBeInTheDocument()
|
|
161
|
-
expect(screen.getByRole("button", { name: /show less/i })).toBeInTheDocument()
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
it("names a tool group for assistive tech but draws no header above it", () => {
|
|
165
|
-
const groupItem: AgentTimelineItem = {
|
|
166
|
-
id: "group-1",
|
|
167
|
-
kind: "tool_group",
|
|
168
|
-
title: "Tool activity",
|
|
169
|
-
calls: [
|
|
170
|
-
{ id: "tool-1", type: "edit", label: "Edit", status: "success", detail: "src/a.ts" },
|
|
171
|
-
{ id: "tool-2", type: "read", label: "Read", status: "success", detail: "src/b.ts" },
|
|
172
|
-
],
|
|
173
|
-
parts: [editPart, readPart],
|
|
174
|
-
}
|
|
175
|
-
render(<AgentTimeline items={[groupItem]} />)
|
|
176
|
-
expect(screen.queryByText("Tool activity")).not.toBeInTheDocument()
|
|
177
|
-
expect(screen.getByRole("group", { name: "Tool activity" })).toBeInTheDocument()
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
it("renders assistant prose without a timestamp header", () => {
|
|
181
|
-
const stamp = new Date(2026, 0, 1, 15, 42)
|
|
182
|
-
render(
|
|
183
|
-
<AgentTimeline
|
|
184
|
-
items={[
|
|
185
|
-
{ id: "a-1", kind: "message", role: "assistant", content: "Done.", timestamp: stamp },
|
|
186
|
-
]}
|
|
187
|
-
/>,
|
|
188
|
-
)
|
|
189
|
-
expect(screen.getByText("Done.")).toBeInTheDocument()
|
|
190
|
-
expect(
|
|
191
|
-
screen.queryByText(
|
|
192
|
-
stamp.toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit" }),
|
|
193
|
-
),
|
|
194
|
-
).not.toBeInTheDocument()
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
it("keeps the user bubble's timestamp", () => {
|
|
198
|
-
const stamp = new Date(2026, 0, 1, 15, 42)
|
|
199
|
-
render(
|
|
200
|
-
<AgentTimeline
|
|
201
|
-
items={[{ id: "u-1", kind: "message", role: "user", content: "Hi", timestamp: stamp }]}
|
|
202
|
-
/>,
|
|
203
|
-
)
|
|
204
|
-
expect(
|
|
205
|
-
screen.getByText(
|
|
206
|
-
stamp.toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit" }),
|
|
207
|
-
),
|
|
208
|
-
).toBeInTheDocument()
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
it("lays rows out in a plain column — no spine gutter, no connector", () => {
|
|
212
|
-
const { container } = render(
|
|
213
|
-
<AgentTimeline
|
|
214
|
-
items={[
|
|
215
|
-
{ id: "a-1", kind: "message", role: "assistant", content: "Reading." },
|
|
216
|
-
toolItem(editPart),
|
|
217
|
-
{ id: "s-1", kind: "status", label: "Type-checks clean", tone: "success" },
|
|
218
|
-
]}
|
|
219
|
-
/>,
|
|
220
|
-
)
|
|
221
|
-
expect(container.querySelector('[class*="grid-cols-"]')).toBeNull()
|
|
222
|
-
expect(container.querySelector(".ring-4")).toBeNull()
|
|
223
|
-
const steps = container.querySelectorAll("[data-timeline-step]")
|
|
224
|
-
expect(Array.from(steps).map((el) => el.getAttribute("data-timeline-step"))).toEqual([
|
|
225
|
-
"prose",
|
|
226
|
-
"tool",
|
|
227
|
-
"card",
|
|
228
|
-
])
|
|
229
|
-
// Tool rows bleed past the prose edge so the glyph sits on it.
|
|
230
|
-
expect(steps[1].className).toContain("-mx-3")
|
|
231
|
-
expect(steps[0].className).not.toContain("-mx-3")
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
it("spaces rows by their neighbours: turns widest, tool sequences tightest", () => {
|
|
235
|
-
const tool2 = { ...toolItem(readPart), id: "item-2" }
|
|
236
|
-
const { container } = render(
|
|
237
|
-
<AgentTimeline
|
|
238
|
-
items={[
|
|
239
|
-
{ id: "u-1", kind: "message", role: "user", content: "Go" },
|
|
240
|
-
toolItem(editPart),
|
|
241
|
-
tool2,
|
|
242
|
-
{ id: "a-1", kind: "message", role: "assistant", content: "Done." },
|
|
243
|
-
{ id: "u-2", kind: "message", role: "user", content: "Thanks" },
|
|
244
|
-
]}
|
|
245
|
-
/>,
|
|
246
|
-
)
|
|
247
|
-
const classes = Array.from(container.querySelectorAll("[data-timeline-step]")).map(
|
|
248
|
-
(el) => el.className,
|
|
249
|
-
)
|
|
250
|
-
expect(classes[0]).not.toMatch(/mt-\d/)
|
|
251
|
-
expect(classes[1]).toContain("mt-4") // user → tool: turn boundary
|
|
252
|
-
expect(classes[2]).toContain("mt-1") // tool → tool
|
|
253
|
-
expect(classes[3]).toContain("mt-1") // tool → prose
|
|
254
|
-
expect(classes[4]).toContain("mt-4") // prose → user
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
it("renders the source part's real input in the expanded detail", async () => {
|
|
258
|
-
const user = userEvent.setup()
|
|
259
|
-
const probePart: ToolPart = {
|
|
260
|
-
type: "tool",
|
|
261
|
-
id: "tool-3",
|
|
262
|
-
tool: "custom_probe",
|
|
263
|
-
state: {
|
|
264
|
-
status: "completed",
|
|
265
|
-
input: { marker: "deep-input-value" },
|
|
266
|
-
output: "done",
|
|
267
|
-
},
|
|
268
|
-
}
|
|
269
|
-
const item: AgentTimelineItem = {
|
|
270
|
-
id: "item-3",
|
|
271
|
-
kind: "tool",
|
|
272
|
-
call: {
|
|
273
|
-
id: "tool-3",
|
|
274
|
-
type: "unknown",
|
|
275
|
-
label: "custom_probe",
|
|
276
|
-
status: "success",
|
|
277
|
-
detail: "probe",
|
|
278
|
-
},
|
|
279
|
-
part: probePart,
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
render(<AgentTimeline items={[item]} />)
|
|
283
|
-
await user.click(screen.getByRole("button", { name: /custom_probe/i }))
|
|
284
|
-
|
|
285
|
-
// ExpandedToolDetail (fed the real part) renders labelled Input/Output
|
|
286
|
-
// sections — the synthesized-part path showed only a bare output block.
|
|
287
|
-
expect(screen.getByText("Input")).toBeInTheDocument()
|
|
288
|
-
expect(screen.getByText("Output")).toBeInTheDocument()
|
|
289
|
-
})
|
|
290
|
-
})
|
|
291
|
-
|
|
292
|
-
describe("transcript type scale", () => {
|
|
293
|
-
it("sets the person's and the agent's message text in one family, size and leading", () => {
|
|
294
|
-
render(
|
|
295
|
-
<AgentTimeline
|
|
296
|
-
items={[
|
|
297
|
-
{ id: "u", kind: "message", role: "user", content: "Can Luz take the 3 pm massage?" },
|
|
298
|
-
{ id: "a", kind: "message", role: "assistant", content: "Yes, Luz is free at 3 pm." },
|
|
299
|
-
]}
|
|
300
|
-
/>,
|
|
301
|
-
)
|
|
302
|
-
const user = screen.getByText("Can Luz take the 3 pm massage?")
|
|
303
|
-
const agent = screen.getByText("Yes, Luz is free at 3 pm.").closest(".tangle-prose")
|
|
304
|
-
const textClasses = (element: Element | null) =>
|
|
305
|
-
[...(element?.classList ?? [])].filter((name) => /^(tangle-transcript-text|text-|leading-|font-)/.test(name)).sort()
|
|
306
|
-
expect(textClasses(user)).toEqual(TRANSCRIPT_TEXT.split(" ").sort())
|
|
307
|
-
expect(textClasses(agent)).toEqual(textClasses(user))
|
|
308
|
-
})
|
|
309
|
-
})
|
|
310
|
-
|
|
311
|
-
describe("AgentTimeline status cards", () => {
|
|
312
|
-
it("marks a failed card with the failed glyph and keeps the triangle for warnings", () => {
|
|
313
|
-
const glyph = (tone: "error" | "warning") =>
|
|
314
|
-
render(<AgentTimeline items={[{ id: tone, kind: "status", label: tone, tone }]} />).container.querySelector("svg")?.getAttribute("class")
|
|
315
|
-
expect(glyph("error")).toContain("lucide-circle-x")
|
|
316
|
-
expect(glyph("warning")).toContain("lucide-triangle-alert")
|
|
317
|
-
})
|
|
318
|
-
})
|
|
319
|
-
|
|
320
|
-
describe("AgentTimeline folds", () => {
|
|
321
|
-
const call = (id: string, type: "read" | "bash", detail: string, status: "success" | "error" = "success"): AgentTimelineItem => ({
|
|
322
|
-
id,
|
|
323
|
-
kind: "tool",
|
|
324
|
-
call: { id, type, label: type === "read" ? "Read" : "Shell", status, detail },
|
|
325
|
-
part: { type: "tool", id, tool: type, state: status === "error" ? { status: "error", input: { path: detail }, error: `File not found: ${detail}` } : { status: "completed", input: { path: detail }, output: "ok" } },
|
|
326
|
-
})
|
|
327
|
-
|
|
328
|
-
it("folds a run of calls into one row that names what they did and opens to the rows", async () => {
|
|
329
|
-
const user = userEvent.setup()
|
|
330
|
-
render(<AgentTimeline items={[{ id: "f", kind: "fold", items: [call("a", "read", "inputs/commission.md"), call("b", "read", "inputs/claims.json"), call("c", "bash", "ls repo")] }]} />)
|
|
331
|
-
const row = screen.getByRole("button", { name: /Read 2 files · Ran 1 command/ })
|
|
332
|
-
expect(screen.queryByText("Shell")).not.toBeInTheDocument()
|
|
333
|
-
await user.click(row)
|
|
334
|
-
expect(screen.getByText("Shell")).toBeInTheDocument()
|
|
335
|
-
expect(screen.getAllByText("Read")).toHaveLength(2)
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
it("keeps a failure visible under a closed fold", () => {
|
|
339
|
-
render(<AgentTimeline items={[{ id: "f", kind: "fold", items: [call("a", "read", "a.md"), call("b", "read", "b.md", "error")] }]} />)
|
|
340
|
-
expect(screen.getByText("1 failed: File not found: b.md")).toBeInTheDocument()
|
|
341
|
-
})
|
|
342
|
-
})
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { render, screen } from "@testing-library/react"
|
|
2
|
-
import { describe, expect, it } from "vitest"
|
|
3
|
-
import { UserMessage } from "./user-message"
|
|
4
|
-
|
|
5
|
-
describe("UserMessage", () => {
|
|
6
|
-
it("renders inverse text in a bubble that wraps in narrow panes", () => {
|
|
7
|
-
const { container } = render(<UserMessage content="Run the tests." />)
|
|
8
|
-
expect(screen.getByText("Run the tests.")).toBeInTheDocument()
|
|
9
|
-
const bubble = container.querySelector(".rounded-2xl")
|
|
10
|
-
expect(bubble).not.toBeNull()
|
|
11
|
-
expect(bubble!.className).toContain("bg-foreground")
|
|
12
|
-
expect(bubble!.className).toContain("text-background")
|
|
13
|
-
expect(container.querySelector(".group")?.className).toContain(
|
|
14
|
-
"min-w-0",
|
|
15
|
-
)
|
|
16
|
-
expect(screen.getByText("Run the tests.").className).toContain(
|
|
17
|
-
"break-words",
|
|
18
|
-
)
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it("keeps the timestamp beside the bubble as a hover-revealed label", () => {
|
|
22
|
-
const stamp = new Date(2026, 0, 1, 15, 42)
|
|
23
|
-
const { container } = render(
|
|
24
|
-
<UserMessage content="Run the tests." timestamp={stamp} />,
|
|
25
|
-
)
|
|
26
|
-
const label = container.querySelector("[data-user-message-time]")
|
|
27
|
-
expect(label).not.toBeNull()
|
|
28
|
-
expect(label!.textContent).toBe(
|
|
29
|
-
stamp.toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit" }),
|
|
30
|
-
)
|
|
31
|
-
expect(label!.className).toContain("opacity-0")
|
|
32
|
-
expect(label!.className).toContain("group-hover:opacity-100")
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it("renders nothing for an empty message", () => {
|
|
36
|
-
const { container } = render(<UserMessage content=" " />)
|
|
37
|
-
expect(container).toBeEmptyDOMElement()
|
|
38
|
-
})
|
|
39
|
-
})
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, afterEach, beforeEach } from "vitest";
|
|
2
|
-
import { renderHook, act } from "@testing-library/react";
|
|
3
|
-
import { useLiveTime } from "./use-live-time";
|
|
4
|
-
|
|
5
|
-
describe("useLiveTime", () => {
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
vi.useFakeTimers();
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
afterEach(() => {
|
|
11
|
-
vi.useRealTimers();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("advances Date.now() snapshot on the given cadence", () => {
|
|
15
|
-
vi.setSystemTime(new Date("2025-01-01T00:00:00Z"));
|
|
16
|
-
const { result } = renderHook(() => useLiveTime(1000));
|
|
17
|
-
const initial = result.current;
|
|
18
|
-
|
|
19
|
-
act(() => {
|
|
20
|
-
vi.advanceTimersByTime(1000);
|
|
21
|
-
});
|
|
22
|
-
expect(result.current).toBeGreaterThan(initial);
|
|
23
|
-
|
|
24
|
-
act(() => {
|
|
25
|
-
vi.advanceTimersByTime(2000);
|
|
26
|
-
});
|
|
27
|
-
expect(result.current).toBeGreaterThanOrEqual(initial + 3000);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("falls back to a safe cadence when intervalMs is NaN", () => {
|
|
31
|
-
vi.setSystemTime(new Date("2025-01-01T00:00:00Z"));
|
|
32
|
-
const setIntervalSpy = vi.spyOn(window, "setInterval");
|
|
33
|
-
renderHook(() => useLiveTime(Number.NaN));
|
|
34
|
-
// Must not pass NaN (browsers coerce to 0 and spin a render loop)
|
|
35
|
-
// nor drop below the 100ms floor.
|
|
36
|
-
const [, delay] = setIntervalSpy.mock.calls[0]!;
|
|
37
|
-
expect(Number.isFinite(delay)).toBe(true);
|
|
38
|
-
expect(delay).toBeGreaterThanOrEqual(100);
|
|
39
|
-
});
|
|
40
|
-
});
|