fix(macos): retain MainWindowController so the client stays alive

The main window controller was created as a local variable in
ConnectWindowController.authSucceeded and never retained — ARC
deallocated it immediately, which destroyed the VoiceCatClient
(connection silently dropped), nil'd every button's weak target
(clicks did nothing), and killed event delivery (channel list,
messages, voice never worked). Symptom: TOFU (in the retained
connect controller) worked, but everything in the main window
was a zombie shell.

Fix: store the MainWindowController in a new field on
ConnectWindowController (which AppDelegate retains for the app's
lifetime). Also add NSLog diagnostics in deinit/bootstrap/handleEvent
so lifecycle and event delivery are observable from the terminal
or Console.app.
This commit is contained in:
2026-06-18 15:52:13 +02:00
parent 33169b01fa
commit 69cd7d80ad
2 changed files with 13 additions and 0 deletions

View File

@@ -106,6 +106,10 @@ final class MainWindowController: NSWindowController, NSWindowDelegate {
required init?(coder: NSCoder) { fatalError() }
deinit {
NSLog("[VoiceCatMac] MainWindowController deinit — client and event handlers are gone")
}
// MARK: - UI construction
private func buildUI() {
@@ -447,6 +451,10 @@ final class MainWindowController: NSWindowController, NSWindowDelegate {
}
ownPermissions = client.getPermissions()
NSLog("[VoiceCatMac] bootstrap: channels=%d users=%d perms{admin=%d kick=%d} currentChannelId=%u",
channels.count, allUsers.count,
ownPermissions.isAdmin, ownPermissions.canKick, currentChannelId)
refreshChannelTree()
refreshUserList()
rebuildScopePicker()
@@ -459,6 +467,9 @@ final class MainWindowController: NSWindowController, NSWindowDelegate {
// MARK: - Event handling
private func handleEvent(_ event: VoiceCatEvent) {
NSLog("[VoiceCatMac] event type=%d result=%d userId=%u channelId=%u streamId=%u text=%@",
event.type.rawValue, event.result.rawValue, event.userId, event.channelId,
event.streamId, event.text ?? "(nil)")
switch event.type {
case .channelList:
channels = client.listChannels()