agent-tasks 1.9.5 → 1.9.7

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/dist/ui/app.js CHANGED
@@ -3,7 +3,7 @@
3
3
  //
4
4
  // WebSocket connection, state management, initialization, tab/filter management,
5
5
  // theme, keyboard navigation, cleanup dialog, theme sync.
6
- // Modules: ui-utils.js, board.js, panel.js, drag.js, inline-edit.js
6
+ // Modules: ui-utils.js, board.js, panel.js, drag.js, inline-edit.js, template.js
7
7
  // =============================================================================
8
8
 
9
9
  window.TaskBoard = window.TaskBoard || {};
@@ -25,6 +25,12 @@ var state = {
25
25
 
26
26
  TaskBoard.state = state;
27
27
 
28
+ TaskBoard._baseUrl = '';
29
+ TaskBoard._fetch = function (url, opts) {
30
+ return fetch(TaskBoard._baseUrl + url, opts);
31
+ };
32
+ TaskBoard._wsUrl = null;
33
+
28
34
  var filters = {
29
35
  search: '',
30
36
  project: '',
@@ -73,22 +79,6 @@ function updateThemeIcon(theme) {
73
79
  if (icon) icon.textContent = theme === 'dark' ? 'light_mode' : 'dark_mode';
74
80
  }
75
81
 
76
- var savedTheme = localStorage.getItem('agent-tasks-theme');
77
- if (savedTheme === 'dark') document.documentElement.setAttribute('data-theme', 'dark');
78
- updateThemeIcon(savedTheme || 'light');
79
-
80
- document.getElementById('theme-toggle').addEventListener('click', () => {
81
- var isDark = document.documentElement.getAttribute('data-theme') === 'dark';
82
- var next = isDark ? 'light' : 'dark';
83
- if (isDark) {
84
- document.documentElement.removeAttribute('data-theme');
85
- } else {
86
- document.documentElement.setAttribute('data-theme', 'dark');
87
- }
88
- localStorage.setItem('agent-tasks-theme', next);
89
- updateThemeIcon(next);
90
- });
91
-
92
82
  // ---- Blocked tasks ----
93
83
 
94
84
  function getBlockedTaskIds() {
@@ -146,37 +136,6 @@ function updateFilterDropdowns() {
146
136
  assigneeSelect.value = currentAssignee;
147
137
  }
148
138
 
149
- document.getElementById('filter-search').addEventListener('input', (e) => {
150
- clearTimeout(searchDebounce);
151
- searchDebounce = setTimeout(() => {
152
- filters.search = e.target.value;
153
- saveFilters();
154
- TaskBoard.resetColumnVisibleCounts();
155
- render();
156
- }, 200);
157
- });
158
-
159
- document.getElementById('filter-project').addEventListener('change', (e) => {
160
- filters.project = e.target.value;
161
- saveFilters();
162
- TaskBoard.resetColumnVisibleCounts();
163
- render();
164
- });
165
-
166
- document.getElementById('filter-assignee').addEventListener('change', (e) => {
167
- filters.assignee = e.target.value;
168
- saveFilters();
169
- TaskBoard.resetColumnVisibleCounts();
170
- render();
171
- });
172
-
173
- document.getElementById('filter-priority').addEventListener('change', (e) => {
174
- filters.minPriority = parseInt(e.target.value) || 0;
175
- saveFilters();
176
- TaskBoard.resetColumnVisibleCounts();
177
- render();
178
- });
179
-
180
139
  function applyRestoredFilters() {
181
140
  var searchInput = document.getElementById('filter-search');
182
141
  if (filters.search && searchInput) searchInput.value = filters.search;
@@ -199,7 +158,7 @@ function render() {
199
158
 
200
159
  function connect() {
201
160
  var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
202
- ws = new WebSocket(`${proto}//${location.host}`);
161
+ ws = new WebSocket(`${proto}//${TaskBoard._wsUrl || location.host}`);
203
162
  setConnectionStatus('connecting');
204
163
 
205
164
  ws.onopen = () => setConnectionStatus('connected');
@@ -395,365 +354,451 @@ function handleEvent(event) {
395
354
  }
396
355
  }
397
356
 
398
- // ---- Event Delegation (board) ----
399
-
400
- document.getElementById('board').addEventListener('click', (e) => {
401
- var action = e.target.closest('[data-action]');
357
+ // ---- Legacy Modal (cleanup only) ----
402
358
 
403
- if (action) {
404
- var act = action.dataset.action;
359
+ function closeModal() {
360
+ document.getElementById('task-modal').hidden = true;
361
+ }
405
362
 
406
- if (act === 'toggle-collapse') {
407
- e.stopPropagation();
408
- var stage = action.dataset.stage;
409
- if (state.collapsedColumns.has(stage)) {
410
- state.collapsedColumns.delete(stage);
411
- } else {
412
- state.collapsedColumns.add(stage);
413
- }
414
- saveCollapsed();
415
- render();
416
- return;
417
- }
363
+ // ---- Init ----
418
364
 
419
- if (act === 'inline-create') {
420
- e.stopPropagation();
421
- TaskBoard.showInlineCreate(action.dataset.stage);
422
- return;
423
- }
365
+ function _init() {
366
+ var savedTheme = localStorage.getItem('agent-tasks-theme');
367
+ if (savedTheme === 'dark') document.documentElement.setAttribute('data-theme', 'dark');
368
+ updateThemeIcon(savedTheme || 'light');
424
369
 
425
- if (act === 'add-task') {
426
- e.stopPropagation();
427
- TaskBoard.showInlineCreate(action.dataset.stage);
428
- return;
370
+ document.getElementById('theme-toggle')?.addEventListener('click', () => {
371
+ var isDark = document.documentElement.getAttribute('data-theme') === 'dark';
372
+ var next = isDark ? 'light' : 'dark';
373
+ if (isDark) {
374
+ document.documentElement.removeAttribute('data-theme');
375
+ } else {
376
+ document.documentElement.setAttribute('data-theme', 'dark');
429
377
  }
378
+ localStorage.setItem('agent-tasks-theme', next);
379
+ updateThemeIcon(next);
380
+ });
430
381
 
431
- if (act === 'cycle-priority') {
432
- e.stopPropagation();
433
- TaskBoard.cyclePriority(parseInt(action.dataset.taskId, 10));
434
- return;
435
- }
382
+ document.getElementById('filter-search')?.addEventListener('input', (e) => {
383
+ clearTimeout(searchDebounce);
384
+ searchDebounce = setTimeout(() => {
385
+ filters.search = e.target.value;
386
+ saveFilters();
387
+ TaskBoard.resetColumnVisibleCounts();
388
+ render();
389
+ }, 200);
390
+ });
436
391
 
437
- if (act === 'change-assignee') {
438
- e.stopPropagation();
439
- TaskBoard.showAssigneeDropdown(parseInt(action.dataset.taskId, 10), action);
440
- return;
441
- }
392
+ document.getElementById('filter-project')?.addEventListener('change', (e) => {
393
+ filters.project = e.target.value;
394
+ saveFilters();
395
+ TaskBoard.resetColumnVisibleCounts();
396
+ render();
397
+ });
442
398
 
443
- if (act === 'show-more') {
444
- e.stopPropagation();
445
- TaskBoard.showMoreCards(action.dataset.stage);
446
- render();
447
- return;
448
- }
449
- }
399
+ document.getElementById('filter-assignee')?.addEventListener('change', (e) => {
400
+ filters.assignee = e.target.value;
401
+ saveFilters();
402
+ TaskBoard.resetColumnVisibleCounts();
403
+ render();
404
+ });
450
405
 
451
- var card = e.target.closest('.task-card[data-task-id]');
452
- if (card) {
453
- TaskBoard.openPanel(parseInt(card.dataset.taskId, 10));
454
- }
455
- });
406
+ document.getElementById('filter-priority')?.addEventListener('change', (e) => {
407
+ filters.minPriority = parseInt(e.target.value) || 0;
408
+ saveFilters();
409
+ TaskBoard.resetColumnVisibleCounts();
410
+ render();
411
+ });
456
412
 
457
- document.getElementById('board').addEventListener('dblclick', (e) => {
458
- var titleEl = e.target.closest('[data-action="edit-title"]');
459
- if (titleEl) {
460
- e.stopPropagation();
461
- TaskBoard.startInlineEdit(titleEl);
462
- }
463
- });
413
+ // ---- Event Delegation (board) ----
464
414
 
465
- document.getElementById('board').addEventListener('keydown', (e) => {
466
- if (e.key === 'Enter') {
467
- var card = e.target.closest('.task-card[data-task-id]');
468
- if (card) TaskBoard.openPanel(parseInt(card.dataset.taskId, 10));
469
- }
470
- });
415
+ document.getElementById('board')?.addEventListener('click', (e) => {
416
+ var action = e.target.closest('[data-action]');
471
417
 
472
- // ---- Collapsed column click (expand) ----
418
+ if (action) {
419
+ var act = action.dataset.action;
473
420
 
474
- document.getElementById('board').addEventListener('click', (e) => {
475
- var col = e.target.closest('.kanban-column.collapsed');
476
- if (col) {
477
- var stage = col.dataset.stage;
478
- state.collapsedColumns.delete(stage);
479
- saveCollapsed();
480
- render();
481
- }
482
- });
421
+ if (act === 'toggle-collapse') {
422
+ e.stopPropagation();
423
+ var stage = action.dataset.stage;
424
+ if (state.collapsedColumns.has(stage)) {
425
+ state.collapsedColumns.delete(stage);
426
+ } else {
427
+ state.collapsedColumns.add(stage);
428
+ }
429
+ saveCollapsed();
430
+ render();
431
+ return;
432
+ }
483
433
 
484
- // ---- Legacy Modal (cleanup only) ----
434
+ if (act === 'inline-create') {
435
+ e.stopPropagation();
436
+ TaskBoard.showInlineCreate(action.dataset.stage);
437
+ return;
438
+ }
485
439
 
486
- function closeModal() {
487
- document.getElementById('task-modal').hidden = true;
488
- }
440
+ if (act === 'add-task') {
441
+ e.stopPropagation();
442
+ TaskBoard.showInlineCreate(action.dataset.stage);
443
+ return;
444
+ }
489
445
 
490
- document.getElementById('modal-close-btn')?.addEventListener('click', closeModal);
491
- document.getElementById('task-modal').addEventListener('click', (e) => {
492
- if (e.target === e.currentTarget) closeModal();
493
- });
446
+ if (act === 'cycle-priority') {
447
+ e.stopPropagation();
448
+ TaskBoard.cyclePriority(parseInt(action.dataset.taskId, 10));
449
+ return;
450
+ }
494
451
 
495
- // ---- Keyboard Navigation ----
452
+ if (act === 'change-assignee') {
453
+ e.stopPropagation();
454
+ TaskBoard.showAssigneeDropdown(parseInt(action.dataset.taskId, 10), action);
455
+ return;
456
+ }
496
457
 
497
- document.addEventListener('keydown', (e) => {
498
- if (e.key === 'Escape') {
499
- if (TaskBoard.getActiveDropdown()) {
500
- TaskBoard.dismissDropdown();
501
- return;
458
+ if (act === 'show-more') {
459
+ e.stopPropagation();
460
+ TaskBoard.showMoreCards(action.dataset.stage);
461
+ render();
462
+ return;
463
+ }
502
464
  }
503
- if (TaskBoard.getActiveInlineCreate()) {
504
- TaskBoard.dismissInlineCreate();
505
- return;
465
+
466
+ var card = e.target.closest('.task-card[data-task-id]');
467
+ if (card) {
468
+ TaskBoard.openPanel(parseInt(card.dataset.taskId, 10));
506
469
  }
507
- if (state.panelTaskId) {
508
- TaskBoard.closePanel();
509
- return;
470
+ });
471
+
472
+ document.getElementById('board')?.addEventListener('dblclick', (e) => {
473
+ var titleEl = e.target.closest('[data-action="edit-title"]');
474
+ if (titleEl) {
475
+ e.stopPropagation();
476
+ TaskBoard.startInlineEdit(titleEl);
510
477
  }
511
- var modal = document.getElementById('task-modal');
512
- if (!modal.hidden) {
513
- closeModal();
514
- return;
478
+ });
479
+
480
+ document.getElementById('board')?.addEventListener('keydown', (e) => {
481
+ if (e.key === 'Enter') {
482
+ var card = e.target.closest('.task-card[data-task-id]');
483
+ if (card) TaskBoard.openPanel(parseInt(card.dataset.taskId, 10));
515
484
  }
516
- var cleanupModal = document.getElementById('cleanup-modal');
517
- if (!cleanupModal.classList.contains('hidden')) {
518
- cleanupModal.classList.add('hidden');
519
- return;
485
+ });
486
+
487
+ // ---- Collapsed column click (expand) ----
488
+
489
+ document.getElementById('board')?.addEventListener('click', (e) => {
490
+ var col = e.target.closest('.kanban-column.collapsed');
491
+ if (col) {
492
+ var stage = col.dataset.stage;
493
+ state.collapsedColumns.delete(stage);
494
+ saveCollapsed();
495
+ render();
520
496
  }
521
- }
497
+ });
522
498
 
523
- var isInput =
524
- document.activeElement?.tagName === 'INPUT' ||
525
- document.activeElement?.tagName === 'TEXTAREA' ||
526
- document.activeElement?.getAttribute('contenteditable') === 'true';
527
-
528
- if (
529
- (e.key === '/' && !e.ctrlKey && !e.metaKey && !isInput) ||
530
- ((e.ctrlKey || e.metaKey) && e.key === 'k')
531
- ) {
532
- e.preventDefault();
533
- document.getElementById('filter-search').focus();
534
- }
535
- });
499
+ document.getElementById('modal-close-btn')?.addEventListener('click', closeModal);
500
+ document.getElementById('task-modal')?.addEventListener('click', (e) => {
501
+ if (e.target === e.currentTarget) closeModal();
502
+ });
536
503
 
537
- // ---- Cleanup Dialog ----
504
+ // ---- Keyboard Navigation ----
538
505
 
539
- document.getElementById('cleanup-btn')?.addEventListener('click', () => {
540
- document.getElementById('cleanup-modal').classList.remove('hidden');
541
- });
506
+ document.addEventListener('keydown', (e) => {
507
+ if (e.key === 'Escape') {
508
+ if (TaskBoard.getActiveDropdown()) {
509
+ TaskBoard.dismissDropdown();
510
+ return;
511
+ }
512
+ if (TaskBoard.getActiveInlineCreate()) {
513
+ TaskBoard.dismissInlineCreate();
514
+ return;
515
+ }
516
+ if (state.panelTaskId) {
517
+ TaskBoard.closePanel();
518
+ return;
519
+ }
520
+ var modal = document.getElementById('task-modal');
521
+ if (modal && !modal.hidden) {
522
+ closeModal();
523
+ return;
524
+ }
525
+ var cleanupModal = document.getElementById('cleanup-modal');
526
+ if (cleanupModal && !cleanupModal.classList.contains('hidden')) {
527
+ cleanupModal.classList.add('hidden');
528
+ return;
529
+ }
530
+ }
531
+
532
+ var isInput =
533
+ document.activeElement?.tagName === 'INPUT' ||
534
+ document.activeElement?.tagName === 'TEXTAREA' ||
535
+ document.activeElement?.getAttribute('contenteditable') === 'true';
536
+
537
+ if (
538
+ (e.key === '/' && !e.ctrlKey && !e.metaKey && !isInput) ||
539
+ ((e.ctrlKey || e.metaKey) && e.key === 'k')
540
+ ) {
541
+ e.preventDefault();
542
+ document.getElementById('filter-search')?.focus();
543
+ }
544
+ });
545
+
546
+ // ---- Cleanup Dialog ----
542
547
 
543
- document.getElementById('cleanup-close-btn')?.addEventListener('click', () => {
544
- document.getElementById('cleanup-modal').classList.add('hidden');
545
- });
548
+ document.getElementById('cleanup-btn')?.addEventListener('click', () => {
549
+ document.getElementById('cleanup-modal').classList.remove('hidden');
550
+ });
546
551
 
547
- document.getElementById('cleanup-modal')?.addEventListener('click', (e) => {
548
- if (e.target === e.currentTarget) {
552
+ document.getElementById('cleanup-close-btn')?.addEventListener('click', () => {
549
553
  document.getElementById('cleanup-modal').classList.add('hidden');
550
- }
551
- });
554
+ });
552
555
 
553
- document.getElementById('cleanup-completed')?.addEventListener('click', () => {
554
- var showToast = TaskBoard.showToast;
555
- document.getElementById('cleanup-modal').classList.add('hidden');
556
- fetch('/api/cleanup', {
557
- method: 'POST',
558
- headers: { 'Content-Type': 'application/json' },
559
- body: JSON.stringify({ force: true }),
560
- })
561
- .then((r) => r.json())
562
- .then((result) => {
563
- showToast(
564
- 'Cleanup complete',
565
- `Purged ${result.purgedTasks} tasks, ${result.purgedComments} comments, ${result.purgedApprovals} approvals`,
566
- 'success',
567
- );
556
+ document.getElementById('cleanup-modal')?.addEventListener('click', (e) => {
557
+ if (e.target === e.currentTarget) {
558
+ document.getElementById('cleanup-modal').classList.add('hidden');
559
+ }
560
+ });
561
+
562
+ document.getElementById('cleanup-completed')?.addEventListener('click', () => {
563
+ var showToast = TaskBoard.showToast;
564
+ document.getElementById('cleanup-modal').classList.add('hidden');
565
+ TaskBoard._fetch('/api/cleanup', {
566
+ method: 'POST',
567
+ headers: { 'Content-Type': 'application/json' },
568
+ body: JSON.stringify({ force: true }),
568
569
  })
569
- .catch(() => showToast('Cleanup failed', 'Network error', 'error'));
570
- });
570
+ .then((r) => r.json())
571
+ .then((result) => {
572
+ showToast(
573
+ 'Cleanup complete',
574
+ `Purged ${result.purgedTasks} tasks, ${result.purgedComments} comments, ${result.purgedApprovals} approvals`,
575
+ 'success',
576
+ );
577
+ })
578
+ .catch(() => showToast('Cleanup failed', 'Network error', 'error'));
579
+ });
571
580
 
572
- document.getElementById('cleanup-everything')?.addEventListener('click', () => {
573
- var showToast = TaskBoard.showToast;
574
- if (
575
- !confirm(
576
- 'This will remove ALL tasks — completed, in-progress, everything. This cannot be undone. Continue?',
581
+ document.getElementById('cleanup-everything')?.addEventListener('click', () => {
582
+ var showToast = TaskBoard.showToast;
583
+ if (
584
+ !confirm(
585
+ 'This will remove ALL tasks — completed, in-progress, everything. This cannot be undone. Continue?',
586
+ )
577
587
  )
578
- )
579
- return;
580
- document.getElementById('cleanup-modal').classList.add('hidden');
581
- fetch('/api/cleanup', {
582
- method: 'POST',
583
- headers: { 'Content-Type': 'application/json' },
584
- body: JSON.stringify({ all: true }),
585
- })
586
- .then((r) => r.json())
587
- .then((result) => {
588
- showToast(
589
- 'Everything purged',
590
- `Purged ${result.purgedTasks} tasks, ${result.purgedComments} comments, ${result.purgedApprovals} approvals`,
591
- 'success',
592
- );
588
+ return;
589
+ document.getElementById('cleanup-modal').classList.add('hidden');
590
+ TaskBoard._fetch('/api/cleanup', {
591
+ method: 'POST',
592
+ headers: { 'Content-Type': 'application/json' },
593
+ body: JSON.stringify({ all: true }),
593
594
  })
594
- .catch(() => showToast('Cleanup failed', 'Network error', 'error'));
595
- });
596
-
597
- // ---- Theme sync from parent (agent-desk) via executeJavaScript ----
598
-
599
- window.addEventListener('message', function (event) {
600
- if (!event.data || event.data.type !== 'theme-sync') return;
601
- var colors = event.data.colors;
602
- if (!colors) return;
603
-
604
- function ensureContrast(bg, fg) {
605
- var lum = function (hex) {
606
- if (!hex || hex.charAt(0) !== '#' || hex.length < 7) return 0.5;
607
- var r = parseInt(hex.slice(1, 3), 16) / 255;
608
- var g = parseInt(hex.slice(3, 5), 16) / 255;
609
- var b = parseInt(hex.slice(5, 7), 16) / 255;
610
- return 0.2126 * r + 0.7152 * g + 0.0722 * b;
611
- };
612
- var bgLum = lum(bg);
613
- return bgLum < 0.5 ? (lum(fg) < 0.4 ? '#e0e0e0' : fg) : lum(fg) > 0.6 ? '#333333' : fg;
614
- }
595
+ .then((r) => r.json())
596
+ .then((result) => {
597
+ showToast(
598
+ 'Everything purged',
599
+ `Purged ${result.purgedTasks} tasks, ${result.purgedComments} comments, ${result.purgedApprovals} approvals`,
600
+ 'success',
601
+ );
602
+ })
603
+ .catch(() => showToast('Cleanup failed', 'Network error', 'error'));
604
+ });
615
605
 
616
- var root = document.documentElement;
617
- var bgColor = colors.bg || null;
606
+ // ---- Theme sync from parent (agent-desk) via executeJavaScript ----
607
+
608
+ window.addEventListener('message', function (event) {
609
+ if (!event.data || event.data.type !== 'theme-sync') return;
610
+ var colors = event.data.colors;
611
+ if (!colors) return;
612
+
613
+ function ensureContrast(bg, fg) {
614
+ var lum = function (hex) {
615
+ if (!hex || hex.charAt(0) !== '#' || hex.length < 7) return 0.5;
616
+ var r = parseInt(hex.slice(1, 3), 16) / 255;
617
+ var g = parseInt(hex.slice(3, 5), 16) / 255;
618
+ var b = parseInt(hex.slice(5, 7), 16) / 255;
619
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
620
+ };
621
+ var bgLum = lum(bg);
622
+ return bgLum < 0.5 ? (lum(fg) < 0.4 ? '#e0e0e0' : fg) : lum(fg) > 0.6 ? '#333333' : fg;
623
+ }
618
624
 
619
- if (colors.bg) root.style.setProperty('--bg', colors.bg);
620
- if (colors.bgSurface) root.style.setProperty('--bg-surface', colors.bgSurface);
621
- if (colors.bgElevated) root.style.setProperty('--bg-elevated', colors.bgElevated);
622
- if (colors.bgHover) root.style.setProperty('--bg-hover', colors.bgHover);
623
- if (colors.bgInset) root.style.setProperty('--bg-inset', colors.bgInset);
625
+ var root = document.documentElement;
626
+ var bgColor = colors.bg || null;
624
627
 
625
- if (colors.border) root.style.setProperty('--border', colors.border);
626
- if (colors.borderLight) root.style.setProperty('--border-light', colors.borderLight);
628
+ if (colors.bg) root.style.setProperty('--bg', colors.bg);
629
+ if (colors.bgSurface) root.style.setProperty('--bg-surface', colors.bgSurface);
630
+ if (colors.bgElevated) root.style.setProperty('--bg-elevated', colors.bgElevated);
631
+ if (colors.bgHover) root.style.setProperty('--bg-hover', colors.bgHover);
632
+ if (colors.bgInset) root.style.setProperty('--bg-inset', colors.bgInset);
627
633
 
628
- if (colors.text)
629
- root.style.setProperty('--text', bgColor ? ensureContrast(bgColor, colors.text) : colors.text);
630
- if (colors.textSecondary)
631
- root.style.setProperty(
632
- '--text-secondary',
633
- bgColor ? ensureContrast(bgColor, colors.textSecondary) : colors.textSecondary,
634
- );
635
- if (colors.textMuted)
636
- root.style.setProperty(
637
- '--text-muted',
638
- bgColor ? ensureContrast(bgColor, colors.textMuted) : colors.textMuted,
639
- );
640
- if (colors.textDim)
641
- root.style.setProperty(
642
- '--text-dim',
643
- bgColor ? ensureContrast(bgColor, colors.textDim) : colors.textDim,
644
- );
634
+ if (colors.border) root.style.setProperty('--border', colors.border);
635
+ if (colors.borderLight) root.style.setProperty('--border-light', colors.borderLight);
645
636
 
646
- if (colors.accent) root.style.setProperty('--accent', colors.accent);
647
- if (colors.accentHover) root.style.setProperty('--accent-hover', colors.accentHover);
648
- if (colors.accentDim) root.style.setProperty('--accent-dim', colors.accentDim);
649
- if (colors.accentSolid) root.style.setProperty('--accent-solid', colors.accentSolid);
650
- if (colors.accentGlow) root.style.setProperty('--accent-glow', colors.accentGlow);
651
-
652
- if (colors.green) root.style.setProperty('--green', colors.green);
653
- if (colors.greenDim) root.style.setProperty('--green-dim', colors.greenDim);
654
- if (colors.yellow) root.style.setProperty('--yellow', colors.yellow);
655
- if (colors.yellowDim) root.style.setProperty('--yellow-dim', colors.yellowDim);
656
- if (colors.orange) root.style.setProperty('--orange', colors.orange);
657
- if (colors.orangeDim) root.style.setProperty('--orange-dim', colors.orangeDim);
658
- if (colors.red) root.style.setProperty('--red', colors.red);
659
- if (colors.redDim) root.style.setProperty('--red-dim', colors.redDim);
660
- if (colors.purple) root.style.setProperty('--purple', colors.purple);
661
- if (colors.purpleDim) root.style.setProperty('--purple-dim', colors.purpleDim);
662
- if (colors.blue) root.style.setProperty('--blue', colors.blue);
663
- if (colors.blueDim) root.style.setProperty('--blue-dim', colors.blueDim);
664
- if (colors.indigo) root.style.setProperty('--indigo', colors.indigo);
665
- if (colors.indigoDim) root.style.setProperty('--indigo-dim', colors.indigoDim);
666
- if (colors.amber) root.style.setProperty('--amber', colors.amber);
667
- if (colors.amberDim) root.style.setProperty('--amber-dim', colors.amberDim);
668
- if (colors.gray) root.style.setProperty('--gray', colors.gray);
669
- if (colors.grayDim) root.style.setProperty('--gray-dim', colors.grayDim);
670
-
671
- if (colors.stageBacklog) root.style.setProperty('--stage-backlog', colors.stageBacklog);
672
- if (colors.stageSpec) root.style.setProperty('--stage-spec', colors.stageSpec);
673
- if (colors.stagePlan) root.style.setProperty('--stage-plan', colors.stagePlan);
674
- if (colors.stageImplement) root.style.setProperty('--stage-implement', colors.stageImplement);
675
- if (colors.stageTest) root.style.setProperty('--stage-test', colors.stageTest);
676
- if (colors.stageReview) root.style.setProperty('--stage-review', colors.stageReview);
677
- if (colors.stageDone) root.style.setProperty('--stage-done', colors.stageDone);
678
- if (colors.stageCancelled) root.style.setProperty('--stage-cancelled', colors.stageCancelled);
679
-
680
- if (colors.focusRing) root.style.setProperty('--focus-ring', colors.focusRing);
681
-
682
- if (colors.isDark !== undefined) {
683
- if (colors.isDark) {
684
- root.style.setProperty(
685
- '--shadow-1',
686
- '0px 1px 2px 0px rgba(0,0,0,0.6), 0px 1px 3px 1px rgba(0,0,0,0.3)',
687
- );
688
- root.style.setProperty(
689
- '--shadow-2',
690
- '0px 1px 2px 0px rgba(0,0,0,0.6), 0px 2px 6px 2px rgba(0,0,0,0.3)',
691
- );
692
- root.style.setProperty(
693
- '--shadow-3',
694
- '0px 1px 3px 0px rgba(0,0,0,0.6), 0px 4px 8px 3px rgba(0,0,0,0.3)',
695
- );
637
+ if (colors.text)
696
638
  root.style.setProperty(
697
- '--shadow-hover',
698
- '0px 2px 4px 0px rgba(0,0,0,0.5), 0px 6px 16px 4px rgba(0,0,0,0.4)',
639
+ '--text',
640
+ bgColor ? ensureContrast(bgColor, colors.text) : colors.text,
699
641
  );
642
+ if (colors.textSecondary)
700
643
  root.style.setProperty(
701
- '--shadow-drag',
702
- '0px 4px 8px 0px rgba(0,0,0,0.5), 0px 12px 32px 6px rgba(0,0,0,0.4)',
644
+ '--text-secondary',
645
+ bgColor ? ensureContrast(bgColor, colors.textSecondary) : colors.textSecondary,
703
646
  );
647
+ if (colors.textMuted)
704
648
  root.style.setProperty(
705
- '--shadow-panel',
706
- '-2px 0px 8px 0px rgba(0,0,0,0.5), -4px 0px 16px 2px rgba(0,0,0,0.3)',
707
- );
708
- } else {
709
- root.style.setProperty(
710
- '--shadow-1',
711
- '0px 1px 2px 0px rgba(0,0,0,0.3), 0px 1px 3px 1px rgba(0,0,0,0.15)',
712
- );
713
- root.style.setProperty(
714
- '--shadow-2',
715
- '0px 1px 2px 0px rgba(0,0,0,0.3), 0px 2px 6px 2px rgba(0,0,0,0.15)',
716
- );
717
- root.style.setProperty(
718
- '--shadow-3',
719
- '0px 1px 3px 0px rgba(0,0,0,0.3), 0px 4px 8px 3px rgba(0,0,0,0.15)',
720
- );
721
- root.style.setProperty(
722
- '--shadow-hover',
723
- '0px 2px 4px 0px rgba(0,0,0,0.25), 0px 4px 12px 4px rgba(0,0,0,0.15)',
649
+ '--text-muted',
650
+ bgColor ? ensureContrast(bgColor, colors.textMuted) : colors.textMuted,
724
651
  );
652
+ if (colors.textDim)
725
653
  root.style.setProperty(
726
- '--shadow-drag',
727
- '0px 4px 8px 0px rgba(0,0,0,0.3), 0px 12px 32px 6px rgba(0,0,0,0.25)',
728
- );
729
- root.style.setProperty(
730
- '--shadow-panel',
731
- '-2px 0px 8px 0px rgba(0,0,0,0.3), -4px 0px 16px 2px rgba(0,0,0,0.15)',
654
+ '--text-dim',
655
+ bgColor ? ensureContrast(bgColor, colors.textDim) : colors.textDim,
732
656
  );
657
+
658
+ if (colors.accent) root.style.setProperty('--accent', colors.accent);
659
+ if (colors.accentHover) root.style.setProperty('--accent-hover', colors.accentHover);
660
+ if (colors.accentDim) root.style.setProperty('--accent-dim', colors.accentDim);
661
+ if (colors.accentSolid) root.style.setProperty('--accent-solid', colors.accentSolid);
662
+ if (colors.accentGlow) root.style.setProperty('--accent-glow', colors.accentGlow);
663
+
664
+ if (colors.green) root.style.setProperty('--green', colors.green);
665
+ if (colors.greenDim) root.style.setProperty('--green-dim', colors.greenDim);
666
+ if (colors.yellow) root.style.setProperty('--yellow', colors.yellow);
667
+ if (colors.yellowDim) root.style.setProperty('--yellow-dim', colors.yellowDim);
668
+ if (colors.orange) root.style.setProperty('--orange', colors.orange);
669
+ if (colors.orangeDim) root.style.setProperty('--orange-dim', colors.orangeDim);
670
+ if (colors.red) root.style.setProperty('--red', colors.red);
671
+ if (colors.redDim) root.style.setProperty('--red-dim', colors.redDim);
672
+ if (colors.purple) root.style.setProperty('--purple', colors.purple);
673
+ if (colors.purpleDim) root.style.setProperty('--purple-dim', colors.purpleDim);
674
+ if (colors.blue) root.style.setProperty('--blue', colors.blue);
675
+ if (colors.blueDim) root.style.setProperty('--blue-dim', colors.blueDim);
676
+ if (colors.indigo) root.style.setProperty('--indigo', colors.indigo);
677
+ if (colors.indigoDim) root.style.setProperty('--indigo-dim', colors.indigoDim);
678
+ if (colors.amber) root.style.setProperty('--amber', colors.amber);
679
+ if (colors.amberDim) root.style.setProperty('--amber-dim', colors.amberDim);
680
+ if (colors.gray) root.style.setProperty('--gray', colors.gray);
681
+ if (colors.grayDim) root.style.setProperty('--gray-dim', colors.grayDim);
682
+
683
+ if (colors.stageBacklog) root.style.setProperty('--stage-backlog', colors.stageBacklog);
684
+ if (colors.stageSpec) root.style.setProperty('--stage-spec', colors.stageSpec);
685
+ if (colors.stagePlan) root.style.setProperty('--stage-plan', colors.stagePlan);
686
+ if (colors.stageImplement) root.style.setProperty('--stage-implement', colors.stageImplement);
687
+ if (colors.stageTest) root.style.setProperty('--stage-test', colors.stageTest);
688
+ if (colors.stageReview) root.style.setProperty('--stage-review', colors.stageReview);
689
+ if (colors.stageDone) root.style.setProperty('--stage-done', colors.stageDone);
690
+ if (colors.stageCancelled) root.style.setProperty('--stage-cancelled', colors.stageCancelled);
691
+
692
+ if (colors.focusRing) root.style.setProperty('--focus-ring', colors.focusRing);
693
+
694
+ if (colors.isDark !== undefined) {
695
+ if (colors.isDark) {
696
+ root.style.setProperty(
697
+ '--shadow-1',
698
+ '0px 1px 2px 0px rgba(0,0,0,0.6), 0px 1px 3px 1px rgba(0,0,0,0.3)',
699
+ );
700
+ root.style.setProperty(
701
+ '--shadow-2',
702
+ '0px 1px 2px 0px rgba(0,0,0,0.6), 0px 2px 6px 2px rgba(0,0,0,0.3)',
703
+ );
704
+ root.style.setProperty(
705
+ '--shadow-3',
706
+ '0px 1px 3px 0px rgba(0,0,0,0.6), 0px 4px 8px 3px rgba(0,0,0,0.3)',
707
+ );
708
+ root.style.setProperty(
709
+ '--shadow-hover',
710
+ '0px 2px 4px 0px rgba(0,0,0,0.5), 0px 6px 16px 4px rgba(0,0,0,0.4)',
711
+ );
712
+ root.style.setProperty(
713
+ '--shadow-drag',
714
+ '0px 4px 8px 0px rgba(0,0,0,0.5), 0px 12px 32px 6px rgba(0,0,0,0.4)',
715
+ );
716
+ root.style.setProperty(
717
+ '--shadow-panel',
718
+ '-2px 0px 8px 0px rgba(0,0,0,0.5), -4px 0px 16px 2px rgba(0,0,0,0.3)',
719
+ );
720
+ } else {
721
+ root.style.setProperty(
722
+ '--shadow-1',
723
+ '0px 1px 2px 0px rgba(0,0,0,0.3), 0px 1px 3px 1px rgba(0,0,0,0.15)',
724
+ );
725
+ root.style.setProperty(
726
+ '--shadow-2',
727
+ '0px 1px 2px 0px rgba(0,0,0,0.3), 0px 2px 6px 2px rgba(0,0,0,0.15)',
728
+ );
729
+ root.style.setProperty(
730
+ '--shadow-3',
731
+ '0px 1px 3px 0px rgba(0,0,0,0.3), 0px 4px 8px 3px rgba(0,0,0,0.15)',
732
+ );
733
+ root.style.setProperty(
734
+ '--shadow-hover',
735
+ '0px 2px 4px 0px rgba(0,0,0,0.25), 0px 4px 12px 4px rgba(0,0,0,0.15)',
736
+ );
737
+ root.style.setProperty(
738
+ '--shadow-drag',
739
+ '0px 4px 8px 0px rgba(0,0,0,0.3), 0px 12px 32px 6px rgba(0,0,0,0.25)',
740
+ );
741
+ root.style.setProperty(
742
+ '--shadow-panel',
743
+ '-2px 0px 8px 0px rgba(0,0,0,0.3), -4px 0px 16px 2px rgba(0,0,0,0.15)',
744
+ );
745
+ }
733
746
  }
734
- }
735
747
 
736
- if (colors.isDark !== undefined) {
737
- var theme = colors.isDark ? 'dark' : 'light';
738
- if (colors.isDark) {
739
- document.documentElement.setAttribute('data-theme', 'dark');
740
- } else {
741
- document.documentElement.removeAttribute('data-theme');
748
+ if (colors.isDark !== undefined) {
749
+ var theme = colors.isDark ? 'dark' : 'light';
750
+ if (colors.isDark) {
751
+ document.documentElement.setAttribute('data-theme', 'dark');
752
+ } else {
753
+ document.documentElement.removeAttribute('data-theme');
754
+ }
755
+ localStorage.setItem('agent-tasks-theme', theme);
756
+ updateThemeIcon(theme);
742
757
  }
743
- localStorage.setItem('agent-tasks-theme', theme);
744
- updateThemeIcon(theme);
745
- }
746
758
 
747
- var themeToggle = document.getElementById('theme-toggle');
748
- if (themeToggle) themeToggle.style.display = 'none';
749
- });
759
+ var themeToggle = document.getElementById('theme-toggle');
760
+ if (themeToggle) themeToggle.style.display = 'none';
761
+ });
762
+
763
+ // ---- Initialize modules ----
764
+
765
+ TaskBoard.initDragEvents();
766
+ TaskBoard.initPanelEvents();
767
+ TaskBoard.initPanelResize();
768
+
769
+ // ---- Boot ----
750
770
 
751
- // ---- Initialize modules ----
771
+ connect();
772
+ }
752
773
 
753
- TaskBoard.initDragEvents();
754
- TaskBoard.initPanelEvents();
755
- TaskBoard.initPanelResize();
774
+ // ---- Plugin mount/unmount ----
775
+
776
+ TaskBoard.mount = function (container, options) {
777
+ options = options || {};
778
+ TaskBoard._baseUrl = options.baseUrl || '';
779
+ TaskBoard._wsUrl = options.wsUrl || null;
780
+ if (options.cssUrl && !document.getElementById('tb-plugin-css')) {
781
+ var link = document.createElement('link');
782
+ link.id = 'tb-plugin-css';
783
+ link.rel = 'stylesheet';
784
+ link.href = options.cssUrl;
785
+ document.head.appendChild(link);
786
+ }
787
+ if (typeof TaskBoard._template === 'function') {
788
+ container.innerHTML = TaskBoard._template();
789
+ }
790
+ _init();
791
+ };
792
+
793
+ TaskBoard.unmount = function () {
794
+ if (ws) {
795
+ ws.onclose = null;
796
+ ws.close();
797
+ ws = null;
798
+ }
799
+ clearTimeout(reconnectTimer);
800
+ };
756
801
 
757
- // ---- Boot ----
802
+ // ---- Auto-init for standalone mode ----
758
803
 
759
- connect();
804
+ _init();
package/dist/ui/drag.js CHANGED
@@ -59,7 +59,7 @@ function onDrop(e, col) {
59
59
  const task = state.tasks.find((t) => t.id === draggedTaskId);
60
60
  if (!task || task.stage === targetStage) return;
61
61
 
62
- fetch(`/api/tasks/${draggedTaskId}/stage`, {
62
+ TaskBoard._fetch(`/api/tasks/${draggedTaskId}/stage`, {
63
63
  method: 'PUT',
64
64
  headers: { 'Content-Type': 'application/json' },
65
65
  body: JSON.stringify({ stage: targetStage }),
@@ -165,6 +165,7 @@
165
165
  <script src="panel.js"></script>
166
166
  <script src="drag.js"></script>
167
167
  <script src="inline-edit.js"></script>
168
+ <script src="template.js"></script>
168
169
  <script src="app.js"></script>
169
170
  </body>
170
171
  </html>
@@ -64,7 +64,7 @@ function dismissInlineCreate() {
64
64
 
65
65
  function createTaskInline(title, stage) {
66
66
  var showToast = TaskBoard.showToast;
67
- fetch('/api/tasks', {
67
+ TaskBoard._fetch('/api/tasks', {
68
68
  method: 'POST',
69
69
  headers: { 'Content-Type': 'application/json' },
70
70
  body: JSON.stringify({ title, stage, created_by: 'dashboard' }),
@@ -205,7 +205,7 @@ function dismissDropdownOnOutsideClick(e) {
205
205
 
206
206
  function updateTask(taskId, updates) {
207
207
  var showToast = TaskBoard.showToast;
208
- fetch(`/api/tasks/${taskId}`, {
208
+ TaskBoard._fetch(`/api/tasks/${taskId}`, {
209
209
  method: 'PUT',
210
210
  headers: { 'Content-Type': 'application/json' },
211
211
  body: JSON.stringify(updates),
package/dist/ui/panel.js CHANGED
@@ -294,13 +294,13 @@ function renderPanelContent(task) {
294
294
  panelBody.innerHTML = html + skeletonHTML;
295
295
 
296
296
  Promise.all([
297
- fetch(`/api/tasks/${task.id}/artifacts`)
297
+ TaskBoard._fetch(`/api/tasks/${task.id}/artifacts`)
298
298
  .then((r) => r.json())
299
299
  .catch(() => []),
300
- fetch(`/api/tasks/${task.id}/comments`)
300
+ TaskBoard._fetch(`/api/tasks/${task.id}/comments`)
301
301
  .then((r) => r.json())
302
302
  .catch(() => []),
303
- fetch(`/api/tasks/${task.id}/subtasks`)
303
+ TaskBoard._fetch(`/api/tasks/${task.id}/subtasks`)
304
304
  .then((r) => r.json())
305
305
  .catch(() => []),
306
306
  ]).then(([artifacts, comments, subtasks]) => {
@@ -379,7 +379,7 @@ function submitComment(taskId) {
379
379
  const content = input?.value?.trim();
380
380
  if (!content) return;
381
381
 
382
- fetch(`/api/tasks/${taskId}/comments`, {
382
+ TaskBoard._fetch(`/api/tasks/${taskId}/comments`, {
383
383
  method: 'POST',
384
384
  headers: { 'Content-Type': 'application/json' },
385
385
  body: JSON.stringify({ content, agent_id: 'dashboard' }),
@@ -0,0 +1,91 @@
1
+ // =============================================================================
2
+ // agent-tasks — Template Module
3
+ //
4
+ // HTML template for plugin mount mode. Extracted from index.html body content.
5
+ // =============================================================================
6
+
7
+ window.TaskBoard = window.TaskBoard || {};
8
+
9
+ TaskBoard._template = function () {
10
+ return (
11
+ '<header role="banner">' +
12
+ '<div class="header-left">' +
13
+ '<span class="material-symbols-outlined brand-icon">view_kanban</span>' +
14
+ '<h1>agent-tasks</h1>' +
15
+ '<span class="version" id="version"></span>' +
16
+ '<span id="connection-status" class="status-badge disconnected" role="status" aria-live="polite">Connecting</span>' +
17
+ '</div>' +
18
+ '<div class="header-right">' +
19
+ '<div class="stats" id="stats" aria-live="polite" aria-atomic="true"></div>' +
20
+ '<button id="cleanup-btn" class="icon-btn" title="Clean up" aria-label="Clean up old tasks">' +
21
+ '<span class="material-symbols-outlined">mop</span>' +
22
+ '</button>' +
23
+ '<button id="theme-toggle" class="icon-btn" title="Toggle theme" aria-label="Toggle theme">' +
24
+ '<span class="material-symbols-outlined theme-icon">dark_mode</span>' +
25
+ '</button>' +
26
+ '</div>' +
27
+ '</header>' +
28
+ '<div class="filter-bar" id="filter-bar" role="search" aria-label="Task filters">' +
29
+ '<div class="filter-group">' +
30
+ '<span class="material-symbols-outlined filter-icon" aria-hidden="true">filter_list</span>' +
31
+ '<input type="text" id="filter-search" class="filter-input" placeholder="Search tasks... (/ or Ctrl+K)" autocomplete="off" aria-label="Search tasks" />' +
32
+ '<select id="filter-project" class="filter-select" aria-label="Filter by project">' +
33
+ '<option value="">All projects</option>' +
34
+ '</select>' +
35
+ '<select id="filter-assignee" class="filter-select" aria-label="Filter by assignee">' +
36
+ '<option value="">All assignees</option>' +
37
+ '</select>' +
38
+ '<select id="filter-priority" class="filter-select" aria-label="Filter by minimum priority">' +
39
+ '<option value="">Any priority</option>' +
40
+ '<option value="1">P1+</option>' +
41
+ '<option value="3">P3+</option>' +
42
+ '<option value="5">P5+</option>' +
43
+ '<option value="10">P10+</option>' +
44
+ '</select>' +
45
+ '</div>' +
46
+ '<div class="filter-chips" id="filter-chips"></div>' +
47
+ '</div>' +
48
+ '<div class="board-wrapper" id="board-wrapper">' +
49
+ '<main id="board" class="kanban-board" role="region" aria-label="Task board"></main>' +
50
+ '<aside id="side-panel" class="side-panel" role="complementary" aria-label="Task details">' +
51
+ '<div class="panel-header" id="panel-header-content"></div>' +
52
+ '<div class="panel-body" id="panel-body"></div>' +
53
+ '</aside>' +
54
+ '</div>' +
55
+ '<div id="loading-overlay" class="loading-overlay" aria-label="Loading">' +
56
+ '<div class="loading-spinner"></div>' +
57
+ '<div class="loading-text">Connecting to agent-tasks...</div>' +
58
+ '</div>' +
59
+ '<div id="task-modal" class="modal-overlay" hidden role="dialog" aria-modal="true" aria-labelledby="modal-title">' +
60
+ '<div class="modal">' +
61
+ '<div class="modal-header">' +
62
+ '<h2 id="modal-title"></h2>' +
63
+ '<button class="icon-btn modal-close" id="modal-close-btn" aria-label="Close dialog">&times;</button>' +
64
+ '</div>' +
65
+ '<div id="modal-body" class="modal-body"></div>' +
66
+ '</div>' +
67
+ '</div>' +
68
+ '<div id="cleanup-modal" class="modal-overlay hidden" role="dialog" aria-modal="true">' +
69
+ '<div class="modal" style="max-width: 420px">' +
70
+ '<div class="modal-header">' +
71
+ '<h2><span class="material-symbols-outlined" style="font-size: 20px; vertical-align: middle; margin-right: 6px">mop</span>Clean Up</h2>' +
72
+ '<button class="icon-btn modal-close" id="cleanup-close-btn" aria-label="Close">&times;</button>' +
73
+ '</div>' +
74
+ '<div class="modal-body">' +
75
+ '<p style="color: var(--text-muted); margin-bottom: 12px">Remove old tasks and stale data:</p>' +
76
+ '<div class="cleanup-options">' +
77
+ '<button id="cleanup-completed" class="cleanup-option">' +
78
+ '<span class="material-symbols-outlined">auto_delete</span>' +
79
+ '<div><strong>Purge completed</strong><span>Remove all completed and cancelled tasks</span></div>' +
80
+ '</button>' +
81
+ '<button id="cleanup-everything" class="cleanup-option cleanup-option-danger">' +
82
+ '<span class="material-symbols-outlined">delete_forever</span>' +
83
+ '<div><strong>Purge everything</strong><span>Remove ALL tasks regardless of status</span></div>' +
84
+ '</button>' +
85
+ '</div>' +
86
+ '</div>' +
87
+ '</div>' +
88
+ '</div>' +
89
+ '<div id="toast-container" class="toast-container" aria-live="polite"></div>'
90
+ );
91
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-tasks",
3
- "version": "1.9.5",
3
+ "version": "1.9.7",
4
4
  "description": "Pipeline-driven task management for AI coding agents — stages, dependencies, artifacts, and multi-agent claiming",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",