← back to Ticket System
desktop-bar: fix PASTE beacon showing every dot twice + double 'PASTE:' prefix
703dc9948ddc2e2d847ec9283ae1a958c733505d · 2026-09-16 16:27:23 -0700 · Steve Abrams
Steve hit this on screen: 3 real pastes rendered as 6 lines, two of them
identical pairs, one reading the absurd 'PASTE: PASTE waiting', and real
entries pushed past the 5-line cap into '...and 1 more'.
Two independent bugs, both in the beacon path:
1. scanOrangeDots() walks BOTH ~/.claude/tab-dots and ~/.codex/tab-dots, and
the dot writer writes the same tty into BOTH (measured: 67/67 pairs
byte-identical, separate inodes, zero divergence). Nothing keyed by tty, so
every dot was appended twice -- deterministic, not a race. Now deduped by
tty, first-wins over a fixed dir order.
2. The line was built as "PASTE: \(label)" while the label already ends with
the engine's 'PASTE waiting' (terminal_status.py maps orange -> that
string), printing PASTE twice. The emoji already says which colour it is.
Ships with a negative test (TicketBar.beacon-test.swift) that replays the
exact screenshot state and asserts 3 lines not 6, no 'PASTE:' prefix, and --
the case dedupe could plausibly break -- that 7 DISTINCT ttys still render
5 + 'and 2 more' rather than being swallowed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Files touched
A desktop-bar/TicketBar.beacon-test.swiftM desktop-bar/TicketBar.swift
Diff
commit 703dc9948ddc2e2d847ec9283ae1a958c733505d
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Sep 16 16:27:23 2026 -0700
desktop-bar: fix PASTE beacon showing every dot twice + double 'PASTE:' prefix
Steve hit this on screen: 3 real pastes rendered as 6 lines, two of them
identical pairs, one reading the absurd 'PASTE: PASTE waiting', and real
entries pushed past the 5-line cap into '...and 1 more'.
Two independent bugs, both in the beacon path:
1. scanOrangeDots() walks BOTH ~/.claude/tab-dots and ~/.codex/tab-dots, and
the dot writer writes the same tty into BOTH (measured: 67/67 pairs
byte-identical, separate inodes, zero divergence). Nothing keyed by tty, so
every dot was appended twice -- deterministic, not a race. Now deduped by
tty, first-wins over a fixed dir order.
2. The line was built as "PASTE: \(label)" while the label already ends with
the engine's 'PASTE waiting' (terminal_status.py maps orange -> that
string), printing PASTE twice. The emoji already says which colour it is.
Ships with a negative test (TicketBar.beacon-test.swift) that replays the
exact screenshot state and asserts 3 lines not 6, no 'PASTE:' prefix, and --
the case dedupe could plausibly break -- that 7 DISTINCT ttys still render
5 + 'and 2 more' rather than being swallowed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---
desktop-bar/TicketBar.beacon-test.swift | 38 +++++++++++++++++++++++++++++++++
desktop-bar/TicketBar.swift | 17 +++++++++++++--
2 files changed, 53 insertions(+), 2 deletions(-)
diff --git a/desktop-bar/TicketBar.beacon-test.swift b/desktop-bar/TicketBar.beacon-test.swift
new file mode 100644
index 00000000..f8d1e24a
--- /dev/null
+++ b/desktop-bar/TicketBar.beacon-test.swift
@@ -0,0 +1,38 @@
+import Foundation
+struct OrangeDot { let tty: String; let label: String }
+// --- verbatim copy of the FIXED logic under test ---
+func render(_ found: [OrangeDot]) -> [String] {
+ var seen = Set<String>()
+ let unique = found.filter { seen.insert($0.tty).inserted }
+ let dots = unique.sorted { $0.tty < $1.tty }
+ let cap = 5
+ var lines = dots.prefix(cap).map { "🟠 \($0.label) [\($0.tty)]" }
+ if dots.count > cap { lines.append("…and \(dots.count - cap) more") }
+ return lines
+}
+// --- the EXACT state from Steve's screenshot: 3 real pastes, each mirrored into both dirs ---
+let screenshot = [
+ OrangeDot(tty:"ttys016", label:"TK-11715 · PASTE waiting"), // .claude
+ OrangeDot(tty:"ttys016", label:"TK-11715 · PASTE waiting"), // .codex (the duplicate)
+ OrangeDot(tty:"ttys030", label:"TK-11812 · PASTE waiting"),
+ OrangeDot(tty:"ttys030", label:"TK-11812 · PASTE waiting"),
+ OrangeDot(tty:"ttys034", label:"PASTE waiting"),
+ OrangeDot(tty:"ttys034", label:"PASTE waiting"),
+]
+let out = render(screenshot)
+print("--- rendered ---"); out.forEach { print($0) }
+var fail = 0
+func check(_ name: String, _ cond: Bool) { print((cond ? "PASS " : "FAIL ") + name); if !cond { fail += 1 } }
+check("3 lines, not 6", out.count == 3)
+check("no '…and N more' (real entries no longer pushed past cap)", !out.contains { $0.contains("more") })
+check("ttys016 appears exactly once", out.filter { $0.contains("ttys016") }.count == 1)
+check("no double PASTE ('PASTE: PASTE')", !out.contains { $0.contains("PASTE: PASTE") })
+check("no 'PASTE:' prefix at all", !out.contains { $0.contains("PASTE:") })
+check("ticket id still shown", out.contains { $0.contains("TK-11715") })
+// NEGATIVE TEST: the detector must still SHOW distinct ttys — dedupe must not swallow real dots.
+let distinct = (1...7).map { OrangeDot(tty:"ttys10\($0)", label:"TK-\($0) · PASTE waiting") }
+let dout = render(distinct)
+check("NEGATIVE: 7 distinct ttys still render 5 + 'and 2 more'", dout.count == 6 && dout.last!.contains("2 more"))
+check("NEGATIVE: an empty set renders nothing", render([]).isEmpty)
+print(fail == 0 ? "\nALL PASS" : "\n\(fail) FAILED")
+exit(fail == 0 ? 0 : 1)
diff --git a/desktop-bar/TicketBar.swift b/desktop-bar/TicketBar.swift
index adc05613..52e1dd79 100644
--- a/desktop-bar/TicketBar.swift
+++ b/desktop-bar/TicketBar.swift
@@ -619,7 +619,16 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
found.append(OrangeDot(tty: tty, label: label.isEmpty ? tty : label))
}
}
- return found.sorted { $0.tty < $1.tty }
+ // DEDUPE BY TTY (TK-11864). `dirs` holds BOTH ~/.claude/tab-dots and ~/.codex/tab-dots,
+ // and the dot writer writes the SAME tty into BOTH (measured: 67/67 pairs byte-identical,
+ // separate inodes, zero divergence). So every orange dot was appended twice and the beacon
+ // rendered each paste on two lines -- 3 real pastes shown as 6, which also pushed real
+ // entries past the 5-line cap into "...and N more". One tty is ONE paste, so key by tty and
+ // keep the first hit; `dirs` order is fixed ([.claude, .codex]) so first-wins is
+ // deterministic, and on the (currently non-existent) divergent case .claude wins.
+ var seen = Set<String>()
+ let unique = found.filter { seen.insert($0.tty).inserted }
+ return unique.sorted { $0.tty < $1.tty }
}
private func buildBeacon() {
@@ -666,7 +675,11 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
return
}
let cap = 5
- var lines = dots.prefix(cap).map { "🟠 PASTE: \($0.label) [\($0.tty)]" }
+ // The dot label already ENDS with the engine's "PASTE waiting" string (terminal_status.py
+ // maps orange -> "PASTE waiting"), so prefixing "PASTE: " printed it twice:
+ // "PASTE: TK-11715 - PASTE waiting", and for an unlabelled dot the absurd "PASTE: PASTE
+ // waiting". The emoji already says which colour this is; the label carries the ticket.
+ var lines = dots.prefix(cap).map { "🟠 \($0.label) [\($0.tty)]" }
if dots.count > cap { lines.append("…and \(dots.count - cap) more") }
beaconLabel.stringValue = lines.joined(separator: "\n")
positionBeacon(lineCount: lines.count)
← e53b3918 Record ordered monitoring cycle with peer ownership changes
·
back to Ticket System
·
Record bounded ordered ticket monitoring cycle yf2320 65af0230 →