Add the window identification overlay
Every profile launches the same Claude Desktop binary with the same icon and title, so with two or more open there was no way to tell which window belonged to which profile at a glance. WindowOverlay pins a small tag — profile name, in that profile's own dot colour — to each running managed profile's window, tracked live via AXObserver notifications rather than polling. Only a persistent process can hold a watcher like that, which is exactly the gap the old one-shot CLI could never close. Shown for every running managed profile, not just when two or more are up: the tag is an identity marker, not merely a disambiguator. Never shown on "default", which isn't a shannoncoat profile at all. Positioned top-right, since top-left is where a window's close/minimize/ zoom controls live. Draggable in case that still obstructs something in a particular layout, on a .nonactivatingPanel so dragging it doesn't steal focus from the window underneath, with the offset persisted. Attachment retries for ~3s rather than requiring the target's first window to already exist: a cold Electron launch can take a second or more between the process starting — which is what triggers the update, via NSWorkspace's launch notification — and its first window existing. Losing that race previously meant the tag silently never appeared at all. Also ignores CHECKPOINT.md, a local session-handoff note. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate, LiveSt
|
||||
let liveState = LiveState()
|
||||
private var currentInfos: [ProfileInfo] = []
|
||||
private var collisionAlertShown = false
|
||||
private var windowOverlays: [String: WindowOverlay] = [:]
|
||||
private var pendingWindowOverlays: Set<String> = []
|
||||
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
menu.delegate = self
|
||||
@@ -50,6 +52,33 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate, LiveSt
|
||||
currentInfos = infos
|
||||
applyTitle(infos)
|
||||
ManageWindowController.shared.update(infos)
|
||||
updateWindowOverlays(infos)
|
||||
}
|
||||
|
||||
// Shown on every running *managed* profile — never on "default",
|
||||
// which isn't a shannoncoat profile at all, just the real underlying
|
||||
// Claude install. Removing an entry here deinits its WindowOverlay,
|
||||
// which tears down its AXObserver and hides the tag panel.
|
||||
private func updateWindowOverlays(_ infos: [ProfileInfo]) {
|
||||
let managed = infos.filter { $0.running && $0.profile.name != "default" }
|
||||
let managedNames = Set(managed.map(\.profile.name))
|
||||
windowOverlays = windowOverlays.filter { managedNames.contains($0.key) }
|
||||
pendingWindowOverlays.formIntersection(managedNames)
|
||||
|
||||
for info in managed
|
||||
where windowOverlays[info.profile.name] == nil && !pendingWindowOverlays.contains(info.profile.name) {
|
||||
guard let pid = info.pid else { continue }
|
||||
let name = info.profile.name
|
||||
pendingWindowOverlays.insert(name)
|
||||
WindowOverlay.attach(profileName: name, color: ProfileColor.dotColor(for: name), pid: pid) { [weak self] overlay in
|
||||
guard let self else { return }
|
||||
pendingWindowOverlays.remove(name)
|
||||
// The profile may have quit again while this was retrying
|
||||
// for its window — don't attach a stale overlay if so.
|
||||
guard currentInfos.contains(where: { $0.profile.name == name && $0.running }) else { return }
|
||||
windowOverlays[name] = overlay
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Two profiles sharing a dir is a fatal misconfiguration (would
|
||||
|
||||
Reference in New Issue
Block a user