chore: comment cleanup pass ahead of open-sourcing
Some checks failed
Build Linux Binaries / linux/amd64 (push) Has been cancelled
Build Linux Binaries / linux/arm64 (push) Has been cancelled

Removes leftover debug scaffolding (stray Console.WriteLine/NSLog traces,
dead nick_buf_ptr, a no-op --print-config flag now implemented for real),
fixes stale/misleading comments (channel passwords are no longer a "future
M5+" feature, a wrong cross-reference, a stale TlsContext::close() mention,
an incomplete BanRecord::subject_type doc, and a smoke test pointing at a
build/m1-dev preset that no longer exists), strips internal M1-M5 milestone
jargon from comments now that the roadmap is done, trims comments that just
restated the following line, and consolidates a few "why" explanations that
were duplicated 2-3 times in the same file.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 10:20:18 +01:00
parent bda37ec27b
commit bba605401d
50 changed files with 229 additions and 331 deletions

View File

@@ -61,7 +61,7 @@ public enum VoiceCatConnectionState: UInt32, Sendable, Equatable {
case tlsHandshake = 2
case authenticating = 3
case connected = 4
/// M4: handshake succeeded, waiting on `confirmServerIdentity()`.
/// Handshake succeeded, waiting on `confirmServerIdentity()`.
case verifyingIdentity = 5
public init(_ cValue: vc_connection_state) {
@@ -125,13 +125,13 @@ public enum VoiceCatEventType: UInt32, Sendable, Equatable {
case talkState = 9
case error = 10
case disconnected = 11
/// M4: reply to `joinChannel()` see `VoiceCatEvent.result` / `.channelId`.
/// Reply to `joinChannel()` see `VoiceCatEvent.result` / `.channelId`.
case joinResult = 12
/// M4: the TOFU server-identity gate see `VoiceCatEvent.tofuStatus` / `.text`.
/// The TOFU server-identity gate see `VoiceCatEvent.tofuStatus` / `.text`.
case serverIdentity = 13
/// M5: async result for moderation/admin/channel operations.
/// Async result for moderation/admin/channel operations.
case genericResult = 14
/// M5: reply to `requestAccountList()` call `listAccounts()` to read.
/// Reply to `requestAccountList()` call `listAccounts()` to read.
case accountList = 15
/// Voice-plane subscription state. `u32a` = 1 (subscribed) or 0 (unsubscribed).
case voiceState = 16

View File

@@ -75,7 +75,7 @@ public struct User: Sendable, Equatable, Identifiable {
}
}
/// Permission bitset mirrors `vc_permissions` (M5).
/// Permission bitset mirrors `vc_permissions`.
public struct Permissions: Sendable, Equatable {
public let canCreateTempChannel: Bool
public let canKick: Bool
@@ -91,7 +91,7 @@ public struct Permissions: Sendable, Equatable {
}
}
/// Account entry mirrors `vc_account` (M5, reply to `listAccounts()`).
/// Account entry mirrors `vc_account` (reply to `listAccounts()`).
public struct Account: Sendable, Equatable {
public let username: String
public let isAdmin: Bool

View File

@@ -219,7 +219,7 @@ public final class VoiceCatClient {
VoiceCatResult(vc_authenticate_user(handle, username, password))
}
// MARK: - TOFU server-identity gate (M4)
// MARK: - TOFU server-identity gate
/// Accept or reject the pending server-identity check. Call after a `.serverIdentity`
/// event. `accept=true` on firstConnect/mismatch updates the pin file and proceeds;
@@ -413,7 +413,7 @@ public final class VoiceCatClient {
/// Global playback volume applied after mixing all remote streams. gain 0.0 = silent,
/// 1.0 = unity (default), >1.0 amplifies. Always LOCAL no protocol traffic. Mirrors the
/// Windows client's `SetOutputVolume` and the C ABI `vc_set_output_volume` added in M5.
/// Windows client's `SetOutputVolume` and the C ABI `vc_set_output_volume`.
@discardableResult
public func setOutputVolume(_ gain: Float) -> VoiceCatResult {
VoiceCatResult(vc_set_output_volume(handle, gain < 0 ? 0 : gain))
@@ -500,7 +500,7 @@ public final class VoiceCatClient {
return Marshaling.devices(&native)
}
// MARK: - M5: Moderation
// MARK: - Moderation
@discardableResult
public func kickUser(_ userId: UInt32, reason: String? = nil) -> VoiceCatResult {
@@ -535,7 +535,7 @@ public final class VoiceCatClient {
VoiceCatResult(vc_move_user(handle, userId, channelId))
}
// MARK: - M5: Channel admin
// MARK: - Channel admin
@discardableResult
public func createChannel(_ info: ChannelEdit) -> VoiceCatResult {
@@ -558,7 +558,7 @@ public final class VoiceCatClient {
VoiceCatResult(vc_delete_channel(handle, channelId))
}
// MARK: - M5: Account admin
// MARK: - Account admin
@discardableResult
public func createAccount(_ username: String, password: String) -> VoiceCatResult {

View File

@@ -64,7 +64,7 @@ private final class ServerHarness {
}
self.port = port
// Provision a known admin account for moderation/admin tests (M5).
// Provision a known admin account for moderation/admin tests.
let adminURL = URL(fileURLWithPath: repoRoot)
.appendingPathComponent("build/dev/bin/voicecat-admin")
guard FileManager.default.isExecutableFile(atPath: adminURL.path) else {
@@ -252,12 +252,12 @@ final class VoiceCatClientSmokeTests: XCTestCase {
XCTAssertTrue(channels.contains { $0.id == 1 && $0.name == "Lobby" },
"expected Lobby (channel 1) in \(channels.map { $0.name })")
// M5: permissions getter round-trip.
// Permissions getter round-trip.
let perms = client.getPermissions()
XCTAssertFalse(perms.isAdmin)
XCTAssertFalse(perms.canKick)
// M5: guest ListAccounts is rejected by the server with a GenericResult proves the
// Guest ListAccounts is rejected by the server with a GenericResult proves the
// moderation wrapper path works end-to-end through the Swift interop layer.
events.removeAll()
XCTAssertEqual(client.requestAccountList(), .ok)

View File

@@ -116,16 +116,6 @@ final class AppState {
ServerListStore.shared.save(servers)
}
// MARK: - Teardown
/// `AppState` is the @Observable app root owned by the SwiftUI `App`; it lives for the
/// whole app process and is torn down only on process exit, at which point OS cleanup
/// suffices. Reconnect state (the in-flight `Task` and the `NWPathMonitor`) is cancelled
/// via `cancelReconnect()` driven by `disconnect()`/`cancelConnect()` and on successful
/// restore those run on user-initiated teardown, which is the only path that matters.
/// (The `Task` captures `[weak self]` and guards on `nil`/`userInitiatedDisconnect`, so a
/// stray task left running when AppState is gone is a no-op; the monitor similarly guards.)
// MARK: - Connect flow
/// Public connect entry. Always starts a fresh session (no restore).
@@ -245,6 +235,13 @@ final class AppState {
/// Cancel any in-flight reconnect task and stop the path monitor. Safe to call when nothing
/// is armed (no-op). Does NOT touch `userInitiatedDisconnect` or `lastSession` callers set
/// those as needed (disconnect/cancelConnect clear them; scheduleReconnect keeps them).
///
/// `AppState` is the @Observable app root owned by the SwiftUI `App`; it lives for the
/// whole app process and is torn down only on process exit, at which point OS cleanup
/// suffices. This method is driven by `disconnect()`/`cancelConnect()` and on successful
/// restore those run on user-initiated teardown, which is the only path that matters.
/// (The reconnect `Task` captures `[weak self]` and guards on `nil`/`userInitiatedDisconnect`,
/// so a stray task left running when AppState is gone is a no-op; the monitor similarly guards.)
private func cancelReconnect() {
reconnectTask?.cancel()
reconnectTask = nil
@@ -310,15 +307,12 @@ final class AppState {
if prevSig == nil { return }
if self.session != nil {
// Connected tear down + reconnect on a meaningful path change.
// `.unsatisfied` (all radios off) OR a primary-interface change (Wi-Ficellular)
// almost always breaks the live TCP connection; reconnecting proactively beats
// waiting for the C core's keepalive/reaper timeout.
// See this method's doc comment for why these conditions trigger a
// proactive reconnect.
if path.status != .satisfied || sig != prevSig {
self.proactiveReconnect()
}
} else if self.lastSession != nil {
// Mid-reconnect a path is available again; fast-fresh the next attempt.
if path.status == .satisfied {
self.reconnectAttempt = 0
self.scheduleReconnect()

View File

@@ -163,13 +163,8 @@ final class IOSAudioRouter: ObservableObject {
/// burns CPU and cycles the audio session on/off (the "glitching" bug).
private var isApplyingConfiguration = false
/// Last `overrideOutputAudioPort` value we successfully applied (`.none` or `.speaker`),
/// so `applyA2dpSpeakerFallback` can skip a redundant `overrideOutputAudioPort` call.
/// That call fires a `.override` route-change notification on every invocation, and on
/// an AirPods disconnect the fallback is invoked once per `recoverAudio()` which
/// itself fires on every non-skipped route change so without this guard the override
/// call and the route-change handler ping-pong: the AirPods-disconnect reinitialize
/// loop (each iteration also rebuilds the AVAudioEngine via reconfigure()).
/// Last `overrideOutputAudioPort` value we successfully applied (`.none` or `.speaker`).
/// See `applyA2dpSpeakerFallback`'s doc comment for why this cache exists.
/// `nil` = "unknown / assume not applied" reset at the top of `applyConfiguration()`
/// because `setCategory` can reset the override out from under us, and on first run.
private var lastAppliedOutputOverride: AVAudioSession.PortOverride?
@@ -382,13 +377,8 @@ final class IOSAudioRouter: ObservableObject {
// 3. Input & mic-capsule configuration.
if captureChannels == .stereo {
// Stereo: enable the built-in mic's .stereo polar pattern AND anchor the input
// route explicitly via setPreferredInput + setInputDataSource. With HFP disabled
// the system routes input to the built-in mic, but without the explicit
// preferred-input anchor the route can collapse during the mode switch
// (.voiceChat .default) and the output dies. The channel count is carried by the
// engine's mic tap + vc_set_capture_channels, NOT via
// setPreferredInputNumberOfChannels(2) that call collapses the A2DP output route.
// See configureStereoCapture's doc comment for the full stereo-capture recipe
// and why each step is necessary.
configureStereoCapture(session: session)
} else if let portId = selectedInputPortId, !portId.isEmpty,
let port = session.availableInputs?.first(where: { $0.uid == portId }) {
@@ -434,8 +424,6 @@ final class IOSAudioRouter: ObservableObject {
do {
try builtIn.setPreferredDataSource(stereoSource)
try stereoSource.setPreferredPolarPattern(.stereo)
// Anchor the input route explicitly; without it the route can collapse during
// the mode switch (.voiceChat .default) and the A2DP output dies.
try session.setPreferredInput(builtIn)
// Commit the data source at the session level. setPreferredDataSource alone only
// sets the port-level preference; setInputDataSource makes it the active source.
@@ -697,8 +685,6 @@ final class IOSAudioRouter: ObservableObject {
}
let desired: AVAudioSession.PortOverride = hasExternalOutput ? .none : .speaker
if desired == lastAppliedOutputOverride {
// Already in the desired state calling overrideOutputAudioPort again would just
// fire a redundant `.override` route-change notification (the loop driver).
logger.debug("A2DP fallback — desired=\(self.overrideLabel(desired)) already applied, skipping")
return
}

View File

@@ -54,11 +54,11 @@ final class SessionState {
var accounts: [Account] = []
var devices: [Device] = []
/// Back-reference to the app state. Once `SessionState.init` overwrites `client.onEvent`
/// (`SessionState.swift:87`), `AppState.handleConnectEvent` no longer receives per-session
/// events so the `.disconnected` event for a LIVE session arrives here in `handleEvent`,
/// not in AppState. This weak ref lets us hand the disconnect back to AppState (which owns
/// the reconnect state machine) so the auto-reconnect fires. Set by AppState on auth success.
/// Back-reference to the app state. Once `SessionState.init` overwrites `client.onEvent`,
/// `AppState.handleConnectEvent` no longer receives per-session events so the
/// `.disconnected` event for a LIVE session arrives here in `handleEvent`, not in AppState.
/// This weak ref lets us hand the disconnect back to AppState (which owns the reconnect
/// state machine) so the auto-reconnect fires. Set by AppState on auth success.
weak var appState: AppState?
// MARK: - Reconnect restore state
@@ -260,17 +260,18 @@ final class SessionState {
// MARK: - Self-channel / server-mute sync
/// Sync currentChannelId from the self user's channelId in the user list. Mirrors macOS
/// MainWindowController.swift:461,491,522. The server auto-places every authed user into
/// the Lobby (channel 1) on connect, but without this sync currentChannelId stays 0 and
/// the mic button (gated on currentChannelId == 0) stays permanently dimmed.
/// MainWindowController's bootstrap/event-handling sync. The server auto-places every
/// authed user into the Lobby (channel 1) on connect, but without this sync
/// currentChannelId stays 0 and the mic button (gated on currentChannelId == 0) stays
/// permanently dimmed.
private func syncSelfChannel() {
if let me = users.first(where: { $0.id == selfUserId }) {
currentChannelId = me.channelId
}
}
/// Apply server-side mute/deafen state mirrors macOS MainWindowController.swift:693-700.
/// iOS was previously ignoring server mute/deafen entirely.
/// Apply server-side mute/deafen state mirrors macOS MainWindowController's handling
/// of UserEvent.UPDATED for the self user.
private func applyServerMuteState(muted: Bool, deafened: Bool) {
if muted && !voiceState.serverMuted { addActivity("You have been server-muted") }
if deafened && !voiceState.serverDeafened { addActivity("You have been server-deafened") }

View File

@@ -48,7 +48,6 @@ struct PerUserTuningView: View {
}
}
.onAppear {
// Load from first stream if available
let streams = streamsForUser
if let first = streams.first {
let (_, state) = session.client.getRemoteStream(userId: user.id, streamId: first.id)

View File

@@ -44,7 +44,6 @@ final class ConnectWindowController: NSWindowController, NSWindowDelegate {
private func buildUI() {
guard let contentView = window?.contentView else { return }
// Server list
let col = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("server"))
col.title = "Saved Servers"
serverTableView.addTableColumn(col)
@@ -61,7 +60,6 @@ final class ConnectWindowController: NSWindowController, NSWindowDelegate {
serverScrollView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(serverScrollView)
// Buttons row
configureButton(addButton, title: "Add…", action: #selector(addClicked))
configureButton(editButton, title: "Edit…", action: #selector(editClicked))
configureButton(removeButton, title: "Remove", action: #selector(removeClicked))
@@ -72,13 +70,11 @@ final class ConnectWindowController: NSWindowController, NSWindowDelegate {
buttonStack.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(buttonStack)
// Status
statusLabel.translatesAutoresizingMaskIntoConstraints = false
statusLabel.textColor = .secondaryLabelColor
statusLabel.setAccessibilityLabel("Connection status")
contentView.addSubview(statusLabel)
// Connect button
connectButton.title = "Connect"
connectButton.bezelStyle = .rounded
connectButton.keyEquivalent = "\r"
@@ -376,7 +372,3 @@ extension ConnectWindowController: NSTableViewDataSource, NSTableViewDelegate {
return cell
}
}
// MARK: - Helper

View File

@@ -185,9 +185,7 @@ final class MainWindowController: NSWindowController, NSWindowDelegate {
required init?(coder: NSCoder) { fatalError() }
deinit {
NSLog("[VoiceCatMac] MainWindowController deinit — client and event handlers are gone")
}
deinit {}
// MARK: - UI construction
@@ -455,10 +453,6 @@ 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)
// Apply initial output volume (default 80% matches Windows client)
client.setOutputVolume(0.8)
@@ -477,9 +471,6 @@ 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()
@@ -1067,7 +1058,7 @@ final class MainWindowController: NSWindowController, NSWindowDelegate {
composeField.stringValue = ""
}
// MARK: - M5: Moderation helpers
// MARK: - Moderation helpers
private func moveUser(_ user: User) {
let sheet = MoveUserSheet(channels: channels, currentChannelId: user.channelId)
@@ -1241,7 +1232,6 @@ final class MainWindowController: NSWindowController, NSWindowDelegate {
presentSheet(sheet)
}
/// Open a PM window from the user context menu.
private func openPmWindow(_ user: User) {
getOrOpenPmWindow(user.id)
}
@@ -1288,17 +1278,13 @@ final class MainWindowController: NSWindowController, NSWindowDelegate {
func windowWillClose(_ notification: Notification) {
if let mon = pttMonitor { NSEvent.removeMonitor(mon) }
NotificationCenter.default.removeObserver(self)
// Close all PM windows
for (_, pmWin) in pmWindows { pmWin.close() }
pmWindows.removeAll()
// Close settings window
settingsWindowController?.close()
settingsWindowController = nil
// Remove app menus we added
if let item = voiceMenuItem { NSApp.mainMenu?.removeItem(item) }
if let item = messagesMenuItem { NSApp.mainMenu?.removeItem(item) }
if let item = adminMenuItem { NSApp.mainMenu?.removeItem(item) }
// Remove Settings menu item + separator from app menu
if let appMenu = NSApp.mainMenu?.item(at: 0)?.submenu {
if let item = settingsMenuItem { appMenu.removeItem(item) }
// Remove the separator we inserted before Quit