← back to Ticket System
Ticket desktop bar: show priority rank/score + created date/time on dropdown rows
59e3c395ba2c0845172f5947e11314748e39c12c · 2026-09-03 10:34:25 -0700 · Steve Abrams
Each status dropdown row now renders 'TK-xxxx #rank p<priority> · title 🕓 <created
date/time>', sorted highest-priority-first. Priority/rank come best-effort from the
viewer's /api/tickets (server-computed, mirrors the board); created date/time is the
create-event ts formatted 'MMM d, h:mm a' (admin-chip standing rule). If the viewer is
unreachable, counts + date/time still render and priority drops off (never blank).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015XNcHvXXepEQar7Qsb7GGy
Files touched
M desktop-bar/TicketBar.swift
Diff
commit 59e3c395ba2c0845172f5947e11314748e39c12c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Sep 3 10:34:25 2026 -0700
Ticket desktop bar: show priority rank/score + created date/time on dropdown rows
Each status dropdown row now renders 'TK-xxxx #rank p<priority> · title 🕓 <created
date/time>', sorted highest-priority-first. Priority/rank come best-effort from the
viewer's /api/tickets (server-computed, mirrors the board); created date/time is the
create-event ts formatted 'MMM d, h:mm a' (admin-chip standing rule). If the viewer is
unreachable, counts + date/time still render and priority drops off (never blank).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015XNcHvXXepEQar7Qsb7GGy
---
desktop-bar/TicketBar.swift | 62 +++++++++++++++++++++++++++++++++++++++++----
1 file changed, 57 insertions(+), 5 deletions(-)
diff --git a/desktop-bar/TicketBar.swift b/desktop-bar/TicketBar.swift
index 097a0259..d84402a0 100644
--- a/desktop-bar/TicketBar.swift
+++ b/desktop-bar/TicketBar.swift
@@ -10,6 +10,7 @@ private struct TicketState {
var title = ""
var status = "open"
var updated = ""
+ var created = ""
}
private final class BarTile: NSButton {
@@ -444,6 +445,41 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
} else { completionHandler(.performDefaultHandling, nil) }
}
+ // ── priority + created-time enrichment ──────────────────────────────────────
+ private let isoParser: ISO8601DateFormatter = {
+ let f = ISO8601DateFormatter(); f.formatOptions = [.withInternetDateTime, .withFractionalSeconds]; return f
+ }()
+ private let isoParserPlain = ISO8601DateFormatter()
+ private let stampFmt: DateFormatter = {
+ let f = DateFormatter(); f.dateFormat = "MMM d, h:mm a"; return f
+ }()
+
+ private func stamp(_ iso: String) -> String {
+ guard !iso.isEmpty else { return "" }
+ guard let d = isoParser.date(from: iso) ?? isoParserPlain.date(from: iso) else { return "" }
+ return "🕓 " + stampFmt.string(from: d)
+ }
+
+ // Best-effort pull of server-computed rank/priority from the viewer API.
+ // Empty (no priority shown) if the viewer is unreachable — counts still work.
+ private func fetchPriorities() -> [String: (rank: Int?, priority: Double)] {
+ guard let url = URL(string: "http://127.0.0.1:9794/api/tickets") else { return [:] }
+ var req = URLRequest(url: url, timeoutInterval: 2.5)
+ req.setValue("Basic " + Data("admin:DW2024!".utf8).base64EncodedString(), forHTTPHeaderField: "Authorization")
+ var result: [String: (Int?, Double)] = [:]
+ let sem = DispatchSemaphore(value: 0)
+ URLSession.shared.dataTask(with: req) { data, _, _ in
+ defer { sem.signal() }
+ guard let data, let arr = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] else { return }
+ for t in arr {
+ guard let id = t["id"] as? String else { continue }
+ result[id] = (t["rank"] as? Int, (t["priority"] as? NSNumber)?.doubleValue ?? 0)
+ }
+ }.resume()
+ _ = sem.wait(timeout: .now() + 3)
+ return result
+ }
+
private func refresh() {
DispatchQueue.global(qos: .utility).async { [weak self] in
guard let self else { return }
@@ -458,7 +494,7 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
let type = event["type"] as? String ?? ""
let timestamp = event["ts"] as? String ?? ""
if type == "create" {
- states[id] = TicketState(id: id, title: event["title"] as? String ?? id, status: "open", updated: timestamp)
+ states[id] = TicketState(id: id, title: event["title"] as? String ?? id, status: "open", updated: timestamp, created: timestamp)
} else if var ticket = states[id] {
if type == "status", let status = event["status"] as? String { ticket.status = status }
ticket.updated = timestamp
@@ -467,12 +503,28 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
if type == "action" || type == "comment" || type == "status" { newest = event }
}
let active = states.values.filter { $0.status != "done" && $0.status != "stopped" }
- let openList = active.filter { $0.status == "open" }.sorted { $0.updated > $1.updated }
- let blockedList = active.filter { $0.status == "blocked" }.sorted { $0.updated > $1.updated }
- let doing = active.filter { $0.status == "doing" }.sorted { $0.updated > $1.updated }
+ let prios = self.fetchPriorities() // best-effort; empty if viewer down
+ // Highest priority first (lowest rank #); un-ranked fall to the bottom, newest first.
+ let byPrio: (TicketState, TicketState) -> Bool = { a, b in
+ let ra = prios[a.id]?.rank ?? Int.max, rb = prios[b.id]?.rank ?? Int.max
+ if ra != rb { return ra < rb }
+ return a.updated > b.updated
+ }
+ let openList = active.filter { $0.status == "open" }.sorted(by: byPrio)
+ let blockedList = active.filter { $0.status == "blocked" }.sorted(by: byPrio)
+ let doing = active.filter { $0.status == "doing" }.sorted(by: byPrio)
let workText = doing.prefix(3).map { "\(self.shortID($0.id)) \(self.fiveWords($0.title))" }.joined(separator: " • ")
let latestText = self.describe(newest)
- let row: (TicketState) -> String = { "\(self.shortID($0.id)) · \($0.title)" }
+ // Row: TK-xxxx #rank p<priority> · title 🕓 created date/time
+ let row: (TicketState) -> String = { t in
+ var prefix = self.shortID(t.id)
+ if let p = prios[t.id] {
+ if let r = p.rank { prefix += " #\(r)" }
+ if p.priority > 0 { prefix += String(format: " p%.1f", p.priority) }
+ }
+ let when = self.stamp(t.created)
+ return when.isEmpty ? "\(prefix) · \(t.title)" : "\(prefix) · \(t.title) \(when)"
+ }
DispatchQueue.main.async {
self.openLabel.stringValue = "OPEN \(openList.count)"
self.blockedLabel.stringValue = "BLOCKED \(blockedList.count)"
← 01ba92d4 record TK-11183 bounded monitoring proof
·
back to Ticket System
·
Ticket desktop bar: dock-position toggle — top/left/right/of 3c6638a4 →