[object Object]

← back to Ticket System

Add PARKED tile to desktop ticket bar, mirroring the board's parked-lane peel-out

0c0dc768a52e0ff59a2cc5d9bf47e79ee3b2ad10 · 2026-09-22 12:22:50 -0700 · Steve Abrams

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 0c0dc768a52e0ff59a2cc5d9bf47e79ee3b2ad10
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Sep 22 12:22:50 2026 -0700

    Add PARKED tile to desktop ticket bar, mirroring the board's parked-lane peel-out
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 desktop-bar/TicketBar.swift | 28 +++++++++++++++++++++-------
 1 file changed, 21 insertions(+), 7 deletions(-)

diff --git a/desktop-bar/TicketBar.swift b/desktop-bar/TicketBar.swift
index 7d645888..102d02e8 100644
--- a/desktop-bar/TicketBar.swift
+++ b/desktop-bar/TicketBar.swift
@@ -72,6 +72,7 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
   private let openLabel = NSTextField(labelWithString: "OPEN …")
   private let blockedLabel = NSTextField(labelWithString: "BLOCKED …")
   private let doingLabel = NSTextField(labelWithString: "DOING …")
+  private let parkedLabel = NSTextField(labelWithString: "PARKED …")
   private let working = NSTextField(labelWithString: "Working · —")
   private let latest = NSTextField(labelWithString: "Latest · —")
   private let cpuLabel = NSTextField(labelWithString: "CPU · —")
@@ -143,7 +144,7 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
 
     isNight = UserDefaults.standard.object(forKey: "ticketBarNight") as? Bool ?? true
     barPosition = UserDefaults.standard.string(forKey: "ticketBarPosition") ?? "top"
-    let countLabels = [openLabel, blockedLabel, doingLabel]
+    let countLabels = [openLabel, blockedLabel, doingLabel, parkedLabel]
     for label in countLabels + [working, latest] {
       label.font = .systemFont(ofSize: 12, weight: countLabels.contains(label) ? .bold : .medium)
       label.lineBreakMode = .byTruncatingTail
@@ -156,6 +157,7 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
     // dropdown listing that status's tickets. Working + Latest unchanged.
     let definitions: [(String, NSTextField, CGFloat)] = [
       ("open", openLabel, 150), ("blocked", blockedLabel, 175), ("doing", doingLabel, 150),
+      ("parked", parkedLabel, 150),
       ("working", working, 560), ("latest", latest, 520)
     ]
     let savedOrder = UserDefaults.standard.stringArray(forKey: "ticketBarOrder") ?? definitions.map(\.0)
@@ -377,6 +379,8 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
                                     : NSColor(calibratedRed: 0.74, green: 0.18, blue: 0.22, alpha: 1)
     doingLabel.textColor   = night ? NSColor(calibratedRed: 0.43, green: 0.70, blue: 0.95, alpha: 1)
                                     : NSColor(calibratedRed: 0.16, green: 0.44, blue: 0.72, alpha: 1)
+    parkedLabel.textColor  = night ? NSColor(calibratedRed: 1.00, green: 0.62, blue: 0.80, alpha: 1)
+                                    : NSColor(calibratedRed: 0.82, green: 0.14, blue: 0.50, alpha: 1)
     working.textColor      = night ? NSColor(calibratedRed: 0.45, green: 0.78, blue: 1.00, alpha: 1)
                                     : NSColor(calibratedRed: 0.10, green: 0.45, blue: 0.82, alpha: 1)
     latest.textColor       = night ? NSColor(calibratedWhite: 0.72, alpha: 1)
@@ -500,7 +504,7 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
     // greyed-out/disabled — and each row is clickable (jumps to that ticket).
     menu.autoenablesItems = false
     let details: [String]
-    if key == "open" || key == "blocked" || key == "doing" {
+    if key == "open" || key == "blocked" || key == "doing" || key == "parked" {
       let list = statusDetails[key] ?? []
       details = list.isEmpty ? ["No \(key) tickets"] : list
     } else if key == "working" {
@@ -1056,6 +1060,7 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
   private struct BoardTicket {
     let id: String, title: String, status: String, updated: String, created: String
     let rank: Int?, priority: Double
+    let parked: Bool
   }
 
   // One GET, no semaphore: the completion already runs off-main; callers hop to main for UI.
@@ -1070,7 +1075,8 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
         guard let id = t["id"] as? String, id.hasPrefix("TK-") else { return nil }
         return BoardTicket(id: id, title: t["title"] as? String ?? id, status: t["status"] as? String ?? "open",
                            updated: t["updated_at"] as? String ?? "", created: t["created_at"] as? String ?? "",
-                           rank: t["rank"] as? Int, priority: (t["priority"] as? NSNumber)?.doubleValue ?? 0)
+                           rank: t["rank"] as? Int, priority: (t["priority"] as? NSNumber)?.doubleValue ?? 0,
+                           parked: t["parked"] as? Bool ?? false)
       })
     }.resume()
   }
@@ -1140,6 +1146,7 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
             self.openLabel.stringValue    = "OPEN —"
             self.blockedLabel.stringValue = "BLOCKED —"
             self.doingLabel.stringValue   = "DOING —"
+            self.parkedLabel.stringValue  = "PARKED —"
             self.working.stringValue = "Working · board unreachable"
             self.statusDetails = [:]
             self.workingDetails = []
@@ -1153,9 +1160,15 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
         if ra != rb { return ra < rb }
         return a.updated > b.updated
       }
-      let openList = tickets.filter { $0.status == "open" }.sorted(by: byPrio)
-      let blockedList = tickets.filter { $0.status == "blocked" }.sorted(by: byPrio)
-      let doing = tickets.filter { $0.status == "doing" }.sorted(by: byPrio)
+      // Mirror the board's peel-out exactly (server.js ~487): an active parked ticket
+      // leaves its status bucket and joins the PARKED lane so it isn't double-counted.
+      func bucket(_ t: BoardTicket) -> String {
+        (t.parked && ["open", "blocked", "doing"].contains(t.status)) ? "parked" : t.status
+      }
+      let openList = tickets.filter { bucket($0) == "open" }.sorted(by: byPrio)
+      let blockedList = tickets.filter { bucket($0) == "blocked" }.sorted(by: byPrio)
+      let doing = tickets.filter { bucket($0) == "doing" }.sorted(by: byPrio)
+      let parkedList = tickets.filter { bucket($0) == "parked" }.sorted(by: byPrio)
       let workText = doing.prefix(3).map { "\(self.shortID($0.id)) \(self.fiveWords($0.title))" }.joined(separator: "  •  ")
       // Row: TK-xxxx  #rank  p<priority>  · title   🕓 created date/time
       let row: (BoardTicket) -> String = { t in
@@ -1171,7 +1184,8 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
         self.openLabel.stringValue    = "OPEN \(openList.count)"
         self.blockedLabel.stringValue = "BLOCKED \(blockedList.count)"
         self.doingLabel.stringValue   = "DOING \(doing.count)"
-        self.statusDetails = ["open": openList.map(row), "blocked": blockedList.map(row), "doing": doing.map(row)]
+        self.parkedLabel.stringValue  = "PARKED \(parkedList.count)"
+        self.statusDetails = ["open": openList.map(row), "blocked": blockedList.map(row), "doing": doing.map(row), "parked": parkedList.map(row)]
         self.working.stringValue = "Working · \(workText.isEmpty ? "none" : workText)"
         self.workingDetails = doing.map(row)
         self.panel.orderFrontRegardless()

← 802dbd64 auto-data-snapshot: 2026-09-22T12:15:47 (1 data files) — dat  ·  back to Ticket System  ·  ticketbar: add one-click Compact/Full toggle button (parity 27c33873 →