From 62068d8335af3b12206a4a867f38c6bf6a0f2325 Mon Sep 17 00:00:00 2001 From: Claudomator Agent Date: Sun, 15 Mar 2026 01:35:13 +0000 Subject: feat: add task count badges to interrupted, ready, and running tabs - Add computeTabBadgeCounts(tasks) exported pure function - Add updateTabBadges(tasks) that updates badge spans in tab buttons - Call updateTabBadges on every poll regardless of active tab - Add .tab-count-badge spans to interrupted/ready/running tab buttons in HTML - Add CSS for .tab-count-badge pill styling (hidden when count is zero) - Add 11 tests in web/test/tab-badges.test.mjs covering all states Co-Authored-By: Claude Sonnet 4.6 --- web/app.js | 42 +++++++++++++++++ web/index.html | 6 +-- web/style.css | 19 ++++++++ web/test/tab-badges.test.mjs | 110 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 web/test/tab-badges.test.mjs (limited to 'web') diff --git a/web/app.js b/web/app.js index bca41fa..a7c18b6 100644 --- a/web/app.js +++ b/web/app.js @@ -339,6 +339,46 @@ export function setTaskFilterTab(tab) { localStorage.setItem('taskFilterTab', tab); } +// ── Tab badge counts ─────────────────────────────────────────────────────────── + +/** + * Computes badge counts for the 'interrupted', 'ready', and 'running' tabs. + * Returns { interrupted: N, ready: N, running: N }. + */ +export function computeTabBadgeCounts(tasks) { + let interrupted = 0; + let ready = 0; + let running = 0; + for (const t of tasks) { + if (INTERRUPTED_STATES.has(t.state)) interrupted++; + if (t.state === 'READY') ready++; + if (t.state === 'RUNNING') running++; + } + return { interrupted, ready, running }; +} + +/** + * Updates the badge count spans inside the tab buttons for + * 'interrupted', 'ready', and 'running'. + * Badge is hidden (display:none) when count is zero. + */ +export function updateTabBadges(tasks, doc = (typeof document !== 'undefined' ? document : null)) { + if (!doc) return; + const counts = computeTabBadgeCounts(tasks); + for (const [tab, count] of Object.entries(counts)) { + const btn = doc.querySelector(`.tab[data-tab="${tab}"]`); + if (!btn) continue; + let badge = btn.querySelector('.tab-count-badge'); + if (!badge) { + badge = doc.createElement('span'); + badge.className = 'tab-count-badge'; + btn.appendChild(badge); + } + badge.textContent = String(count); + badge.hidden = count === 0; + } +} + // ── Stats computations ───────────────────────────────────────────────────────── /** @@ -961,6 +1001,8 @@ async function poll() { const tasks = await fetchTasks(); if (isUserEditing()) return; + updateTabBadges(tasks); + const activeTab = getActiveTab(); switch (activeTab) { case 'queue': diff --git a/web/index.html b/web/index.html index 19cba2c..59bc56e 100644 --- a/web/index.html +++ b/web/index.html @@ -23,9 +23,9 @@