← 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
M data/codex-yoloforever/ledger.jsonlM desktop-bar/TicketBar.swift
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 →