@a5c-ai/babysitter-observer-dashboard 1.0.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/LICENSE +21 -0
- package/README.md +490 -0
- package/next.config.mjs +25 -0
- package/package.json +104 -0
- package/postcss.config.mjs +8 -0
- package/src/app/actions/__tests__/approve-breakpoint.test.ts +246 -0
- package/src/app/actions/approve-breakpoint.ts +145 -0
- package/src/app/api/config/route.ts +137 -0
- package/src/app/api/digest/route.ts +45 -0
- package/src/app/api/runs/[runId]/events/route.ts +56 -0
- package/src/app/api/runs/[runId]/route.ts +84 -0
- package/src/app/api/runs/[runId]/tasks/[effectId]/route.ts +44 -0
- package/src/app/api/runs/route.ts +48 -0
- package/src/app/api/stream/route.ts +136 -0
- package/src/app/api/test/route.ts +1 -0
- package/src/app/api/version/route.ts +57 -0
- package/src/app/globals.css +555 -0
- package/src/app/icon.svg +20 -0
- package/src/app/layout.tsx +39 -0
- package/src/app/not-found.tsx +16 -0
- package/src/app/page.tsx +120 -0
- package/src/app/runs/[runId]/page.tsx +279 -0
- package/src/cli.ts +271 -0
- package/src/components/breakpoint/__tests__/breakpoint-approval.test.tsx +212 -0
- package/src/components/breakpoint/__tests__/breakpoint-panel.test.tsx +130 -0
- package/src/components/breakpoint/__tests__/file-preview.test.tsx +313 -0
- package/src/components/breakpoint/breakpoint-approval.tsx +138 -0
- package/src/components/breakpoint/breakpoint-panel.tsx +95 -0
- package/src/components/breakpoint/file-preview.tsx +215 -0
- package/src/components/dashboard/.gitkeep +0 -0
- package/src/components/dashboard/__tests__/breakpoint-banner.test.tsx +177 -0
- package/src/components/dashboard/__tests__/catch-up-banner.test.tsx +141 -0
- package/src/components/dashboard/__tests__/executive-summary-banner.test.tsx +164 -0
- package/src/components/dashboard/__tests__/kpi-grid.test.tsx +101 -0
- package/src/components/dashboard/__tests__/pagination-controls.test.tsx +125 -0
- package/src/components/dashboard/__tests__/project-accordion.test.tsx +97 -0
- package/src/components/dashboard/__tests__/project-list-view.test.tsx +174 -0
- package/src/components/dashboard/__tests__/project-search-input.test.tsx +110 -0
- package/src/components/dashboard/__tests__/project-section-header.test.tsx +91 -0
- package/src/components/dashboard/__tests__/project-section.test.tsx +151 -0
- package/src/components/dashboard/__tests__/run-card.test.tsx +164 -0
- package/src/components/dashboard/__tests__/run-filter-bar.test.tsx +109 -0
- package/src/components/dashboard/__tests__/run-list.test.tsx +123 -0
- package/src/components/dashboard/__tests__/search-filter.test.tsx +150 -0
- package/src/components/dashboard/__tests__/virtualized-run-list.test.tsx +179 -0
- package/src/components/dashboard/breakpoint-banner.tsx +301 -0
- package/src/components/dashboard/catch-up-banner.tsx +88 -0
- package/src/components/dashboard/executive-summary-banner.tsx +174 -0
- package/src/components/dashboard/global-search.tsx +323 -0
- package/src/components/dashboard/kpi-grid.tsx +140 -0
- package/src/components/dashboard/pagination-controls.tsx +100 -0
- package/src/components/dashboard/project-accordion.tsx +72 -0
- package/src/components/dashboard/project-health-card.tsx +536 -0
- package/src/components/dashboard/project-list-view.tsx +246 -0
- package/src/components/dashboard/project-search-input.tsx +41 -0
- package/src/components/dashboard/project-section-header.tsx +73 -0
- package/src/components/dashboard/project-section.tsx +89 -0
- package/src/components/dashboard/run-card.tsx +218 -0
- package/src/components/dashboard/run-filter-bar.tsx +100 -0
- package/src/components/dashboard/run-list.tsx +77 -0
- package/src/components/dashboard/search-filter.tsx +69 -0
- package/src/components/dashboard/virtualized-run-list.tsx +130 -0
- package/src/components/details/.gitkeep +0 -0
- package/src/components/details/__tests__/agent-panel.test.tsx +236 -0
- package/src/components/details/__tests__/json-tree.test.tsx +347 -0
- package/src/components/details/__tests__/log-viewer.test.tsx +168 -0
- package/src/components/details/__tests__/task-detail.test.tsx +212 -0
- package/src/components/details/__tests__/timing-panel.test.tsx +271 -0
- package/src/components/details/agent-panel.tsx +234 -0
- package/src/components/details/json-tree/categorize.ts +131 -0
- package/src/components/details/json-tree/index.tsx +120 -0
- package/src/components/details/json-tree/json-node.tsx +223 -0
- package/src/components/details/json-tree/smart-summary.tsx +596 -0
- package/src/components/details/json-tree/tree-controls.tsx +47 -0
- package/src/components/details/json-tree.tsx +9 -0
- package/src/components/details/log-viewer.tsx +140 -0
- package/src/components/details/task-detail.tsx +114 -0
- package/src/components/details/timing-panel.tsx +247 -0
- package/src/components/events/.gitkeep +0 -0
- package/src/components/events/__tests__/event-item.test.tsx +211 -0
- package/src/components/events/__tests__/event-stream.test.tsx +225 -0
- package/src/components/events/event-item.tsx +121 -0
- package/src/components/events/event-stream.tsx +260 -0
- package/src/components/notifications/.gitkeep +0 -0
- package/src/components/notifications/__tests__/notification-panel.test.tsx +287 -0
- package/src/components/notifications/__tests__/notification-provider.test.tsx +585 -0
- package/src/components/notifications/__tests__/toast-stack.test.tsx +217 -0
- package/src/components/notifications/notification-panel.tsx +124 -0
- package/src/components/notifications/notification-provider.tsx +175 -0
- package/src/components/notifications/toast-stack.tsx +75 -0
- package/src/components/pipeline/.gitkeep +0 -0
- package/src/components/pipeline/__tests__/parallel-group.test.tsx +88 -0
- package/src/components/pipeline/__tests__/pipeline-view.test.tsx +345 -0
- package/src/components/pipeline/__tests__/step-card.test.tsx +330 -0
- package/src/components/pipeline/parallel-group.tsx +39 -0
- package/src/components/pipeline/pipeline-view.tsx +197 -0
- package/src/components/pipeline/step-card.tsx +166 -0
- package/src/components/providers/event-stream-provider.tsx +29 -0
- package/src/components/providers.tsx +24 -0
- package/src/components/shared/.gitkeep +0 -0
- package/src/components/shared/__tests__/empty-state.test.tsx +49 -0
- package/src/components/shared/__tests__/friendly-id.test.tsx +47 -0
- package/src/components/shared/__tests__/kbd.test.tsx +45 -0
- package/src/components/shared/__tests__/kind-badge.test.tsx +71 -0
- package/src/components/shared/__tests__/metrics-row.test.tsx +74 -0
- package/src/components/shared/__tests__/outcome-banner.test.tsx +71 -0
- package/src/components/shared/__tests__/progress-bar.test.tsx +89 -0
- package/src/components/shared/__tests__/session-pill.test.tsx +62 -0
- package/src/components/shared/__tests__/settings-modal.test.tsx +201 -0
- package/src/components/shared/__tests__/shortcuts-help.test.tsx +103 -0
- package/src/components/shared/__tests__/status-badge.test.tsx +98 -0
- package/src/components/shared/__tests__/theme-provider.test.tsx +100 -0
- package/src/components/shared/__tests__/truncated-id.test.tsx +53 -0
- package/src/components/shared/app-footer.tsx +80 -0
- package/src/components/shared/app-header.tsx +160 -0
- package/src/components/shared/empty-state.tsx +18 -0
- package/src/components/shared/error-boundary.tsx +81 -0
- package/src/components/shared/friendly-id.tsx +48 -0
- package/src/components/shared/kbd.tsx +15 -0
- package/src/components/shared/kind-badge.tsx +51 -0
- package/src/components/shared/metrics-row.tsx +106 -0
- package/src/components/shared/outcome-banner.tsx +56 -0
- package/src/components/shared/progress-bar.tsx +42 -0
- package/src/components/shared/session-pill.tsx +69 -0
- package/src/components/shared/settings-modal.tsx +509 -0
- package/src/components/shared/shortcuts-help.tsx +113 -0
- package/src/components/shared/status-badge.tsx +110 -0
- package/src/components/shared/theme-provider.tsx +46 -0
- package/src/components/shared/truncated-id.tsx +51 -0
- package/src/components/ui/.gitkeep +0 -0
- package/src/components/ui/__tests__/accordion.test.tsx +96 -0
- package/src/components/ui/__tests__/badge.test.tsx +69 -0
- package/src/components/ui/__tests__/button.test.tsx +113 -0
- package/src/components/ui/__tests__/tabs.test.tsx +75 -0
- package/src/components/ui/__tests__/tooltip.test.tsx +90 -0
- package/src/components/ui/accordion.tsx +61 -0
- package/src/components/ui/badge.tsx +25 -0
- package/src/components/ui/button.tsx +40 -0
- package/src/components/ui/card.tsx +21 -0
- package/src/components/ui/scroll-area.tsx +35 -0
- package/src/components/ui/separator.tsx +24 -0
- package/src/components/ui/tabs.tsx +64 -0
- package/src/components/ui/tooltip.tsx +37 -0
- package/src/hooks/.gitkeep +0 -0
- package/src/hooks/__tests__/use-animated-number.test.ts +184 -0
- package/src/hooks/__tests__/use-batched-updates.test.ts +315 -0
- package/src/hooks/__tests__/use-event-stream.test.ts +243 -0
- package/src/hooks/__tests__/use-keyboard.test.ts +217 -0
- package/src/hooks/__tests__/use-notifications.test.ts +230 -0
- package/src/hooks/__tests__/use-polling.test.ts +274 -0
- package/src/hooks/__tests__/use-project-runs.test.ts +163 -0
- package/src/hooks/__tests__/use-projects.test.ts +248 -0
- package/src/hooks/__tests__/use-run-dashboard.test.ts +168 -0
- package/src/hooks/__tests__/use-run-detail.test.ts +273 -0
- package/src/hooks/__tests__/use-smart-polling.test.ts +305 -0
- package/src/hooks/use-animated-number.ts +87 -0
- package/src/hooks/use-batched-updates.ts +150 -0
- package/src/hooks/use-event-stream.ts +150 -0
- package/src/hooks/use-keyboard.ts +45 -0
- package/src/hooks/use-notifications.ts +82 -0
- package/src/hooks/use-persisted-state.ts +60 -0
- package/src/hooks/use-polling.ts +60 -0
- package/src/hooks/use-project-runs.ts +51 -0
- package/src/hooks/use-projects.ts +26 -0
- package/src/hooks/use-run-dashboard.ts +207 -0
- package/src/hooks/use-run-detail.ts +77 -0
- package/src/hooks/use-smart-polling.ts +144 -0
- package/src/lib/.gitkeep +0 -0
- package/src/lib/__tests__/cn.test.ts +69 -0
- package/src/lib/__tests__/config-loader.test.ts +210 -0
- package/src/lib/__tests__/config.test.ts +561 -0
- package/src/lib/__tests__/error-handler.test.ts +143 -0
- package/src/lib/__tests__/fetcher.test.ts +517 -0
- package/src/lib/__tests__/global-registry.test.ts +214 -0
- package/src/lib/__tests__/parser.test.ts +1532 -0
- package/src/lib/__tests__/path-resolver.test.ts +112 -0
- package/src/lib/__tests__/run-cache.test.ts +591 -0
- package/src/lib/__tests__/server-init.test.ts +512 -0
- package/src/lib/__tests__/source-discovery.test.ts +246 -0
- package/src/lib/__tests__/utils.test.ts +160 -0
- package/src/lib/__tests__/watcher.test.ts +227 -0
- package/src/lib/cn.ts +6 -0
- package/src/lib/config-loader.ts +195 -0
- package/src/lib/config.ts +20 -0
- package/src/lib/error-handler.ts +76 -0
- package/src/lib/fetcher.ts +394 -0
- package/src/lib/global-registry.ts +117 -0
- package/src/lib/parser.ts +794 -0
- package/src/lib/path-resolver.ts +16 -0
- package/src/lib/run-cache.ts +404 -0
- package/src/lib/server-init.ts +226 -0
- package/src/lib/services/__tests__/run-query-service.test.ts +819 -0
- package/src/lib/services/run-query-service.ts +286 -0
- package/src/lib/source-discovery.ts +216 -0
- package/src/lib/utils.ts +103 -0
- package/src/lib/watcher.ts +265 -0
- package/src/test/fixtures.ts +269 -0
- package/src/test/mocks/handlers.ts +110 -0
- package/src/test/mocks/server.ts +17 -0
- package/src/test/setup.ts +200 -0
- package/src/test/test-utils.tsx +36 -0
- package/src/types/.gitkeep +0 -0
- package/src/types/breakpoint.ts +17 -0
- package/src/types/index.ts +214 -0
- package/tsconfig.json +50 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useState } from "react";
|
|
4
|
+
import { ChevronRight, Copy, Check } from "lucide-react";
|
|
5
|
+
import { cn } from "@/lib/cn";
|
|
6
|
+
|
|
7
|
+
/* ------------------------------------------------------------------ */
|
|
8
|
+
/* CopyButton */
|
|
9
|
+
/* ------------------------------------------------------------------ */
|
|
10
|
+
|
|
11
|
+
/** Unified copy button -- size='sm' for inline JSON values, size='md' for metadata/findings */
|
|
12
|
+
export function CopyButton({ value, size = "md", className: extraClass }: { value: string; size?: "sm" | "md"; className?: string }) {
|
|
13
|
+
const [copied, setCopied] = useState(false);
|
|
14
|
+
const handleCopy = (e: React.MouseEvent) => {
|
|
15
|
+
e.stopPropagation();
|
|
16
|
+
navigator.clipboard.writeText(value).then(() => {
|
|
17
|
+
setCopied(true);
|
|
18
|
+
setTimeout(() => setCopied(false), 1200);
|
|
19
|
+
}).catch(() => {});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const sizeClasses = size === "sm"
|
|
23
|
+
? "h-4 w-4"
|
|
24
|
+
: "min-h-[44px] min-w-[44px]";
|
|
25
|
+
const iconClasses = size === "sm"
|
|
26
|
+
? "h-2.5 w-2.5"
|
|
27
|
+
: "h-3 w-3";
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
onClick={handleCopy}
|
|
33
|
+
className={cn(
|
|
34
|
+
"inline-flex items-center justify-center rounded text-foreground-muted hover:text-primary hover:bg-primary-muted transition-all",
|
|
35
|
+
sizeClasses,
|
|
36
|
+
size === "sm" && "opacity-0 group-hover/json-row:opacity-100 ml-1",
|
|
37
|
+
size === "md" && "shrink-0",
|
|
38
|
+
extraClass,
|
|
39
|
+
)}
|
|
40
|
+
title="Copy"
|
|
41
|
+
>
|
|
42
|
+
{copied ? <Check className={cn(iconClasses, "text-success")} /> : <Copy className={iconClasses} />}
|
|
43
|
+
</button>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* ------------------------------------------------------------------ */
|
|
48
|
+
/* Primitive value renderer -- neon brand colors */
|
|
49
|
+
/* ------------------------------------------------------------------ */
|
|
50
|
+
|
|
51
|
+
const PrimitiveValue = React.memo(function PrimitiveValue({ value }: { value: unknown }) {
|
|
52
|
+
if (value === null) {
|
|
53
|
+
return <span className="text-foreground-muted italic">null</span>;
|
|
54
|
+
}
|
|
55
|
+
if (value === undefined) {
|
|
56
|
+
return <span className="text-foreground-muted italic">undefined</span>;
|
|
57
|
+
}
|
|
58
|
+
if (typeof value === "string") {
|
|
59
|
+
return <span className="text-success">"{value}"</span>;
|
|
60
|
+
}
|
|
61
|
+
if (typeof value === "number") {
|
|
62
|
+
return <span className="text-warning">{String(value)}</span>;
|
|
63
|
+
}
|
|
64
|
+
if (typeof value === "boolean") {
|
|
65
|
+
return <span className="text-primary">{String(value)}</span>;
|
|
66
|
+
}
|
|
67
|
+
// Fallback for anything unexpected
|
|
68
|
+
return <span className="text-foreground-secondary">{String(value)}</span>;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
PrimitiveValue.displayName = "PrimitiveValue";
|
|
72
|
+
|
|
73
|
+
/* ------------------------------------------------------------------ */
|
|
74
|
+
/* Recursive JSON node */
|
|
75
|
+
/* ------------------------------------------------------------------ */
|
|
76
|
+
|
|
77
|
+
interface JsonNodeProps {
|
|
78
|
+
/** The key name to display (null for root or array elements) */
|
|
79
|
+
keyName: string | null;
|
|
80
|
+
/** The value to render */
|
|
81
|
+
value: unknown;
|
|
82
|
+
/** Whether to default to expanded */
|
|
83
|
+
defaultExpanded?: boolean;
|
|
84
|
+
/** Whether this is the last item in its parent (controls trailing comma) */
|
|
85
|
+
isLast?: boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const JsonNode = React.memo(function JsonNode({ keyName, value, defaultExpanded, isLast = true }: JsonNodeProps) {
|
|
89
|
+
const isObject = value !== null && typeof value === "object" && !Array.isArray(value);
|
|
90
|
+
const isArray = Array.isArray(value);
|
|
91
|
+
const isExpandable = isObject || isArray;
|
|
92
|
+
|
|
93
|
+
// Determine default expanded state based on size thresholds
|
|
94
|
+
const [expanded, setExpanded] = useState((): boolean => {
|
|
95
|
+
if (defaultExpanded !== undefined) return defaultExpanded;
|
|
96
|
+
if (isObject) {
|
|
97
|
+
return Object.keys(value as Record<string, unknown>).length <= 10;
|
|
98
|
+
}
|
|
99
|
+
if (isArray) {
|
|
100
|
+
return (value as unknown[]).length <= 5;
|
|
101
|
+
}
|
|
102
|
+
return true;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const toggle = () => setExpanded((prev) => !prev);
|
|
106
|
+
|
|
107
|
+
// Key label prefix -- neon cyan for keys
|
|
108
|
+
const keyLabel = keyName !== null ? (
|
|
109
|
+
<>
|
|
110
|
+
<span className="text-secondary">{keyName}</span>
|
|
111
|
+
<span className="text-foreground-muted">: </span>
|
|
112
|
+
</>
|
|
113
|
+
) : null;
|
|
114
|
+
|
|
115
|
+
// Leaf / primitive node
|
|
116
|
+
if (!isExpandable) {
|
|
117
|
+
const copyVal = typeof value === "string" ? value : JSON.stringify(value);
|
|
118
|
+
return (
|
|
119
|
+
<div className="group/json-row flex items-baseline py-px px-1 rounded hover:bg-background-secondary transition-colors">
|
|
120
|
+
{keyLabel}
|
|
121
|
+
<PrimitiveValue value={value} />
|
|
122
|
+
{!isLast && <span className="text-foreground-muted">,</span>}
|
|
123
|
+
<CopyButton value={copyVal} size="sm" />
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Object or Array node
|
|
129
|
+
const entries = isArray
|
|
130
|
+
? (value as unknown[]).map((v, i) => [String(i), v] as const)
|
|
131
|
+
: Object.entries(value as Record<string, unknown>);
|
|
132
|
+
|
|
133
|
+
const openBracket = isArray ? "[" : "{";
|
|
134
|
+
const closeBracket = isArray ? "]" : "}";
|
|
135
|
+
const itemCount = entries.length;
|
|
136
|
+
const countLabel = isArray
|
|
137
|
+
? `${itemCount} item${itemCount !== 1 ? "s" : ""}`
|
|
138
|
+
: `${itemCount} key${itemCount !== 1 ? "s" : ""}`;
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<div>
|
|
142
|
+
{/* Toggle row */}
|
|
143
|
+
<div
|
|
144
|
+
className="flex items-baseline py-0.5 px-1 rounded cursor-pointer hover:bg-background-secondary transition-colors select-none"
|
|
145
|
+
onClick={toggle}
|
|
146
|
+
role="button"
|
|
147
|
+
tabIndex={0}
|
|
148
|
+
onKeyDown={(e) => {
|
|
149
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
150
|
+
e.preventDefault();
|
|
151
|
+
toggle();
|
|
152
|
+
}
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
<ChevronRight
|
|
156
|
+
className={cn(
|
|
157
|
+
"h-3 w-3 shrink-0 text-primary transition-transform duration-150 mr-1 relative top-[1px]",
|
|
158
|
+
expanded && "rotate-90"
|
|
159
|
+
)}
|
|
160
|
+
/>
|
|
161
|
+
{keyLabel}
|
|
162
|
+
<span className="text-foreground-muted">{openBracket}</span>
|
|
163
|
+
{!expanded && (
|
|
164
|
+
<>
|
|
165
|
+
<span className="mx-1 text-xs leading-tight text-foreground-muted bg-background-tertiary px-1.5 py-0.5 rounded">
|
|
166
|
+
{countLabel}
|
|
167
|
+
</span>
|
|
168
|
+
<span className="text-foreground-muted">{closeBracket}</span>
|
|
169
|
+
{!isLast && <span className="text-foreground-muted">,</span>}
|
|
170
|
+
</>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
{/* Children */}
|
|
175
|
+
{expanded && (
|
|
176
|
+
<div className="animate-[fadeIn_100ms_ease-out]">
|
|
177
|
+
<div className="pl-4 border-l border-primary/20 ml-1.5">
|
|
178
|
+
{entries.map(([key, val], idx) => (
|
|
179
|
+
<JsonNode
|
|
180
|
+
key={key}
|
|
181
|
+
keyName={isArray ? null : key}
|
|
182
|
+
value={val}
|
|
183
|
+
isLast={idx === entries.length - 1}
|
|
184
|
+
/>
|
|
185
|
+
))}
|
|
186
|
+
{itemCount === 0 && (
|
|
187
|
+
<div className="py-px px-1 text-foreground-muted italic">empty</div>
|
|
188
|
+
)}
|
|
189
|
+
</div>
|
|
190
|
+
<div className="flex items-baseline py-px px-1">
|
|
191
|
+
<span className="text-foreground-muted">{closeBracket}</span>
|
|
192
|
+
{!isLast && <span className="text-foreground-muted">,</span>}
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
)}
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
JsonNode.displayName = "JsonNode";
|
|
201
|
+
|
|
202
|
+
/* ------------------------------------------------------------------ */
|
|
203
|
+
/* Standalone JsonTreeView (generic, works with any data) */
|
|
204
|
+
/* ------------------------------------------------------------------ */
|
|
205
|
+
|
|
206
|
+
interface JsonTreeViewProps {
|
|
207
|
+
data: unknown;
|
|
208
|
+
defaultExpanded?: boolean;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function JsonTreeView({ data, defaultExpanded }: JsonTreeViewProps) {
|
|
212
|
+
if (data === undefined || data === null) {
|
|
213
|
+
return <span className="text-foreground-muted">{String(data ?? "null")}</span>;
|
|
214
|
+
}
|
|
215
|
+
return (
|
|
216
|
+
<div className="font-mono text-xs">
|
|
217
|
+
<JsonNode keyName={null} value={data} defaultExpanded={defaultExpanded} isLast />
|
|
218
|
+
</div>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export { JsonNode };
|
|
223
|
+
export type { JsonNodeProps, JsonTreeViewProps };
|