@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.
Files changed (205) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +490 -0
  3. package/next.config.mjs +25 -0
  4. package/package.json +104 -0
  5. package/postcss.config.mjs +8 -0
  6. package/src/app/actions/__tests__/approve-breakpoint.test.ts +246 -0
  7. package/src/app/actions/approve-breakpoint.ts +145 -0
  8. package/src/app/api/config/route.ts +137 -0
  9. package/src/app/api/digest/route.ts +45 -0
  10. package/src/app/api/runs/[runId]/events/route.ts +56 -0
  11. package/src/app/api/runs/[runId]/route.ts +84 -0
  12. package/src/app/api/runs/[runId]/tasks/[effectId]/route.ts +44 -0
  13. package/src/app/api/runs/route.ts +48 -0
  14. package/src/app/api/stream/route.ts +136 -0
  15. package/src/app/api/test/route.ts +1 -0
  16. package/src/app/api/version/route.ts +57 -0
  17. package/src/app/globals.css +555 -0
  18. package/src/app/icon.svg +20 -0
  19. package/src/app/layout.tsx +39 -0
  20. package/src/app/not-found.tsx +16 -0
  21. package/src/app/page.tsx +120 -0
  22. package/src/app/runs/[runId]/page.tsx +279 -0
  23. package/src/cli.ts +271 -0
  24. package/src/components/breakpoint/__tests__/breakpoint-approval.test.tsx +212 -0
  25. package/src/components/breakpoint/__tests__/breakpoint-panel.test.tsx +130 -0
  26. package/src/components/breakpoint/__tests__/file-preview.test.tsx +313 -0
  27. package/src/components/breakpoint/breakpoint-approval.tsx +138 -0
  28. package/src/components/breakpoint/breakpoint-panel.tsx +95 -0
  29. package/src/components/breakpoint/file-preview.tsx +215 -0
  30. package/src/components/dashboard/.gitkeep +0 -0
  31. package/src/components/dashboard/__tests__/breakpoint-banner.test.tsx +177 -0
  32. package/src/components/dashboard/__tests__/catch-up-banner.test.tsx +141 -0
  33. package/src/components/dashboard/__tests__/executive-summary-banner.test.tsx +164 -0
  34. package/src/components/dashboard/__tests__/kpi-grid.test.tsx +101 -0
  35. package/src/components/dashboard/__tests__/pagination-controls.test.tsx +125 -0
  36. package/src/components/dashboard/__tests__/project-accordion.test.tsx +97 -0
  37. package/src/components/dashboard/__tests__/project-list-view.test.tsx +174 -0
  38. package/src/components/dashboard/__tests__/project-search-input.test.tsx +110 -0
  39. package/src/components/dashboard/__tests__/project-section-header.test.tsx +91 -0
  40. package/src/components/dashboard/__tests__/project-section.test.tsx +151 -0
  41. package/src/components/dashboard/__tests__/run-card.test.tsx +164 -0
  42. package/src/components/dashboard/__tests__/run-filter-bar.test.tsx +109 -0
  43. package/src/components/dashboard/__tests__/run-list.test.tsx +123 -0
  44. package/src/components/dashboard/__tests__/search-filter.test.tsx +150 -0
  45. package/src/components/dashboard/__tests__/virtualized-run-list.test.tsx +179 -0
  46. package/src/components/dashboard/breakpoint-banner.tsx +301 -0
  47. package/src/components/dashboard/catch-up-banner.tsx +88 -0
  48. package/src/components/dashboard/executive-summary-banner.tsx +174 -0
  49. package/src/components/dashboard/global-search.tsx +323 -0
  50. package/src/components/dashboard/kpi-grid.tsx +140 -0
  51. package/src/components/dashboard/pagination-controls.tsx +100 -0
  52. package/src/components/dashboard/project-accordion.tsx +72 -0
  53. package/src/components/dashboard/project-health-card.tsx +536 -0
  54. package/src/components/dashboard/project-list-view.tsx +246 -0
  55. package/src/components/dashboard/project-search-input.tsx +41 -0
  56. package/src/components/dashboard/project-section-header.tsx +73 -0
  57. package/src/components/dashboard/project-section.tsx +89 -0
  58. package/src/components/dashboard/run-card.tsx +218 -0
  59. package/src/components/dashboard/run-filter-bar.tsx +100 -0
  60. package/src/components/dashboard/run-list.tsx +77 -0
  61. package/src/components/dashboard/search-filter.tsx +69 -0
  62. package/src/components/dashboard/virtualized-run-list.tsx +130 -0
  63. package/src/components/details/.gitkeep +0 -0
  64. package/src/components/details/__tests__/agent-panel.test.tsx +236 -0
  65. package/src/components/details/__tests__/json-tree.test.tsx +347 -0
  66. package/src/components/details/__tests__/log-viewer.test.tsx +168 -0
  67. package/src/components/details/__tests__/task-detail.test.tsx +212 -0
  68. package/src/components/details/__tests__/timing-panel.test.tsx +271 -0
  69. package/src/components/details/agent-panel.tsx +234 -0
  70. package/src/components/details/json-tree/categorize.ts +131 -0
  71. package/src/components/details/json-tree/index.tsx +120 -0
  72. package/src/components/details/json-tree/json-node.tsx +223 -0
  73. package/src/components/details/json-tree/smart-summary.tsx +596 -0
  74. package/src/components/details/json-tree/tree-controls.tsx +47 -0
  75. package/src/components/details/json-tree.tsx +9 -0
  76. package/src/components/details/log-viewer.tsx +140 -0
  77. package/src/components/details/task-detail.tsx +114 -0
  78. package/src/components/details/timing-panel.tsx +247 -0
  79. package/src/components/events/.gitkeep +0 -0
  80. package/src/components/events/__tests__/event-item.test.tsx +211 -0
  81. package/src/components/events/__tests__/event-stream.test.tsx +225 -0
  82. package/src/components/events/event-item.tsx +121 -0
  83. package/src/components/events/event-stream.tsx +260 -0
  84. package/src/components/notifications/.gitkeep +0 -0
  85. package/src/components/notifications/__tests__/notification-panel.test.tsx +287 -0
  86. package/src/components/notifications/__tests__/notification-provider.test.tsx +585 -0
  87. package/src/components/notifications/__tests__/toast-stack.test.tsx +217 -0
  88. package/src/components/notifications/notification-panel.tsx +124 -0
  89. package/src/components/notifications/notification-provider.tsx +175 -0
  90. package/src/components/notifications/toast-stack.tsx +75 -0
  91. package/src/components/pipeline/.gitkeep +0 -0
  92. package/src/components/pipeline/__tests__/parallel-group.test.tsx +88 -0
  93. package/src/components/pipeline/__tests__/pipeline-view.test.tsx +345 -0
  94. package/src/components/pipeline/__tests__/step-card.test.tsx +330 -0
  95. package/src/components/pipeline/parallel-group.tsx +39 -0
  96. package/src/components/pipeline/pipeline-view.tsx +197 -0
  97. package/src/components/pipeline/step-card.tsx +166 -0
  98. package/src/components/providers/event-stream-provider.tsx +29 -0
  99. package/src/components/providers.tsx +24 -0
  100. package/src/components/shared/.gitkeep +0 -0
  101. package/src/components/shared/__tests__/empty-state.test.tsx +49 -0
  102. package/src/components/shared/__tests__/friendly-id.test.tsx +47 -0
  103. package/src/components/shared/__tests__/kbd.test.tsx +45 -0
  104. package/src/components/shared/__tests__/kind-badge.test.tsx +71 -0
  105. package/src/components/shared/__tests__/metrics-row.test.tsx +74 -0
  106. package/src/components/shared/__tests__/outcome-banner.test.tsx +71 -0
  107. package/src/components/shared/__tests__/progress-bar.test.tsx +89 -0
  108. package/src/components/shared/__tests__/session-pill.test.tsx +62 -0
  109. package/src/components/shared/__tests__/settings-modal.test.tsx +201 -0
  110. package/src/components/shared/__tests__/shortcuts-help.test.tsx +103 -0
  111. package/src/components/shared/__tests__/status-badge.test.tsx +98 -0
  112. package/src/components/shared/__tests__/theme-provider.test.tsx +100 -0
  113. package/src/components/shared/__tests__/truncated-id.test.tsx +53 -0
  114. package/src/components/shared/app-footer.tsx +80 -0
  115. package/src/components/shared/app-header.tsx +160 -0
  116. package/src/components/shared/empty-state.tsx +18 -0
  117. package/src/components/shared/error-boundary.tsx +81 -0
  118. package/src/components/shared/friendly-id.tsx +48 -0
  119. package/src/components/shared/kbd.tsx +15 -0
  120. package/src/components/shared/kind-badge.tsx +51 -0
  121. package/src/components/shared/metrics-row.tsx +106 -0
  122. package/src/components/shared/outcome-banner.tsx +56 -0
  123. package/src/components/shared/progress-bar.tsx +42 -0
  124. package/src/components/shared/session-pill.tsx +69 -0
  125. package/src/components/shared/settings-modal.tsx +509 -0
  126. package/src/components/shared/shortcuts-help.tsx +113 -0
  127. package/src/components/shared/status-badge.tsx +110 -0
  128. package/src/components/shared/theme-provider.tsx +46 -0
  129. package/src/components/shared/truncated-id.tsx +51 -0
  130. package/src/components/ui/.gitkeep +0 -0
  131. package/src/components/ui/__tests__/accordion.test.tsx +96 -0
  132. package/src/components/ui/__tests__/badge.test.tsx +69 -0
  133. package/src/components/ui/__tests__/button.test.tsx +113 -0
  134. package/src/components/ui/__tests__/tabs.test.tsx +75 -0
  135. package/src/components/ui/__tests__/tooltip.test.tsx +90 -0
  136. package/src/components/ui/accordion.tsx +61 -0
  137. package/src/components/ui/badge.tsx +25 -0
  138. package/src/components/ui/button.tsx +40 -0
  139. package/src/components/ui/card.tsx +21 -0
  140. package/src/components/ui/scroll-area.tsx +35 -0
  141. package/src/components/ui/separator.tsx +24 -0
  142. package/src/components/ui/tabs.tsx +64 -0
  143. package/src/components/ui/tooltip.tsx +37 -0
  144. package/src/hooks/.gitkeep +0 -0
  145. package/src/hooks/__tests__/use-animated-number.test.ts +184 -0
  146. package/src/hooks/__tests__/use-batched-updates.test.ts +315 -0
  147. package/src/hooks/__tests__/use-event-stream.test.ts +243 -0
  148. package/src/hooks/__tests__/use-keyboard.test.ts +217 -0
  149. package/src/hooks/__tests__/use-notifications.test.ts +230 -0
  150. package/src/hooks/__tests__/use-polling.test.ts +274 -0
  151. package/src/hooks/__tests__/use-project-runs.test.ts +163 -0
  152. package/src/hooks/__tests__/use-projects.test.ts +248 -0
  153. package/src/hooks/__tests__/use-run-dashboard.test.ts +168 -0
  154. package/src/hooks/__tests__/use-run-detail.test.ts +273 -0
  155. package/src/hooks/__tests__/use-smart-polling.test.ts +305 -0
  156. package/src/hooks/use-animated-number.ts +87 -0
  157. package/src/hooks/use-batched-updates.ts +150 -0
  158. package/src/hooks/use-event-stream.ts +150 -0
  159. package/src/hooks/use-keyboard.ts +45 -0
  160. package/src/hooks/use-notifications.ts +82 -0
  161. package/src/hooks/use-persisted-state.ts +60 -0
  162. package/src/hooks/use-polling.ts +60 -0
  163. package/src/hooks/use-project-runs.ts +51 -0
  164. package/src/hooks/use-projects.ts +26 -0
  165. package/src/hooks/use-run-dashboard.ts +207 -0
  166. package/src/hooks/use-run-detail.ts +77 -0
  167. package/src/hooks/use-smart-polling.ts +144 -0
  168. package/src/lib/.gitkeep +0 -0
  169. package/src/lib/__tests__/cn.test.ts +69 -0
  170. package/src/lib/__tests__/config-loader.test.ts +210 -0
  171. package/src/lib/__tests__/config.test.ts +561 -0
  172. package/src/lib/__tests__/error-handler.test.ts +143 -0
  173. package/src/lib/__tests__/fetcher.test.ts +517 -0
  174. package/src/lib/__tests__/global-registry.test.ts +214 -0
  175. package/src/lib/__tests__/parser.test.ts +1532 -0
  176. package/src/lib/__tests__/path-resolver.test.ts +112 -0
  177. package/src/lib/__tests__/run-cache.test.ts +591 -0
  178. package/src/lib/__tests__/server-init.test.ts +512 -0
  179. package/src/lib/__tests__/source-discovery.test.ts +246 -0
  180. package/src/lib/__tests__/utils.test.ts +160 -0
  181. package/src/lib/__tests__/watcher.test.ts +227 -0
  182. package/src/lib/cn.ts +6 -0
  183. package/src/lib/config-loader.ts +195 -0
  184. package/src/lib/config.ts +20 -0
  185. package/src/lib/error-handler.ts +76 -0
  186. package/src/lib/fetcher.ts +394 -0
  187. package/src/lib/global-registry.ts +117 -0
  188. package/src/lib/parser.ts +794 -0
  189. package/src/lib/path-resolver.ts +16 -0
  190. package/src/lib/run-cache.ts +404 -0
  191. package/src/lib/server-init.ts +226 -0
  192. package/src/lib/services/__tests__/run-query-service.test.ts +819 -0
  193. package/src/lib/services/run-query-service.ts +286 -0
  194. package/src/lib/source-discovery.ts +216 -0
  195. package/src/lib/utils.ts +103 -0
  196. package/src/lib/watcher.ts +265 -0
  197. package/src/test/fixtures.ts +269 -0
  198. package/src/test/mocks/handlers.ts +110 -0
  199. package/src/test/mocks/server.ts +17 -0
  200. package/src/test/setup.ts +200 -0
  201. package/src/test/test-utils.tsx +36 -0
  202. package/src/types/.gitkeep +0 -0
  203. package/src/types/breakpoint.ts +17 -0
  204. package/src/types/index.ts +214 -0
  205. 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">&quot;{value}&quot;</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 };