@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,214 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { getGlobal, clearGlobal, clearAllGlobals } from '../global-registry';
3
+
4
+ describe('global-registry', () => {
5
+ beforeEach(() => {
6
+ clearAllGlobals();
7
+ });
8
+
9
+ // -----------------------------------------------------------------------
10
+ // getGlobal
11
+ // -----------------------------------------------------------------------
12
+ describe('getGlobal', () => {
13
+ it('initialises the value via factory on first call', () => {
14
+ const result = getGlobal('__observer_run_cache__', () => new Map());
15
+
16
+ expect(result).toBeInstanceOf(Map);
17
+ expect(result.size).toBe(0);
18
+ });
19
+
20
+ it('returns the same instance on subsequent calls (HMR persistence)', () => {
21
+ const first = getGlobal('__observer_run_cache__', () => new Map());
22
+ first.set('key', { some: 'value' });
23
+
24
+ const second = getGlobal('__observer_run_cache__', () => new Map());
25
+
26
+ expect(second).toBe(first);
27
+ expect(second.size).toBe(1);
28
+ });
29
+
30
+ it('does not call factory again after initial creation', () => {
31
+ let callCount = 0;
32
+ const factory = () => {
33
+ callCount++;
34
+ return new Map();
35
+ };
36
+
37
+ getGlobal('__observer_run_cache__', factory);
38
+ getGlobal('__observer_run_cache__', factory);
39
+ getGlobal('__observer_run_cache__', factory);
40
+
41
+ expect(callCount).toBe(1);
42
+ });
43
+
44
+ it('stores different keys independently', () => {
45
+ const cache = getGlobal('__observer_run_cache__', () => new Map());
46
+ const initState = getGlobal('__observer_init__', () => ({
47
+ initialized: false,
48
+ initPromise: null,
49
+ cleanup: null,
50
+ }));
51
+
52
+ expect(cache).toBeInstanceOf(Map);
53
+ expect(initState).toHaveProperty('initialized', false);
54
+ expect(cache).not.toBe(initState);
55
+ });
56
+
57
+ it('creates the registry container on first access', () => {
58
+ // Before any call, the registry might not exist
59
+ globalThis.__observer_registry__ = undefined;
60
+
61
+ getGlobal('__observer_run_cache__', () => new Map());
62
+
63
+ expect(globalThis.__observer_registry__).toBeDefined();
64
+ expect(globalThis.__observer_registry__!.__observer_run_cache__).toBeInstanceOf(Map);
65
+ });
66
+
67
+ it('works with complex object types (watcher state)', () => {
68
+ const state = getGlobal('__observer_watchers__', () => ({
69
+ activeWatchers: new Map(),
70
+ debounceTimers: new Map(),
71
+ rescanTimer: null,
72
+ }));
73
+
74
+ expect(state.activeWatchers).toBeInstanceOf(Map);
75
+ expect(state.debounceTimers).toBeInstanceOf(Map);
76
+ expect(state.rescanTimer).toBeNull();
77
+
78
+ // Mutate and verify persistence
79
+ state.rescanTimer = setTimeout(() => {}, 0);
80
+ clearTimeout(state.rescanTimer);
81
+
82
+ const samState = getGlobal('__observer_watchers__', () => ({
83
+ activeWatchers: new Map(),
84
+ debounceTimers: new Map(),
85
+ rescanTimer: null,
86
+ }));
87
+
88
+ expect(samState).toBe(state);
89
+ expect(samState.rescanTimer).not.toBeNull();
90
+ });
91
+
92
+ it('works with Set-containing types (debounce state)', () => {
93
+ const ds = getGlobal('__observer_sse_debounce__', () => ({
94
+ pendingRunDirs: new Set<string>(),
95
+ timer: null,
96
+ windowOpen: false,
97
+ }));
98
+
99
+ ds.pendingRunDirs.add('/runs/r1');
100
+ ds.windowOpen = true;
101
+
102
+ const same = getGlobal('__observer_sse_debounce__', () => ({
103
+ pendingRunDirs: new Set<string>(),
104
+ timer: null,
105
+ windowOpen: false,
106
+ }));
107
+
108
+ expect(same.pendingRunDirs.has('/runs/r1')).toBe(true);
109
+ expect(same.windowOpen).toBe(true);
110
+ });
111
+ });
112
+
113
+ // -----------------------------------------------------------------------
114
+ // clearGlobal
115
+ // -----------------------------------------------------------------------
116
+ describe('clearGlobal', () => {
117
+ it('removes a specific key from the registry', () => {
118
+ getGlobal('__observer_run_cache__', () => new Map());
119
+ getGlobal('__observer_init__', () => ({
120
+ initialized: false,
121
+ initPromise: null,
122
+ cleanup: null,
123
+ }));
124
+
125
+ clearGlobal('__observer_run_cache__');
126
+
127
+ // The init state should still exist
128
+ expect(globalThis.__observer_registry__!.__observer_init__).toBeDefined();
129
+
130
+ // The cache should be gone, so next getGlobal should create a new one
131
+ const fresh = getGlobal('__observer_run_cache__', () => new Map());
132
+ expect(fresh.size).toBe(0);
133
+ });
134
+
135
+ it('is safe to call when registry does not exist', () => {
136
+ globalThis.__observer_registry__ = undefined;
137
+ expect(() => clearGlobal('__observer_run_cache__')).not.toThrow();
138
+ });
139
+
140
+ it('is safe to call when key does not exist', () => {
141
+ globalThis.__observer_registry__ = {};
142
+ expect(() => clearGlobal('__observer_run_cache__')).not.toThrow();
143
+ });
144
+ });
145
+
146
+ // -----------------------------------------------------------------------
147
+ // clearAllGlobals
148
+ // -----------------------------------------------------------------------
149
+ describe('clearAllGlobals', () => {
150
+ it('removes the entire registry', () => {
151
+ getGlobal('__observer_run_cache__', () => new Map());
152
+ getGlobal('__observer_init__', () => ({
153
+ initialized: false,
154
+ initPromise: null,
155
+ cleanup: null,
156
+ }));
157
+
158
+ clearAllGlobals();
159
+
160
+ expect(globalThis.__observer_registry__).toBeUndefined();
161
+ });
162
+
163
+ it('causes getGlobal to re-create values after clearing', () => {
164
+ const original = getGlobal('__observer_run_cache__', () => new Map());
165
+ original.set('key', 'value');
166
+
167
+ clearAllGlobals();
168
+
169
+ const fresh = getGlobal('__observer_run_cache__', () => new Map());
170
+ expect(fresh).not.toBe(original);
171
+ expect(fresh.size).toBe(0);
172
+ });
173
+
174
+ it('is safe to call multiple times', () => {
175
+ clearAllGlobals();
176
+ clearAllGlobals();
177
+ clearAllGlobals();
178
+ expect(globalThis.__observer_registry__).toBeUndefined();
179
+ });
180
+ });
181
+
182
+ // -----------------------------------------------------------------------
183
+ // HMR simulation
184
+ // -----------------------------------------------------------------------
185
+ describe('HMR safety', () => {
186
+ it('preserves state across simulated module re-evaluations', () => {
187
+ // Simulate first module load: store data
188
+ const cache1 = getGlobal('__observer_run_cache__', () => new Map());
189
+ cache1.set('run-001', { digest: 'some-data' });
190
+
191
+ // Simulate HMR: clear the module-level reference but NOT globalThis
192
+ // (this is what actually happens during HMR — globalThis persists)
193
+ // Just call getGlobal again as if the module were freshly evaluated
194
+ const cache2 = getGlobal('__observer_run_cache__', () => new Map());
195
+
196
+ expect(cache2).toBe(cache1);
197
+ expect(cache2.get('run-001')).toEqual({ digest: 'some-data' });
198
+ });
199
+
200
+ it('preserves EventEmitter listeners across simulated HMR', async () => {
201
+ const { EventEmitter } = await import('events');
202
+
203
+ const emitter = getGlobal('__observer_watcher_events__', () => new EventEmitter());
204
+ let called = false;
205
+ emitter.on('test', () => { called = true; });
206
+
207
+ // Simulate HMR: re-access the same global
208
+ const sameEmitter = getGlobal('__observer_watcher_events__', () => new EventEmitter());
209
+ sameEmitter.emit('test');
210
+
211
+ expect(called).toBe(true);
212
+ });
213
+ });
214
+ });