Billy Barrow před 1 měsícem
rodič
revize
5b49de0a35
3 změnil soubory, kde provedl 117 přidání a 6 odebrání
  1. 34 0
      src/pages/app.css
  2. 34 6
      src/pages/task_log.js
  3. 49 0
      test/test_task_log_js.py

+ 34 - 0
src/pages/app.css

@@ -989,6 +989,40 @@ dialog#quote-comment-dialog blockquote {
     border-color: var(--warning);
     color: var(--warning);
 }
+/* A non-live task (parked, blocked, done...) names its state: the
+   status badge palette carries the colour, the dot holds still. */
+.log-live.still .live-dot { animation: none; opacity: 0.85; }
+.log-live.still.badge-suspended {
+    background: #f3e8ff;
+    border-color: #8250df55;
+    color: var(--plan);
+}
+.log-live.still.badge-draft {
+    background: var(--line-soft);
+    border-color: var(--line);
+    color: var(--ink-muted);
+}
+.log-live.still.badge-queued {
+    background: #88843333;
+    border-color: #4d4d3355;
+    color: #4d4d33;
+}
+.log-live.still.badge-blocked,
+.log-live.still.badge-review_requested {
+    background: #fff8c5;
+    border-color: var(--warning);
+    color: var(--warning);
+}
+.log-live.still.badge-done {
+    background: #dafbe1;
+    border-color: var(--success);
+    color: var(--success);
+}
+.log-live.still.badge-failed {
+    background: #ffebe9;
+    border-color: var(--danger);
+    color: var(--danger);
+}
 .live-dot {
     animation: live-pulse 1.6s ease-in-out infinite;
     background: currentColor;

+ 34 - 6
src/pages/task_log.js

@@ -91,18 +91,44 @@
             var stream = null;
             var liveBadge = null;
             var liveLabel = null;
+            var taskStatus = '';
+            var streamState = 'live';
 
-            // The live pill: green pulse while the stream holds,
-            // amber while EventSource retries after a drop.
-            function setLive(state) {
+            // The badge reflects the TASK first: only a working (or
+            // assigned) task streams live events, so every other
+            // status names itself on the pill - still, no pulse,
+            // the task page's own display words. The stream's
+            // health (reconnecting...) only rides a live task.
+            var LIVE_STATUSES = { working: 1, assigned: 1 };
+            var BADGE_LABELS = { suspended: 'parked',
+                review_requested: 'review requested' };
+
+            function updateBadge() {
                 if (!liveBadge) return;
-                liveBadge.className = 'log-live' + (state === 'retry' ? ' retry' : '');
+                var cls = 'log-live';
+                var label;
+                if (LIVE_STATUSES[taskStatus]) {
+                    if (streamState === 'retry') {
+                        cls += ' retry';
+                        label = 'reconnecting...';
+                    } else {
+                        label = 'live';
+                    }
+                } else {
+                    cls += ' still badge badge-' + taskStatus;
+                    label = BADGE_LABELS[taskStatus] || taskStatus || 'idle';
+                }
+                liveBadge.className = cls;
                 if (liveLabel) {
-                    liveLabel.textContent = state === 'retry'
-                        ? 'reconnecting...' : 'live';
+                    liveLabel.textContent = label;
                 }
             }
 
+            function setLive(state) {
+                streamState = state;
+                updateBadge();
+            }
+
             function kindClass(kind) {
                 return 'event-kind-' + String(kind || '').replace(/[^a-z0-9-]/g, '');
             }
@@ -1383,6 +1409,8 @@
                 moreButton = document.querySelector('[data-log-more]');
                 liveBadge = document.querySelector('[data-log-live]');
                 liveLabel = document.querySelector('[data-live-label]');
+                taskStatus = strText(page.task_status);
+                updateBadge();
                 var header = document.querySelector('[data-log-title]');
                 if (header) {
                     header.textContent = '#' + page.task_id + ' \u2014 '

+ 49 - 0
test/test_task_log_js.py

@@ -885,6 +885,23 @@ def attach_pairs(rows, folded):
     return calls_by_id, rides_by_id
 
 
+# --- The state badge: only a working (or assigned) task streams
+# live; every other status names itself on the pill, still. ---
+
+LIVE_STATUSES = {"working", "assigned"}
+BADGE_LABELS = {"suspended": "parked", "review_requested": "review requested"}
+
+
+def badge_line(status, stream_state="live"):
+    """PORT of updateBadge -> (class, label)."""
+    if status in LIVE_STATUSES:
+        if stream_state == "retry":
+            return "log-live retry", "reconnecting..."
+        return "log-live", "live"
+    label = BADGE_LABELS.get(status, status) or "idle"
+    return "log-live still badge badge-" + status, label
+
+
 DIFF_CONTEXT = 2
 DIFF_MAX_LINES = 400
 
@@ -1452,6 +1469,38 @@ class ResultAttachmentTests(unittest.TestCase):
         self.assertEqual((calls_by_id, rides_by_id), ({}, {}))
 
 
+class BadgeLineTests(unittest.TestCase):
+    """The badge reflects the TASK first: live only while a working
+    (or assigned) task streams; every other status names itself."""
+
+    def test_working_is_live(self):
+        self.assertEqual(badge_line("working"), ("log-live", "live"))
+
+    def test_assigned_is_live(self):
+        self.assertEqual(badge_line("assigned"), ("log-live", "live"))
+
+    def test_retry_only_rides_a_live_task(self):
+        self.assertEqual(badge_line("working", "retry"),
+                         ("log-live retry", "reconnecting..."))
+        self.assertEqual(badge_line("failed", "retry")[1], "failed")
+
+    def test_suspended_shows_as_parked(self):
+        cls, label = badge_line("suspended")
+        self.assertEqual(label, "parked")
+        self.assertIn("still badge badge-suspended", cls)
+
+    def test_review_requested_uses_the_task_page_word(self):
+        self.assertEqual(badge_line("review_requested")[1],
+                         "review requested")
+
+    def test_terminal_states_name_themselves(self):
+        self.assertEqual(badge_line("done")[1], "done")
+        self.assertEqual(badge_line("failed")[1], "failed")
+
+    def test_unknown_status_falls_back_to_idle(self):
+        self.assertEqual(badge_line("")[1], "idle")
+
+
 class ToolResultRenderTests(unittest.TestCase):
 
     def evt(self, payload):