hzl-web 1.33.1 → 1.34.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.
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,EACL,WAAW,EACX,UAAU,EAGX,MAAM,UAAU,CAAC;AAGlB,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,WAAW,CAAC;IACzB,UAAU,EAAE,UAAU,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAgGD,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,GAAG,YAAY,CA0RpE"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,WAAW,EACX,UAAU,EAIX,MAAM,UAAU,CAAC;AAGlB,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,WAAW,CAAC;IACzB,UAAU,EAAE,UAAU,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAqHD,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,GAAG,YAAY,CAwYpE"}
package/dist/server.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createServer } from 'http';
2
- import { AmbiguousPrefixError, } from 'hzl-core';
2
+ import { EventType, AmbiguousPrefixError, } from 'hzl-core';
3
3
  import { DASHBOARD_HTML } from './ui-embed.js';
4
4
  // Date filter presets in days
5
5
  const DATE_PRESETS = {
@@ -9,6 +9,9 @@ const DATE_PRESETS = {
9
9
  '14d': 14,
10
10
  '30d': 30,
11
11
  };
12
+ const STREAM_POLL_MS = 2000;
13
+ const STREAM_KEEPALIVE_MS = 15000;
14
+ const STREAM_EVENT_TYPES = Object.values(EventType);
12
15
  function parseUrl(url) {
13
16
  const idx = url.indexOf('?');
14
17
  if (idx === -1) {
@@ -33,8 +36,14 @@ function serverError(res, error) {
33
36
  const message = error instanceof Error ? error.message : 'Internal Server Error';
34
37
  json(res, { error: message }, 500);
35
38
  }
39
+ function writeSseEvent(res, event, data) {
40
+ res.write(`event: ${event}\n`);
41
+ res.write(`data: ${JSON.stringify(data)}\n\n`);
42
+ }
36
43
  export function createWebServer(options) {
37
44
  const { port, host = '0.0.0.0', allowFraming = false, taskService, eventStore } = options;
45
+ const sockets = new Set();
46
+ const activeStreamResponses = new Set();
38
47
  // Route handlers
39
48
  function handleTasks(params, res) {
40
49
  const dueMonth = params.get('due_month');
@@ -175,6 +184,15 @@ export function createWebServer(options) {
175
184
  // Get task titles for these events (batched query to avoid N+1)
176
185
  const taskIds = [...new Set(rawEvents.map((e) => e.task_id).filter(Boolean))];
177
186
  const titleMap = taskService.getTaskTitlesByIds(taskIds);
187
+ const taskMetadataMap = new Map();
188
+ for (const taskId of taskIds) {
189
+ const task = taskService.getTaskById(taskId);
190
+ taskMetadataMap.set(taskId, {
191
+ task_assignee: task?.assignee ?? null,
192
+ task_description: task?.description ?? null,
193
+ task_status: task?.status ?? null,
194
+ });
195
+ }
178
196
  const events = rawEvents.map((e) => ({
179
197
  id: e.rowid,
180
198
  event_id: e.event_id,
@@ -185,6 +203,9 @@ export function createWebServer(options) {
185
203
  agent_id: e.agent_id ?? null,
186
204
  timestamp: e.timestamp,
187
205
  task_title: titleMap.get(e.task_id) ?? null,
206
+ task_assignee: taskMetadataMap.get(e.task_id)?.task_assignee ?? null,
207
+ task_description: taskMetadataMap.get(e.task_id)?.task_description ?? null,
208
+ task_status: taskMetadataMap.get(e.task_id)?.task_status ?? null,
188
209
  }));
189
210
  json(res, { events });
190
211
  }
@@ -197,6 +218,63 @@ export function createWebServer(options) {
197
218
  };
198
219
  json(res, response);
199
220
  }
221
+ function getLatestEventId() {
222
+ const latest = eventStore.getRecentEvents({
223
+ sinceId: 0,
224
+ limit: 1,
225
+ types: STREAM_EVENT_TYPES,
226
+ });
227
+ return latest[0]?.rowid ?? 0;
228
+ }
229
+ function handleStream(req, res) {
230
+ activeStreamResponses.add(res);
231
+ res.writeHead(200, {
232
+ 'Content-Type': 'text/event-stream; charset=utf-8',
233
+ 'Cache-Control': 'no-cache, no-transform',
234
+ Connection: 'keep-alive',
235
+ 'X-Accel-Buffering': 'no',
236
+ });
237
+ if (typeof res.flushHeaders === 'function') {
238
+ res.flushHeaders();
239
+ }
240
+ res.write('retry: 2000\n\n');
241
+ let lastEventId = getLatestEventId();
242
+ const readyPayload = {
243
+ live: true,
244
+ latest_event_id: lastEventId,
245
+ };
246
+ writeSseEvent(res, 'ready', readyPayload);
247
+ const pollTimer = setInterval(() => {
248
+ if (req.destroyed || res.destroyed || res.writableEnded) {
249
+ return;
250
+ }
251
+ const latestId = getLatestEventId();
252
+ if (latestId > lastEventId) {
253
+ lastEventId = latestId;
254
+ const updatePayload = { latest_event_id: latestId };
255
+ writeSseEvent(res, 'update', updatePayload);
256
+ }
257
+ }, STREAM_POLL_MS);
258
+ const keepAliveTimer = setInterval(() => {
259
+ if (req.destroyed || res.destroyed || res.writableEnded) {
260
+ return;
261
+ }
262
+ res.write(': keep-alive\n\n');
263
+ }, STREAM_KEEPALIVE_MS);
264
+ let closed = false;
265
+ const cleanup = () => {
266
+ if (closed)
267
+ return;
268
+ closed = true;
269
+ activeStreamResponses.delete(res);
270
+ clearInterval(pollTimer);
271
+ clearInterval(keepAliveTimer);
272
+ req.off('close', cleanup);
273
+ res.off('close', cleanup);
274
+ };
275
+ req.on('close', cleanup);
276
+ res.on('close', cleanup);
277
+ }
200
278
  function handleRoot(res) {
201
279
  const csp = allowFraming
202
280
  ? "default-src 'self'; script-src 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'unsafe-inline'; frame-ancestors *"
@@ -234,6 +312,10 @@ export function createWebServer(options) {
234
312
  handleStats(res);
235
313
  return;
236
314
  }
315
+ if (pathname === '/api/events/stream' || pathname === '/api/stream') {
316
+ handleStream(req, res);
317
+ return;
318
+ }
237
319
  // /api/tasks/:id routes
238
320
  const taskMatch = pathname.match(/^\/api\/tasks\/([^/]+)$/);
239
321
  if (taskMatch) {
@@ -262,15 +344,37 @@ export function createWebServer(options) {
262
344
  }
263
345
  }
264
346
  const server = createServer(handleRequest);
347
+ server.on('connection', (socket) => {
348
+ sockets.add(socket);
349
+ socket.on('close', () => {
350
+ sockets.delete(socket);
351
+ });
352
+ });
265
353
  server.listen(port, host);
266
354
  return {
267
355
  close: () => new Promise((resolve, reject) => {
356
+ for (const streamRes of activeStreamResponses) {
357
+ if (!streamRes.writableEnded && !streamRes.destroyed) {
358
+ streamRes.end();
359
+ }
360
+ }
268
361
  server.close((err) => {
269
362
  if (err)
270
363
  reject(err);
271
364
  else
272
365
  resolve();
273
366
  });
367
+ if (typeof server.closeIdleConnections === 'function') {
368
+ server.closeIdleConnections();
369
+ }
370
+ if (typeof server.closeAllConnections === 'function') {
371
+ server.closeAllConnections();
372
+ }
373
+ else {
374
+ for (const socket of sockets) {
375
+ socket.destroy();
376
+ }
377
+ }
274
378
  }),
275
379
  port,
276
380
  host,
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -46,6 +46,11 @@ describe('hzl-web server', () => {
46
46
  const data = await res.json().catch(() => null);
47
47
  return { status: res.status, data };
48
48
  }
49
+ async function fetchText(path) {
50
+ const res = await globalThis.fetch(`${server.url}${path}`);
51
+ const body = await res.text();
52
+ return { status: res.status, body };
53
+ }
49
54
  describe('server configuration', () => {
50
55
  it('starts on specified port', async () => {
51
56
  const s = createServer(4500);
@@ -79,6 +84,28 @@ describe('hzl-web server', () => {
79
84
  // Prevent afterEach from closing again
80
85
  server = undefined;
81
86
  });
87
+ it('closes promptly even with an active SSE stream', async () => {
88
+ const s = createServer(4504);
89
+ const controller = new AbortController();
90
+ const response = await globalThis.fetch(`${s.url}/api/events/stream`, {
91
+ headers: { Accept: 'text/event-stream' },
92
+ signal: controller.signal,
93
+ });
94
+ expect(response.status).toBe(200);
95
+ const closePromise = s.close();
96
+ const closedQuickly = await Promise.race([
97
+ closePromise.then(() => true),
98
+ new Promise((resolve) => setTimeout(() => resolve(false), 500)),
99
+ ]);
100
+ if (!closedQuickly) {
101
+ controller.abort();
102
+ await closePromise;
103
+ }
104
+ expect(closedQuickly).toBe(true);
105
+ controller.abort();
106
+ // Prevent afterEach from closing again
107
+ server = undefined;
108
+ });
82
109
  });
83
110
  describe('GET /api/tasks', () => {
84
111
  it('returns empty task list initially', async () => {
@@ -107,6 +134,24 @@ describe('hzl-web server', () => {
107
134
  project: 'test-project',
108
135
  });
109
136
  });
137
+ it('preserves assignee names with spaces and emojis', async () => {
138
+ taskService.createTask({
139
+ title: 'Space Name',
140
+ project: 'test-project',
141
+ assignee: 'Trevin C',
142
+ });
143
+ taskService.createTask({
144
+ title: 'Emoji Name',
145
+ project: 'test-project',
146
+ assignee: 'Clara 📝',
147
+ });
148
+ createServer(4518);
149
+ const { data } = await fetchJson('/api/tasks');
150
+ const tasks = data.tasks;
151
+ const assigneeByTitle = new Map(tasks.map((task) => [task.title, task.assignee]));
152
+ expect(assigneeByTitle.get('Space Name')).toBe('Trevin C');
153
+ expect(assigneeByTitle.get('Emoji Name')).toBe('Clara 📝');
154
+ });
110
155
  it('filters by project', async () => {
111
156
  projectService.createProject('other-project');
112
157
  taskService.createTask({ title: 'Task 1', project: 'test-project' });
@@ -444,13 +489,22 @@ describe('hzl-web server', () => {
444
489
  const { data } = await fetchJson('/api/events?since=99999');
445
490
  expect(data.events).toHaveLength(0);
446
491
  });
447
- it('includes task titles in events', async () => {
448
- taskService.createTask({ title: 'Named Task', project: 'test-project' });
492
+ it('includes task metadata fields in events', async () => {
493
+ const task = taskService.createTask({
494
+ title: 'Named Task',
495
+ project: 'test-project',
496
+ assignee: 'Clara 📝',
497
+ description: 'Task details for activity filtering',
498
+ });
499
+ taskService.setStatus(task.task_id, TaskStatus.Ready);
449
500
  createServer(4552);
450
501
  const { data } = await fetchJson('/api/events?since=0');
451
502
  const events = data.events;
452
- const createEvent = events.find((e) => e.type === 'task_created');
503
+ const createEvent = events.find((e) => e.task_id === task.task_id && e.type === 'task_created');
453
504
  expect(createEvent?.task_title).toBe('Named Task');
505
+ expect(createEvent?.task_assignee).toBe('Clara 📝');
506
+ expect(createEvent?.task_description).toBe('Task details for activity filtering');
507
+ expect(createEvent?.task_status).toBe(TaskStatus.Ready);
454
508
  });
455
509
  });
456
510
  describe('GET /api/tasks/:id/events', () => {
@@ -481,6 +535,50 @@ describe('hzl-web server', () => {
481
535
  expect(data.events).toHaveLength(1);
482
536
  });
483
537
  });
538
+ describe('SSE + client wiring contracts', () => {
539
+ it('exposes an SSE route with event-stream content type', async () => {
540
+ server = createServer(4555);
541
+ const res = await globalThis.fetch(`${server.url}/api/events/stream`, {
542
+ headers: { Accept: 'text/event-stream' },
543
+ });
544
+ expect(res.status).toBe(200);
545
+ expect(res.headers.get('content-type') ?? '').toMatch(/text\/event-stream/i);
546
+ await res.body?.cancel();
547
+ });
548
+ it('includes EventSource usage and wiring in dashboard HTML', async () => {
549
+ server = createServer(4556);
550
+ const { body } = await fetchText('/');
551
+ const eventSourceInit = body.match(/(?:const|let|var)\s+([a-zA-Z_$][\w$]*)\s*=\s*new\s+EventSource\s*\([\s\S]*?\)/i);
552
+ expect(eventSourceInit).toBeTruthy();
553
+ expect(body).toMatch(/SSE_ENDPOINT\s*=\s*['"]\/api\/events\/stream['"]/i);
554
+ const eventSourceVar = eventSourceInit?.[1];
555
+ expect(eventSourceVar).toBeTruthy();
556
+ const eventSourceWiring = new RegExp(`${eventSourceVar}\\s*\\.\\s*(?:onopen|onmessage|onerror|addEventListener\\s*\\()`, 'i');
557
+ expect(body).toMatch(eventSourceWiring);
558
+ });
559
+ it('does not rely on setInterval(poll, ...) as the primary update loop', async () => {
560
+ server = createServer(4557);
561
+ const { body } = await fetchText('/');
562
+ expect(body).not.toMatch(/setInterval\s*\(\s*poll\s*,/i);
563
+ });
564
+ it('checks both visibility and focus before opening SSE connections', async () => {
565
+ server = createServer(4558);
566
+ const { body } = await fetchText('/');
567
+ const connectSetup = body.match(/function\s+connectEventStream\s*\([^)]*\)\s*\{[\s\S]*?if\s*\(\s*!\s*([a-zA-Z_$][\w$]*)\s*\(\s*\)\s*\)\s*\{[\s\S]{0,260}?pauseLiveUpdates\s*\(\s*\)[\s\S]{0,260}?return[\s\S]*?new\s+EventSource\s*\(/i);
568
+ expect(connectSetup).toBeTruthy();
569
+ const liveUpdateGate = connectSetup?.[1];
570
+ expect(liveUpdateGate).toBeTruthy();
571
+ const gateFunctionPattern = new RegExp(`function\\s+${liveUpdateGate}\\s*\\([^)]*\\)\\s*\\{[\\s\\S]*?(?:document\\.(?:hidden|visibilityState)[\\s\\S]*?document\\.hasFocus\\s*\\(\\s*\\)|document\\.hasFocus\\s*\\(\\s*\\)[\\s\\S]*?document\\.(?:hidden|visibilityState))`, 'i');
572
+ expect(body).toMatch(gateFunctionPattern);
573
+ });
574
+ it('wires blur/focus/visibilitychange handlers to pause and resume live updates', async () => {
575
+ server = createServer(4559);
576
+ const { body } = await fetchText('/');
577
+ expect(body).toMatch(/(?:window|document)\s*\.\s*addEventListener\s*\(\s*['"]blur['"]\s*,[\s\S]{0,240}?pauseLiveUpdates\s*\(/i);
578
+ expect(body).toMatch(/(?:window|document)\s*\.\s*addEventListener\s*\(\s*['"]focus['"]\s*,[\s\S]{0,240}?resumeLiveUpdates\s*\(/i);
579
+ expect(body).toMatch(/document\s*\.\s*addEventListener\s*\(\s*['"]visibilitychange['"]\s*,[\s\S]{0,500}?pauseLiveUpdates\s*\([\s\S]{0,500}?resumeLiveUpdates\s*\(/i);
580
+ });
581
+ });
484
582
  describe('GET / (dashboard HTML)', () => {
485
583
  it('serves HTML at root', async () => {
486
584
  server = createServer(4560);
@@ -505,6 +603,154 @@ describe('hzl-web server', () => {
505
603
  expect(res.headers.get('x-content-type-options')).toBe('nosniff');
506
604
  expect(res.headers.get('referrer-policy')).toBe('no-referrer');
507
605
  });
606
+ it('renders project metadata in the top-right header and assignee metadata in card footer', async () => {
607
+ server = createServer(4590);
608
+ const { body } = await fetchText('/');
609
+ const renderCardBlock = body.match(/function\s+renderCard\s*\([^)]*\)\s*\{[\s\S]*?return\s*`[\s\S]*?`;\s*}/i);
610
+ expect(renderCardBlock).toBeTruthy();
611
+ expect(renderCardBlock?.[0]).toMatch(/const\s+projectHtml\s*=\s*`[\s\S]*class=["']card-project["'][\s\S]*`;/i);
612
+ expect(renderCardBlock?.[0]).toMatch(/const\s+assigneeClass\s*=\s*hasAssignee\s*\?\s*['"][^'"]*\bcard-assignee\b[^'"]*['"]\s*:\s*['"][^'"]*\bcard-assignee\b[^'"]*['"]/i);
613
+ expect(renderCardBlock?.[0]).toMatch(/const\s+assigneeHtml\s*=\s*`[\s\S]*class=["']\$\{assigneeClass\}["'][\s\S]*`;/i);
614
+ expect(renderCardBlock?.[0]).toMatch(/<div[^>]*class=["']card-header-right["'][^>]*>[\s\S]*\$\{projectHtml\}[\s\S]*<\/div>/i);
615
+ expect(renderCardBlock?.[0]).toMatch(/<div[^>]*class=["']card-meta["'][^>]*>[\s\S]*\$\{assigneeHtml\}[\s\S]*<\/div>/i);
616
+ });
617
+ it('truncates card assignee labels to 10 characters plus ellipsis', async () => {
618
+ server = createServer(4598);
619
+ const { body } = await fetchText('/');
620
+ const renderCardBlock = body.match(/function\s+renderCard\s*\([^)]*\)\s*\{[\s\S]*?return\s*`[\s\S]*?`;\s*}/i);
621
+ expect(renderCardBlock).toBeTruthy();
622
+ expect(body).toMatch(/function\s+truncateCardLabel\s*\(value,\s*maxChars\s*=\s*10\)\s*\{/i);
623
+ expect(renderCardBlock?.[0]).toMatch(/const\s+assigneeCardText\s*=\s*truncateCardLabel\(assigneeText,\s*10\)/i);
624
+ expect(renderCardBlock?.[0]).toMatch(/title="\$\{escapeHtml\(assigneeText\)\}"[^>]*>\$\{escapeHtml\(assigneeCardText\)\}<\/span>/i);
625
+ });
626
+ it('binds modal assignee metadata with an Unassigned fallback value', async () => {
627
+ server = createServer(4591);
628
+ const { body } = await fetchText('/');
629
+ const openTaskModalBlock = body.match(/async\s+function\s+openTaskModal\s*\([^)]*\)\s*\{[\s\S]*?let\s+html\s*=\s*`[\s\S]*?`;/i);
630
+ expect(openTaskModalBlock).toBeTruthy();
631
+ expect(openTaskModalBlock?.[0]).toMatch(/const\s+assigneeValue\s*=\s*hasAssignee[\s\S]*<span[^>]*class=["']modal-meta-fallback["'][^>]*>\s*Unassigned\s*<\/span>/i);
632
+ expect(openTaskModalBlock?.[0]).toMatch(/<div[^>]*class=["']modal-meta-label["'][^>]*>\s*Assignee\s*<\/div>[\s\S]*?<div[^>]*class=["']modal-meta-value["'][^>]*>\s*\$\{assigneeValue\}\s*<\/div>/i);
633
+ });
634
+ it('binds modal task id display to task.task_id', async () => {
635
+ server = createServer(4592);
636
+ const { body } = await fetchText('/');
637
+ const hasTaskIdSourceBinding = /data\.task\.task_id/i.test(body);
638
+ const hasModalTaskIdDisplayBinding = /modalTaskIdValue\s*\.\s*textContent\s*=\s*(?:data\.task\.task_id|[a-zA-Z0-9_$]*taskId[a-zA-Z0-9_$]*)/i.test(body);
639
+ expect(hasTaskIdSourceBinding).toBe(true);
640
+ expect(hasModalTaskIdDisplayBinding).toBe(true);
641
+ });
642
+ it('includes a modal copy control for task id', async () => {
643
+ server = createServer(4593);
644
+ const { body } = await fetchText('/');
645
+ expect(body).toMatch(/<div[^>]*class=["']modal-task-id-row["'][^>]*>[\s\S]*id=["']modalTaskIdValue["'][\s\S]*<button[^>]*id=["']modalTaskIdCopy["'][^>]*>[\s\S]*\bcopy\b[\s\S]*<\/button>/i);
646
+ });
647
+ it('includes a copy handler that uses clipboard API and/or execCommand fallback', async () => {
648
+ server = createServer(4594);
649
+ const { body } = await fetchText('/');
650
+ const hasCopyHandlerFunction = /(?:async\s+)?function\s+[a-zA-Z0-9_$]*copy[a-zA-Z0-9_$]*\s*\(/i.test(body) ||
651
+ /(?:const|let)\s+[a-zA-Z0-9_$]*copy[a-zA-Z0-9_$]*\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/i.test(body);
652
+ const hasClipboardOrExecCommand = /navigator\.clipboard\s*\.\s*writeText\s*\(|document\.execCommand\(\s*["']copy["']\s*\)/i.test(body);
653
+ expect(hasCopyHandlerFunction).toBe(true);
654
+ expect(hasClipboardOrExecCommand).toBe(true);
655
+ });
656
+ it('includes assignee filter select with id assigneeFilter', async () => {
657
+ server = createServer(4563);
658
+ const { body } = await fetchText('/');
659
+ expect(body).toMatch(/<select[^>]*id=["']assigneeFilter["'][^>]*>/i);
660
+ });
661
+ it('includes a default assignee option containing Any Agent', async () => {
662
+ server = createServer(4564);
663
+ const { body } = await fetchText('/');
664
+ const assigneeSelect = body.match(/<select[^>]*id=["']assigneeFilter["'][^>]*>([\s\S]*?)<\/select>/i);
665
+ expect(assigneeSelect).toBeTruthy();
666
+ expect(assigneeSelect?.[1]).toMatch(/<option[^>]*>[\s\S]*?Any Agent[\s\S]*?<\/option>/i);
667
+ });
668
+ it('includes script wiring for assignee filter change handling', async () => {
669
+ server = createServer(4565);
670
+ const { body } = await fetchText('/');
671
+ const hasAssigneeReference = /getElementById\(\s*['"]assigneeFilter['"]\s*\)/.test(body) ||
672
+ /querySelector\(\s*['"]#assigneeFilter['"]\s*\)/.test(body) ||
673
+ /assigneeFilter/.test(body);
674
+ const hasAssigneeChangeListener = /assigneeFilter\s*\.\s*addEventListener\(\s*['"]change['"]/.test(body) ||
675
+ /getElementById\(\s*['"]assigneeFilter['"]\s*\)\s*\.\s*addEventListener\(\s*['"]change['"]/.test(body) ||
676
+ /querySelector\(\s*['"]#assigneeFilter['"]\s*\)\s*\.\s*addEventListener\(\s*['"]change['"]/.test(body);
677
+ expect(hasAssigneeReference).toBe(true);
678
+ expect(hasAssigneeChangeListener).toBe(true);
679
+ });
680
+ it('preserves full assignee strings in board and activity filter wiring', async () => {
681
+ server = createServer(4600);
682
+ const { body } = await fetchText('/');
683
+ expect(body).toMatch(/function\s+getAssigneeValue\s*\(value\)\s*\{[\s\S]*value\.trim\(\)\.length\s*>\s*0[\s\S]*\?\s*value\s*:\s*['"]{2}/i);
684
+ expect(body).toMatch(/filtered\s*=\s*filtered\.filter\(\s*task\s*=>\s*getAssigneeValue\(task\.assignee\)\s*===\s*assigneeFilter\.value\s*\)/i);
685
+ expect(body).toMatch(/const\s+assignee\s*=\s*getAssigneeValue\(task\.assignee\);\s*if\s*\(!assignee\)\s*continue;[\s\S]*option\.value\s*=\s*assignee/i);
686
+ expect(body).toMatch(/const\s+taskAssignee\s*=\s*getAssigneeValue\(event\.task_assignee\);\s*if\s*\(taskAssignee\)\s*return\s+taskAssignee;[\s\S]*return\s+getAssigneeValue\(event\.data\?\.assignee\)/i);
687
+ });
688
+ it('includes activity assignee and keyword filter controls', async () => {
689
+ server = createServer(4566);
690
+ const { body } = await fetchText('/');
691
+ expect(body).toMatch(/<select[^>]*id=["']activityAssigneeFilter["'][^>]*>/i);
692
+ expect(body).toMatch(/<input[^>]*id=["']activityKeywordFilter["'][^>]*>/i);
693
+ });
694
+ it('applies activity keyword filtering only at 3+ characters', async () => {
695
+ server = createServer(4567);
696
+ const { body } = await fetchText('/');
697
+ expect(body).toMatch(/keyword\.length\s*>=\s*3/);
698
+ });
699
+ it('includes activity item markup with task id binding attribute', async () => {
700
+ server = createServer(4568);
701
+ const { body } = await fetchText('/');
702
+ expect(body).toMatch(/<div(?=[^>]*\bclass=["'][^"']*\bactivity-item\b[^"']*["'])(?=[^>]*\bdata-task-id\s*=\s*["'][^"']*event\.task_id[^"']*["'])[^>]*>/i);
703
+ });
704
+ it('includes activity list click delegation wired to openTaskModal', async () => {
705
+ server = createServer(4569);
706
+ const { body } = await fetchText('/');
707
+ expect(body).toMatch(/(?:activityList|getElementById\(\s*["']activityList["']\s*\)|querySelector\(\s*["']#activityList["']\s*\))\s*\.\s*addEventListener\(\s*["']click["']\s*,[\s\S]*?closest\(\s*["']\.activity-item["']\s*\)[\s\S]*?openTaskModal\b/i);
708
+ });
709
+ it('shows static Live text with green connection-dot live state when stream is healthy', async () => {
710
+ server = createServer(4598);
711
+ const { body } = await fetchText('/');
712
+ expect(body).toMatch(/\.connection-dot\.live\s*\{[\s\S]*--status-done/i);
713
+ expect(body).toMatch(/connectionText\.textContent\s*=\s*['"]Live['"]/);
714
+ expect(body).not.toMatch(/Live\s*\$\{ago\}s/);
715
+ });
716
+ it('uses hidden-by-default column scrollbars with scroll/touch reveal behavior', async () => {
717
+ server = createServer(4599);
718
+ const { body } = await fetchText('/');
719
+ expect(body).toMatch(/\.column-cards\s*\{[\s\S]*scrollbar-width:\s*none[\s\S]*-ms-overflow-style:\s*none/i);
720
+ expect(body).toMatch(/\.column-cards\.is-scrolling[\s\S]*::\-webkit-scrollbar/i);
721
+ expect(body).toMatch(/function\s+bindColumnScrollIndicators\s*\(/i);
722
+ expect(body).toMatch(/classList\.add\(\s*['"]is-scrolling['"]\s*\)/i);
723
+ });
724
+ it('renders an explicit activity actor/author element in task modal activity entries', async () => {
725
+ server = createServer(4595);
726
+ const { body } = await fetchText('/');
727
+ const activityMarkupBlock = body.match(/displayTaskActivity\.map\([\s\S]*?\)\.join\(\s*['"]{2}\s*\)/i);
728
+ expect(activityMarkupBlock).toBeTruthy();
729
+ expect(activityMarkupBlock?.[0]).toMatch(/class=["'][^"']*(?:activity|event)[^"']*(?:actor|author)[^"']*["']/i);
730
+ expect(activityMarkupBlock?.[0]).toMatch(/\$\{escapeHtml\(\s*actor\s*\)\}/i);
731
+ });
732
+ it('uses dedicated modal classes for checkpoint and activity author fields (not just .comment-author)', async () => {
733
+ server = createServer(4596);
734
+ const { body } = await fetchText('/');
735
+ const modalMarkupBlock = body.match(/async\s+function\s+openTaskModal\s*\([^)]*\)\s*\{[\s\S]*?modalBody\.innerHTML\s*=\s*html\s*;/i);
736
+ const checkpointMarkupBlock = modalMarkupBlock?.[0].match(/visibleCheckpoints\.map\([\s\S]*?\)\.join\(\s*['"]{2}\s*\)/i);
737
+ const activityMarkupBlock = modalMarkupBlock?.[0].match(/displayTaskActivity\.map\([\s\S]*?\)\.join\(\s*['"]{2}\s*\)/i);
738
+ expect(modalMarkupBlock).toBeTruthy();
739
+ expect(checkpointMarkupBlock).toBeTruthy();
740
+ expect(activityMarkupBlock).toBeTruthy();
741
+ expect(checkpointMarkupBlock?.[0]).toMatch(/class=["'][^"']*\b(?:modal-|task-)?(?:checkpoint|cp)-[^"']*["']/i);
742
+ expect(activityMarkupBlock?.[0]).toMatch(/class=["'][^"']*\b(?:modal-|task-)?(?:activity|event)-[^"']*["']/i);
743
+ expect(checkpointMarkupBlock?.[0]).not.toMatch(/\bcomment-author\b/i);
744
+ expect(activityMarkupBlock?.[0]).not.toMatch(/\bcomment-author\b/i);
745
+ });
746
+ it('styles modal checkpoint author/name with dedicated non-accent class rules', async () => {
747
+ server = createServer(4597);
748
+ const { body } = await fetchText('/');
749
+ const checkpointStyleRule = body.match(/\.(?:modal-|task-)?(?:checkpoint|cp)-[\w-]*(?:entry|item|author|name|title|meta)?[\w-]*\s*\{[\s\S]*?\}/i);
750
+ expect(checkpointStyleRule).toBeTruthy();
751
+ expect(checkpointStyleRule?.[0]).toMatch(/(?:color|border(?:-left|-color)?)\s*:/i);
752
+ expect(checkpointStyleRule?.[0]).not.toMatch(/--accent|--status-in-progress|orange/i);
753
+ });
508
754
  });
509
755
  describe('404 handling', () => {
510
756
  it('returns 404 for unknown routes', async () => {