[object Object]

← back to Ticket System

auto-data-snapshot: 2026-09-11T14:39:38 (2 data files) — desktop-bar/TicketBar.swift.bak-20260911-143435 run-ticket.sh.bak-20260911-142309

16c35905946e244f9823864e641754b358380e9a · 2026-09-11 14:39:40 -0700 · auto-commit-fleet

Files touched

Diff

commit 16c35905946e244f9823864e641754b358380e9a
Author: auto-commit-fleet <steve@designerwallcoverings.com>
Date:   Fri Sep 11 14:39:40 2026 -0700

    auto-data-snapshot: 2026-09-11T14:39:38 (2 data files) — desktop-bar/TicketBar.swift.bak-20260911-143435 run-ticket.sh.bak-20260911-142309
---
 desktop-bar/TicketBar.swift.bak-20260911-143435 | 967 ++++++++++++++++++++++++
 run-ticket.sh.bak-20260911-142309               |  82 ++
 2 files changed, 1049 insertions(+)

diff --git a/desktop-bar/TicketBar.swift.bak-20260911-143435 b/desktop-bar/TicketBar.swift.bak-20260911-143435
new file mode 100644
index 00000000..385c488d
--- /dev/null
+++ b/desktop-bar/TicketBar.swift.bak-20260911-143435
@@ -0,0 +1,967 @@
+import AppKit
+import WebKit
+
+private let ledgerURL = FileManager.default.homeDirectoryForCurrentUser
+  .appendingPathComponent(".claude/tickets/events.jsonl")
+private let viewerURL = URL(string: "http://127.0.0.1:9794/?section=all&layout=grid")!
+
+private struct TicketState {
+  var id = ""
+  var title = ""
+  var status = "open"
+  var updated = ""
+  var created = ""
+}
+
+private final class BarTile: NSButton {
+  let key: String
+  let label: NSTextField
+  var menuProvider: (() -> NSMenu)?
+  var clickHandler: (() -> Void)?
+  var dragHandler: ((String, CGFloat) -> Void)?
+  var shouldSuppressClick: (() -> Bool)?
+  var widthConstraint: NSLayoutConstraint!
+
+  init(key: String, label: NSTextField, width: CGFloat) {
+    self.key = key
+    self.label = label
+    super.init(frame: .zero)
+    title = ""
+    isBordered = false
+    target = self
+    action = #selector(showDropdown)
+    setAccessibilityLabel("\(key.capitalized) ticket tile")
+    wantsLayer = true
+    layer?.cornerRadius = 6
+    layer?.backgroundColor = NSColor(calibratedWhite: 0.11, alpha: 0.9).cgColor
+    label.translatesAutoresizingMaskIntoConstraints = false
+    addSubview(label)
+    NSLayoutConstraint.activate([
+      label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 9),
+      label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -9),
+      label.centerYAnchor.constraint(equalTo: centerYAnchor)
+    ])
+    widthConstraint = widthAnchor.constraint(equalToConstant: width)
+    widthConstraint.isActive = true
+    heightAnchor.constraint(equalToConstant: 28).isActive = true
+    toolTip = "Click for details and layout controls"
+  }
+
+  required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
+  override func acceptsFirstMouse(for event: NSEvent?) -> Bool { true }
+  @objc private func showDropdown() {
+    // A drag (vertical bar-slide or horizontal tile-reorder) sets this true so the
+    // trailing mouse-up click doesn't also pop the menu.
+    if shouldSuppressClick?() == true { return }
+    NSLog("TicketBar tile pressed: %@", key)
+    guard let menu = menuProvider?() else { return }
+    NSApp.activate(ignoringOtherApps: true)
+    menu.popUp(positioning: nil, at: NSPoint(x: 4, y: bounds.height - 2), in: self)
+  }
+
+  override func rightMouseDown(with event: NSEvent) {
+    guard let menu = menuProvider?() else { return }
+    NSApp.activate(ignoringOtherApps: true)
+    menu.popUp(positioning: nil, at: NSPoint(x: 4, y: bounds.height - 2), in: self)
+  }
+}
+
+private final class BarController: NSObject, NSApplicationDelegate, NSWindowDelegate, WKNavigationDelegate {
+  private var panel: NSPanel!
+  private let openLabel = NSTextField(labelWithString: "OPEN …")
+  private let blockedLabel = NSTextField(labelWithString: "BLOCKED …")
+  private let doingLabel = NSTextField(labelWithString: "DOING …")
+  private let working = NSTextField(labelWithString: "Working · —")
+  private let latest = NSTextField(labelWithString: "Latest · —")
+  private let cpuLabel = NSTextField(labelWithString: "CPU · —")
+  private var stack: NSStackView!
+  private var tiles: [BarTile] = []
+  private var openButton: NSButton!
+  private var themeButton: NSButton!
+  private var posButton: NSButton!
+  private var dotsButton: NSButton!
+  private var sniperButton: NSButton!
+  private var hamburger: NSButton!
+  private var isNight = true
+  private var barPosition = "top"                 // top | left | right | off
+  private var horizWidths: [String: CGFloat] = [:] // tile widths captured for horizontal (top) mode
+  private let vbarWidth: CGFloat = 300
+  private var contentContainer: NSView!
+  private var stackConstraints: [NSLayoutConstraint] = []
+  private var alignCenterY: NSLayoutConstraint!  // vertical centering (Top mode)
+  private var alignTop: NSLayoutConstraint!      // top-pinned (Left/Right docks)
+  private var statusDetails: [String: [String]] = [:]
+  private var workingDetails: [String] = []
+  private var latestDetail = "No activity yet"
+  private var ticketPopup: NSWindow?
+  private var timer: Timer?
+  private var dragMonitor: Any?
+  private var draggingKey: String?
+  private var dragStartX: CGFloat?
+  private var dragStartMouseY: CGFloat?
+  private var dragStartPanelY: CGFloat?
+  private var barSlid = false
+  private var didDrag = false
+  private var resizing = false
+  private var resizeStartMouse = CGPoint.zero
+  private var resizeStartSize: CGFloat = 0
+  private var gripView: NSView!
+
+  func applicationDidFinishLaunching(_ notification: Notification) {
+    NSApp.setActivationPolicy(.accessory)
+    buildPanel()
+    refresh()
+    refreshSystem()
+    timer = Timer.scheduledTimer(withTimeInterval: 4, repeats: true) { [weak self] _ in self?.refresh() }
+    NotificationCenter.default.addObserver(self, selector: #selector(reposition), name: NSApplication.didChangeScreenParametersNotification, object: nil)
+  }
+
+  private func buildPanel() {
+    panel = NSPanel(contentRect: .zero, styleMask: [.borderless, .nonactivatingPanel], backing: .buffered, defer: false)
+    panel.level = .statusBar
+    panel.collectionBehavior = [.canJoinAllSpaces, .stationary, .fullScreenAuxiliary]
+    panel.isOpaque = false
+    panel.backgroundColor = NSColor(calibratedWhite: 0.055, alpha: 0.97)
+    panel.hasShadow = true
+    panel.hidesOnDeactivate = false
+
+    isNight = UserDefaults.standard.object(forKey: "ticketBarNight") as? Bool ?? true
+    barPosition = UserDefaults.standard.string(forKey: "ticketBarPosition") ?? "top"
+    let countLabels = [openLabel, blockedLabel, doingLabel]
+    for label in countLabels + [working, latest] {
+      label.font = .systemFont(ofSize: 12, weight: countLabels.contains(label) ? .bold : .medium)
+      label.lineBreakMode = .byTruncatingTail
+      label.maximumNumberOfLines = 1
+    }
+    // Text/tile/panel colors are theme-driven — see applyTheme().
+
+    let savedWidths = UserDefaults.standard.dictionary(forKey: "ticketBarWidths") as? [String: CGFloat] ?? [:]
+    // OPEN / BLOCKED / DOING are now THREE separate tiles — each opens its own
+    // dropdown listing that status's tickets. Working + Latest unchanged.
+    let definitions: [(String, NSTextField, CGFloat)] = [
+      ("open", openLabel, 150), ("blocked", blockedLabel, 175), ("doing", doingLabel, 150),
+      ("working", working, 560), ("latest", latest, 520)
+    ]
+    let savedOrder = UserDefaults.standard.stringArray(forKey: "ticketBarOrder") ?? definitions.map(\.0)
+    let byKey = Dictionary(uniqueKeysWithValues: definitions.map { ($0.0, $0) })
+    let ordered = savedOrder.compactMap { byKey[$0] } + definitions.filter { !savedOrder.contains($0.0) }
+    tiles = ordered.map { key, label, defaultWidth in
+      let tile = BarTile(key: key, label: label, width: savedWidths[key] ?? defaultWidth)
+      tile.dragHandler = { [weak self] key, x in self?.moveTile(key, toward: x) }
+      tile.menuProvider = { [weak self] in self?.menu(for: key) ?? NSMenu() }
+      tile.shouldSuppressClick = { [weak self] in self?.didDrag ?? false }
+      // No clickHandler: a clean left-click opens the tile's DROPDOWN menu (details
+      // + layout controls). A vertical drag slides the whole bar; a horizontal drag
+      // on a tile reorders it. The web control window is reachable from the menu's
+      // "Open Control Window" item and from the "Open Fleet ↗" button.
+      return tile
+    }
+    horizWidths = Dictionary(uniqueKeysWithValues: tiles.map { ($0.key, $0.widthConstraint.constant) })
+    let open = NSButton(title: "Open Fleet ↗", target: self, action: #selector(openViewer))
+    open.bezelStyle = .inline
+    openButton = open
+    themeButton = NSButton(title: isNight ? "🌙" : "☀️", target: self, action: #selector(toggleTheme))
+    themeButton.bezelStyle = .inline
+    themeButton.isBordered = false
+    themeButton.toolTip = "Toggle night / day"
+    posButton = NSButton(title: "▔", target: self, action: #selector(cyclePosition))
+    posButton.bezelStyle = .inline
+    posButton.isBordered = false
+    posButton.toolTip = "Dock position: top → left → right → off"
+    // ◉ 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"
+    cpuLabel.font = .monospacedDigitSystemFont(ofSize: 12, weight: .medium)
+    cpuLabel.lineBreakMode = .byTruncatingTail
+    cpuLabel.alignment = .center
+    cpuLabel.widthAnchor.constraint(equalToConstant: 112).isActive = true
+    sniperButton = NSButton(title: "RAM Sniper · ON", target: self, action: #selector(toggleRamSniper))
+    sniperButton.bezelStyle = .inline
+    sniperButton.wantsLayer = true
+    sniperButton.layer?.cornerRadius = 6
+    sniperButton.layer?.borderWidth = 1
+    sniperButton.toolTip = "Toggle the background CPU/RAM priority sniper"
+    sniperButton.setContentHuggingPriority(.required, for: .horizontal)
+    stack = NSStackView(views: tiles + [cpuLabel, sniperButton, open, themeButton, posButton, dotsButton])
+    stack.orientation = .horizontal
+    stack.spacing = 6
+    stack.alignment = .centerY
+    stack.distribution = .fill
+    for tile in tiles { tile.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) }
+    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
+    // stack is REMOVED from the view so its width can't force the panel wide.
+    contentContainer = NSView()
+    let container = contentContainer!
+    stack.translatesAutoresizingMaskIntoConstraints = false
+    container.clipsToBounds = true
+    container.addSubview(stack)
+    alignCenterY = stack.centerYAnchor.constraint(equalTo: container.centerYAnchor)
+    alignTop = stack.topAnchor.constraint(equalTo: container.topAnchor, constant: 10)
+    stackConstraints = [
+      stack.centerXAnchor.constraint(equalTo: container.centerXAnchor),
+      stack.leadingAnchor.constraint(greaterThanOrEqualTo: container.leadingAnchor, constant: 14),
+      stack.trailingAnchor.constraint(lessThanOrEqualTo: container.trailingAnchor, constant: -10)
+    ]
+    NSLayoutConstraint.activate(stackConstraints + [alignCenterY])
+    hamburger = NSButton(title: "☰", target: self, action: #selector(openFromHamburger))
+    hamburger.isBordered = false
+    hamburger.font = .systemFont(ofSize: 18, weight: .bold)
+    hamburger.contentTintColor = .white
+    hamburger.toolTip = "Show ticket bar"
+    hamburger.translatesAutoresizingMaskIntoConstraints = false
+    hamburger.isHidden = true
+    container.addSubview(hamburger)
+    NSLayoutConstraint.activate([
+      hamburger.centerXAnchor.constraint(equalTo: container.centerXAnchor),
+      hamburger.centerYAnchor.constraint(equalTo: container.centerYAnchor)
+    ])
+    // Visible resize grip at the draggable edge (positioned per-dock in applyPosition).
+    gripView = NSView(frame: .zero)
+    gripView.wantsLayer = true
+    gripView.layer?.backgroundColor = NSColor(calibratedRed: 0.30, green: 0.60, blue: 1.0, alpha: 0.9).cgColor
+    gripView.layer?.cornerRadius = 2
+    gripView.toolTip = "Drag to resize the bar"
+    container.addSubview(gripView)
+    panel.contentView = container
+    installDragMonitor()
+    applyTheme()
+    applyPosition()
+    panel.orderFrontRegardless()
+  }
+
+  // The draggable resize edge for the current dock, in window (bottom-left) coords.
+  private func inResizeZone(_ loc: CGPoint) -> Bool {
+    let f = panel.frame
+    switch barPosition {
+    case "left":  return loc.x >= f.width - 12
+    case "right": return loc.x <= 12
+    case "top":   return loc.y <= 9
+    default:      return false
+    }
+  }
+  private func positionGrip() {
+    guard gripView != nil else { return }
+    let f = panel.frame
+    switch barPosition {
+    case "left":  gripView.isHidden = false; gripView.frame = NSRect(x: f.width - 6, y: f.height/2 - 60, width: 5, height: 120)
+    case "right": gripView.isHidden = false; gripView.frame = NSRect(x: 1, y: f.height/2 - 60, width: 5, height: 120)
+    case "top":   gripView.isHidden = false; gripView.frame = NSRect(x: f.width/2 - 60, y: 1, width: 120, height: 4)
+    default:      gripView.isHidden = true
+    }
+  }
+
+  private static let positionOrder = ["top", "left", "right", "off"]
+  @objc private func cyclePosition() {
+    let i = (BarController.positionOrder.firstIndex(of: barPosition) ?? 0) + 1
+    setPosition(BarController.positionOrder[i % BarController.positionOrder.count])
+  }
+  @objc private func openFromHamburger() { setPosition("top") }
+  private func setPosition(_ p: String) {
+    barPosition = p
+    UserDefaults.standard.set(p, forKey: "ticketBarPosition")
+    applyPosition()
+  }
+
+  // Persisted per-mode bar size: width for the vertical docks, height for the top bar.
+  private func vBarWidth() -> CGFloat { CGFloat((UserDefaults.standard.object(forKey: "ticketBarVWidth") as? Double) ?? 300) }
+  private func topBarHeight() -> CGFloat { CGFloat((UserDefaults.standard.object(forKey: "ticketBarHeight") as? Double) ?? 38) }
+  @objc private func barBigger(_ s: NSMenuItem)  { adjustBarSize(by: 1) }
+  @objc private func barSmaller(_ s: NSMenuItem) { adjustBarSize(by: -1) }
+  @objc private func cyclePositionMenu(_ s: NSMenuItem) { cyclePosition() }
+  // Expand/shrink the WHOLE bar: cross-axis width (left/right) or height (top).
+  private func adjustBarSize(by dir: CGFloat) {
+    if barPosition == "left" || barPosition == "right" {
+      UserDefaults.standard.set(Double(min(700, max(180, vBarWidth() + dir * 40))), forKey: "ticketBarVWidth")
+    } else if barPosition == "top" {
+      UserDefaults.standard.set(Double(min(300, max(28, topBarHeight() + dir * 16))), forKey: "ticketBarHeight")
+    }
+    applyPosition()
+  }
+
+  // Dock the bar top (horizontal), left/right (vertical), or collapse to a ☰ hamburger.
+  private func applyPosition() {
+    guard let screen = panel.screen ?? NSScreen.main ?? NSScreen.screens.first else { return }
+    let v = screen.visibleFrame
+    if barPosition == "off" {
+      if stack.superview != nil {
+        NSLayoutConstraint.deactivate(stackConstraints)
+        stack.removeFromSuperview()
+      }
+      hamburger.isHidden = false
+      gripView?.isHidden = true
+      let w: CGFloat = 46, h: CGFloat = 32
+      panel.setFrame(NSRect(x: v.minX + 8, y: v.maxY - h, width: w, height: h), display: true)
+      panel.orderFrontRegardless()
+      return
+    }
+    if stack.superview == nil {
+      contentContainer.addSubview(stack)
+      NSLayoutConstraint.activate(stackConstraints)
+    }
+    stack.isHidden = false
+    hamburger.isHidden = true
+    let vertical = (barPosition == "left" || barPosition == "right")
+    stack.orientation = vertical ? .vertical : .horizontal
+    stack.alignment = vertical ? .leading : .centerY
+    // Top-pin the tile group in the vertical docks; center it in the Top bar.
+    alignCenterY.isActive = !vertical
+    alignTop.isActive = vertical
+    let vW = self.vBarWidth()
+    // Tile widths: uniform in a vertical bar; restore the horizontal widths on top.
+    for tile in tiles {
+      tile.widthConstraint.constant = vertical ? (vW - 44) : (horizWidths[tile.key] ?? tile.widthConstraint.constant)
+    }
+    switch barPosition {
+    case "left":
+      panel.setFrame(NSRect(x: v.minX, y: v.minY, width: vW, height: v.height), display: true)
+    case "right":
+      panel.setFrame(NSRect(x: v.maxX - vW, y: v.minY, width: vW, height: v.height), display: true)
+    default: // top
+      let barHeight = self.topBarHeight()
+      let requested = CGFloat(UserDefaults.standard.double(forKey: "ticketBarYOffset"))
+      let offset = min(max(0, requested), max(0, v.height - barHeight))
+      panel.setFrame(NSRect(x: v.minX, y: v.maxY - barHeight - offset, width: v.width, height: barHeight), display: true)
+    }
+    posButton.title = ["top": "▔", "left": "▏", "right": "▕"][barPosition] ?? "▔"
+    positionGrip()
+    panel.orderFrontRegardless()
+  }
+
+  @objc private func toggleTheme() {
+    isNight.toggle()
+    UserDefaults.standard.set(isNight, forKey: "ticketBarNight")
+    applyTheme()
+  }
+
+  // Night = dark bar (default), Day = light bar. Toggled by the 🌙/☀️ button.
+  private func applyTheme() {
+    let night = isNight
+    panel.backgroundColor = night ? NSColor(calibratedWhite: 0.055, alpha: 0.97)
+                                  : NSColor(calibratedWhite: 0.95, alpha: 0.98)
+    let tileBG = (night ? NSColor(calibratedWhite: 0.11, alpha: 0.90)
+                        : NSColor(calibratedWhite: 0.86, alpha: 0.95)).cgColor
+    for tile in tiles { tile.layer?.backgroundColor = tileBG }
+    openLabel.textColor    = night ? NSColor(calibratedRed: 0.31, green: 0.63, blue: 1.00, alpha: 1)
+                                    : NSColor(calibratedRed: 0.13, green: 0.38, blue: 0.82, alpha: 1)
+    blockedLabel.textColor = night ? NSColor(calibratedRed: 0.88, green: 0.42, blue: 0.46, alpha: 1)
+                                    : 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)
+    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)
+                                    : NSColor(calibratedWhite: 0.30, alpha: 1)
+    cpuLabel.textColor     = night ? NSColor(calibratedRed: 0.45, green: 0.90, blue: 0.65, alpha: 1)
+                                    : NSColor(calibratedRed: 0.08, green: 0.42, blue: 0.24, alpha: 1)
+    openButton?.contentTintColor = night ? .white : NSColor(calibratedWhite: 0.15, alpha: 1)
+    themeButton?.title = night ? "🌙" : "☀️"
+    styleSniperButton(active: sniperButton?.title.contains("ON") == true)
+    panel.invalidateShadow()
+    panel.contentView?.needsDisplay = true
+  }
+
+  private func installDragMonitor() {
+    guard dragMonitor == nil else { return }
+    dragMonitor = NSEvent.addLocalMonitorForEvents(matching: [.leftMouseDown, .leftMouseDragged, .leftMouseUp]) { [weak self] event in
+      guard let self, event.window === self.panel else { return event }
+      switch event.type {
+      case .leftMouseDown:
+        self.didDrag = false
+        self.barSlid = false
+        // Grab the resize edge? (right edge of left dock, left edge of right dock, bottom of top bar)
+        self.resizing = self.inResizeZone(event.locationInWindow)
+        if self.resizing {
+          self.resizeStartMouse = NSEvent.mouseLocation
+          self.resizeStartSize = (self.barPosition == "top") ? self.topBarHeight() : self.vBarWidth()
+        }
+        self.draggingKey = self.tiles.first(where: {
+          $0.bounds.contains($0.convert(event.locationInWindow, from: nil))
+        })?.key
+        self.dragStartX = event.locationInWindow.x
+        self.dragStartMouseY = NSEvent.mouseLocation.y   // global screen Y
+        self.dragStartPanelY = self.panel.frame.origin.y
+      case .leftMouseDragged:
+        // Edge-drag RESIZE takes priority over slide/reorder.
+        if self.resizing {
+          self.didDrag = true
+          let m = NSEvent.mouseLocation
+          switch self.barPosition {
+          case "left":
+            UserDefaults.standard.set(Double(min(700, max(180, self.resizeStartSize + (m.x - self.resizeStartMouse.x)))), forKey: "ticketBarVWidth")
+          case "right":
+            UserDefaults.standard.set(Double(min(700, max(180, self.resizeStartSize + (self.resizeStartMouse.x - m.x)))), forKey: "ticketBarVWidth")
+          case "top":
+            UserDefaults.standard.set(Double(min(300, max(28, self.resizeStartSize + (self.resizeStartMouse.y - m.y)))), forKey: "ticketBarHeight")
+          default: break
+          }
+          self.applyPosition()
+          return event
+        }
+        let dy = NSEvent.mouseLocation.y - (self.dragStartMouseY ?? NSEvent.mouseLocation.y)
+        let dx = event.locationInWindow.x - (self.dragStartX ?? event.locationInWindow.x)
+        // Vertical drag → SLIDE the whole bar up/down (Top mode only). Horizontal drag on a tile → reorder.
+        if self.barPosition == "top", self.barSlid || (abs(dy) > 4 && abs(dy) >= abs(dx)) {
+          self.barSlid = true
+          self.didDrag = true
+          self.slideBar(toPanelY: (self.dragStartPanelY ?? self.panel.frame.origin.y) + dy)
+        } else if let key = self.draggingKey, abs(dx) > 3 {
+          self.didDrag = true
+          self.moveTile(key, toward: event.locationInWindow.x)
+        }
+      case .leftMouseUp:
+        if self.barSlid { self.persistBarOffset() }
+        self.resizing = false
+        self.draggingKey = nil
+        self.dragStartX = nil
+        self.dragStartMouseY = nil
+        self.dragStartPanelY = nil
+        // Keep didDrag true through the click that this mouse-up triggers (so the
+        // menu is suppressed after a drag), then clear it for the next gesture.
+        let dragged = self.didDrag
+        DispatchQueue.main.async { if dragged { self.didDrag = false } }
+      default: break
+      }
+      return event
+    }
+  }
+
+  // Move the whole bar to a new vertical position, clamped on-screen.
+  private func slideBar(toPanelY y: CGFloat) {
+    guard let screen = panel.screen ?? NSScreen.main ?? NSScreen.screens.first else { return }
+    let v = screen.visibleFrame
+    let barHeight: CGFloat = 38
+    let clampedY = min(max(v.minY, y), v.maxY - barHeight)
+    panel.setFrame(NSRect(x: v.minX, y: clampedY, width: v.width, height: barHeight), display: true)
+  }
+
+  // Persist the bar's current vertical position as an offset below the top.
+  private func persistBarOffset() {
+    guard let screen = panel.screen ?? NSScreen.main ?? NSScreen.screens.first else { return }
+    let v = screen.visibleFrame
+    let offset = max(0, (v.maxY - 38) - panel.frame.origin.y)
+    UserDefaults.standard.set(Double(offset), forKey: "ticketBarYOffset")
+  }
+
+  private func moveTile(_ key: String, toward windowX: CGFloat) {
+    guard let source = tiles.firstIndex(where: { $0.key == key }) else { return }
+    var target = source
+    for (index, tile) in tiles.enumerated() where tile.key != key {
+      let mid = tile.convert(tile.bounds, to: nil).midX
+      if windowX > mid { target = index }
+    }
+    if windowX < tiles.first?.convert(tiles.first!.bounds, to: nil).midX ?? 0 { target = 0 }
+    guard target != source else { return }
+    let tile = tiles.remove(at: source)
+    target = min(target, tiles.count)
+    tiles.insert(tile, at: target)
+    stack.removeArrangedSubview(tile)
+    tile.removeFromSuperview()
+    stack.insertArrangedSubview(tile, at: target)
+    UserDefaults.standard.set(tiles.map(\.key), forKey: "ticketBarOrder")
+  }
+
+  private func menu(for key: String) -> NSMenu {
+    let menu = NSMenu(title: key)
+    let openItem = NSMenuItem(title: "Open Control Window ↗", action: #selector(openControlFromMenu), keyEquivalent: "")
+    openItem.target = self
+    menu.addItem(openItem)
+    menu.addItem(.separator())
+    // autoenable OFF so the detail rows render in normal readable text instead of
+    // 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" {
+      let list = statusDetails[key] ?? []
+      details = list.isEmpty ? ["No \(key) tickets"] : list
+    } else if key == "working" {
+      details = workingDetails.isEmpty ? ["No tickets currently marked doing"] : workingDetails
+    } else {
+      details = [latestDetail]
+    }
+    for detail in details.prefix(12) {
+      let item = NSMenuItem(title: detail, action: #selector(openDetailTicket(_:)), keyEquivalent: "")
+      item.target = self
+      item.representedObject = detail
+      item.isEnabled = true
+      menu.addItem(item)
+    }
+    menu.addItem(.separator())
+    for (title, action) in [
+      ("Move Left", #selector(moveLeft(_:))), ("Move Right", #selector(moveRight(_:))),
+      ("Move Bar Up", #selector(moveBarUp(_:))), ("Move Bar Down", #selector(moveBarDown(_:))),
+      ("Tile Wider", #selector(wider(_:))), ("Tile Narrower", #selector(narrower(_:))),
+      ("Bar Bigger (expand)", #selector(barBigger(_:))), ("Bar Smaller", #selector(barSmaller(_:))),
+      ("Change Dock ▸ top/left/right/off", #selector(cyclePositionMenu(_:)))
+    ] {
+      let item = NSMenuItem(title: title, action: action, keyEquivalent: "")
+      item.target = self
+      item.representedObject = key
+      menu.addItem(item)
+    }
+    return menu
+  }
+
+  @objc private func openControlFromMenu() { openTicketPopup() }
+
+  // Click a ticket detail row → open the control window filtered to that ticket.
+  @objc private func openDetailTicket(_ sender: NSMenuItem) {
+    let text = sender.representedObject as? String ?? ""
+    guard let range = text.range(of: "TK-\\d+", options: .regularExpression) else {
+      openTicketPopup(); return
+    }
+    let id = String(text[range])
+    openTicketPopup()
+    // Retry a few times so it lands whether the web view was just created or already loaded.
+    for delay in [0.2, 0.6, 1.2] {
+      DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
+        guard let web = self?.ticketPopup?.contentView as? WKWebView else { return }
+        web.evaluateJavaScript("window.setQ ? (setQ('\(id)'), true) : (location.hash='q=\(id)')", completionHandler: nil)
+      }
+    }
+  }
+
+  // Vertical bar placement — "ticketBarYOffset" is pixels BELOW the top of the
+  // visible frame (0 = pinned under the menu bar, the original behavior).
+  @objc private func moveBarUp(_ sender: NSMenuItem) { nudgeBar(by: -40) }
+  @objc private func moveBarDown(_ sender: NSMenuItem) { nudgeBar(by: 40) }
+  private func nudgeBar(by delta: CGFloat) {
+    let current = CGFloat(UserDefaults.standard.double(forKey: "ticketBarYOffset"))
+    UserDefaults.standard.set(Double(current + delta), forKey: "ticketBarYOffset")
+    reposition()
+  }
+
+  @objc private func moveLeft(_ sender: NSMenuItem) { shift(sender.representedObject as? String, by: -1) }
+  @objc private func moveRight(_ sender: NSMenuItem) { shift(sender.representedObject as? String, by: 1) }
+  private func shift(_ key: String?, by delta: Int) {
+    guard let key, let index = tiles.firstIndex(where: { $0.key == key }) else { return }
+    let target = max(0, min(tiles.count - 1, index + delta))
+    guard target != index else { return }
+    let tile = tiles.remove(at: index)
+    tiles.insert(tile, at: target)
+    stack.removeArrangedSubview(tile); tile.removeFromSuperview(); stack.insertArrangedSubview(tile, at: target)
+    UserDefaults.standard.set(tiles.map(\.key), forKey: "ticketBarOrder")
+  }
+  @objc private func wider(_ sender: NSMenuItem) { resize(sender.representedObject as? String, by: 80) }
+  @objc private func narrower(_ sender: NSMenuItem) { resize(sender.representedObject as? String, by: -80) }
+  private func resize(_ key: String?, by delta: CGFloat) {
+    guard let tile = tiles.first(where: { $0.key == key }) else { return }
+    tile.widthConstraint.constant = max(180, min(900, tile.widthConstraint.constant + delta))
+    UserDefaults.standard.set(Dictionary(uniqueKeysWithValues: tiles.map { ($0.key, $0.widthConstraint.constant) }), forKey: "ticketBarWidths")
+  }
+
+  private func divider() -> NSView {
+    let view = NSView(frame: NSRect(x: 0, y: 0, width: 1, height: 18))
+    view.wantsLayer = true
+    view.layer?.backgroundColor = NSColor(calibratedWhite: 0.28, alpha: 1).cgColor
+    view.widthAnchor.constraint(equalToConstant: 1).isActive = true
+    return view
+  }
+
+  @objc private func reposition() { applyPosition() }
+
+  @objc private func openViewer() { NSWorkspace.shared.open(viewerURL) }
+
+  // ── live CPU + RAM Sniper control ─────────────────────────────────────────
+  private func runCommand(_ executable: String, _ args: [String]) -> String {
+    let task = Process(); task.executableURL = URL(fileURLWithPath: executable); task.arguments = args
+    let pipe = Pipe(); task.standardOutput = pipe; task.standardError = Pipe()
+    do { try task.run(); task.waitUntilExit() } catch { return "" }
+    return String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
+  }
+
+  private func refreshSystem() {
+    DispatchQueue.global(qos: .utility).async { [weak self] in
+      guard let self else { return }
+      let top = self.runCommand("/usr/bin/top", ["-l", "1", "-n", "0", "-s", "0"])
+      let cpuUsed: Double
+      let cpuText: String
+      if let line = top.split(separator: "\n").first(where: { $0.contains("CPU usage") }),
+         let user = line.split(separator: ",").first(where: { $0.contains("user") }),
+         let sys = line.split(separator: ",").first(where: { $0.contains("sys") }) {
+        let number: (Substring) -> Double = { part in Double(part.split(separator: "%").first?.split(separator: " ").last ?? "0") ?? 0 }
+        cpuUsed = number(user) + number(sys)
+        cpuText = String(format: "CPU · %.0f%%", cpuUsed)
+      } else { cpuUsed = 0; cpuText = "CPU · —" }
+      var sniper = !self.runCommand("/usr/bin/pgrep", ["-f", "/Users/macstudio3/bin/ram-sniper.sh"]).trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
+      if !sniper && cpuUsed >= 75 {
+        self.startRamSniper()
+        DispatchQueue.main.async { self.flashSniperButton() }
+        sniper = true
+      }
+      DispatchQueue.main.async {
+        self.cpuLabel.stringValue = cpuText
+        self.sniperButton.title = sniper ? "RAM Sniper · ON" : "RAM Sniper · OFF"
+        self.styleSniperButton(active: sniper)
+      }
+    }
+  }
+
+  private func styleSniperButton(active: Bool) {
+    guard let button = sniperButton else { return }
+    let green = NSColor(calibratedRed: 0.38, green: 0.92, blue: 0.58, alpha: 0.30)
+    let tint = active ? NSColor(calibratedRed: 0.62, green: 1.0, blue: 0.75, alpha: 1) : NSColor(calibratedWhite: 0.58, alpha: 0.72)
+    button.layer?.backgroundColor = active ? green.cgColor : NSColor.clear.cgColor
+    button.layer?.borderColor = active ? tint.cgColor : NSColor.clear.cgColor
+    button.contentTintColor = tint
+  }
+
+  @objc private func toggleRamSniper() {
+    let running = !runCommand("/usr/bin/pgrep", ["-f", "/Users/macstudio3/bin/ram-sniper.sh"]).trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
+    if running {
+      _ = runCommand("/usr/bin/pkill", ["-TERM", "-f", "/Users/macstudio3/bin/ram-sniper.sh"])
+    } else {
+      startRamSniper()
+    }
+    DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { [weak self] in self?.refreshSystem() }
+  }
+
+  private func startRamSniper() {
+    let task = Process(); task.executableURL = URL(fileURLWithPath: "/bin/zsh")
+    task.arguments = ["-lc", "nohup /Users/macstudio3/bin/ram-sniper.sh >/tmp/ram-sniper.stdout 2>&1 &"]
+    try? task.run()
+  }
+
+  private func flashSniperButton() {
+    guard let button = sniperButton else { return }
+    button.layer?.backgroundColor = NSColor(calibratedRed: 0.20, green: 1.0, blue: 0.35, alpha: 0.95).cgColor
+    button.layer?.borderColor = NSColor.white.cgColor
+    DispatchQueue.main.asyncAfter(deadline: .now() + 0.75) { [weak self] in
+      self?.styleSniperButton(active: true)
+    }
+  }
+
+  // ── ◉ 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)
+    }
+    // Path-A "gather all my yellows in one place" — yellow-only quick jump
+    // (needs-direction tabs), distinct from the 3-color "want me" action above.
+    let yellowCount = entries.filter { $0.color == "yellow" }.count
+    if yellowCount > 0 {
+      let focusYellow = NSMenuItem(title: "🟡 Gather my yellows (\(yellowCount))", action: #selector(focusAllYellow), keyEquivalent: "")
+      focusYellow.target = self
+      menu.addItem(focusYellow)
+    }
+    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)
+    }
+  }
+
+  // Path-A navigator: bring every 🟡 needs-direction terminal forward in one click.
+  @objc private func focusAllYellow() {
+    for entry in scanDots() where entry.color == "yellow" {
+      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 {
+      centerOnBarScreen(ticketPopup)
+      ticketPopup.makeKeyAndOrderFront(nil)
+      ticketPopup.orderFrontRegardless()
+      NSApp.activate(ignoringOtherApps: true)
+      return
+    }
+    let size = NSSize(width: 1480, height: 860)
+    let popup = NSPanel(contentRect: NSRect(origin: .zero, size: size), styleMask: [.titled, .closable, .resizable, .miniaturizable, .nonactivatingPanel], backing: .buffered, defer: false)
+    popup.title = "Fleet Ticket Control · All / Open / Blocked / Doing"
+    popup.level = .statusBar
+    popup.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
+    popup.minSize = NSSize(width: 900, height: 560)
+    popup.isReleasedWhenClosed = false
+    popup.hidesOnDeactivate = false
+    popup.isFloatingPanel = true
+    popup.becomesKeyOnlyIfNeeded = false
+    popup.delegate = self
+    let web = WKWebView(frame: popup.contentView?.bounds ?? .zero)
+    web.navigationDelegate = self
+    web.autoresizingMask = [.width, .height]
+    popup.contentView = web
+    // Let WebKit's HTTP Basic challenge establish a session-wide credential;
+    // a one-off Authorization header would authenticate the HTML navigation but
+    // leave the board's same-origin fetch() calls unauthenticated and blank.
+    web.load(URLRequest(url: viewerURL))
+    centerOnBarScreen(popup)
+    ticketPopup = popup
+    panel.addChildWindow(popup, ordered: .above)
+    popup.makeKeyAndOrderFront(nil)
+    popup.orderFrontRegardless()
+    NSApp.activate(ignoringOtherApps: true)
+  }
+
+  func windowWillClose(_ notification: Notification) {
+    guard let closing = notification.object as? NSWindow, closing === ticketPopup else { return }
+    panel.removeChildWindow(closing)
+    ticketPopup = nil
+  }
+
+  private func centerOnBarScreen(_ window: NSWindow) {
+    let screen = panel.screen ?? NSScreen.main ?? NSScreen.screens.first
+    guard let visible = screen?.visibleFrame else { window.center(); return }
+    let width = min(window.frame.width, visible.width - 40)
+    let height = min(window.frame.height, visible.height - 40)
+    window.setFrame(NSRect(
+      x: visible.midX - width / 2,
+      y: visible.midY - height / 2,
+      width: width,
+      height: height
+    ), display: true)
+    NSLog("TicketBar control frame %@ on bar screen %@", NSStringFromRect(window.frame), NSStringFromRect(visible))
+  }
+
+  func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
+    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic {
+      completionHandler(.useCredential, URLCredential(user: "admin", password: "DW2024!", persistence: .forSession))
+    } 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() {
+    refreshSystem()
+    DispatchQueue.global(qos: .utility).async { [weak self] in
+      guard let self else { return }
+      do {
+        let text = try String(contentsOf: ledgerURL, encoding: .utf8)
+        var states: [String: TicketState] = [:]
+        var newest: [String: Any]?
+        for line in text.split(separator: "\n") {
+          guard let data = line.data(using: .utf8),
+                let event = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
+                let id = event["id"] as? String, id.hasPrefix("TK-") else { continue }
+          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, created: timestamp)
+          } else if var ticket = states[id] {
+            if type == "status", let status = event["status"] as? String { ticket.status = status }
+            ticket.updated = timestamp
+            states[id] = ticket
+          }
+          if type == "action" || type == "comment" || type == "status" { newest = event }
+        }
+        let active = states.values.filter { $0.status != "done" && $0.status != "stopped" }
+        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)
+        // 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)"
+          self.doingLabel.stringValue   = "DOING \(doing.count)"
+          self.statusDetails = ["open": openList.map(row), "blocked": blockedList.map(row), "doing": doing.map(row)]
+          self.working.stringValue = "Working · \(workText.isEmpty ? "none" : workText)"
+          self.latest.stringValue = "Latest · \(latestText)"
+          self.workingDetails = doing.map(row)
+          self.latestDetail = latestText
+          self.panel.orderFrontRegardless()
+          if let popup = self.ticketPopup, !popup.isMiniaturized { popup.orderFrontRegardless() }
+        }
+      } catch {
+        DispatchQueue.main.async { self.latest.stringValue = "Latest · ledger unavailable" }
+      }
+    }
+  }
+
+  private func shortID(_ id: String) -> String {
+    let parts = id.split(separator: "-")
+    return parts.count > 1 ? "TK-\(parts[1])" : id
+  }
+
+  private func fiveWords(_ text: String) -> String {
+    text.split(whereSeparator: { $0.isWhitespace }).prefix(5).joined(separator: " ")
+  }
+
+  private func describe(_ event: [String: Any]?) -> String {
+    guard let event else { return "no activity" }
+    let id = shortID(event["id"] as? String ?? "")
+    let type = event["type"] as? String ?? "event"
+    let detail = (event["text"] as? String) ?? (event["status"] as? String) ?? "updated"
+    return "\(id) \(type) · \(fiveWords(detail))"
+  }
+}
+
+let app = NSApplication.shared
+private let delegate = BarController()
+app.delegate = delegate
+app.run()
diff --git a/run-ticket.sh.bak-20260911-142309 b/run-ticket.sh.bak-20260911-142309
new file mode 100755
index 00000000..6a401f04
--- /dev/null
+++ b/run-ticket.sh.bak-20260911-142309
@@ -0,0 +1,82 @@
+#!/usr/bin/env bash
+# run-ticket.sh — open ONE new iTerm2 window with `claude` pre-loaded to drive a
+# ticket to done (the "Run Now" action from the board; mirrors ~/.zsh/task.sh but
+# hardened: it takes only a VALIDATED ticket id + an optional cwd, so no agent-
+# authored ticket title ever reaches a shell). Steve, TK-10527 (2026-08-13):
+# "run ticket like the approvals opens a new iterm2 terminal with claude loaded first."
+#
+# Usage: run-ticket.sh <TK-id> [start-dir] [claude-sonnet|claude-opus|claude-haiku|claude-opus-5|claude-sonnet-5|claude-fable|codex|codex-gpt6|codex-gpt52|local-qwen-27b|local-qwen-14b]
+set -euo pipefail
+
+ID="${1:?usage: run-ticket.sh <TK-id> [start-dir]}"
+# Hard gate: id must be TK-<digits> optionally + a lowercase slug. Reject anything else.
+if [[ ! "$ID" =~ ^TK-[0-9]+(-[a-z0-9-]+)?$ ]]; then
+  echo "run-ticket: refusing unsafe id '$ID'" >&2; exit 2
+fi
+IDNUM="${ID#TK-}"; IDNUM="${IDNUM%%-*}"
+
+CWD="${2:-$HOME}"
+# only allow an existing dir under $HOME; else fall back home (never an arbitrary path)
+case "$CWD" in
+  "$HOME"|"$HOME"/*) [[ -d "$CWD" ]] || CWD="$HOME" ;;
+  *) CWD="$HOME" ;;
+esac
+
+PROFILE="${3:-claude-sonnet}"
+case "$PROFILE" in
+  claude-sonnet) RUNNER=(claude --model sonnet); AGENT_PREFIX=claude-run ;;
+  claude-opus)   RUNNER=(claude --model opus);   AGENT_PREFIX=claude-run ;;
+  claude-haiku)  RUNNER=(claude --model haiku);  AGENT_PREFIX=claude-run ;;
+  claude-opus-5)   RUNNER=(claude --model claude-opus-5);   AGENT_PREFIX=claude-run ;;
+  claude-sonnet-5) RUNNER=(claude --model claude-sonnet-5); AGENT_PREFIX=claude-run ;;
+  claude-fable)    RUNNER=(claude --model claude-fable-5-1); AGENT_PREFIX=claude-run ;;
+  codex)         RUNNER=(/Users/macstudio3/.local/bin/codex); AGENT_PREFIX=codex-run ;;
+  codex-gpt6)    RUNNER=(/Users/macstudio3/.local/bin/codex --model gpt-6-astra); AGENT_PREFIX=codex-run ;;
+  codex-gpt52)   RUNNER=(/Users/macstudio3/.local/bin/codex --model gpt-5.2); AGENT_PREFIX=codex-run ;;
+  local-qwen-27b) RUNNER=(/Users/macstudio3/.local/bin/codex --oss --local-provider ollama --model qwen3.8-27b-heretic:latest); AGENT_PREFIX=local-qwen-27b-run ;;
+  local-qwen-14b) RUNNER=(/Users/macstudio3/.local/bin/codex --oss --local-provider ollama --model qwen3:14b); AGENT_PREFIX=local-qwen-14b-run ;;
+  *) echo "run-ticket: refusing unknown profile '$PROFILE'" >&2; exit 2 ;;
+esac
+
+# The prompt references ONLY the safe id — the session pulls full context itself
+# via `tk show`. Encodes Steve's standing operating loop (tickets-first, copious
+# notes, gated actions draft to pending-approval).
+read -r -d '' PROMPT <<PROMPT_EOF || true
+export TK_AGENT=${AGENT_PREFIX}-${IDNUM}. You are driving ticket ${ID} to completion. First run: tk inbox (act on any DMs), then tk show ${ID} for full context. You now OWN this ticket: tk take ${ID}. Work it to done per AGENTS.md/CLAUDE.md's operating loop — tk log EVERY action, tk comment reasoning, dm peers with tk dm if you need help. Any customer-facing / destructive / spend / DNS / publish / send-to-list / remote-push / canonical dw_unified or Shopify write is GATED: draft it to ~/.claude/yolo-queue/pending-approval/ and STOP — never auto-fire. When the work is genuinely complete AND verified: tk done ${ID}.
+PROMPT_EOF
+
+# Build the inner shell payload, then escape for an AppleScript double-quoted literal
+# (same technique as task.sh): backslash first, then double-quote.
+# Title the window with the SHORT ticket number so iTerm's profile appends the running
+# job -> "TK-<n> (node)" = ticket + job ("both", per Steve TK-10573). A backgrounded
+# re-assert fires ~5s in (after zsh's shell-integration precmd hook has run, which would
+# otherwise overwrite the launch-time name) and again on a short loop for the first minute.
+retitle="( for i in 1 2 3 4; do sleep 5; \"$HOME/bin/tk-iterm-title\" TK-${IDNUM}; done ) >/dev/null 2>&1 &"
+printf -v runner_cmd '%q ' "${RUNNER[@]}"
+payload="cd $(printf %q "$CWD") && ${retitle} ${runner_cmd}$(printf %q "$PROMPT")"
+osa_title="TK-${IDNUM}"
+esc() { local s="$1"; s="${s//\\/\\\\}"; s="${s//\"/\\\"}"; printf '%s' "$s"; }
+osa_payload="$(esc "$payload")"
+osa_title="$(esc "$osa_title")"
+
+if [[ "${RUN_TICKET_DRY_RUN:-0}" == "1" ]]; then
+  printf 'profile=%s\nagent=%s-%s\npayload=%s\n' "$PROFILE" "$AGENT_PREFIX" "$IDNUM" "$payload"
+  exit 0
+fi
+
+/usr/bin/osascript <<OSA
+tell application "iTerm"
+  set newWin to (create window with default profile)
+  set newPane to current session of newWin
+  tell newPane
+    set name to "$osa_title"
+    write text "$osa_payload"
+  end tell
+  activate
+  select newWin
+end tell
+OSA
+# Belt-and-suspenders: LaunchServices foregrounding works from a pm2 background daemon
+# where a bare Apple-event `activate` may not. Harmless if iTerm is already front.
+/usr/bin/open -a iTerm 2>/dev/null || /usr/bin/open -a iTerm2 2>/dev/null || true
+echo "run-ticket: launched iTerm2 window for $ID (cwd $CWD)"

← 516f09bb run-ticket: add local-qwen-14b-mac1 runner to offload qwen t  ·  back to Ticket System  ·  ticket bar: show swap / qwen-27b sessions / ollama reachabil 73d9bb51 →