@urateam/dashboard 0.1.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 (69) hide show
  1. package/dist/__tests__/layout.test.d.ts +2 -0
  2. package/dist/__tests__/layout.test.d.ts.map +1 -0
  3. package/dist/__tests__/layout.test.js +138 -0
  4. package/dist/__tests__/layout.test.js.map +1 -0
  5. package/dist/__tests__/security.repro.test.d.ts +16 -0
  6. package/dist/__tests__/security.repro.test.d.ts.map +1 -0
  7. package/dist/__tests__/security.repro.test.js +249 -0
  8. package/dist/__tests__/security.repro.test.js.map +1 -0
  9. package/dist/__tests__/server.test.d.ts +2 -0
  10. package/dist/__tests__/server.test.d.ts.map +1 -0
  11. package/dist/__tests__/server.test.js +268 -0
  12. package/dist/__tests__/server.test.js.map +1 -0
  13. package/dist/__tests__/ui-refresh.test.d.ts +2 -0
  14. package/dist/__tests__/ui-refresh.test.d.ts.map +1 -0
  15. package/dist/__tests__/ui-refresh.test.js +860 -0
  16. package/dist/__tests__/ui-refresh.test.js.map +1 -0
  17. package/dist/routes/config.d.ts +4 -0
  18. package/dist/routes/config.d.ts.map +1 -0
  19. package/dist/routes/config.js +12 -0
  20. package/dist/routes/config.js.map +1 -0
  21. package/dist/routes/coordination.d.ts +4 -0
  22. package/dist/routes/coordination.d.ts.map +1 -0
  23. package/dist/routes/coordination.js +22 -0
  24. package/dist/routes/coordination.js.map +1 -0
  25. package/dist/routes/errors.d.ts +4 -0
  26. package/dist/routes/errors.d.ts.map +1 -0
  27. package/dist/routes/errors.js +36 -0
  28. package/dist/routes/errors.js.map +1 -0
  29. package/dist/routes/runs.d.ts +4 -0
  30. package/dist/routes/runs.d.ts.map +1 -0
  31. package/dist/routes/runs.js +72 -0
  32. package/dist/routes/runs.js.map +1 -0
  33. package/dist/routes/tokens.d.ts +4 -0
  34. package/dist/routes/tokens.d.ts.map +1 -0
  35. package/dist/routes/tokens.js +47 -0
  36. package/dist/routes/tokens.js.map +1 -0
  37. package/dist/server.d.ts +19 -0
  38. package/dist/server.d.ts.map +1 -0
  39. package/dist/server.js +107 -0
  40. package/dist/server.js.map +1 -0
  41. package/dist/views/config.d.ts +3 -0
  42. package/dist/views/config.d.ts.map +1 -0
  43. package/dist/views/config.js +47 -0
  44. package/dist/views/config.js.map +1 -0
  45. package/dist/views/coordination.d.ts +3 -0
  46. package/dist/views/coordination.d.ts.map +1 -0
  47. package/dist/views/coordination.js +63 -0
  48. package/dist/views/coordination.js.map +1 -0
  49. package/dist/views/errors.d.ts +14 -0
  50. package/dist/views/errors.d.ts.map +1 -0
  51. package/dist/views/errors.js +52 -0
  52. package/dist/views/errors.js.map +1 -0
  53. package/dist/views/layout.d.ts +21 -0
  54. package/dist/views/layout.d.ts.map +1 -0
  55. package/dist/views/layout.js +82 -0
  56. package/dist/views/layout.js.map +1 -0
  57. package/dist/views/run-detail.d.ts +35 -0
  58. package/dist/views/run-detail.d.ts.map +1 -0
  59. package/dist/views/run-detail.js +129 -0
  60. package/dist/views/run-detail.js.map +1 -0
  61. package/dist/views/run-feed.d.ts +14 -0
  62. package/dist/views/run-feed.d.ts.map +1 -0
  63. package/dist/views/run-feed.js +80 -0
  64. package/dist/views/run-feed.js.map +1 -0
  65. package/dist/views/tokens.d.ts +13 -0
  66. package/dist/views/tokens.d.ts.map +1 -0
  67. package/dist/views/tokens.js +66 -0
  68. package/dist/views/tokens.js.map +1 -0
  69. package/package.json +28 -0
@@ -0,0 +1,860 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { layout } from "../views/layout.js";
3
+ import { runDetailView } from "../views/run-detail.js";
4
+ import { runFeedView } from "../views/run-feed.js";
5
+ import { tokensView } from "../views/tokens.js";
6
+ import { coordinationView } from "../views/coordination.js";
7
+ describe("Dashboard UI Refresh — Visual & Accessibility", () => {
8
+ // These tests verify visual/structural properties of the layout at the root
9
+ // path (no basePath prefix). Clear DASHBOARD_BASE_PATH so that env-var
10
+ // contamination from other test files does not affect results.
11
+ let savedBasePath;
12
+ beforeEach(() => {
13
+ savedBasePath = process.env.DASHBOARD_BASE_PATH;
14
+ delete process.env.DASHBOARD_BASE_PATH;
15
+ });
16
+ afterEach(() => {
17
+ if (savedBasePath === undefined) {
18
+ delete process.env.DASHBOARD_BASE_PATH;
19
+ }
20
+ else {
21
+ process.env.DASHBOARD_BASE_PATH = savedBasePath;
22
+ }
23
+ });
24
+ describe("Modern Typography & Font Stack", () => {
25
+ it("should include Inter font from Google Fonts", () => {
26
+ const html = layout("Test", "");
27
+ expect(html).toContain("fonts.googleapis.com");
28
+ expect(html).toContain("fonts.gstatic.com");
29
+ expect(html).toContain("family=Inter");
30
+ });
31
+ it("should use system-ui fallback in CSS", () => {
32
+ const html = layout("Test", "");
33
+ // The font is loaded via Google Fonts and styled in style.css
34
+ expect(html).toContain("Inter:wght@400;500;600;700");
35
+ });
36
+ it("should apply preconnect hints for font loading performance", () => {
37
+ const html = layout("Test", "");
38
+ expect(html).toContain('<link rel="preconnect" href="https://fonts.googleapis.com">');
39
+ expect(html).toContain('<link rel="preconnect" href="https://fonts.gstatic.com"');
40
+ });
41
+ });
42
+ describe("Dark Mode Support", () => {
43
+ it("should include viewport meta tag for responsive design", () => {
44
+ const html = layout("Test", "");
45
+ expect(html).toContain('name="viewport"');
46
+ expect(html).toContain("width=device-width");
47
+ expect(html).toContain("initial-scale=1");
48
+ });
49
+ it("should have dark mode CSS with prefers-color-scheme media query", () => {
50
+ const html = layout("Test", "");
51
+ // The CSS link should be present; dark mode is handled in style.css
52
+ expect(html).toContain('href="/static/style.css"');
53
+ // The tests should verify style.css contains @media (prefers-color-scheme: dark)
54
+ });
55
+ });
56
+ describe("Favicon Implementation", () => {
57
+ it("should include SVG emoji favicon", () => {
58
+ const html = layout("Test", "");
59
+ expect(html).toContain("data:image/svg+xml");
60
+ expect(html).toContain("⚡"); // Lightning emoji favicon
61
+ });
62
+ it("should use data-URI favicon to avoid extra HTTP requests", () => {
63
+ const html = layout("Test", "");
64
+ expect(html).toContain('<link rel="icon" href="data:image/svg');
65
+ });
66
+ });
67
+ describe("Status Badges & Colors", () => {
68
+ const mockRun = {
69
+ id: "run-1",
70
+ issueId: "BEC-102",
71
+ issueTitle: "UI Refresh",
72
+ pipelineKey: "auto-implement",
73
+ repoUrl: "https://github.com/test/repo",
74
+ branch: "feature/ui",
75
+ status: "completed",
76
+ startedAt: new Date("2024-01-01T10:00:00Z"),
77
+ completedAt: new Date("2024-01-01T10:05:00Z"),
78
+ prUrl: "https://github.com/test/repo/pull/1",
79
+ totalInputTokens: 1000,
80
+ totalOutputTokens: 2000,
81
+ errorMessage: null,
82
+ };
83
+ const mockStage = {
84
+ id: "stage-1",
85
+ stage: "implement",
86
+ status: "completed",
87
+ startedAt: new Date("2024-01-01T10:00:00Z"),
88
+ completedAt: new Date("2024-01-01T10:05:00Z"),
89
+ inputTokens: 500,
90
+ outputTokens: 1000,
91
+ turns: 3,
92
+ handoffArtifact: null,
93
+ errorMessage: null,
94
+ };
95
+ it("should render status badges with correct CSS classes", () => {
96
+ const html = runDetailView(mockRun, [mockStage], [], 1, 0);
97
+ expect(html).toContain('class="badge badge-completed"');
98
+ });
99
+ it("should display status badges for all pipeline stages", () => {
100
+ const stages = [
101
+ { ...mockStage, status: "completed" },
102
+ { ...mockStage, status: "running", id: "stage-2", stage: "test" },
103
+ { ...mockStage, status: "failed", id: "stage-3", stage: "review" },
104
+ ];
105
+ const html = runDetailView(mockRun, stages, [], 1, 0);
106
+ expect(html).toContain('badge-completed');
107
+ expect(html).toContain('badge-running');
108
+ expect(html).toContain('badge-failed');
109
+ });
110
+ it("should show all status badge colors in feed view", () => {
111
+ const run = {
112
+ id: "run-1",
113
+ issueId: "BEC-102",
114
+ issueTitle: "UI Refresh",
115
+ pipelineKey: "auto-implement",
116
+ repoUrl: "https://github.com/test/repo",
117
+ status: "running",
118
+ startedAt: new Date("2024-01-01T10:00:00Z"),
119
+ completedAt: null,
120
+ totalInputTokens: 1000,
121
+ totalOutputTokens: 2000,
122
+ };
123
+ const html = runFeedView([run]);
124
+ expect(html).toContain('badge-running');
125
+ });
126
+ });
127
+ describe("Progress Indicators & Animations", () => {
128
+ it("should render animated badge for running status", () => {
129
+ const mockRun = {
130
+ id: "run-1",
131
+ issueId: "BEC-102",
132
+ issueTitle: "UI Refresh",
133
+ pipelineKey: "auto-implement",
134
+ repoUrl: "https://github.com/test/repo",
135
+ branch: "feature/ui",
136
+ status: "running",
137
+ startedAt: new Date(Date.now() - 5000), // 5 seconds ago
138
+ completedAt: null,
139
+ prUrl: null,
140
+ totalInputTokens: 1000,
141
+ totalOutputTokens: 2000,
142
+ errorMessage: null,
143
+ };
144
+ const mockStage = {
145
+ id: "stage-1",
146
+ stage: "implement",
147
+ status: "running",
148
+ startedAt: new Date(Date.now() - 5000),
149
+ completedAt: null,
150
+ inputTokens: 500,
151
+ outputTokens: 1000,
152
+ turns: 3,
153
+ handoffArtifact: null,
154
+ errorMessage: null,
155
+ };
156
+ const html = runDetailView(mockRun, [mockStage], [], 1, 0);
157
+ expect(html).toContain('badge-running');
158
+ expect(html).toContain("(running)"); // Duration badge should show running state
159
+ });
160
+ it("should have timeline visual indicators for different stages", () => {
161
+ const stages = [
162
+ {
163
+ id: "stage-1",
164
+ stage: "triage",
165
+ status: "completed",
166
+ startedAt: new Date("2024-01-01T10:00:00Z"),
167
+ completedAt: new Date("2024-01-01T10:01:00Z"),
168
+ inputTokens: 100,
169
+ outputTokens: 200,
170
+ turns: 1,
171
+ handoffArtifact: null,
172
+ errorMessage: null,
173
+ },
174
+ {
175
+ id: "stage-2",
176
+ stage: "implement",
177
+ status: "running",
178
+ startedAt: new Date("2024-01-01T10:02:00Z"),
179
+ completedAt: null,
180
+ inputTokens: 500,
181
+ outputTokens: 1000,
182
+ turns: 3,
183
+ handoffArtifact: null,
184
+ errorMessage: null,
185
+ },
186
+ ];
187
+ const mockRun = {
188
+ id: "run-1",
189
+ issueId: "BEC-102",
190
+ issueTitle: "UI Refresh",
191
+ pipelineKey: "auto-implement",
192
+ repoUrl: "https://github.com/test/repo",
193
+ branch: "feature/ui",
194
+ status: "running",
195
+ startedAt: new Date("2024-01-01T10:00:00Z"),
196
+ completedAt: null,
197
+ prUrl: null,
198
+ totalInputTokens: 600,
199
+ totalOutputTokens: 1200,
200
+ errorMessage: null,
201
+ };
202
+ const html = runDetailView(mockRun, stages, [], 1, 0);
203
+ expect(html).toContain("timeline-item completed");
204
+ expect(html).toContain("timeline-item running");
205
+ });
206
+ });
207
+ describe("Responsive Layout", () => {
208
+ it("should wrap tables in responsive container", () => {
209
+ const run = {
210
+ id: "run-1",
211
+ issueId: "BEC-102",
212
+ issueTitle: "UI Refresh",
213
+ pipelineKey: "auto-implement",
214
+ repoUrl: "https://github.com/test/repo",
215
+ status: "completed",
216
+ startedAt: new Date(),
217
+ completedAt: new Date(),
218
+ totalInputTokens: 1000,
219
+ totalOutputTokens: 2000,
220
+ };
221
+ const html = runFeedView([run]);
222
+ expect(html).toContain('class="table-wrapper"');
223
+ });
224
+ it("should include viewport meta tag for mobile responsiveness", () => {
225
+ const html = layout("Test", "");
226
+ expect(html).toContain('<meta name="viewport"');
227
+ expect(html).toContain("width=device-width");
228
+ });
229
+ it("should use responsive table with proper overflow handling", () => {
230
+ const run = {
231
+ id: "run-1",
232
+ issueId: "BEC-102",
233
+ issueTitle: "Very Long Title That Might Overflow on Mobile Devices",
234
+ pipelineKey: "auto-implement",
235
+ repoUrl: "https://github.com/test/very-long-repo-name/repository",
236
+ status: "completed",
237
+ startedAt: new Date(),
238
+ completedAt: new Date(),
239
+ totalInputTokens: 1000,
240
+ totalOutputTokens: 2000,
241
+ };
242
+ const html = runFeedView([run]);
243
+ expect(html).toContain('class="table-wrapper"');
244
+ // CSS will handle overflow-x styling via style.css
245
+ });
246
+ });
247
+ describe("Card-Style Layout", () => {
248
+ it("should use card layout on run detail page", () => {
249
+ const mockRun = {
250
+ id: "run-1",
251
+ issueId: "BEC-102",
252
+ issueTitle: "UI Refresh",
253
+ pipelineKey: "auto-implement",
254
+ repoUrl: "https://github.com/test/repo",
255
+ branch: "feature/ui",
256
+ status: "completed",
257
+ startedAt: new Date(),
258
+ completedAt: new Date(),
259
+ prUrl: null,
260
+ totalInputTokens: 1000,
261
+ totalOutputTokens: 2000,
262
+ errorMessage: null,
263
+ };
264
+ const mockStage = {
265
+ id: "stage-1",
266
+ stage: "implement",
267
+ status: "completed",
268
+ startedAt: new Date(),
269
+ completedAt: new Date(),
270
+ inputTokens: 500,
271
+ outputTokens: 1000,
272
+ turns: 3,
273
+ handoffArtifact: null,
274
+ errorMessage: null,
275
+ };
276
+ const html = runDetailView(mockRun, [mockStage], [], 1, 0);
277
+ expect(html).toContain('class="card"');
278
+ expect(html.match(/class="card"/g)?.length).toBeGreaterThanOrEqual(3); // Multiple cards
279
+ });
280
+ it("should use card layout on tokens page", () => {
281
+ const html = tokensView([{ date: "2024-01-01", inputTokens: 1000, outputTokens: 2000 }], [{ key: "auto-implement", inputTokens: 1000, outputTokens: 2000 }], [{ key: "implement", inputTokens: 500, outputTokens: 1000 }]);
282
+ expect(html).toContain('class="card"');
283
+ expect(html.match(/class="card"/g)?.length).toBeGreaterThanOrEqual(3);
284
+ });
285
+ });
286
+ describe("Sticky Navigation", () => {
287
+ it("should have sticky positioned navigation", () => {
288
+ const html = layout("Test", "");
289
+ expect(html).toContain("<nav>");
290
+ // CSS will handle position: sticky via style.css
291
+ });
292
+ it("should include navigation links", () => {
293
+ const html = layout("Test", "");
294
+ expect(html).toContain('href="/">Runs</a>');
295
+ expect(html).toContain('href="/tokens">Tokens</a>');
296
+ expect(html).toContain('href="/errors">Errors</a>');
297
+ expect(html).toContain('href="/config">Config</a>');
298
+ expect(html).toContain('href="/coordination">Coordination</a>');
299
+ });
300
+ it("should include branded logo in nav", () => {
301
+ const html = layout("Test", "");
302
+ expect(html).toContain("⚡ urateam");
303
+ });
304
+ });
305
+ describe("Relative Timestamps", () => {
306
+ it("should display relative time in run feed", () => {
307
+ const now = Date.now();
308
+ const oneHourAgo = new Date(now - 60 * 60 * 1000);
309
+ const run = {
310
+ id: "run-1",
311
+ issueId: "BEC-102",
312
+ issueTitle: "UI Refresh",
313
+ pipelineKey: "auto-implement",
314
+ repoUrl: "https://github.com/test/repo",
315
+ status: "completed",
316
+ startedAt: oneHourAgo,
317
+ completedAt: new Date(now - 30 * 60 * 1000),
318
+ totalInputTokens: 1000,
319
+ totalOutputTokens: 2000,
320
+ };
321
+ const html = runFeedView([run]);
322
+ expect(html).toMatch(/\dh ago/); // Should contain "1h ago" or similar
323
+ });
324
+ it("should display relative time in run detail page", () => {
325
+ const now = Date.now();
326
+ const twoHoursAgo = new Date(now - 2 * 60 * 60 * 1000);
327
+ const mockRun = {
328
+ id: "run-1",
329
+ issueId: "BEC-102",
330
+ issueTitle: "UI Refresh",
331
+ pipelineKey: "auto-implement",
332
+ repoUrl: "https://github.com/test/repo",
333
+ branch: "feature/ui",
334
+ status: "completed",
335
+ startedAt: twoHoursAgo,
336
+ completedAt: new Date(now - 60 * 60 * 1000),
337
+ prUrl: null,
338
+ totalInputTokens: 1000,
339
+ totalOutputTokens: 2000,
340
+ errorMessage: null,
341
+ };
342
+ const mockStage = {
343
+ id: "stage-1",
344
+ stage: "implement",
345
+ status: "completed",
346
+ startedAt: twoHoursAgo,
347
+ completedAt: new Date(now - 60 * 60 * 1000),
348
+ inputTokens: 500,
349
+ outputTokens: 1000,
350
+ turns: 3,
351
+ handoffArtifact: null,
352
+ errorMessage: null,
353
+ };
354
+ const html = runDetailView(mockRun, [mockStage], [], 1, 0);
355
+ expect(html).toMatch(/\dh ago/); // Should show relative time
356
+ });
357
+ it("should display relative time in logs", () => {
358
+ const now = Date.now();
359
+ const logTime = new Date(now - 5 * 60 * 1000); // 5 minutes ago
360
+ const mockRun = {
361
+ id: "run-1",
362
+ issueId: "BEC-102",
363
+ issueTitle: "UI Refresh",
364
+ pipelineKey: "auto-implement",
365
+ repoUrl: "https://github.com/test/repo",
366
+ branch: "feature/ui",
367
+ status: "completed",
368
+ startedAt: new Date(now - 10 * 60 * 1000),
369
+ completedAt: new Date(now - 1 * 60 * 1000),
370
+ prUrl: null,
371
+ totalInputTokens: 1000,
372
+ totalOutputTokens: 2000,
373
+ errorMessage: null,
374
+ };
375
+ const mockStage = {
376
+ id: "stage-1",
377
+ stage: "implement",
378
+ status: "completed",
379
+ startedAt: new Date(now - 10 * 60 * 1000),
380
+ completedAt: new Date(now - 1 * 60 * 1000),
381
+ inputTokens: 500,
382
+ outputTokens: 1000,
383
+ turns: 3,
384
+ handoffArtifact: null,
385
+ errorMessage: null,
386
+ };
387
+ const logs = [
388
+ {
389
+ id: "log-1",
390
+ timestamp: logTime,
391
+ type: "message",
392
+ content: "Starting agent execution",
393
+ },
394
+ ];
395
+ const html = runDetailView(mockRun, [mockStage], logs, 1, 1);
396
+ expect(html).toContain("ago"); // Should show relative time in logs
397
+ });
398
+ it("should display relative time in coordination view", () => {
399
+ const now = Date.now();
400
+ const tenMinutesAgo = new Date(now - 10 * 60 * 1000);
401
+ const entries = [
402
+ {
403
+ id: "work-1",
404
+ runId: "run-1",
405
+ issueId: "BEC-102",
406
+ stage: "implement",
407
+ filesModified: ["src/main.ts", "src/utils.ts"],
408
+ startedAt: tenMinutesAgo,
409
+ updatedAt: new Date(now - 2 * 60 * 1000),
410
+ },
411
+ ];
412
+ const html = coordinationView(entries);
413
+ expect(html).toMatch(/\dm ago/); // Should show relative time
414
+ });
415
+ });
416
+ describe("Run Duration Display", () => {
417
+ it("should display duration prominently in run detail", () => {
418
+ const startTime = new Date("2024-01-01T10:00:00Z");
419
+ const endTime = new Date("2024-01-01T10:05:00Z");
420
+ const mockRun = {
421
+ id: "run-1",
422
+ issueId: "BEC-102",
423
+ issueTitle: "UI Refresh",
424
+ pipelineKey: "auto-implement",
425
+ repoUrl: "https://github.com/test/repo",
426
+ branch: "feature/ui",
427
+ status: "completed",
428
+ startedAt: startTime,
429
+ completedAt: endTime,
430
+ prUrl: null,
431
+ totalInputTokens: 1000,
432
+ totalOutputTokens: 2000,
433
+ errorMessage: null,
434
+ };
435
+ const mockStage = {
436
+ id: "stage-1",
437
+ stage: "implement",
438
+ status: "completed",
439
+ startedAt: startTime,
440
+ completedAt: endTime,
441
+ inputTokens: 500,
442
+ outputTokens: 1000,
443
+ turns: 3,
444
+ handoffArtifact: null,
445
+ errorMessage: null,
446
+ };
447
+ const html = runDetailView(mockRun, [mockStage], [], 1, 0);
448
+ expect(html).toContain("duration-badge");
449
+ expect(html).toContain("5m"); // Should show 5 minute duration
450
+ });
451
+ it("should display duration in feed view", () => {
452
+ const startTime = new Date("2024-01-01T10:00:00Z");
453
+ const endTime = new Date("2024-01-01T10:03:30Z");
454
+ const run = {
455
+ id: "run-1",
456
+ issueId: "BEC-102",
457
+ issueTitle: "UI Refresh",
458
+ pipelineKey: "auto-implement",
459
+ repoUrl: "https://github.com/test/repo",
460
+ status: "completed",
461
+ startedAt: startTime,
462
+ completedAt: endTime,
463
+ totalInputTokens: 1000,
464
+ totalOutputTokens: 2000,
465
+ };
466
+ const html = runFeedView([run]);
467
+ expect(html).toContain("3m 30s");
468
+ });
469
+ });
470
+ describe("Collapsible Log Sections", () => {
471
+ it("should render collapsible details section for logs", () => {
472
+ const mockRun = {
473
+ id: "run-1",
474
+ issueId: "BEC-102",
475
+ issueTitle: "UI Refresh",
476
+ pipelineKey: "auto-implement",
477
+ repoUrl: "https://github.com/test/repo",
478
+ branch: "feature/ui",
479
+ status: "completed",
480
+ startedAt: new Date(),
481
+ completedAt: new Date(),
482
+ prUrl: null,
483
+ totalInputTokens: 1000,
484
+ totalOutputTokens: 2000,
485
+ errorMessage: null,
486
+ };
487
+ const mockStage = {
488
+ id: "stage-1",
489
+ stage: "implement",
490
+ status: "completed",
491
+ startedAt: new Date(),
492
+ completedAt: new Date(),
493
+ inputTokens: 500,
494
+ outputTokens: 1000,
495
+ turns: 3,
496
+ handoffArtifact: null,
497
+ errorMessage: null,
498
+ };
499
+ const html = runDetailView(mockRun, [mockStage], [], 1, 0);
500
+ expect(html).toContain("<details");
501
+ expect(html).toContain("log-section");
502
+ });
503
+ it("should show log section open by default", () => {
504
+ const mockRun = {
505
+ id: "run-1",
506
+ issueId: "BEC-102",
507
+ issueTitle: "UI Refresh",
508
+ pipelineKey: "auto-implement",
509
+ repoUrl: "https://github.com/test/repo",
510
+ branch: "feature/ui",
511
+ status: "completed",
512
+ startedAt: new Date(),
513
+ completedAt: new Date(),
514
+ prUrl: null,
515
+ totalInputTokens: 1000,
516
+ totalOutputTokens: 2000,
517
+ errorMessage: null,
518
+ };
519
+ const mockStage = {
520
+ id: "stage-1",
521
+ stage: "implement",
522
+ status: "completed",
523
+ startedAt: new Date(),
524
+ completedAt: new Date(),
525
+ inputTokens: 500,
526
+ outputTokens: 1000,
527
+ turns: 3,
528
+ handoffArtifact: null,
529
+ errorMessage: null,
530
+ };
531
+ const html = runDetailView(mockRun, [mockStage], [], 1, 0);
532
+ expect(html).toContain("<details class=\"log-section\" open>");
533
+ });
534
+ });
535
+ describe("Token Usage Bar Chart", () => {
536
+ it("should display bar chart on tokens page", () => {
537
+ const html = tokensView([{ date: "2024-01-01", inputTokens: 5000, outputTokens: 10000 }], [{ key: "auto-implement", inputTokens: 5000, outputTokens: 10000 }], [{ key: "implement", inputTokens: 3000, outputTokens: 6000 }]);
538
+ expect(html).toContain("bar-chart");
539
+ expect(html).toContain("bar-fill");
540
+ });
541
+ it("should show input and output bars separately", () => {
542
+ const html = tokensView([{ date: "2024-01-01", inputTokens: 5000, outputTokens: 10000 }], [], []);
543
+ expect(html).toContain("bar-fill-input");
544
+ expect(html).toContain("bar-fill-output");
545
+ });
546
+ it("should display legend for bar colors", () => {
547
+ const html = tokensView([{ date: "2024-01-01", inputTokens: 5000, outputTokens: 10000 }], [], []);
548
+ expect(html).toContain("Input");
549
+ expect(html).toContain("Output");
550
+ });
551
+ it("should scale bars proportionally", () => {
552
+ const html = tokensView([
553
+ { date: "2024-01-01", inputTokens: 1000, outputTokens: 2000 },
554
+ { date: "2024-01-02", inputTokens: 2000, outputTokens: 4000 },
555
+ ], [], []);
556
+ expect(html).toContain("width:");
557
+ });
558
+ });
559
+ describe("Table Row Hover Effects", () => {
560
+ it("should have table elements for hover styling", () => {
561
+ const run = {
562
+ id: "run-1",
563
+ issueId: "BEC-102",
564
+ issueTitle: "UI Refresh",
565
+ pipelineKey: "auto-implement",
566
+ repoUrl: "https://github.com/test/repo",
567
+ status: "completed",
568
+ startedAt: new Date(),
569
+ completedAt: new Date(),
570
+ totalInputTokens: 1000,
571
+ totalOutputTokens: 2000,
572
+ };
573
+ const html = runFeedView([run]);
574
+ expect(html).toContain("<tbody>");
575
+ expect(html).toContain("<tr>");
576
+ // CSS will handle hover effects via style.css (tbody tr:hover td)
577
+ });
578
+ });
579
+ describe("Pure CSS & No Build Step", () => {
580
+ it("should not use Tailwind classes", () => {
581
+ const mockRun = {
582
+ id: "run-1",
583
+ issueId: "BEC-102",
584
+ issueTitle: "UI Refresh",
585
+ pipelineKey: "auto-implement",
586
+ repoUrl: "https://github.com/test/repo",
587
+ branch: "feature/ui",
588
+ status: "completed",
589
+ startedAt: new Date(),
590
+ completedAt: new Date(),
591
+ prUrl: null,
592
+ totalInputTokens: 1000,
593
+ totalOutputTokens: 2000,
594
+ errorMessage: null,
595
+ };
596
+ const html = layout("Test", "<div>content</div>");
597
+ // Check that no Tailwind utility classes are present
598
+ const tailwindClasses = ["m-1", "p-4", "text-lg", "flex", "grid"];
599
+ for (const cls of tailwindClasses) {
600
+ expect(html).not.toContain(`class="${cls}`);
601
+ }
602
+ });
603
+ it("should use semantic HTML only", () => {
604
+ const html = layout("Test", "");
605
+ expect(html).toContain("<!DOCTYPE html>");
606
+ expect(html).toContain("<html lang=\"en\">");
607
+ expect(html).toContain("<head>");
608
+ expect(html).toContain("<body>");
609
+ });
610
+ it("should use only static CSS file", () => {
611
+ const html = layout("Test", "");
612
+ expect(html).toContain('href="/static/style.css"');
613
+ // Should not reference dynamic CSS or CSS-in-JS
614
+ expect(html).not.toMatch(/href=.*\.module\.css/);
615
+ });
616
+ it("should not include client-side JS frameworks (except HTMX)", () => {
617
+ const html = layout("Test", "");
618
+ expect(html).not.toContain("react");
619
+ expect(html).not.toContain("vue");
620
+ expect(html).not.toContain("angular");
621
+ expect(html).toContain("htmx.org");
622
+ });
623
+ });
624
+ describe("HTMX Functionality", () => {
625
+ it("should include HTMX CDN link", () => {
626
+ const html = layout("Test", "");
627
+ expect(html).toContain("https://unpkg.com/htmx.org");
628
+ });
629
+ it("should preserve HTMX attributes in run feed", () => {
630
+ const run = {
631
+ id: "run-1",
632
+ issueId: "BEC-102",
633
+ issueTitle: "UI Refresh",
634
+ pipelineKey: "auto-implement",
635
+ repoUrl: "https://github.com/test/repo",
636
+ status: "completed",
637
+ startedAt: new Date(),
638
+ completedAt: new Date(),
639
+ totalInputTokens: 1000,
640
+ totalOutputTokens: 2000,
641
+ };
642
+ const html = runFeedView([run]);
643
+ expect(html).toContain("hx-get");
644
+ expect(html).toContain("hx-trigger");
645
+ expect(html).toContain("hx-swap");
646
+ });
647
+ it("should preserve HTMX attributes in coordination view", () => {
648
+ const entries = [
649
+ {
650
+ id: "work-1",
651
+ runId: "run-1",
652
+ issueId: "BEC-102",
653
+ stage: "implement",
654
+ filesModified: ["src/main.ts"],
655
+ startedAt: new Date(),
656
+ updatedAt: new Date(),
657
+ },
658
+ ];
659
+ const html = coordinationView(entries);
660
+ expect(html).toContain("hx-get");
661
+ expect(html).toContain("hx-trigger");
662
+ expect(html).toContain("hx-swap");
663
+ });
664
+ it("should have auto-refresh intervals set correctly", () => {
665
+ const run = {
666
+ id: "run-1",
667
+ issueId: "BEC-102",
668
+ issueTitle: "UI Refresh",
669
+ pipelineKey: "auto-implement",
670
+ repoUrl: "https://github.com/test/repo",
671
+ status: "completed",
672
+ startedAt: new Date(),
673
+ completedAt: new Date(),
674
+ totalInputTokens: 1000,
675
+ totalOutputTokens: 2000,
676
+ };
677
+ const html = runFeedView([run]);
678
+ expect(html).toContain('hx-trigger="every 5s"');
679
+ });
680
+ });
681
+ describe("Accessibility — Keyboard Navigation", () => {
682
+ it("should include focus-visible styles in HTML structure", () => {
683
+ const html = layout("Test", "");
684
+ expect(html).toContain("<nav>");
685
+ expect(html).toContain("<a");
686
+ // CSS will handle focus-visible styling
687
+ });
688
+ it("should have proper semantic link elements", () => {
689
+ const html = layout("Test", "");
690
+ expect(html).toMatch(/<a[^>]*href="[^"]*"[^>]*>/);
691
+ });
692
+ it("should have collapsible sections with proper semantics", () => {
693
+ const mockRun = {
694
+ id: "run-1",
695
+ issueId: "BEC-102",
696
+ issueTitle: "UI Refresh",
697
+ pipelineKey: "auto-implement",
698
+ repoUrl: "https://github.com/test/repo",
699
+ branch: "feature/ui",
700
+ status: "completed",
701
+ startedAt: new Date(),
702
+ completedAt: new Date(),
703
+ prUrl: null,
704
+ totalInputTokens: 1000,
705
+ totalOutputTokens: 2000,
706
+ errorMessage: null,
707
+ };
708
+ const mockStage = {
709
+ id: "stage-1",
710
+ stage: "implement",
711
+ status: "completed",
712
+ startedAt: new Date(),
713
+ completedAt: new Date(),
714
+ inputTokens: 500,
715
+ outputTokens: 1000,
716
+ turns: 3,
717
+ handoffArtifact: null,
718
+ errorMessage: null,
719
+ };
720
+ const html = runDetailView(mockRun, [mockStage], [], 1, 0);
721
+ expect(html).toContain("<details");
722
+ expect(html).toContain("<summary");
723
+ });
724
+ it("should have proper heading hierarchy", () => {
725
+ const mockRun = {
726
+ id: "run-1",
727
+ issueId: "BEC-102",
728
+ issueTitle: "UI Refresh",
729
+ pipelineKey: "auto-implement",
730
+ repoUrl: "https://github.com/test/repo",
731
+ branch: "feature/ui",
732
+ status: "completed",
733
+ startedAt: new Date(),
734
+ completedAt: new Date(),
735
+ prUrl: null,
736
+ totalInputTokens: 1000,
737
+ totalOutputTokens: 2000,
738
+ errorMessage: null,
739
+ };
740
+ const mockStage = {
741
+ id: "stage-1",
742
+ stage: "implement",
743
+ status: "completed",
744
+ startedAt: new Date(),
745
+ completedAt: new Date(),
746
+ inputTokens: 500,
747
+ outputTokens: 1000,
748
+ turns: 3,
749
+ handoffArtifact: null,
750
+ errorMessage: null,
751
+ };
752
+ const html = runDetailView(mockRun, [mockStage], [], 1, 0);
753
+ // The run detail view uses h2 for section titles (h1 is added by layout)
754
+ expect(html).toMatch(/<h2[^>]*>/); // Section titles
755
+ expect(html).toContain("Run Details"); // Verify headings exist
756
+ });
757
+ });
758
+ describe("Accessibility — Contrast Ratios", () => {
759
+ it("should use high contrast text colors", () => {
760
+ const html = layout("Test", "");
761
+ // The CSS should have proper contrast ratios defined in color variables
762
+ expect(html).toContain('href="/static/style.css"');
763
+ });
764
+ it("should provide sufficient contrast for status badges", () => {
765
+ const mockRun = {
766
+ id: "run-1",
767
+ issueId: "BEC-102",
768
+ issueTitle: "UI Refresh",
769
+ pipelineKey: "auto-implement",
770
+ repoUrl: "https://github.com/test/repo",
771
+ branch: "feature/ui",
772
+ status: "failed",
773
+ startedAt: new Date(),
774
+ completedAt: new Date(),
775
+ prUrl: null,
776
+ totalInputTokens: 1000,
777
+ totalOutputTokens: 2000,
778
+ errorMessage: "Test error",
779
+ };
780
+ const mockStage = {
781
+ id: "stage-1",
782
+ stage: "implement",
783
+ status: "failed",
784
+ startedAt: new Date(),
785
+ completedAt: new Date(),
786
+ inputTokens: 500,
787
+ outputTokens: 1000,
788
+ turns: 3,
789
+ handoffArtifact: null,
790
+ errorMessage: "Stage failed",
791
+ };
792
+ const html = runDetailView(mockRun, [mockStage], [], 1, 0);
793
+ expect(html).toContain('badge-failed');
794
+ });
795
+ });
796
+ describe("Empty State Handling", () => {
797
+ it("should display empty state for coordination view", () => {
798
+ const html = coordinationView([]);
799
+ expect(html).toContain("No agents currently active");
800
+ expect(html).toContain("empty");
801
+ });
802
+ it("should display empty state for token feed", () => {
803
+ const html = tokensView([], [], []);
804
+ expect(html).toContain("No data");
805
+ });
806
+ });
807
+ describe("URL-safe run ID encoding in href attributes", () => {
808
+ it("run-feed: run ID with '+' produces %2B in href", () => {
809
+ const run = {
810
+ id: "run+special",
811
+ issueId: "BEC-108",
812
+ issueTitle: "URL encoding test",
813
+ pipelineKey: "auto-implement",
814
+ repoUrl: "https://github.com/test/repo",
815
+ status: "done",
816
+ startedAt: new Date("2024-01-01T10:00:00Z"),
817
+ completedAt: new Date("2024-01-01T10:05:00Z"),
818
+ totalInputTokens: 100,
819
+ totalOutputTokens: 200,
820
+ };
821
+ const html = runFeedView([run]);
822
+ expect(html).toContain("/runs/run%2Bspecial");
823
+ expect(html).not.toContain("/runs/run+special");
824
+ });
825
+ it("run-detail: run ID with '/' produces %2F in pagination hrefs", () => {
826
+ const run = {
827
+ id: "run/special",
828
+ issueId: "BEC-108",
829
+ issueTitle: "URL encoding test",
830
+ pipelineKey: "auto-implement",
831
+ repoUrl: "https://github.com/test/repo",
832
+ branch: "feature/test",
833
+ status: "done",
834
+ startedAt: new Date("2024-01-01T10:00:00Z"),
835
+ completedAt: new Date("2024-01-01T10:05:00Z"),
836
+ prUrl: null,
837
+ totalInputTokens: 100,
838
+ totalOutputTokens: 200,
839
+ errorMessage: null,
840
+ };
841
+ const mockStage = {
842
+ id: "stage-1",
843
+ stage: "implement",
844
+ status: "done",
845
+ startedAt: new Date("2024-01-01T10:00:00Z"),
846
+ completedAt: new Date("2024-01-01T10:04:00Z"),
847
+ inputTokens: 100,
848
+ outputTokens: 200,
849
+ turns: 3,
850
+ handoffArtifact: null,
851
+ errorMessage: null,
852
+ };
853
+ // Use enough total logs to trigger pagination (>50 per page)
854
+ const html = runDetailView(run, [mockStage], [], 1, 200);
855
+ expect(html).toContain("/runs/run%2Fspecial");
856
+ expect(html).not.toContain("/runs/run/special");
857
+ });
858
+ });
859
+ });
860
+ //# sourceMappingURL=ui-refresh.test.js.map