hzl-web 1.19.1 → 1.20.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 (2) hide show
  1. package/dist/ui/index.html +203 -28
  2. package/package.json +2 -2
@@ -531,6 +531,18 @@
531
531
  font-size: 12px;
532
532
  }
533
533
 
534
+ .modal-progress {
535
+ color: var(--accent);
536
+ background: rgba(245, 158, 11, 0.15);
537
+ padding: 2px 8px;
538
+ border-radius: 4px;
539
+ }
540
+
541
+ .modal-progress.complete {
542
+ color: var(--status-done);
543
+ background: rgba(34, 197, 94, 0.15);
544
+ }
545
+
534
546
  .modal-description {
535
547
  background: var(--bg-primary);
536
548
  padding: 12px;
@@ -571,6 +583,73 @@
571
583
  white-space: pre-wrap;
572
584
  }
573
585
 
586
+ .show-more-btn {
587
+ width: 100%;
588
+ padding: 8px;
589
+ background: var(--bg-primary);
590
+ border: 1px dashed var(--border);
591
+ border-radius: 4px;
592
+ color: var(--text-secondary);
593
+ font-family: var(--font-mono);
594
+ font-size: 12px;
595
+ cursor: pointer;
596
+ transition: border-color 0.15s, color 0.15s;
597
+ }
598
+
599
+ .show-more-btn:hover {
600
+ border-color: var(--accent);
601
+ color: var(--accent);
602
+ }
603
+
604
+ .modal-tabs {
605
+ display: flex;
606
+ gap: 0;
607
+ border-bottom: 1px solid var(--border);
608
+ margin-bottom: 12px;
609
+ }
610
+
611
+ .modal-tab {
612
+ padding: 8px 16px;
613
+ background: none;
614
+ border: none;
615
+ color: var(--text-muted);
616
+ font-family: var(--font-mono);
617
+ font-size: 12px;
618
+ cursor: pointer;
619
+ border-bottom: 2px solid transparent;
620
+ margin-bottom: -1px;
621
+ }
622
+
623
+ .modal-tab:hover {
624
+ color: var(--text-primary);
625
+ }
626
+
627
+ .modal-tab.active {
628
+ color: var(--accent);
629
+ border-bottom-color: var(--accent);
630
+ }
631
+
632
+ .modal-tab:disabled {
633
+ opacity: 0.4;
634
+ cursor: not-allowed;
635
+ }
636
+
637
+ .modal-tab-count {
638
+ background: var(--bg-primary);
639
+ padding: 1px 6px;
640
+ border-radius: 8px;
641
+ font-size: 10px;
642
+ margin-left: 6px;
643
+ }
644
+
645
+ .modal-tab-content {
646
+ display: none;
647
+ }
648
+
649
+ .modal-tab-content.active {
650
+ display: block;
651
+ }
652
+
574
653
  /* Activity Panel */
575
654
  .activity-panel {
576
655
  position: fixed;
@@ -1003,6 +1082,11 @@
1003
1082
  let pollTimer = null;
1004
1083
  let lastPollTime = null;
1005
1084
  let selectedTask = null;
1085
+ let showAllComments = false;
1086
+ let showAllCheckpoints = false;
1087
+ let activeModalTab = 'comments';
1088
+ const COMMENT_DISPLAY_LIMIT = 15;
1089
+ const CHECKPOINT_DISPLAY_LIMIT = 15;
1006
1090
  let activeTab = 'ready';
1007
1091
  let activeView = 'kanban';
1008
1092
  let graphInstance = null;
@@ -1645,10 +1729,17 @@
1645
1729
  }).join('');
1646
1730
  }
1647
1731
 
1648
- async function openTaskModal(taskId) {
1732
+ async function openTaskModal(taskId, preserveShowAll = false) {
1649
1733
  // Track this request to handle rapid clicks (race condition prevention)
1650
1734
  const requestId = ++pendingModalRequestId;
1651
1735
 
1736
+ // Reset expansion state for new tasks
1737
+ if (!preserveShowAll) {
1738
+ showAllComments = false;
1739
+ showAllCheckpoints = false;
1740
+ activeModalTab = 'comments';
1741
+ }
1742
+
1652
1743
  try {
1653
1744
  const data = await fetchTaskDetail(taskId);
1654
1745
 
@@ -1658,6 +1749,9 @@
1658
1749
  selectedTask = data;
1659
1750
  modalTitle.textContent = data.task.title;
1660
1751
 
1752
+ const progressValue = data.task.progress ?? 0;
1753
+ const progressClass = progressValue >= 100 ? 'modal-progress complete' : 'modal-progress';
1754
+
1661
1755
  let html = `
1662
1756
  <div class="modal-section">
1663
1757
  <div class="modal-meta">
@@ -1665,6 +1759,10 @@
1665
1759
  <div class="modal-meta-label">Status</div>
1666
1760
  <div class="modal-meta-value">${data.task.status}</div>
1667
1761
  </div>
1762
+ <div class="modal-meta-item">
1763
+ <div class="modal-meta-label">Progress</div>
1764
+ <div class="modal-meta-value"><span class="${progressClass}">${progressValue}%</span></div>
1765
+ </div>
1668
1766
  <div class="modal-meta-item">
1669
1767
  <div class="modal-meta-label">Project</div>
1670
1768
  <div class="modal-meta-value">${escapeHtml(data.task.project)}</div>
@@ -1719,39 +1817,79 @@
1719
1817
  `;
1720
1818
  }
1721
1819
 
1722
- if (data.comments.length > 0) {
1820
+ // Tabbed interface for comments and checkpoints
1821
+ if (data.comments.length > 0 || data.checkpoints.length > 0) {
1822
+ // Default to checkpoints tab if no comments
1823
+ if (data.comments.length === 0 && activeModalTab === 'comments') {
1824
+ activeModalTab = 'checkpoints';
1825
+ }
1826
+
1827
+ const hasMoreComments = data.comments.length > COMMENT_DISPLAY_LIMIT && !showAllComments;
1828
+ const visibleComments = hasMoreComments
1829
+ ? data.comments.slice(-COMMENT_DISPLAY_LIMIT)
1830
+ : data.comments;
1831
+ const hiddenCommentCount = Math.max(0, data.comments.length - COMMENT_DISPLAY_LIMIT);
1832
+
1833
+ const hasMoreCheckpoints = data.checkpoints.length > CHECKPOINT_DISPLAY_LIMIT && !showAllCheckpoints;
1834
+ const visibleCheckpoints = hasMoreCheckpoints
1835
+ ? data.checkpoints.slice(-CHECKPOINT_DISPLAY_LIMIT)
1836
+ : data.checkpoints;
1837
+ const hiddenCheckpointCount = Math.max(0, data.checkpoints.length - CHECKPOINT_DISPLAY_LIMIT);
1838
+
1723
1839
  html += `
1724
1840
  <div class="modal-section">
1725
- <div class="modal-section-title">Comments (${data.comments.length})</div>
1726
- <div class="modal-comments">
1727
- ${data.comments.map(c => `
1728
- <div class="comment">
1729
- <div class="comment-header">
1730
- <span class="comment-author">${escapeHtml(c.agent_id || c.author || 'Unknown')}</span>
1731
- <span>${formatTime(c.timestamp)}</span>
1732
- </div>
1733
- <div class="comment-text">${escapeHtml(c.text)}</div>
1841
+ <div class="modal-tabs">
1842
+ <button class="modal-tab ${activeModalTab === 'comments' ? 'active' : ''}" data-tab="comments" ${data.comments.length === 0 ? 'disabled' : ''}>
1843
+ Comments<span class="modal-tab-count">${data.comments.length}</span>
1844
+ </button>
1845
+ <button class="modal-tab ${activeModalTab === 'checkpoints' ? 'active' : ''}" data-tab="checkpoints" ${data.checkpoints.length === 0 ? 'disabled' : ''}>
1846
+ Checkpoints<span class="modal-tab-count">${data.checkpoints.length}</span>
1847
+ </button>
1848
+ </div>
1849
+
1850
+ <div class="modal-tab-content ${activeModalTab === 'comments' ? 'active' : ''}" data-tab-content="comments">
1851
+ ${data.comments.length === 0 ? '<div class="empty-column">No comments</div>' : `
1852
+ <div class="modal-comments">
1853
+ ${hasMoreComments ? `
1854
+ <button class="show-more-btn" id="showMoreComments">
1855
+ Show ${hiddenCommentCount} earlier comment${hiddenCommentCount === 1 ? '' : 's'}
1856
+ </button>
1857
+ ` : ''}
1858
+ ${visibleComments.map(c => `
1859
+ <div class="comment">
1860
+ <div class="comment-header">
1861
+ <span class="comment-author">${escapeHtml(c.agent_id || c.author || 'Unknown')}</span>
1862
+ <span>${formatTime(c.timestamp)}</span>
1863
+ </div>
1864
+ <div class="comment-text">${escapeHtml(c.text)}</div>
1865
+ </div>
1866
+ `).join('')}
1734
1867
  </div>
1735
- `).join('')}
1868
+ `}
1736
1869
  </div>
1737
- </div>
1738
- `;
1739
- }
1740
1870
 
1741
- if (data.checkpoints.length > 0) {
1742
- html += `
1743
- <div class="modal-section">
1744
- <div class="modal-section-title">Checkpoints (${data.checkpoints.length})</div>
1745
- <div class="modal-comments">
1746
- ${data.checkpoints.map(cp => `
1747
- <div class="comment">
1748
- <div class="comment-header">
1749
- <span class="comment-author">${escapeHtml(cp.name)}</span>
1750
- <span>${formatTime(cp.timestamp)}</span>
1751
- </div>
1752
- <div class="comment-text">${JSON.stringify(cp.data, null, 2)}</div>
1871
+ <div class="modal-tab-content ${activeModalTab === 'checkpoints' ? 'active' : ''}" data-tab-content="checkpoints">
1872
+ ${data.checkpoints.length === 0 ? '<div class="empty-column">No checkpoints</div>' : `
1873
+ <div class="modal-comments">
1874
+ ${hasMoreCheckpoints ? `
1875
+ <button class="show-more-btn" id="showMoreCheckpoints">
1876
+ Show ${hiddenCheckpointCount} earlier checkpoint${hiddenCheckpointCount === 1 ? '' : 's'}
1877
+ </button>
1878
+ ` : ''}
1879
+ ${visibleCheckpoints.map(cp => {
1880
+ const hasData = cp.data && Object.keys(cp.data).length > 0;
1881
+ return `
1882
+ <div class="comment">
1883
+ <div class="comment-header">
1884
+ <span class="comment-author">${escapeHtml(cp.name)}</span>
1885
+ <span>${formatTime(cp.timestamp)}</span>
1886
+ </div>
1887
+ ${hasData ? `<div class="comment-text">${escapeHtml(JSON.stringify(cp.data, null, 2))}</div>` : ''}
1888
+ </div>
1889
+ `;
1890
+ }).join('')}
1753
1891
  </div>
1754
- `).join('')}
1892
+ `}
1755
1893
  </div>
1756
1894
  </div>
1757
1895
  `;
@@ -1759,6 +1897,43 @@
1759
1897
 
1760
1898
  modalBody.innerHTML = html;
1761
1899
  modalOverlay.classList.add('open');
1900
+
1901
+ // Attach tab switching handlers (DOM-only, no re-fetch)
1902
+ modalBody.querySelectorAll('.modal-tab').forEach(tab => {
1903
+ tab.addEventListener('click', () => {
1904
+ if (tab.hasAttribute('disabled')) return;
1905
+ const targetTab = tab.dataset.tab;
1906
+
1907
+ // Update tab active states
1908
+ modalBody.querySelectorAll('.modal-tab').forEach(t =>
1909
+ t.classList.toggle('active', t.dataset.tab === targetTab));
1910
+
1911
+ // Update content visibility
1912
+ modalBody.querySelectorAll('.modal-tab-content').forEach(c =>
1913
+ c.classList.toggle('active', c.dataset.tabContent === targetTab));
1914
+
1915
+ // Update global state for re-opens
1916
+ activeModalTab = targetTab;
1917
+ });
1918
+ });
1919
+
1920
+ // Attach "show more comments" handler if present
1921
+ const showMoreCommentsBtn = document.getElementById('showMoreComments');
1922
+ if (showMoreCommentsBtn) {
1923
+ showMoreCommentsBtn.addEventListener('click', () => {
1924
+ showAllComments = true;
1925
+ openTaskModal(taskId, true);
1926
+ });
1927
+ }
1928
+
1929
+ // Attach "show more checkpoints" handler if present
1930
+ const showMoreCheckpointsBtn = document.getElementById('showMoreCheckpoints');
1931
+ if (showMoreCheckpointsBtn) {
1932
+ showMoreCheckpointsBtn.addEventListener('click', () => {
1933
+ showAllCheckpoints = true;
1934
+ openTaskModal(taskId, true);
1935
+ });
1936
+ }
1762
1937
  } catch (error) {
1763
1938
  console.error('Failed to load task:', error);
1764
1939
  alert('Failed to load task details');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hzl-web",
3
- "version": "1.19.1",
3
+ "version": "1.20.0",
4
4
  "description": "Web dashboard for HZL - A Kanban-style task monitoring UI.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -52,7 +52,7 @@
52
52
  "visualization"
53
53
  ],
54
54
  "dependencies": {
55
- "hzl-core": "1.19.1"
55
+ "hzl-core": "1.20.0"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@types/node": "^20.10.0",