[object Object]

← back to Ticket System

desktop-bar: add ◉ terminal-dots dropdown (multi-select + focus window; backed by /allcolordots)

02eb728ba9959ce2b6ff4d654cb745aaa660b1f3 · 2026-09-03 12:26:26 -0700 · Steve

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

Files touched

Diff

commit 02eb728ba9959ce2b6ff4d654cb745aaa660b1f3
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Sep 3 12:26:26 2026 -0700

    desktop-bar: add ◉ terminal-dots dropdown (multi-select + focus window; backed by /allcolordots)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 data/codex-yoloforever/ledger.jsonl |   2 +
 desktop-bar/TicketBar.swift         | 138 +++++++++++++++++++++++++++++++++++-
 2 files changed, 139 insertions(+), 1 deletion(-)

diff --git a/data/codex-yoloforever/ledger.jsonl b/data/codex-yoloforever/ledger.jsonl
index 2d5f21eb..a55745e7 100644
--- a/data/codex-yoloforever/ledger.jsonl
+++ b/data/codex-yoloforever/ledger.jsonl
@@ -62,3 +62,5 @@
 {"timestamp":"2026-09-03T17:50:00Z","ticket":"TK-11184-codex-yoloforever-bounded-monitoring-cyc","action":"Fresh board and canonical-owner audit found no safe reversible ungated non-colliding pickup. Preserved TK-11155 active ownership, rejected its misleading live-owned execute row, and preserved TK-10993 dirty-worktree plus C/D/E gates. No underlying project or external mutation.","dtd_verdict":"A=read-only canonical owner/backlog audit, 2/2 valid local voters (Codex+Qwen), mandatory Codex debate FINAL KEEP","cody_verdict":"SHIP IT 4/5; top fix=refresh next-cycle snapshot and reject every live-owned execute row","final_dtd_verdict":"SHIP 2/2 valid local voters (Codex+Qwen), mandatory Codex debate FINAL KEEP; monitoring record only","tests_evidence":["data/codex-yoloforever/evidence/20260903-1050-TK11184/e2e-proof.json","snapshot SHA256 aea778ce841217c55c155fafeebaa5549e116008dccb4750a0560c742af37eea","ticketmaster urgency 18/18 PASS","live-owned execute rejection PASS","TK-10993 dirty collision and gates PASS","zero-cost guard exact PASS"],"next_seed":"Fresh inbox and ticketmaster snapshot next launchd cycle; reject live-owned execute rows and do not reuse this point-in-time eligibility conclusion.","gated_memos":[],"cost":"$0"}
 {"timestamp":"2026-09-03T18:13:34.521Z","ticket":"TK-10928-cron-issue-com-steve-dw-backup-canary","action":"Fresh board had zero safe pickups; preserved live ownership of TK-11155 and performed one owner-routed read-only TK-10928 canary/scheduler/disk recheck. Cody reproduced transient evidence and the exact artifact/scheduler/report were persisted in a checksummed durable bundle. No target, service, policy, history, threshold, external, or production mutation.","dtd_verdict":"A=read-only canary/scheduler/disk recheck via vp-operations, 2/2 valid local voters (Codex+Qwen); post-decision phase inconclusive, preliminary retained at reduced confidence","cody_verdict":"FIX FIRST 3/5; top fix=persist exact artifact, scheduler state, compact report, baseline and checksums","final_dtd_verdict":"SHIP 2/2 valid local voters (Codex+Qwen); mandatory Codex debate FINAL KEEP; bounded evidence increment only","tests_evidence":["data/codex-yoloforever/evidence/TK-10928-20260903T1806Z/e2e-proof.json","SHA256SUMS 5/5 PASS","artifact semantics FAIL=2 WARN=10 PASS","scheduler runs=18 exit=1 PASS","wrong-hash rejection PASS","A2A correlation PASS","zero-cost guard exact PASS"],"next_seed":"Fresh inbox and ticketmaster snapshot next launchd cycle; after next 04:00 scheduler run correlate exit and exact artifact identity read-only; keep TK-10928 remediation gated/open.","gated_memos":["pending-approval/TK-10928-history-bloat-decision.md"],"cost":"$0"}
 {"timestamp":"2026-09-03T18:25:52Z","ticket":"TK-11188-codex-yoloforever-bounded-monitoring-cyc","action":"Fresh board and canonical-owner audit found no safe reversible ungated non-colliding unowned pickup. Preserved TK-11155 active ownership and all TK-10993 C/D/E rails. Produced only frozen local evidence and monitoring records; no target or external mutation.","dtd_verdict":"A=bounded read-only eligibility/ownership/collision audit, 2/2 valid local voters (Codex+Qwen), mandatory Codex debate FINAL KEEP","cody_verdict":"SHIP IT 3/5 for monitoring HOLD only; top fix=full-board enumeration next cycle; reproduced top-25-of-65 limitation and live-owned execute conflict; writable-file claim disproved by mode 0444","final_dtd_verdict":"SHIP after 1-1 tie resolved by stronger scoped argument; mandatory Codex debate FINAL KEEP; monitoring record only","tests_evidence":["data/codex-yoloforever/evidence/TK11188-owner-audit-uEkvec/e2e-proof.json","ticketmaster urgency 18/18 PASS","frozen audit/snapshot SHA256 PASS","canonical ticket fold PASS","live-owned execute rejection PASS","Track C baseline/reconciliation gate PASS","JSON/JSONL parse PASS","zero-cost guard exact PASS"],"next_seed":"Fresh inbox and ticketmaster snapshot next launchd cycle; enumerate the full active board before any broad no-eligible claim and fail closed on every execute row with live=true.","gated_memos":[],"cost":"$0"}
+{"timestamp":"2026-09-03T18:50:00Z","ticket":"TK-11189-codex-yoloforever-bounded-monitoring-cyc","action":"Fresh snapshot and canonical-owner audit found no safe reversible ungated non-colliding unowned pickup. Preserved all TK-10993 Track C/D/E rails. Cody's reproduced evidence-provenance defect was corrected with a durable frozen event log, snapshot, fold reconciliation, and E2E proof; no underlying project, service, external, production, or catalog mutation.","dtd_verdict":"A=read-only full-board eligibility/ownership/collision/gate audit, 2/2 valid local voters (Codex+Qwen), mandatory Codex debate FINAL KEEP","cody_verdict":"FIX FIRST 4/5; top fix=correct false 64/65 chronology and persist exact fold inputs/output. Reproduced and fixed: snapshot 61 + post-snapshot TK-11189 = 62 task records; +2 excluded scheduled_job = owner 64; later TK-11190 = current 65.","final_dtd_verdict":"FIX-THEN-SHIP after 1-1 split with SHIP; conservative tie resolution because a reproduced evidence fix was required; mandatory Codex debate FINAL KEEP; fix applied and passed.","tests_evidence":["data/codex-yoloforever/evidence/TK-11189-20260903T1848Z/e2e-proof.json","frozen events SHA256 e9a89c6f8f1226054f5609b914b42b85171e86c37bc95eacd0e622ffbfbe4f45","snapshot SHA256 45cce4be67cf61c1eec652532011aaeed4dce450d7794152dbc6ef4934f82665","canonical fold replay PASS","61/64/65 record-level reconciliation PASS","ticketmaster urgency 18/18 PASS","wrong-hash rejection PASS","JSON parse PASS","zero-cost guard exact PASS"],"next_seed":"Fresh inbox and full-board ticketmaster snapshot next launchd cycle; if still no eligible pickup, perform read-only TK-10928 scheduler correlation at its recheck. Do not reuse this point-in-time eligibility conclusion.","gated_memos":[],"cost":"$0"}
+{"timestamp":"2026-09-03T19:20:23Z","ticket":"TK-11196-codex-yoloforever-bounded-monitoring-cyc","action":"Fresh ticketmaster selector reported zero actionable pickups and materialized zero execute/fast-local rows. Initial DTD committed a bounded no-op; no canonical owner execution was authorized. Cody independently shipped the scoped HOLD/no-op. No underlying ticket, project, process, service, external, production, catalog, or gated state changed.","dtd_verdict":"C=no further audit/record gated-only after 1-1 valid-voter split (Codex A, Qwen C) and mandatory Codex FINAL OVERTURN; 2/6 availability","cody_verdict":"SHIP IT 4/5 for bounded no-op/HOLD only; top fix=state exact point-in-time selector scope (67 reported total, 25 materialized, zero executable rows), never claim exhaustive all-67 audit","final_dtd_verdict":"SHIP 2/2 valid local voters; mandatory Codex debate FINAL KEEP; monitoring record only","tests_evidence":["data/codex-yoloforever/evidence/TK-11196-20260903T1919Z/e2e-proof.json","snapshot sha256 aee6d9ad6579f425d95ed3c60b2caca5132fa09f7a3db1d9047297dfc42bdd9e","canonical locked ticket fold PASS","selector boundary and negative executable-row assertion PASS","ticketmaster urgency 18/18 PASS","initial/final DTD artifact parse PASS","zero-cost guard exact PASS","live JSONL direct-parse concurrency failure captured and replaced by locked canonical fold"],"next_seed":"Fresh inbox and ticketmaster snapshot next launchd cycle; do not reuse this timestamped selector conclusion. If no pickup appears, continue read-only backlog monitoring without touching live-owned, dirty, or gated work.","gated_memos":[],"cost":"$0"}
diff --git a/desktop-bar/TicketBar.swift b/desktop-bar/TicketBar.swift
index 100d0263..e5047f9e 100644
--- a/desktop-bar/TicketBar.swift
+++ b/desktop-bar/TicketBar.swift
@@ -78,6 +78,7 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
   private var openButton: NSButton!
   private var themeButton: NSButton!
   private var posButton: NSButton!
+  private var dotsButton: NSButton!
   private var hamburger: NSButton!
   private var isNight = true
   private var barPosition = "top"                 // top | left | right | off
@@ -164,7 +165,13 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
     posButton.bezelStyle = .inline
     posButton.isBordered = false
     posButton.toolTip = "Dock position: top → left → right → off"
-    stack = NSStackView(views: tiles + [open, themeButton, posButton])
+    // ◉ Terminal-dots dropdown — built FRESH on each open by shelling out to the
+    // /allcolordots scanner (see showDotsMenu). Styled like the theme/pos buttons.
+    dotsButton = NSButton(title: "◉", target: self, action: #selector(showDotsMenu))
+    dotsButton.bezelStyle = .inline
+    dotsButton.isBordered = false
+    dotsButton.toolTip = "Terminal dots"
+    stack = NSStackView(views: tiles + [open, themeButton, posButton, dotsButton])
     stack.orientation = .horizontal
     stack.spacing = 6
     stack.alignment = .centerY
@@ -173,6 +180,7 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
     open.setContentHuggingPriority(.required, for: .horizontal)
     themeButton.setContentHuggingPriority(.required, for: .horizontal)
     posButton.setContentHuggingPriority(.required, for: .horizontal)
+    dotsButton.setContentHuggingPriority(.required, for: .horizontal)
 
     // Center the tile group in the bar. Collapsed hamburger (off-mode) lives in the
     // top-left corner and is shown only when barPosition == "off". In off mode the
@@ -545,6 +553,134 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
 
   @objc private func openViewer() { NSWorkspace.shared.open(viewerURL) }
 
+  // ── ◉ Terminal-dots dropdown ────────────────────────────────────────────────
+  // SELECTION MODEL: click-to-jump (the robust fallback). AppKit does not support
+  // keeping an NSMenu open across checkbox toggles without private API / custom
+  // item views, so — per the task's fallback — clicking a terminal row focuses
+  // that one window immediately, and a "▶ Focus all that want me" item focuses
+  // every orange+purple+yellow terminal at once. The menu is rebuilt FRESH on
+  // every open by re-running the /allcolordots scanner.
+  private struct DotEntry { let tty: String; let color: String; let label: String }
+  private static let dotOrder = ["orange", "purple", "yellow", "green", "pink", "none"]
+  private static let dotEmoji: [String: String] = [
+    "orange": "🟠", "purple": "🟣", "yellow": "🟡", "green": "🟢", "pink": "🩷", "none": "⚪️"
+  ]
+  private static let wantColors: Set<String> = ["orange", "purple", "yellow"]
+
+  // Shell out to the scanner and parse its JSON array (already priority-sorted).
+  private func scanDots() -> [DotEntry] {
+    let task = Process()
+    task.executableURL = URL(fileURLWithPath: "/bin/bash")
+    task.arguments = ["-lc", "bash /Users/macstudio3/.claude/skills/allcolordots/allcolordots.sh --json"]
+    let out = Pipe()
+    task.standardOutput = out
+    task.standardError = Pipe()
+    do { try task.run() } catch { return [] }
+    let data = out.fileHandleForReading.readDataToEndOfFile()
+    task.waitUntilExit()
+    guard let arr = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] else { return [] }
+    return arr.compactMap { row in
+      guard let tty = row["tty"] as? String else { return nil }
+      return DotEntry(tty: tty, color: row["color"] as? String ?? "none", label: row["label"] as? String ?? tty)
+    }
+  }
+
+  @objc private func showDotsMenu() {
+    let menu = buildDotsMenu()
+    NSApp.activate(ignoringOtherApps: true)
+    guard let dotsButton else { return }
+    menu.popUp(positioning: nil, at: NSPoint(x: 0, y: dotsButton.bounds.height + 2), in: dotsButton)
+  }
+
+  private func buildDotsMenu() -> NSMenu {
+    let menu = NSMenu(title: "dots")
+    menu.autoenablesItems = false
+    let entries = scanDots()
+    if entries.isEmpty {
+      let none = NSMenuItem(title: "No dots (scanner returned nothing)", action: nil, keyEquivalent: "")
+      none.isEnabled = false
+      menu.addItem(none)
+      menu.addItem(.separator())
+      let refresh = NSMenuItem(title: "🔄 Refresh", action: #selector(showDotsMenu), keyEquivalent: "")
+      refresh.target = self
+      menu.addItem(refresh)
+      return menu
+    }
+    let wantCount = entries.filter { BarController.wantColors.contains($0.color) }.count
+    let header = NSMenuItem(title: "Terminals — \(wantCount) want you", action: nil, keyEquivalent: "")
+    header.isEnabled = false
+    menu.addItem(header)
+    if wantCount > 0 {
+      let focusAll = NSMenuItem(title: "▶ Focus all that want me (\(wantCount))", action: #selector(focusAllWanting), keyEquivalent: "")
+      focusAll.target = self
+      menu.addItem(focusAll)
+    }
+    menu.addItem(.separator())
+    for color in BarController.dotOrder {
+      let group = entries.filter { $0.color == color }
+      guard !group.isEmpty else { continue }
+      let emoji = BarController.dotEmoji[color] ?? "•"
+      let section = NSMenuItem(title: "\(emoji) \(color.capitalized) (\(group.count))", action: nil, keyEquivalent: "")
+      section.isEnabled = false
+      menu.addItem(section)
+      for entry in group {
+        let item = NSMenuItem(title: entry.label, action: #selector(focusDotTerminal(_:)), keyEquivalent: "")
+        item.target = self
+        item.representedObject = entry.tty
+        item.isEnabled = true
+        menu.addItem(item)
+      }
+    }
+    menu.addItem(.separator())
+    let refresh = NSMenuItem(title: "🔄 Refresh", action: #selector(showDotsMenu), keyEquivalent: "")
+    refresh.target = self
+    menu.addItem(refresh)
+    let board = NSMenuItem(title: "Open board ↗", action: #selector(openViewer), keyEquivalent: "")
+    board.target = self
+    menu.addItem(board)
+    return menu
+  }
+
+  @objc private func focusDotTerminal(_ sender: NSMenuItem) {
+    guard let tty = sender.representedObject as? String else { return }
+    focusTerminal(tty: tty)
+  }
+
+  @objc private func focusAllWanting() {
+    for entry in scanDots() where BarController.wantColors.contains(entry.color) {
+      focusTerminal(tty: entry.tty)
+    }
+  }
+
+  // Bring the iTerm2 window/tab/session whose tty matches to the front. The
+  // scanner reports "ttys011"; iTerm reports "/dev/ttys011". No-ops silently if
+  // iTerm2 isn't the terminal (that's fine).
+  private func focusTerminal(tty: String) {
+    let dev = tty.hasPrefix("/dev/") ? tty : "/dev/\(tty)"
+    let script = """
+    tell application "iTerm2"
+      repeat with w in windows
+        repeat with t in tabs of w
+          repeat with s in sessions of t
+            if tty of s is "\(dev)" then
+              select w
+              select t
+              select s
+              activate
+              return
+            end if
+          end repeat
+        end repeat
+      end repeat
+    end tell
+    tell application "iTerm2" to activate
+    """
+    let task = Process()
+    task.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
+    task.arguments = ["-e", script]
+    try? task.run()
+  }
+
   private func openTicketPopup() {
     NSLog("TicketBar opening control window")
     if let ticketPopup {

← 03487e80 Ticket desktop bar: drag-to-resize the bar edge with a visib  ·  back to Ticket System  ·  Record TK-10993 evidence-only hold cycle bbec66f7 →