@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,200 @@
|
|
|
1
|
+
import { expect, afterEach, vi } from 'vitest';
|
|
2
|
+
import { cleanup } from '@testing-library/react';
|
|
3
|
+
import * as matchers from '@testing-library/jest-dom/matchers';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
6
|
+
// Mock next/navigation
|
|
7
|
+
vi.mock('next/navigation', () => ({
|
|
8
|
+
useRouter: () => ({ push: vi.fn(), replace: vi.fn(), back: vi.fn(), prefetch: vi.fn() }),
|
|
9
|
+
usePathname: () => '/',
|
|
10
|
+
useSearchParams: () => new URLSearchParams(),
|
|
11
|
+
useParams: () => ({}),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
// Mock next/dynamic — pass through to the real component synchronously.
|
|
15
|
+
// The loader function (e.g. () => import("./agent-panel")) returns a Promise,
|
|
16
|
+
// but in vitest the module is already loaded. We extract the module path from
|
|
17
|
+
// the loader's toString() to resolve it synchronously. As a fallback, we use
|
|
18
|
+
// async resolution with React state.
|
|
19
|
+
vi.mock('next/dynamic', () => {
|
|
20
|
+
const dynamic = (loader: () => Promise<any>, _opts?: any) => {
|
|
21
|
+
// Attempt synchronous resolution: call loader and intercept the result.
|
|
22
|
+
// In vitest, the dynamic import resolves on the next microtick, so we
|
|
23
|
+
// eagerly kick it off and cache the result for subsequent renders.
|
|
24
|
+
let Resolved: React.ComponentType<any> | null = null;
|
|
25
|
+
const loadPromise = loader().then((mod: any) => {
|
|
26
|
+
Resolved = mod.default || mod;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const DynamicComponent = (props: any) => {
|
|
30
|
+
const [Comp, setComp] = React.useState<React.ComponentType<any> | null>(() => Resolved);
|
|
31
|
+
|
|
32
|
+
React.useEffect(() => {
|
|
33
|
+
if (!Comp && !Resolved) {
|
|
34
|
+
loadPromise.then(() => {
|
|
35
|
+
if (Resolved) setComp(() => Resolved);
|
|
36
|
+
});
|
|
37
|
+
} else if (!Comp && Resolved) {
|
|
38
|
+
setComp(() => Resolved);
|
|
39
|
+
}
|
|
40
|
+
}, [Comp]);
|
|
41
|
+
|
|
42
|
+
const Active = Comp || Resolved;
|
|
43
|
+
if (Active) {
|
|
44
|
+
return React.createElement(Active, props);
|
|
45
|
+
}
|
|
46
|
+
if (_opts?.loading) {
|
|
47
|
+
return React.createElement(_opts.loading, {});
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
};
|
|
51
|
+
DynamicComponent.displayName = 'DynamicComponent';
|
|
52
|
+
(DynamicComponent as any).preload = () => loadPromise;
|
|
53
|
+
return DynamicComponent;
|
|
54
|
+
};
|
|
55
|
+
return { __esModule: true, default: dynamic };
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Mock lucide-react to avoid React version mismatch in monorepo
|
|
59
|
+
// (observer has React 18 locally, root has React 19)
|
|
60
|
+
vi.mock('lucide-react', () => {
|
|
61
|
+
const createIconMock = (name: string) => {
|
|
62
|
+
const Icon = React.forwardRef<SVGSVGElement, any>(
|
|
63
|
+
function IconMock(props: any, ref: any) {
|
|
64
|
+
return React.createElement('svg', {
|
|
65
|
+
...props,
|
|
66
|
+
ref,
|
|
67
|
+
'data-testid': `icon-${name}`,
|
|
68
|
+
'data-lucide': name,
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
Icon.displayName = name;
|
|
73
|
+
return Icon;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// All icon names used across the codebase
|
|
77
|
+
const iconNames = [
|
|
78
|
+
'Activity', 'AlertCircle', 'AlertTriangle', 'ArrowLeft', 'ArrowRight',
|
|
79
|
+
'ArrowUpDown', 'Bell', 'Bot',
|
|
80
|
+
'CalendarDays', 'Check', 'CheckCircle2', 'ChevronDown', 'ChevronLeft',
|
|
81
|
+
'ChevronRight', 'ChevronUp', 'Circle', 'Clock', 'Code', 'Cog', 'Copy',
|
|
82
|
+
'ExternalLink', 'Eye', 'EyeOff', 'FileJson', 'FileText', 'FolderOpen',
|
|
83
|
+
'GitBranch', 'Github',
|
|
84
|
+
'Hand', 'Hash', 'HelpCircle', 'History', 'Inbox', 'Info', 'Layers',
|
|
85
|
+
'Loader2', 'Moon', 'Palette',
|
|
86
|
+
'Pause', 'Percent', 'Pin', 'Plus', 'Puzzle', 'RefreshCw', 'Search', 'Settings',
|
|
87
|
+
'Sun', 'Tag', 'Terminal', 'Timer', 'Trash2', 'Wifi', 'WifiOff',
|
|
88
|
+
'X', 'XCircle',
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
const mocks: Record<string, any> = {};
|
|
92
|
+
for (const name of iconNames) {
|
|
93
|
+
mocks[name] = createIconMock(name);
|
|
94
|
+
}
|
|
95
|
+
return mocks;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Extend vitest's expect with jest-dom matchers
|
|
99
|
+
expect.extend(matchers);
|
|
100
|
+
|
|
101
|
+
// Clean up after each test.
|
|
102
|
+
// We also re-apply the raf/caf polyfills BEFORE cleanup because some tests
|
|
103
|
+
// (e.g. use-animated-number) call vi.restoreAllMocks() which removes stubs
|
|
104
|
+
// including cancelAnimationFrame. React's passive-effect cleanup still needs it.
|
|
105
|
+
afterEach(() => {
|
|
106
|
+
if (typeof globalThis.cancelAnimationFrame !== 'function') {
|
|
107
|
+
globalThis.cancelAnimationFrame = (id: number) => clearTimeout(id);
|
|
108
|
+
}
|
|
109
|
+
if (typeof globalThis.requestAnimationFrame !== 'function') {
|
|
110
|
+
globalThis.requestAnimationFrame = (cb: FrameRequestCallback) => setTimeout(cb, 0) as unknown as number;
|
|
111
|
+
}
|
|
112
|
+
cleanup();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Mock window.matchMedia
|
|
116
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
117
|
+
writable: true,
|
|
118
|
+
value: (query: string) => ({
|
|
119
|
+
matches: false,
|
|
120
|
+
media: query,
|
|
121
|
+
onchange: null,
|
|
122
|
+
addListener: () => {},
|
|
123
|
+
removeListener: () => {},
|
|
124
|
+
addEventListener: () => {},
|
|
125
|
+
removeEventListener: () => {},
|
|
126
|
+
dispatchEvent: () => false,
|
|
127
|
+
}),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Mock IntersectionObserver
|
|
131
|
+
class MockIntersectionObserver implements IntersectionObserver {
|
|
132
|
+
readonly root: Element | null = null;
|
|
133
|
+
readonly rootMargin: string = '';
|
|
134
|
+
readonly thresholds: ReadonlyArray<number> = [];
|
|
135
|
+
|
|
136
|
+
constructor(
|
|
137
|
+
private callback: IntersectionObserverCallback,
|
|
138
|
+
_options?: IntersectionObserverInit,
|
|
139
|
+
) {}
|
|
140
|
+
|
|
141
|
+
observe(): void {}
|
|
142
|
+
unobserve(): void {}
|
|
143
|
+
disconnect(): void {}
|
|
144
|
+
takeRecords(): IntersectionObserverEntry[] {
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
Object.defineProperty(window, 'IntersectionObserver', {
|
|
150
|
+
writable: true,
|
|
151
|
+
value: MockIntersectionObserver,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Mock navigator.clipboard (configurable so userEvent can re-stub it)
|
|
155
|
+
Object.defineProperty(navigator, 'clipboard', {
|
|
156
|
+
writable: true,
|
|
157
|
+
configurable: true,
|
|
158
|
+
value: {
|
|
159
|
+
writeText: async (_text: string) => {},
|
|
160
|
+
readText: async () => '',
|
|
161
|
+
write: async () => {},
|
|
162
|
+
read: async () => [],
|
|
163
|
+
addEventListener: () => {},
|
|
164
|
+
removeEventListener: () => {},
|
|
165
|
+
dispatchEvent: () => false,
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Mock ResizeObserver (used by Radix UI components)
|
|
170
|
+
class MockResizeObserver {
|
|
171
|
+
observe(): void {}
|
|
172
|
+
unobserve(): void {}
|
|
173
|
+
disconnect(): void {}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
Object.defineProperty(window, 'ResizeObserver', {
|
|
177
|
+
writable: true,
|
|
178
|
+
value: MockResizeObserver,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Polyfill requestAnimationFrame/cancelAnimationFrame for jsdom
|
|
182
|
+
// Some jsdom versions do not expose these globals — ensure they exist on both
|
|
183
|
+
// window and globalThis so component cleanup callbacks can find them.
|
|
184
|
+
const _raf = (cb: FrameRequestCallback) => setTimeout(cb, 0) as unknown as number;
|
|
185
|
+
const _caf = (id: number) => clearTimeout(id);
|
|
186
|
+
|
|
187
|
+
if (typeof globalThis.requestAnimationFrame === 'undefined') {
|
|
188
|
+
globalThis.requestAnimationFrame = _raf;
|
|
189
|
+
}
|
|
190
|
+
if (typeof globalThis.cancelAnimationFrame === 'undefined') {
|
|
191
|
+
globalThis.cancelAnimationFrame = _caf;
|
|
192
|
+
}
|
|
193
|
+
if (typeof window !== 'undefined') {
|
|
194
|
+
if (typeof window.requestAnimationFrame === 'undefined') {
|
|
195
|
+
window.requestAnimationFrame = _raf;
|
|
196
|
+
}
|
|
197
|
+
if (typeof window.cancelAnimationFrame === 'undefined') {
|
|
198
|
+
window.cancelAnimationFrame = _caf;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React, { type ReactElement } from 'react';
|
|
2
|
+
import { render, type RenderOptions } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Minimal test wrapper that provides the contexts needed by most components.
|
|
7
|
+
* We avoid importing the real Providers tree because it depends on hooks that
|
|
8
|
+
* fire network requests (polling, SSE) which would interfere with tests.
|
|
9
|
+
* Individual tests that need a specific provider can compose their own wrapper.
|
|
10
|
+
*/
|
|
11
|
+
function TestWrapper({ children }: { children: React.ReactNode }) {
|
|
12
|
+
return <>{children}</>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Custom render that wraps the component in TestWrapper.
|
|
17
|
+
* Use this instead of importing render directly from @testing-library/react.
|
|
18
|
+
*/
|
|
19
|
+
function customRender(
|
|
20
|
+
ui: ReactElement,
|
|
21
|
+
options?: Omit<RenderOptions, 'wrapper'> & { wrapper?: React.ComponentType },
|
|
22
|
+
) {
|
|
23
|
+
const Wrapper = options?.wrapper ?? TestWrapper;
|
|
24
|
+
return render(ui, { wrapper: Wrapper, ...options });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Re-export everything from testing-library
|
|
28
|
+
export * from '@testing-library/react';
|
|
29
|
+
|
|
30
|
+
// Override render with our custom version
|
|
31
|
+
export { customRender as render };
|
|
32
|
+
|
|
33
|
+
// Export a pre-configured userEvent instance
|
|
34
|
+
export function setupUser() {
|
|
35
|
+
return userEvent.setup();
|
|
36
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Breakpoint-related types
|
|
2
|
+
|
|
3
|
+
export interface BreakpointFile {
|
|
4
|
+
path: string;
|
|
5
|
+
format: string;
|
|
6
|
+
language?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface BreakpointPayload {
|
|
10
|
+
question: string;
|
|
11
|
+
title: string;
|
|
12
|
+
options?: string[];
|
|
13
|
+
context?: {
|
|
14
|
+
files?: BreakpointFile[];
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// Re-export breakpoint types
|
|
2
|
+
export * from "./breakpoint";
|
|
3
|
+
|
|
4
|
+
// Run status
|
|
5
|
+
export type RunStatus = "pending" | "waiting" | "completed" | "failed";
|
|
6
|
+
|
|
7
|
+
// Task/effect kind
|
|
8
|
+
export type TaskKind = "node" | "agent" | "skill" | "breakpoint" | "shell" | "sleep";
|
|
9
|
+
|
|
10
|
+
// Task status
|
|
11
|
+
export type TaskStatus = "requested" | "resolved" | "error";
|
|
12
|
+
|
|
13
|
+
// Journal event types
|
|
14
|
+
export type EventType =
|
|
15
|
+
| "RUN_CREATED"
|
|
16
|
+
| "EFFECT_REQUESTED"
|
|
17
|
+
| "EFFECT_RESOLVED"
|
|
18
|
+
| "RUN_COMPLETED"
|
|
19
|
+
| "RUN_FAILED";
|
|
20
|
+
|
|
21
|
+
// Journal event
|
|
22
|
+
export interface JournalEvent {
|
|
23
|
+
seq: number;
|
|
24
|
+
id: string;
|
|
25
|
+
ts: string;
|
|
26
|
+
type: EventType;
|
|
27
|
+
payload: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface RunCreatedPayload {
|
|
31
|
+
runId: string;
|
|
32
|
+
processId: string;
|
|
33
|
+
processRevision?: string;
|
|
34
|
+
entrypoint?: {
|
|
35
|
+
importPath: string;
|
|
36
|
+
exportName: string;
|
|
37
|
+
};
|
|
38
|
+
inputsRef?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface EffectRequestedPayload {
|
|
42
|
+
effectId: string;
|
|
43
|
+
invocationKey: string;
|
|
44
|
+
stepId: string;
|
|
45
|
+
taskId: string;
|
|
46
|
+
kind: TaskKind;
|
|
47
|
+
label: string;
|
|
48
|
+
taskDefRef?: string;
|
|
49
|
+
inputsRef?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface EffectResolvedPayload {
|
|
53
|
+
effectId: string;
|
|
54
|
+
status: "ok" | "error";
|
|
55
|
+
resultRef?: string;
|
|
56
|
+
stdoutRef?: string;
|
|
57
|
+
stderrRef?: string;
|
|
58
|
+
startedAt?: string;
|
|
59
|
+
finishedAt?: string;
|
|
60
|
+
error?: {
|
|
61
|
+
name: string;
|
|
62
|
+
message: string;
|
|
63
|
+
stack?: string;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Parsed task/effect
|
|
68
|
+
export interface TaskEffect {
|
|
69
|
+
effectId: string;
|
|
70
|
+
kind: TaskKind;
|
|
71
|
+
title: string;
|
|
72
|
+
label: string;
|
|
73
|
+
status: TaskStatus;
|
|
74
|
+
invocationKey: string;
|
|
75
|
+
stepId: string;
|
|
76
|
+
taskId: string;
|
|
77
|
+
requestedAt: string;
|
|
78
|
+
resolvedAt?: string;
|
|
79
|
+
startedAt?: string;
|
|
80
|
+
finishedAt?: string;
|
|
81
|
+
duration?: number; // ms
|
|
82
|
+
error?: {
|
|
83
|
+
name: string;
|
|
84
|
+
message: string;
|
|
85
|
+
stack?: string;
|
|
86
|
+
};
|
|
87
|
+
breakpointQuestion?: string;
|
|
88
|
+
agent?: {
|
|
89
|
+
name: string;
|
|
90
|
+
prompt?: {
|
|
91
|
+
role: string;
|
|
92
|
+
task: string;
|
|
93
|
+
instructions: string[];
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Full task detail (on-demand)
|
|
99
|
+
export interface TaskDetail extends TaskEffect {
|
|
100
|
+
input?: Record<string, unknown>;
|
|
101
|
+
result?: Record<string, unknown>;
|
|
102
|
+
stdout?: string;
|
|
103
|
+
stderr?: string;
|
|
104
|
+
taskDef?: Record<string, unknown>;
|
|
105
|
+
breakpoint?: import("./breakpoint").BreakpointPayload;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Run summary
|
|
109
|
+
export interface Run {
|
|
110
|
+
runId: string;
|
|
111
|
+
processId: string;
|
|
112
|
+
status: RunStatus;
|
|
113
|
+
createdAt: string;
|
|
114
|
+
updatedAt: string;
|
|
115
|
+
completedAt?: string;
|
|
116
|
+
sessionId?: string;
|
|
117
|
+
tasks: TaskEffect[];
|
|
118
|
+
events: JournalEvent[];
|
|
119
|
+
totalTasks: number;
|
|
120
|
+
completedTasks: number;
|
|
121
|
+
failedTasks: number;
|
|
122
|
+
duration?: number; // ms
|
|
123
|
+
failedStep?: string;
|
|
124
|
+
failureError?: string;
|
|
125
|
+
failureMessage?: string;
|
|
126
|
+
breakpointQuestion?: string;
|
|
127
|
+
sourceLabel?: string;
|
|
128
|
+
projectName?: string;
|
|
129
|
+
isStale?: boolean;
|
|
130
|
+
waitingKind?: 'breakpoint' | 'task';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Lightweight digest for polling
|
|
134
|
+
export interface RunDigest {
|
|
135
|
+
runId: string;
|
|
136
|
+
latestSeq: number;
|
|
137
|
+
status: RunStatus;
|
|
138
|
+
taskCount: number;
|
|
139
|
+
completedTasks: number;
|
|
140
|
+
updatedAt: string;
|
|
141
|
+
pendingBreakpoints?: number;
|
|
142
|
+
breakpointQuestion?: string;
|
|
143
|
+
breakpointEffectId?: string;
|
|
144
|
+
sourceLabel?: string;
|
|
145
|
+
projectName?: string;
|
|
146
|
+
isStale?: boolean;
|
|
147
|
+
waitingKind?: 'breakpoint' | 'task';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Project grouping for dashboard
|
|
151
|
+
export interface ProjectGroup {
|
|
152
|
+
projectName: string;
|
|
153
|
+
runs: Run[];
|
|
154
|
+
totalRuns: number;
|
|
155
|
+
activeRuns: number;
|
|
156
|
+
completedRuns: number;
|
|
157
|
+
failedRuns: number;
|
|
158
|
+
latestUpdate: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Breakpoint info for a single waiting run
|
|
162
|
+
export interface BreakpointRunInfo {
|
|
163
|
+
runId: string;
|
|
164
|
+
effectId: string;
|
|
165
|
+
projectName: string;
|
|
166
|
+
processId: string;
|
|
167
|
+
breakpointQuestion: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Project summary (lightweight, no run payloads)
|
|
171
|
+
export interface ProjectSummary {
|
|
172
|
+
projectName: string;
|
|
173
|
+
totalRuns: number;
|
|
174
|
+
activeRuns: number;
|
|
175
|
+
completedRuns: number;
|
|
176
|
+
failedRuns: number;
|
|
177
|
+
staleRuns: number;
|
|
178
|
+
totalTasks: number;
|
|
179
|
+
completedTasksAggregate: number;
|
|
180
|
+
latestUpdate: string;
|
|
181
|
+
pendingBreakpoints: number;
|
|
182
|
+
breakpointRuns: BreakpointRunInfo[];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Session info
|
|
186
|
+
export interface SessionInfo {
|
|
187
|
+
sessionId: string;
|
|
188
|
+
active: boolean;
|
|
189
|
+
startedAt?: string;
|
|
190
|
+
runId?: string;
|
|
191
|
+
iteration?: number;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// API response types
|
|
195
|
+
export interface DigestResponse {
|
|
196
|
+
runs: RunDigest[];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export interface RunsResponse {
|
|
200
|
+
runs: Run[];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export interface RunDetailResponse {
|
|
204
|
+
run: Run;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export interface EventsResponse {
|
|
208
|
+
events: JournalEvent[];
|
|
209
|
+
total: number;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export interface TaskDetailResponse {
|
|
213
|
+
task: TaskDetail;
|
|
214
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": [
|
|
5
|
+
"dom",
|
|
6
|
+
"dom.iterable",
|
|
7
|
+
"esnext"
|
|
8
|
+
],
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"module": "esnext",
|
|
15
|
+
"moduleResolution": "bundler",
|
|
16
|
+
"resolveJsonModule": true,
|
|
17
|
+
"isolatedModules": true,
|
|
18
|
+
"jsx": "react-jsx",
|
|
19
|
+
"incremental": true,
|
|
20
|
+
"plugins": [
|
|
21
|
+
{
|
|
22
|
+
"name": "next"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"typeRoots": [
|
|
26
|
+
"./node_modules/@types",
|
|
27
|
+
"../../node_modules/@types"
|
|
28
|
+
],
|
|
29
|
+
"paths": {
|
|
30
|
+
"@/*": [
|
|
31
|
+
"./src/*"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"include": [
|
|
36
|
+
"next-env.d.ts",
|
|
37
|
+
"**/*.ts",
|
|
38
|
+
"**/*.tsx",
|
|
39
|
+
".next/types/**/*.ts",
|
|
40
|
+
".next/dev/types/**/*.ts"
|
|
41
|
+
],
|
|
42
|
+
"exclude": [
|
|
43
|
+
"node_modules",
|
|
44
|
+
"e2e",
|
|
45
|
+
"playwright.config.ts",
|
|
46
|
+
"src/**/__tests__/**",
|
|
47
|
+
"src/**/*.test.ts",
|
|
48
|
+
"src/**/*.test.tsx"
|
|
49
|
+
]
|
|
50
|
+
}
|