diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16f9eb2f0..076e3169c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -332,8 +332,8 @@ jobs: # Build the Debug app + UI test bundle once and share it with every job that # only needs to *run* it (socket-integration-tests, tests-build-and-lag, - # ui-regressions). Those three jobs used to each pay their own ~7 min - # "Build app" step for an identical build; this collapses that to one. + # ui-regressions). Building once here saves each of those three jobs + # its own ~7 min "Build app" step for an identical build. # unit-tests keeps building its own artifacts: it uses the programa-unit # scheme/test host, a different target from the programa app scheme built # here, so its output isn't the same product. @@ -1175,13 +1175,11 @@ jobs: DISPLAY_DONE="/tmp/programa-ui-test-display-${TOKEN}.done" HELPER_LOG="/tmp/programa-ui-test-display-${TOKEN}-helper.log" - # This churn test used to spin up its own second CGVirtualDisplay - # alongside the persistent one from the previous step. A headless CI - # session only reliably holds one live CGVirtualDisplay at a time — - # the second instantiation was reproducibly failing with "Failed to - # create CGVirtualDisplay" on both retry attempts (a masked bug: an - # earlier version of this step didn't fail the job when that - # happened, so it silently never ran the regression test at all). + # This churn test must not spin up a second CGVirtualDisplay alongside + # the persistent one from the previous step. A headless CI session + # only reliably holds one live CGVirtualDisplay at a time; a second + # instantiation fails with "Failed to create CGVirtualDisplay" on + # every retry. # Reuse the persistent display's ID instead of creating a second one. PERSISTENT_ID_PATH="/tmp/programa-vdisplay-persistent.id" if [ ! -f "$PERSISTENT_ID_PATH" ]; then diff --git a/CLI/CLI+AgentEventAdapters.swift b/CLI/CLI+AgentEventAdapters.swift index 4305fec3a..a29e843b7 100644 --- a/CLI/CLI+AgentEventAdapters.swift +++ b/CLI/CLI+AgentEventAdapters.swift @@ -37,9 +37,9 @@ extension ProgramaCLI { } /// Reports a surface's agent activity state, then a matching `agent.event`, in that - /// order, with the same workspace/surface ids -- combines the two adjacent calls that - /// used to sit back-to-back at each hook call site in CLI+Hooks.swift. Same two socket - /// sends, same order, same arguments as before; purely a call-site collapse. + /// order, with the same workspace/surface ids -- combines the two adjacent calls a hook + /// call site in CLI+Hooks.swift would otherwise make back-to-back: two socket + /// sends, in that order, with the same arguments. func reportAgentStateAndEvent( client: SocketClient, provider: String, @@ -60,9 +60,9 @@ extension ProgramaCLI { } /// Sends `agent.needs_input` (docs/plans/agent-state-unification.md T2): one atomic - /// socket call replacing the `notification.create_for_target` + + /// socket call covering the `notification.create_for_target` + /// `workspace.set_status` "Needs input" + `surface.report_agent_state` trio a hook - /// used to send independently for the same "needs input" moment. + /// would otherwise send independently for the same "needs input" moment. func reportAgentNeedsInput( client: SocketClient, provider: String, @@ -107,8 +107,7 @@ extension ProgramaCLI { } /// Maps a classified notification subtitle ("Permission" / "Waiting") to the matching - /// `agent.event` type. Mirrors the `switch summary.subtitle` blocks that used to sit - /// inline at the Claude Code and Codex notification-hook call sites in CLI+Hooks.swift. + /// `agent.event` type. Shared by the Claude Code and Codex notification-hook call sites in CLI+Hooks.swift. func reportAgentEventForClassifiedNotificationSubtitle( client: SocketClient, provider: String, diff --git a/CLI/CLI+Hooks.swift b/CLI/CLI+Hooks.swift index 5533b8b13..b18291729 100644 --- a/CLI/CLI+Hooks.swift +++ b/CLI/CLI+Hooks.swift @@ -730,7 +730,7 @@ extension ProgramaCLI { let mappedSession = ctx.parsed.sessionId.flatMap { ctx.store.lookup(sessionId: $0) } // Deliberately best-effort, for the same reason as the surfaceId resolution - // further down. A throw here used to abort the hook before any of the three + // further down. A throw here would abort the hook before any of the three // independent clears below, while Claude Code continued with a stale red // "blocked" badge that nothing would clear. // diff --git a/CLI/programa.swift b/CLI/programa.swift index 5f175b369..133fa9583 100644 --- a/CLI/programa.swift +++ b/CLI/programa.swift @@ -357,7 +357,7 @@ enum CLIProcessRunner { /// Execution context passed to a `CommandDescriptor`'s `execute` closure. /// -/// Bundles everything a command body previously read from `run()`'s locals +/// Bundles everything a command body reads from `run()`'s locals /// (commandArgs, client, jsonOutput, idFormat, windowId, and the literal /// command name, needed by handlers shared across several names such as /// the tmux-compat group). @@ -431,7 +431,7 @@ struct CLIArgumentGrammar { /// Single source of truth for a CLI command's name(s), its one-line entry in /// the grouped `Commands:` help block, and how it executes. /// -/// This collapses what used to be independently maintained in three places: +/// This one table covers what would otherwise be three separate places: /// the kebab-case dispatch switch, the free-text `usage()` help string, and /// (implicitly) the "is this a known command" check used for unknown-command /// handling. A command added to the table automatically gets dispatch, @@ -4402,7 +4402,7 @@ struct ProgramaCLI { return trimmed.isEmpty ? nil : trimmed } - // MARK: - Race command (fan one prompt across N agents in isolated worktrees, v1: spawn only) + // MARK: - Race command (fan one prompt across N agents in isolated worktrees, spawn only) /// The only agents `race` supports: the three that report state via lifecycle hooks /// (Claude Code, OpenCode, Codex). Racing needs a trustworthy idle signal, which @@ -4411,14 +4411,7 @@ struct ProgramaCLI { /// elsewhere in the app for status badges. private static let raceSupportedAgents: [String] = ["claude", "opencode", "codex"] - /// Builds the shell command line that starts an agent with `prompt` as its initial task. - /// Reuses the same bare binary names Programa already invokes elsewhere for these agents - /// (`initialTerminalInput: "claude\n"` for the "New Claude Code Workspace" shortcut in - /// AppDelegate.swift, and the `process_names` in Resources/AgentDetection/*.json), just - /// with the prompt appended as a shell-quoted positional argument so the agent starts - /// working immediately instead of sitting at its own empty prompt. No prior art exists in - /// this codebase for "agent + initial prompt argument" specifically, so this is a new, - /// minimal one-place mapping rather than an existing construction being reused verbatim. + /// Builds the shell command line that starts an agent with `prompt` as a shell-quoted positional argument. private func raceAgentLaunchCommand(agent: String, prompt: String) -> String { "\(agent) \(shellSingleQuote(prompt))" } @@ -4477,9 +4470,8 @@ struct ProgramaCLI { // Fail immediately, before creating anything, if the cwd isn't inside a git repo. let repo = try resolveWorktreeRepoRoot(explicit: nil) - // Racing twice in a row used to collide on every index, because the branches were - // always `/1.../N`. Start after whatever `/` branches - // already exist so a second run just continues the numbering. + // Start after whatever `/` branches already exist so repeated runs + // continue the numbering instead of colliding. let taken = existingRaceIndexes(repo: repo, prefix: prefix) let firstIndex = (taken.max() ?? 0) + 1 let indexes = Array(firstIndex..<(firstIndex + n)) diff --git a/Sources/AgentActivityState.swift b/Sources/AgentActivityState.swift index f137a984d..3fe9a4594 100644 --- a/Sources/AgentActivityState.swift +++ b/Sources/AgentActivityState.swift @@ -121,9 +121,9 @@ extension AgentPresence { } } -/// The single derived glyph/label/tint for a workspace's aggregate agent state, replacing the -/// three independent renderings that used to read `panelAgentStates`, `statusEntries["claude_code"]`, -/// and the notification subtitle separately. `TabItemView` is the only consumer. +/// The single derived glyph/label/tint for a workspace's aggregate agent state, derived once from +/// `panelAgentStates`, `statusEntries["claude_code"]`, and the notification subtitle +/// so no consumer renders them independently. `TabItemView` is the only consumer. struct SidebarAgentIndicator: Equatable { enum Tint: Equatable { case blocked diff --git a/Sources/AppDelegate+SessionSnapshotPersistence.swift b/Sources/AppDelegate+SessionSnapshotPersistence.swift index d2230a113..74e8d31d2 100644 --- a/Sources/AppDelegate+SessionSnapshotPersistence.swift +++ b/Sources/AppDelegate+SessionSnapshotPersistence.swift @@ -2,9 +2,9 @@ import AppKit import Bonsplit import Foundation -/// Session snapshot build/save/persist machinery, extracted from `AppDelegate.swift` to stay -/// under its structural line budget (`scripts/check-structural-budgets.py`). Behavior-neutral -/// split: everything here still runs on `AppDelegate`, just from a sibling file. +/// Session snapshot build/save/persist machinery, kept out of `AppDelegate.swift` to stay +/// under its structural line budget (`scripts/check-structural-budgets.py`). +/// Everything here runs on `AppDelegate`, from a sibling file. extension AppDelegate { /// A synchronous save returns the disk-write result; an asynchronous save returns queue /// acceptance and reports the completed disk write through its main-queue completion. diff --git a/Sources/AppDelegate+UITestCmdClick.swift b/Sources/AppDelegate+UITestCmdClick.swift index c51528746..942c0b699 100644 --- a/Sources/AppDelegate+UITestCmdClick.swift +++ b/Sources/AppDelegate+UITestCmdClick.swift @@ -4,7 +4,6 @@ import SwiftUI import Bonsplit import CoreServices import UserNotifications -import Combine import ObjectiveC.runtime import Darwin diff --git a/Sources/AppDelegate+UITestStressWorkspaces.swift b/Sources/AppDelegate+UITestStressWorkspaces.swift index eea5fdaec..a6efaf053 100644 --- a/Sources/AppDelegate+UITestStressWorkspaces.swift +++ b/Sources/AppDelegate+UITestStressWorkspaces.swift @@ -4,7 +4,6 @@ import SwiftUI import Bonsplit import CoreServices import UserNotifications -import Combine import ObjectiveC.runtime import Darwin diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index a700a49ea..5dd94b641 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -7,7 +7,6 @@ import UserNotifications import Combine import ObjectiveC.runtime import Darwin -import Security private enum ProgramaThemeNotifications { static let reloadConfig = Notification.Name("com.darkroom.programa.themes.reload-config") @@ -2264,15 +2263,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate, @preconcurrency UNUser /// Runs everything that must be re-asserted after the Mac wakes from sleep. /// See `SessionEscrow.swift`'s `serve` death-detection loop for the - /// confirmed root cause this responds to: the escrow holder used to - /// measure heartbeat staleness with wall-clock time, so any sleep longer - /// than `SessionEscrowPolicy.heartbeatStaleAfter` (6s) made it declare - /// every escrowed PTY dead the instant the app woke back up and start - /// reading from them concurrently with the still-alive app, corrupting - /// terminal output until the app was force-quit. That's fixed at the - /// source (mach-uptime-based staleness, which pauses across sleep), but - /// this handler still proactively refreshes the escrow connection and - /// renderer state on every wake as defense in depth and to leave a + /// failure this guards against: if the escrow holder measured heartbeat + /// staleness with wall-clock time, any sleep longer than + /// `SessionEscrowPolicy.heartbeatStaleAfter` (6s) would make it declare + /// every escrowed PTY dead the instant the app woke and start reading + /// from them concurrently with the still-alive app, corrupting terminal + /// output until force-quit. Staleness is mach-uptime based (pauses across + /// sleep), and this handler additionally refreshes the escrow connection + /// and renderer state on every wake as defense in depth and to leave a /// record in the always-on diagnostics log if it ever happens again. private func handleSystemDidWake() { dilog("wake.lifecycle", "didWake") diff --git a/Sources/BackgroundLogWriter.swift b/Sources/BackgroundLogWriter.swift index 326404d64..e7c71a274 100644 --- a/Sources/BackgroundLogWriter.swift +++ b/Sources/BackgroundLogWriter.swift @@ -1,6 +1,6 @@ import Foundation -// MARK: - BackgroundLogWriter (ported from upstream cmux cb2129a5a1) +// MARK: - BackgroundLogWriter // // Avoids a per-call FileManager.fileExists + FileHandle(forWritingTo:) // open -> seekToEnd -> write -> close pattern, which would run synchronously under a lock diff --git a/Sources/ClosedTerminalUndoStore.swift b/Sources/ClosedTerminalUndoStore.swift index d69478674..5cc18ecf0 100644 --- a/Sources/ClosedTerminalUndoStore.swift +++ b/Sources/ClosedTerminalUndoStore.swift @@ -1,7 +1,6 @@ import Foundation -/// Generic, closure-based undo staging for terminal panel closes (issue #140, ported from -/// upstream cmux's task-undo-close-grace-period). Closing a terminal stages it here instead of +/// Generic, closure-based undo staging for terminal panel closes (issue #140). Closing a terminal stages it here instead of /// tearing it down immediately; if nothing restores it within the grace period, `finalize` runs /// the real teardown. /// @@ -37,7 +36,7 @@ final class ClosedTerminalUndoStore { /// Stages a close for undo. `restore` reattaches the closed panel; `finalize` performs the /// real teardown once the grace period elapses without a restore. Returns an opaque id that - /// can be used to cancel staging early (not currently needed by any caller). + /// can be used to cancel staging early. @discardableResult func stage(restore: @escaping () -> Void, finalize: @escaping () -> Void) -> UUID { let id = UUID() diff --git a/Sources/CommandPaletteController.swift b/Sources/CommandPaletteController.swift index 28d554893..335842eb0 100644 --- a/Sources/CommandPaletteController.swift +++ b/Sources/CommandPaletteController.swift @@ -1,7 +1,7 @@ // Command-palette state ownership. // -// CommandPaletteController owns every @State property that used to live on -// ContentView and is exclusively used by the command palette (query, mode, +// CommandPaletteController owns every piece of palette state that is +// exclusively used by the command palette (query, mode, // search corpus/results, rename/workspace-description drafts, focus-restore // targets, usage history, etc.). ContentView holds a single // `@StateObject private var commandPaletteController` and exposes each @@ -15,13 +15,12 @@ // SwiftUI View-only property wrapper and cannot be hosted on an // ObservableObject. // -// Access-level widening: CommandPaletteMode, CommandPaletteRestoreFocusTarget, +// CommandPaletteMode, CommandPaletteRestoreFocusTarget, // CommandPaletteTextSelectionBehavior, and CommandPaletteMultilineTextEditorRepresentable -// were `private` nested types inside ContentView; widened to internal (dropped -// the `private` modifier) so this controller (a different file) can reference -// them. No other behavior change. CommandPaletteCommand, CommandPaletteSearchResult, +// are internal nested types of ContentView so this controller (a different file) can +// reference them. CommandPaletteCommand, CommandPaletteSearchResult, // CommandPaletteListScope, CommandPalettePendingActivation, and -// CommandPaletteUsageEntry were already internal nested types; referenced here +// CommandPaletteUsageEntry are also internal nested types; referenced here // via `ContentView.` qualification since nested-type name lookup requires it // from outside the enclosing type's lexical scope. diff --git a/Sources/CommandPaletteSearchEngine.swift b/Sources/CommandPaletteSearchEngine.swift index 1d77a9892..9c57a0ba7 100644 --- a/Sources/CommandPaletteSearchEngine.swift +++ b/Sources/CommandPaletteSearchEngine.swift @@ -172,7 +172,7 @@ enum CommandPaletteFuzzyMatcher { // the two interpretations point at candidates of very different lengths/title // positions: `distancePenalty`/`trailingPenalty` (both scale with the matched // word's position and the candidate's overall length) can swing by hundreds of - // points, which used to swamp a mere 10-point base gap and let a shorter, + // points, which would swamp a mere 10-point base gap and let a shorter, // less-relevant command's stray-extra-character interpretation outrank a // longer command's genuine one-character-omitted match on its own keyword. return 260 diff --git a/Sources/ContentView+CommandPalette.swift b/Sources/ContentView+CommandPalette.swift index e50ad0f7b..0f44b8e11 100644 --- a/Sources/ContentView+CommandPalette.swift +++ b/Sources/ContentView+CommandPalette.swift @@ -9,13 +9,8 @@ // remain on the `ContentView` struct in ContentView.swift, since SwiftUI's // `@State` cannot be split across files without a larger controller-based // rewrite (tracked as a follow-up). -// -// A handful of members these functions call into were widened from `private` -// to `internal` (no other behavior change) so this extension can see them; -// see the CV1 PR description for the exact list. import AppKit -import Combine import SwiftUI extension ContentView { diff --git a/Sources/ContentView+SidebarResizer.swift b/Sources/ContentView+SidebarResizer.swift index 6c7ba82c7..1ae99ee6e 100644 --- a/Sources/ContentView+SidebarResizer.swift +++ b/Sources/ContentView+SidebarResizer.swift @@ -2,8 +2,8 @@ // The backing @State stays on `ContentView` (SwiftUI requires stored properties on the primary // declaration); those properties, plus `SidebarResizerHandle`, `updateSidebarResizerBandState`, // `installSidebarResizerPointerMonitorIfNeeded`, `removeSidebarResizerPointerMonitor`, and -// `sidebarResizerOverlay`, were widened from `private` to internal so this extension and -// ContentView.swift's view body can both see them. See the PR description for the exact list. +// `sidebarResizerOverlay`, are internal so this extension and +// ContentView.swift's view body can both see them. import AppKit import SwiftUI diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 2836b1658..63d961537 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -4,7 +4,6 @@ import Combine import ImageIO import SwiftUI import ObjectiveC -import UniformTypeIdentifiers struct ContentView: View { @ObservedObject var updateViewModel: UpdateViewModel diff --git a/Sources/DebugWindows.swift b/Sources/DebugWindows.swift index 0ca01d769..10513dba1 100644 --- a/Sources/DebugWindows.swift +++ b/Sources/DebugWindows.swift @@ -2,7 +2,6 @@ import AppKit import SwiftUI import Darwin import Bonsplit -import UniformTypeIdentifiers private func localizedDebugLabel(_ value: String) -> String { String(localized: String.LocalizationValue(value)) diff --git a/Sources/GhosttyApp.swift b/Sources/GhosttyApp.swift index a235231a9..a16f832c6 100644 --- a/Sources/GhosttyApp.swift +++ b/Sources/GhosttyApp.swift @@ -1,18 +1,12 @@ import Foundation import SwiftUI import AppKit -import Metal import QuartzCore -import Combine import CoreText import Darwin -import Carbon.HIToolbox import Bonsplit -import IOSurface -import UniformTypeIdentifiers // MARK: - GhosttyApp -// `GhosttySurfaceCallbackContext` widened private -> internal (also constructed from TerminalSurface.swift). private func programaRuntimeReadClipboardCallback( _ userdata: UnsafeMutableRawPointer?, @@ -70,9 +64,6 @@ private enum GhosttyClipboardRequestIdentityRegistry { } } -// Widened from private to internal: also constructed directly from -// TerminalSurface.swift. -// // DATA-ONLY. Ghostty's IO thread invokes callbacks (clipboard, actions) that read this // object SYNCHRONOUSLY, before any main-thread hop. It must therefore never hold a // reference -- weak or strong -- to a Swift UI/runtime object such as `GhosttyNSView` or @@ -116,16 +107,16 @@ final class GhosttySurfaceCallbackContext { /// `read_clipboard_cb`/`confirm_read_clipboard_cb` resolve this off the ghostty IO thread /// (see the "off-main" comment on `runtimeReadClipboardCallback` below) -- so a /// main-actor-only registry is NOT sound here. This uses a lock instead. Unlike the -/// original fix (#262), the registry now OWNS the data: `register`/`updateTabId` store the +/// original fix (#262), the registry OWNS the data: `register`/`updateTabId` store the /// snapshot directly, keyed by pointer, so `resolve(from:)` is a pure dictionary lookup -- /// it never calls `Unmanaged.fromOpaque(_:).takeUnretainedValue()` and never touches the -/// `GhosttySurfaceCallbackContext` instance at all. This removes two defects the pointer-set -/// version had: (1) the DEBUG stale-pointer `dlog` used to fire while `lock` was held, and -/// dlog does synchronous real-time file I/O -- on the frequent stale-teardown path under -/// surface churn that serialized every ghostty callback on file I/O; now the log is emitted -/// after the lock is released. (2) `resolve` used to read `context.tabId`, which took the +/// `GhosttySurfaceCallbackContext` instance at all. This avoids two defects a pointer-set +/// design has: (1) a DEBUG stale-pointer `dlog` fired while `lock` is held would do +/// synchronous real-time file I/O on the frequent stale-teardown path under surface +/// churn, serializing every ghostty callback on file I/O; the log is emitted +/// after the lock is released. (2) reading `context.tabId` in `resolve` would take the /// context's own lock while still holding the registry lock (a nested-lock pattern) and -/// required dereferencing a pointer that is exactly what we're trying to make un-dereferenceable. +/// require dereferencing a pointer that is exactly what we're trying to make un-dereferenceable. enum GhosttySurfaceUserdataRegistry { private static let lock = NSLock() private static var liveContexts: [UnsafeMutableRawPointer: GhosttySurfaceCallbackSnapshot] = [:] @@ -451,8 +442,8 @@ class GhosttyApp { private let backgroundLogURL = GhosttyApp.resolveBackgroundLogURL() private let backgroundLogStartUptime = ProcessInfo.processInfo.systemUptime // Guards only the cheap `backgroundLogSequence` increment below; the actual file - // write is offloaded to `backgroundLogWriter`'s serial queue (ported from upstream - // cmux cb2129a5a1) so callers never block on FileHandle open/seek/write/close. + // write is offloaded to `backgroundLogWriter`'s serial queue + // so callers never block on FileHandle open/seek/write/close. private let backgroundLogLock = NSLock() private var backgroundLogSequence: UInt64 = 0 // Non-lazy: `handleAction`/`logBackground` run on Ghostty callback threads, and @@ -520,13 +511,13 @@ class GhosttyApp { // Initialize Ghostty library first let result = ghostty_init(UInt(CommandLine.argc), CommandLine.unsafeArgv) if result != GHOSTTY_SUCCESS { - print("Failed to initialize ghostty: \(result)") + NSLog("Failed to initialize ghostty: \(result)") return } // Load config guard let primaryConfig = ghostty_config_new() else { - print("Failed to create ghostty config") + NSLog("Failed to create ghostty config") return } @@ -687,7 +678,7 @@ class GhosttyApp { ghostty_config_free(primaryConfig) guard let fallbackConfig = ghostty_config_new() else { - print("Failed to create ghostty fallback config") + NSLog("Failed to create ghostty fallback config") return } @@ -706,7 +697,7 @@ class GhosttyApp { Self.initLog("ghostty_app_new(fallback) failed") Self.dumpConfigDiagnostics(fallbackConfig, label: "fallback") #endif - print("Failed to create ghostty app") + NSLog("Failed to create ghostty app") ghostty_config_free(fallbackConfig) return } @@ -842,8 +833,6 @@ class GhosttyApp { /// default based on the system's preferred languages without overriding /// user-managed fallback chains or configured fonts that already cover /// the affected CJK ranges. - /// - /// See: https://github.com/manaflow-ai/cmux/pull/1017 private func loadCJKFontFallbackIfNeeded(_ config: ghostty_config_t) { guard let mappings = Self.autoInjectedCJKFontMappings() else { return } @@ -2077,7 +2066,7 @@ class GhosttyApp { .flatMap { String(cString: $0) } ?? "" if let tabId = callbackTabId, let surfaceId = callbackSurfaceId { - // Coalesced (ported from upstream cmux c30733e5e6): shells/agent CLIs + // Coalesced: shells/agent CLIs // that rewrite the title on every render would otherwise flood the // main actor with one NotificationCenter post per keystroke. The // dispatcher guarantees the last title set within the window is @@ -2408,8 +2397,8 @@ class GhosttyApp { #if DEBUG /// Test-only seam exposing the private `callbackContext(from:)` resolution (which /// wraps `GhosttySurfaceUserdataRegistry.resolve(from:)`) so tests can verify a stale - /// userdata pointer resolves to `nil` instead of dereferencing freed memory. Widened - /// from `private` to an internal, DEBUG-only static func rather than exposing the + /// userdata pointer resolves to `nil` instead of dereferencing freed memory. This is an + /// internal, DEBUG-only static func rather than exposing the /// registry or the callback context type directly, matching the DEBUG-only test /// scaffolding pattern used elsewhere (e.g. `TerminalSurface.replaceSurfaceWithFreedPointerForTesting`). static func debugCallbackContextResolves(from userdata: UnsafeMutableRawPointer?) -> Bool { diff --git a/Sources/GhosttyNSView.swift b/Sources/GhosttyNSView.swift index 20823798c..2d5066607 100644 --- a/Sources/GhosttyNSView.swift +++ b/Sources/GhosttyNSView.swift @@ -3,10 +3,6 @@ import SwiftUI import AppKit import Metal import QuartzCore -import Combine -import CoreText -import Darwin -import Carbon.HIToolbox import Bonsplit import IOSurface import UniformTypeIdentifiers @@ -163,10 +159,9 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { var trackingArea: NSTrackingArea? private var windowObserver: NSObjectProtocol? private var screenParametersObserver: NSObjectProtocol? - // Teardown-race guard (ported from #258's GhosttySurfaceScrollView fix; this was - // the residual crasher on the reland gate — the `notification.object as? NSWindow` - // downcast in windowDidChangeScreen implicitly retaining a freed notification - // object). NotificationCenter's removeObserver does NOT cancel a block already + // Teardown-race guard (same pattern as GhosttySurfaceScrollView; it protects the + // `notification.object as? NSWindow` downcast in windowDidChangeScreen from + // implicitly retaining a freed notification object). NotificationCenter's removeObserver does NOT cancel a block already // handed off to the main queue — during rapid window create/close, a block // enqueued against the *previous* window/registration batch can still run after // viewDidMoveToWindow has already torn down and re-registered observers for a diff --git a/Sources/GhosttySurfaceScrollView.swift b/Sources/GhosttySurfaceScrollView.swift index 2adbccd53..328857763 100644 --- a/Sources/GhosttySurfaceScrollView.swift +++ b/Sources/GhosttySurfaceScrollView.swift @@ -3,13 +3,9 @@ import SwiftUI import AppKit import Metal import QuartzCore -import Combine -import CoreText import Darwin -import Carbon.HIToolbox import Bonsplit import IOSurface -import UniformTypeIdentifiers // MARK: - Scroll View Wrapper // Layering contract: SurfaceSearchOverlay stays mounted from this file — see @@ -145,9 +141,6 @@ final class GhosttySurfaceScrollView: NSView { private let backgroundView: NSView private let scrollView: GhosttyScrollView private let documentView: NSView - // Widened from private to internal (immutable `let`, so this only grants - // read access): read from the debug-only RenderStats extension - // (GhosttyTerminalView+RenderStats.swift). let surfaceView: GhosttyNSView private let inactiveOverlayView: GhosttyFlashOverlayView private let dropZoneOverlayView: GhosttyFlashOverlayView @@ -250,8 +243,6 @@ final class GhosttySurfaceScrollView: NSView { flashCounts[surfaceId, default: 0] += 1 } - // Widened from private to internal: called from the debug-only RenderStats - // extension (GhosttyTerminalView+RenderStats.swift). static func contentsKey(for layer: CALayer?) -> String { guard let modelLayer = layer else { return "nil" } // Prefer the presentation layer to better reflect what the user sees on screen. @@ -277,8 +268,6 @@ final class GhosttySurfaceScrollView: NSView { return String(describing: contents) } - // Widened from private to internal: called from the debug-only RenderStats - // extension (GhosttyTerminalView+RenderStats.swift). static func updatePresentStats(surfaceId: UUID, layer: CALayer?) -> (count: Int, last: CFTimeInterval, key: String) { let key = contentsKey(for: layer) if lastContentsKeys[surfaceId] != key { diff --git a/Sources/GhosttyTerminalScrollBoost.swift b/Sources/GhosttyTerminalScrollBoost.swift index efbc65db8..768904458 100644 --- a/Sources/GhosttyTerminalScrollBoost.swift +++ b/Sources/GhosttyTerminalScrollBoost.swift @@ -1,7 +1,7 @@ import AppKit -/// Classifies a terminal scroll event for the historical precise-delta 2x -/// boost (ported from upstream cmux da4e4bc460). +/// Classifies a terminal scroll event for the precise-delta 2x +/// boost. /// /// Trackpads and Magic Mouse drive a continuous gesture phase; high-res plain /// wheels (e.g. Logitech free-spin) report precise deltas but leave both diff --git a/Sources/GhosttyTerminalSupport.swift b/Sources/GhosttyTerminalSupport.swift index 072bee0b1..f3ee9c6fe 100644 --- a/Sources/GhosttyTerminalSupport.swift +++ b/Sources/GhosttyTerminalSupport.swift @@ -1,12 +1,7 @@ import Foundation import SwiftUI import AppKit -import Metal -import QuartzCore -import Combine -import CoreText import Darwin -import Carbon.HIToolbox import Bonsplit import IOSurface import UniformTypeIdentifiers @@ -50,8 +45,6 @@ func programaShouldUseClearWindowBackground(for opacity: Double) -> Bool { programaShouldUseTransparentBackgroundWindow() || opacity < 0.999 } -// Widened from private to internal: used by both GhosttyApp.swift and -// GhosttyNSView.swift. func programaTransparentWindowBaseColor() -> NSColor { // A tiny non-zero alpha matches Ghostty's window compositing behavior on macOS and // avoids visual artifacts that can happen with a fully clear window background. diff --git a/Sources/GhosttyTerminalView+Accessibility.swift b/Sources/GhosttyTerminalView+Accessibility.swift index 7894c90fa..4d48e420f 100644 --- a/Sources/GhosttyTerminalView+Accessibility.swift +++ b/Sources/GhosttyTerminalView+Accessibility.swift @@ -1,15 +1,9 @@ import Foundation import SwiftUI import AppKit -import Metal import QuartzCore -import Combine -import CoreText -import Darwin -import Carbon.HIToolbox import Bonsplit import IOSurface -import UniformTypeIdentifiers // MARK: - GhosttyNSView + Accessibility // diff --git a/Sources/GhosttyTerminalView+DragDrop.swift b/Sources/GhosttyTerminalView+DragDrop.swift index d16c178e6..bea90cca3 100644 --- a/Sources/GhosttyTerminalView+DragDrop.swift +++ b/Sources/GhosttyTerminalView+DragDrop.swift @@ -1,15 +1,7 @@ import Foundation import SwiftUI import AppKit -import Metal -import QuartzCore -import Combine -import CoreText -import Darwin -import Carbon.HIToolbox import Bonsplit -import IOSurface -import UniformTypeIdentifiers // MARK: - GhosttyNSView + Drag & Drop // diff --git a/Sources/GhosttyTerminalView+IME.swift b/Sources/GhosttyTerminalView+IME.swift index 087c8f0ac..8686c9c53 100644 --- a/Sources/GhosttyTerminalView+IME.swift +++ b/Sources/GhosttyTerminalView+IME.swift @@ -1,15 +1,8 @@ import Foundation import SwiftUI import AppKit -import Metal -import QuartzCore -import Combine -import CoreText -import Darwin import Carbon.HIToolbox import Bonsplit -import IOSurface -import UniformTypeIdentifiers // MARK: - GhosttyNSView + IME (NSTextInputClient) // diff --git a/Sources/GhosttyTerminalView+Keyboard.swift b/Sources/GhosttyTerminalView+Keyboard.swift index 17d23479a..e8242148f 100644 --- a/Sources/GhosttyTerminalView+Keyboard.swift +++ b/Sources/GhosttyTerminalView+Keyboard.swift @@ -1,15 +1,7 @@ import Foundation import SwiftUI import AppKit -import Metal -import QuartzCore -import Combine -import CoreText -import Darwin -import Carbon.HIToolbox import Bonsplit -import IOSurface -import UniformTypeIdentifiers // MARK: - GhosttyNSView + Keyboard // diff --git a/Sources/GhosttyTerminalView+Mouse.swift b/Sources/GhosttyTerminalView+Mouse.swift index c9187fdce..b9b4ff042 100644 --- a/Sources/GhosttyTerminalView+Mouse.swift +++ b/Sources/GhosttyTerminalView+Mouse.swift @@ -1,15 +1,9 @@ import Foundation import SwiftUI import AppKit -import Metal import QuartzCore -import Combine -import CoreText import Darwin -import Carbon.HIToolbox import Bonsplit -import IOSurface -import UniformTypeIdentifiers // MARK: - GhosttyNSView + Mouse // @@ -921,8 +915,7 @@ extension GhosttyNSView { var y = event.scrollingDeltaY let precision = event.hasPreciseScrollingDeltas // Boost only gesture-driven precise deltas; high-res mouse wheels - // report precise deltas with no phase and must not be doubled - // (ported from upstream cmux da4e4bc460). + // report precise deltas with no phase and must not be doubled. if GhosttyTerminalScrollBoost(event: event).shouldDoublePreciseScrollDelta { x *= 2 y *= 2 diff --git a/Sources/GhosttyTerminalView+RenderStats.swift b/Sources/GhosttyTerminalView+RenderStats.swift index f8cc4c92a..3ff71cf97 100644 --- a/Sources/GhosttyTerminalView+RenderStats.swift +++ b/Sources/GhosttyTerminalView+RenderStats.swift @@ -1,15 +1,8 @@ import Foundation import SwiftUI import AppKit -import Metal -import QuartzCore -import Combine -import CoreText -import Darwin -import Carbon.HIToolbox import Bonsplit import IOSurface -import UniformTypeIdentifiers // MARK: - GhosttySurfaceScrollView + RenderStats // diff --git a/Sources/GhosttyTerminalView+SwiftUIWrapper.swift b/Sources/GhosttyTerminalView+SwiftUIWrapper.swift index 6b6ecc960..d24d949a3 100644 --- a/Sources/GhosttyTerminalView+SwiftUIWrapper.swift +++ b/Sources/GhosttyTerminalView+SwiftUIWrapper.swift @@ -1,15 +1,8 @@ import Foundation import SwiftUI import AppKit -import Metal import QuartzCore -import Combine -import CoreText -import Darwin -import Carbon.HIToolbox import Bonsplit -import IOSurface -import UniformTypeIdentifiers // MARK: - GhosttyTerminalView (SwiftUI Wrapper) // diff --git a/Sources/GhosttyTitleUpdateDispatcher.swift b/Sources/GhosttyTitleUpdateDispatcher.swift index 227254bf4..ee2a065b7 100644 --- a/Sources/GhosttyTitleUpdateDispatcher.swift +++ b/Sources/GhosttyTitleUpdateDispatcher.swift @@ -1,10 +1,10 @@ import Foundation -// MARK: - GhosttyTitleUpdateDispatcher (ported from upstream cmux c30733e5e6) +// MARK: - GhosttyTitleUpdateDispatcher // -// Every GHOSTTY_ACTION_SET_TITLE callback used to post `.ghosttyDidSetTitle` via an -// unconditional `DispatchQueue.main.async`. Shells and agent CLIs that rewrite the -// title on every render (e.g. progress spinners) flood the main actor with +// Posting `.ghosttyDidSetTitle` through an unconditional `DispatchQueue.main.async` on +// every GHOSTTY_ACTION_SET_TITLE callback would let shells and agent CLIs that rewrite +// the title on every render (e.g. progress spinners) flood the main actor with // NotificationCenter posts during typing, adding up to real input latency. // // This dispatcher coalesces title updates per surface: at most one @@ -12,9 +12,6 @@ import Foundation // that arrive while a flush is already scheduled overwrite the pending title rather // than queuing a second post, but the *last* title set within the window is always // the one delivered -- no title is ever silently dropped. -// -// Only the coalescing machinery is ported here. Upstream's notification-hook / -// desktop-notification ingress is cmux agent-chat machinery programa doesn't have. final class GhosttyTitleUpdateDispatcher { /// Identifies the surface a title update belongs to. Coalescing is scoped per key /// so a burst of title churn on one surface never delays or drops updates for diff --git a/Sources/GitMetadataProber.swift b/Sources/GitMetadataProber.swift index 63639af0f..9e5eae621 100644 --- a/Sources/GitMetadataProber.swift +++ b/Sources/GitMetadataProber.swift @@ -615,7 +615,6 @@ struct GitMetadataProber { pullRequestLookupThrottle.removeAll() } - // Widened from `private` to `internal`: called from TabManager.swift. nonisolated static func initialWorkspaceGitMetadataSnapshot( for directory: String ) -> InitialWorkspaceGitMetadataSnapshot { @@ -1122,7 +1121,6 @@ struct GitMetadataProber { return String(trimmed.prefix(180)) } - // Widened from `private` to `internal`: called from TabManager.swift. nonisolated static func normalizedBranchName(_ branch: String?) -> String? { let trimmed = branch?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" return trimmed.isEmpty ? nil : trimmed diff --git a/Sources/PortScanner.swift b/Sources/PortScanner.swift index 585fa401f..6c8f8c864 100644 --- a/Sources/PortScanner.swift +++ b/Sources/PortScanner.swift @@ -2,7 +2,7 @@ import AppKit import Foundation import os -/// Batched port scanner that replaces per-shell `ps + lsof` scanning. +/// Batched port scanner: one coalesced `ps + lsof` pass instead of per-shell scanning. /// /// Each shell sends a lightweight `report_tty` + `ports_kick` over the socket. /// PortScanner coalesces kicks across all panels, then runs a single @@ -692,7 +692,7 @@ final class PortScanner: @unchecked Sendable { } /// Every `ps`/`lsof` run is bounded: a hung `lsof` (for example on a stale network mount) - /// used to block this serial queue, and with it all port scanning, for the rest of the + /// would block this serial queue, and with it all port scanning, for the rest of the /// session. Returns nil when the tool timed out or could not run; callers skip the cycle. private static let subprocessTimeout: TimeInterval = 5 private static let subprocessStdoutLimit = 16 * 1024 * 1024 diff --git a/Sources/ProgramaConfigExecutor.swift b/Sources/ProgramaConfigExecutor.swift index 8731c052c..be573c957 100644 --- a/Sources/ProgramaConfigExecutor.swift +++ b/Sources/ProgramaConfigExecutor.swift @@ -143,7 +143,7 @@ struct ProgramaConfigExecutor { /// /// | directory | `confirm:` | prompt? | /// |-----------|------------|---------| - /// | untrusted | absent | **yes** -- this is the case that used to run silently | + /// | untrusted | absent | **yes** -- this is the case that must never run silently | /// | untrusted | true | yes | /// | trusted | absent | no | /// | trusted | true | yes -- author's "ask me anyway" marker | @@ -162,17 +162,15 @@ struct ProgramaConfigExecutor { /// The single gate in front of everything a `programa.json` can run or send. Returns false /// when the user declined, in which case the caller must not proceed. /// - /// Trust is the app's decision, not the config file's. It previously was not: the gate ran - /// only `if command.confirm == true`, and `confirm` is a field in the very file we are - /// deciding whether to trust, so any config could opt out of being checked by simply - /// omitting it. `workspace`-type commands were never gated at all, despite being able to - /// carry an arbitrary `command` and `env` per surface. Between them, cloning a repo and - /// picking a plausible-looking entry out of the command palette ran arbitrary shell with no - /// prompt. Recipes are the same shape of risk with a different payload: a cloned repo could + /// Trust is the app's decision, not the config file's. Gating on + /// `command.confirm == true` alone would let any config opt out of being checked by omitting + /// it, since `confirm` is a field in the very file being judged. `workspace`-type commands + /// can carry an arbitrary `command` and `env` per surface, so they are gated too. Otherwise, + /// cloning a repo and picking a plausible-looking entry out of the command palette would run + /// arbitrary shell with no prompt. Recipes are the same shape of risk with a different payload: a cloned repo could /// ship a recipe whose prompt text is chosen by an attacker and fired at the user's coding /// agent, so they go through this exact same gate rather than a parallel path. /// - /// Now: /// - untrusted directory: always confirm, whatever the file says, for every entry type; /// - trusted directory: run, unless the author set `confirm: true`, which stays meaningful /// as a "this one is destructive, ask me anyway" marker (commands only -- recipes have no @@ -307,8 +305,8 @@ struct ProgramaConfigExecutor { /// unfiltered (only Programa's own `PROGRAMA_*` identity keys are protected, see /// `protectedStartupEnvironmentKeys` in TerminalSurface.swift), so `ZDOTDIR` / /// `BASH_ENV` / `ENV` set here execute a repo-controlled shell startup file the - /// moment the surface spawns. Filtering surfaces down to "has a command" -- as this - /// function used to -- let such a surface contribute nothing to the dialog, so the + /// moment the surface spawns. Filtering surfaces down to "has a command" -- as a + /// command-only filter would -- lets such a surface contribute nothing to the dialog, so the /// user would approve a workspace the dialog claimed ran nothing. Every surface that /// sets `command`, `cwd`, or `env` must produce a visible line. private static func surfaceDescriptions(in node: ProgramaLayoutNode) -> [String] { diff --git a/Sources/ReviewDiffProber.swift b/Sources/ReviewDiffProber.swift index fd615989f..3fcfa83bc 100644 --- a/Sources/ReviewDiffProber.swift +++ b/Sources/ReviewDiffProber.swift @@ -75,7 +75,7 @@ struct ReviewDiffProber { private nonisolated static func uncommittedSnapshot(repoRoot: String) -> ReviewDiffSnapshot { // `HEAD` doesn't resolve on an unborn branch (a repo with commits never made yet). Diff // against the empty tree in that case so staged files before the first commit still show - // up as added, instead of `git diff HEAD` failing and (previously) reading as clean. + // up as added, instead of `git diff HEAD` failing and reading as clean. let diffRangeArgs = headDiffRangeArgs(repoRoot: repoRoot) switch diffAndOverrides(repoRoot: repoRoot, diffRangeArgs: diffRangeArgs) { case .failure(let error): @@ -158,8 +158,7 @@ struct ReviewDiffProber { } /// `git diff --numstat` reports `-\t-\t` for binary files -- a cheap single extra - /// invocation used to build a binary-file set before the unified-diff parse, per - /// docs/plans/diff-review-panel.md §3 point 4. + /// invocation builds a binary-file set before the unified-diff parse. private nonisolated static func binaryFilePaths(repoRoot: String, diffRangeArgs: [String]) -> Result, ReviewDiffError> { switch runCommandResult( directory: repoRoot, diff --git a/Sources/SessionAutosaveCoordinator.swift b/Sources/SessionAutosaveCoordinator.swift index 0dc994575..54787da03 100644 --- a/Sources/SessionAutosaveCoordinator.swift +++ b/Sources/SessionAutosaveCoordinator.swift @@ -255,9 +255,9 @@ final class SessionAutosaveCoordinator { } } - // Widened from `fileprivate` to `internal`: NSWindow.programa_sendEvent(_:) (in - // WindowSwizzles.swift) calls AppDelegate.shared?.sessionAutosave.recordTypingActivity() - // from a different file. Refs #95, #187. + // NSWindow.programa_sendEvent(_:) (in WindowSwizzles.swift) calls + // AppDelegate.shared?.sessionAutosave.recordTypingActivity() from a different file. + // Refs #95, #187. // // HOT PATH: called on every keyDown via NSWindow.programa_sendEvent. Keep this a single // property write -- no allocation, logging, formatting, or locks. diff --git a/Sources/SessionEscrow.swift b/Sources/SessionEscrow.swift index d81354a69..1397c8d05 100644 --- a/Sources/SessionEscrow.swift +++ b/Sources/SessionEscrow.swift @@ -131,10 +131,10 @@ import Bonsplit /// `HeldSession` carries a `stopRequested` flag (mutated only under /// `SessionEscrowHolder.registryLock`) and a `DispatchSemaphore` the drain /// thread signals exactly once, right after it stops. The drain loop itself -/// no longer blocks in a bare `read()`: it `poll()`s the fd together with +/// never blocks in a bare `read()`: it `poll()`s the fd together with /// the session's self-pipe (`HeldSession.requestStop()` writes one byte to /// it), so a stop is noticed immediately even while the child is producing -/// no output at all, and only calls the actual (now effectively non-blocking, since +/// no output at all, and only calls the actual (effectively non-blocking, since /// `poll` already confirmed readability) `read()` once data is pending -- /// this is the "bounded by one `drainReadBufferSize` read" a retrieval /// waits out. A retrieval request sets `stopRequested`, blocks on the @@ -479,15 +479,12 @@ enum EscrowWireFormat { /// `session_escrow_shim.h`, wired into this target via /// `programa-Bridging-Header.h`). /// -/// The fd-passing send/receive used to be hand-rolled here in Swift, -/// manually computing the `cmsghdr` control-message layout since Darwin's -/// `` `CMSG_*` macros are C preprocessor macros and aren't -/// importable into Swift. That version's `sendmsg` failed with `EINVAL` in -/// a real repro despite the byte-offset arithmetic matching the macros on -/// paper -- marshalling `msghdr`/`cmsghdr` by hand across several nested -/// `withUnsafe...` closures is exactly the kind of detail that's easy to -/// get subtly wrong in a way that's hard to diagnose without a debugger on -/// the actual kernel call. `session_escrow_shim.c` does the same job in +/// Darwin's `` `CMSG_*` macros are C preprocessor macros and aren't +/// importable into Swift, so marshalling `msghdr`/`cmsghdr` by hand across several +/// nested `withUnsafe...` closures is easy to get subtly wrong (a hand-rolled +/// `sendmsg` failed with `EINVAL` despite byte offsets matching the macros on +/// paper) and hard to diagnose without a debugger on the actual kernel call. +/// `session_escrow_shim.c` does the same job in /// ~30 lines of plain C using the real `CMSG_*` macros, which is strictly /// more reliable for this one piece; everything else (framing, session /// bookkeeping, heartbeats, retry/backoff) stays in Swift. @@ -2512,13 +2509,12 @@ enum SessionEscrowHolder { /// exactly the same file (same path, same rotation cap) the app's own /// `SessionWALStore` was writing before it died. Runs on a dedicated /// thread -- this process has no rendering/latency constraints, unlike - /// the app -- but no longer blocks in a bare `read()`: it `poll()`s + /// the app -- and never blocks in a bare `read()`: it `poll()`s /// first (bounded, `SessionEscrowPolicy.drainPollIntervalMilliseconds`) /// so `session.stopRequested` (set by a retrieval, see /// `handleRetrieveRequest`) is noticed promptly even while the child is /// producing no output. See the file-level "Drain/retrieve - /// coordination" doc comment for the full contract. Unlike the - /// pre-slice-2 version, this method does NOT unconditionally close + /// coordination" doc comment for the full contract. This method does NOT unconditionally close /// `session.fd` on every exit path -- only on a genuine end-of-stream /// (the child truly exited). Stopping for a retrieval must leave the fd /// open and untouched so it can be handed back; closing it here would diff --git a/Sources/SessionPersistence.swift b/Sources/SessionPersistence.swift index fceb5bcee..e34c70261 100644 --- a/Sources/SessionPersistence.swift +++ b/Sources/SessionPersistence.swift @@ -1197,9 +1197,8 @@ enum TerminalReplayModeReset { /// transcript for a session whose child did not survive to be reattached) /// for injection into a freshly spawned surface's revive-seed path /// (`TerminalSurface.pendingReviveSeed` / `seedRevivedScrollbackIfPending`), -/// instead of the old temp-file + `PROGRAMA_RESTORE_SCROLLBACK_FILE` + -/// shell-rc-`cat` mechanism this replaces (formerly -/// `SessionScrollbackReplayStore`). A pure text transform now -- no temp +/// rather than a temp-file + `PROGRAMA_RESTORE_SCROLLBACK_FILE` + +/// shell-rc-`cat` mechanism. A pure text transform -- no temp /// files, no environment variables, no shell integration involved -- because /// the seed path bypasses the pty entirely (`ghostty_surface_process_output`) /// exactly like the escrow-revive path already does. See diff --git a/Sources/SessionWALStore.swift b/Sources/SessionWALStore.swift index d230b2a87..19edaf0e1 100644 --- a/Sources/SessionWALStore.swift +++ b/Sources/SessionWALStore.swift @@ -812,8 +812,7 @@ enum SessionWALCore { /// Extracted out of the singleton/file-system-coupled store method so it /// is directly unit-testable: the raw-byte suffix cut below has the exact /// same torn-escape/torn-UTF-8 hazard `readFrameAndDelta` already guards - /// against at its own cut sites (~554) -- this call site was previously - /// missing that guard, leaking orphaned CSI/UTF-8 fragments as literal + /// against at its own cut sites (~554), so orphaned CSI/UTF-8 fragments never leak as literal /// replayed text. static func decodeFallbackScrollbackText( rotated: Data?, diff --git a/Sources/SettingsView.swift b/Sources/SettingsView.swift index 7dccab75e..0e5936a0a 100644 --- a/Sources/SettingsView.swift +++ b/Sources/SettingsView.swift @@ -234,7 +234,6 @@ struct SettingsView: View { #if DEBUG dlog("notification.ui enableTapped state=\(state)") #endif - NSLog("notification.ui enableTapped state=%@", state) switch notificationStore.authorizationState { case .unknown, .notDetermined: notificationStore.requestAuthorizationFromSettings() @@ -346,7 +345,7 @@ struct SettingsView: View { .safeAreaInset(edge: .top, spacing: 0) { ZStack(alignment: .top) { // A gradient that fades to clear never fully hides what scrolls behind - // it, so rows used to read straight through the tab strip. A solid + // it, so rows read straight through the tab strip. A solid // header material plus a hairline on scroll is what AppKit does. AboutVisualEffectBackground(material: .headerView, blendingMode: .withinWindow) diff --git a/Sources/SidebarDragDrop.swift b/Sources/SidebarDragDrop.swift index 5f47c7974..9ee6bfb49 100644 --- a/Sources/SidebarDragDrop.swift +++ b/Sources/SidebarDragDrop.swift @@ -3,10 +3,8 @@ // drop edge/indicator/planner, auto-scroll planner, and drag lifecycle/failsafe // types, consolidated from several non-contiguous locations in ContentView.swift. // -// Access-level widening: SidebarDragFailsafeMonitor and SidebarExternalDropOverlay -// were file-private to the old ContentView.swift; widened to internal (dropped -// the `private` modifier) because VerticalTabsSidebar and ContentView, which stay -// in ContentView.swift, still construct/reference them. No other behavior change. +// SidebarDragFailsafeMonitor and SidebarExternalDropOverlay are internal because +// VerticalTabsSidebar and ContentView (in ContentView.swift) construct/reference them. import AppKit import Bonsplit diff --git a/Sources/SidebarVisuals.swift b/Sources/SidebarVisuals.swift index ed21f1e36..fa0780f5b 100644 --- a/Sources/SidebarVisuals.swift +++ b/Sources/SidebarVisuals.swift @@ -2,12 +2,10 @@ // Holds sidebar footer/help-menu/scrim/blur/material/tint/preset enums, // visual-effect/backdrop views, and the NSColor extension. // -// Access-level widening: SidebarFooter, SidebarTopScrim, SidebarScrollViewResolver, -// SidebarEmptyArea, ClearScrollBackground, DraggableFolderIcon, -// TitlebarLeadingInsetReader, and SidebarBackdrop were -// file-private to the old ContentView.swift; widened to internal (dropped the -// `private` modifier) because VerticalTabsSidebar and ContentView, which stay in -// ContentView.swift, still construct/reference them. No other behavior change. +// SidebarFooter, SidebarTopScrim, SidebarScrollViewResolver, SidebarEmptyArea, +// ClearScrollBackground, DraggableFolderIcon, TitlebarLeadingInsetReader, and +// SidebarBackdrop are internal because VerticalTabsSidebar and ContentView +// (in ContentView.swift) construct/reference them. import AppKit import Bonsplit diff --git a/Sources/TabItemView.swift b/Sources/TabItemView.swift index 0b9b6bf07..28357a8c7 100644 --- a/Sources/TabItemView.swift +++ b/Sources/TabItemView.swift @@ -313,7 +313,7 @@ struct TabItemView: View, Equatable { return SidebarTitle.strippingLeadingStatusGlyph(tab.title) } - /// Status entry keys/values that used to duplicate agent state in the metadata rows. + /// Status entry keys/values that duplicate agent state in the metadata rows. /// Filtered out at render time so older CLI builds that still send them don't double up /// with the badge above. private static let agentStateStatusKeys: Set = ["claude_code", "codex", "opencode"] diff --git a/Sources/TabManager+UITestHarness.swift b/Sources/TabManager+UITestHarness.swift index 37bff17d0..1daf126f1 100644 --- a/Sources/TabManager+UITestHarness.swift +++ b/Sources/TabManager+UITestHarness.swift @@ -305,7 +305,6 @@ extension TabManager { return (attached, hasSurface, firstResponder) } - // Widened from `private` to `internal`: called from TabManager.init() in TabManager.swift. func setupUITestFocusShortcutsIfNeeded() { guard !didSetupUITestFocusShortcuts else { return } didSetupUITestFocusShortcuts = true @@ -333,7 +332,6 @@ extension TabManager { ) } - // Widened from `private` to `internal`: called from TabManager.init() in TabManager.swift. func setupSplitCloseRightUITestIfNeeded() { guard !didSetupSplitCloseRightUITest else { return } didSetupSplitCloseRightUITest = true @@ -845,7 +843,6 @@ extension TabManager { return object } - // Widened from `private` to `internal`: called from TabManager.init() in TabManager.swift. func setupChildExitKeyboardUITestIfNeeded() { guard !didSetupChildExitKeyboardUITest else { return } didSetupChildExitKeyboardUITest = true diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index 27d74a571..2272529ab 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -765,8 +765,6 @@ class TabManager: ObservableObject { #endif #if DEBUG - // Widened from `private` to `internal` (default access) so TabManager+UITestHarness.swift, - // which owns the setup functions that mutate this state, can see it across files. var didSetupSplitCloseRightUITest = false var didSetupUITestFocusShortcuts = false var didSetupChildExitKeyboardUITest = false @@ -1011,7 +1009,9 @@ class TabManager: ObservableObject { if panel.searchState == nil { panel.searchState = TerminalSurface.SearchState() } - NSLog("Find: startSearch workspace=%@ panel=%@", panel.workspaceId.uuidString, panel.id.uuidString) +#if DEBUG + dlog("Find: startSearch workspace=\(panel.workspaceId.uuidString) panel=\(panel.id.uuidString)") +#endif NotificationCenter.default.post(name: .ghosttySearchFocus, object: panel.surface) _ = panel.performBindingAction("start_search") return @@ -1029,7 +1029,9 @@ class TabManager: ObservableObject { if panel.searchState == nil { panel.searchState = TerminalSurface.SearchState() } - NSLog("Find: searchSelection workspace=%@ panel=%@", panel.workspaceId.uuidString, panel.id.uuidString) +#if DEBUG + dlog("Find: searchSelection workspace=\(panel.workspaceId.uuidString) panel=\(panel.id.uuidString)") +#endif NotificationCenter.default.post(name: .ghosttySearchFocus, object: panel.surface) _ = panel.performBindingAction("search_selection") } @@ -2811,7 +2813,7 @@ class TabManager: ObservableObject { // MARK: - Panel/Surface ID Access - /// Returns the focused panel ID for a tab (replaces focusedSurfaceId) + /// Returns the focused panel ID for a tab. func focusedPanelId(for tabId: UUID) -> UUID? { workspace(withId: tabId)?.focusedPanelId } diff --git a/Sources/TerminalController+Telemetry.swift b/Sources/TerminalController+Telemetry.swift index 4eaccf91f..233d8db7a 100644 --- a/Sources/TerminalController+Telemetry.swift +++ b/Sources/TerminalController+Telemetry.swift @@ -440,10 +440,9 @@ extension TerminalController { } /// Atomically reports a surface as blocked on user input and posts the matching - /// notification (docs/plans/agent-state-unification.md T2): replaces the three - /// independent socket calls (`notification.create_for_target` + - /// `workspace.set_status` "Needs input" + `surface.report_agent_state`) a hook used to - /// make for the same "needs input" moment, any one of which could fail alone. Parses + /// notification in one call, instead of three independent socket calls + /// (`notification.create_for_target` + `workspace.set_status` "Needs input" + + /// `surface.report_agent_state`), any one of which could fail alone. Parses /// and validates off-main; hops to main once via `v2ScheduleSurfaceTelemetryMutation` /// (notification creation must run on main -- it drives AppKit) and performs the state /// write, notification, and supervision update in that single hop. diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index 8576e9fd8..7c1f109b0 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -973,7 +973,7 @@ class TerminalController { // Create socket let newServerSocket = socket(AF_UNIX, SOCK_STREAM, 0) guard newServerSocket >= 0 else { - print("TerminalController: Failed to create socket") + NSLog("TerminalController: Failed to create socket") return } @@ -1002,7 +1002,7 @@ class TerminalController { close(newServerSocket) return case .failure: - print("TerminalController: Failed to bind socket") + NSLog("TerminalController: Failed to bind socket") close(newServerSocket) return } @@ -1011,7 +1011,7 @@ class TerminalController { // Listen guard listen(newServerSocket, Self.socketListenBacklog) >= 0 else { - print("TerminalController: Failed to listen on socket") + NSLog("TerminalController: Failed to listen on socket") close(newServerSocket) return } @@ -1031,7 +1031,6 @@ class TerminalController { } listenerActivated = true let listenerSocket = newServerSocket - print("TerminalController: Listening on \(activeSocketPath)") dilog("socket.listener", "start path=\(activeSocketPath) accessMode=\(accessMode)") NotificationCenter.default.post( name: .socketListenerDidStart, @@ -1247,9 +1246,7 @@ class TerminalController { (socketPath, mode_t(accessMode.socketFilePermissions)) } if chmod(currentSocketPath, permissions) != 0 { - print( - "TerminalController: Failed to set socket permissions to \(String(permissions, radix: 8)) for \(currentSocketPath)" - ) + NSLog("TerminalController: Failed to set socket permissions to %@ for %@", String(permissions, radix: 8), currentSocketPath) } } diff --git a/Sources/TerminalCopyMode.swift b/Sources/TerminalCopyMode.swift index 66bdd3a78..9b0c1b236 100644 --- a/Sources/TerminalCopyMode.swift +++ b/Sources/TerminalCopyMode.swift @@ -1,19 +1,14 @@ import Foundation import SwiftUI import AppKit -import Metal -import QuartzCore -import Combine import CoreText -import Darwin import Carbon.HIToolbox import Bonsplit import IOSurface -import UniformTypeIdentifiers // MARK: - Terminal Keyboard Copy Mode // Four `terminalKeyTable*`/`terminalKeyboardCopyModeIndicatorText` items below -// widened private -> internal (used from GhosttyNSView.swift/GhosttySurfaceScrollView.swift). +// are internal: used from GhosttyNSView.swift/GhosttySurfaceScrollView.swift. enum TerminalKeyboardCopyModeSelectionMove: String, Equatable { case left diff --git a/Sources/TerminalDirectoryOpener.swift b/Sources/TerminalDirectoryOpener.swift index 7d401c339..adaaaa98a 100644 --- a/Sources/TerminalDirectoryOpener.swift +++ b/Sources/TerminalDirectoryOpener.swift @@ -2,10 +2,7 @@ import AppKit import SwiftUI import Bonsplit import CoreServices -import UserNotifications -import Combine import ObjectiveC.runtime -import Darwin enum FinderServicePathResolver { private static func canonicalDirectoryPath(_ path: String) -> String { diff --git a/Sources/TerminalSurface.swift b/Sources/TerminalSurface.swift index 82aa6fde7..0775fa269 100644 --- a/Sources/TerminalSurface.swift +++ b/Sources/TerminalSurface.swift @@ -1,7 +1,6 @@ import Foundation import SwiftUI import AppKit -import Metal import QuartzCore import Combine import CoreText @@ -9,7 +8,6 @@ import Darwin import Carbon.HIToolbox import Bonsplit import IOSurface -import UniformTypeIdentifiers import os // MARK: - Debug Render Instrumentation @@ -1256,7 +1254,7 @@ final class TerminalSurface: Identifiable, ObservableObject { #endif guard let app = GhosttyApp.shared.app else { - print("Ghostty app not initialized") + NSLog("Ghostty app not initialized") #if DEBUG Self.surfaceLog("createSurface FAILED surface=\(id.uuidString): ghostty app not initialized") #endif @@ -1588,7 +1586,7 @@ final class TerminalSurface: Identifiable, ObservableObject { } GhosttySurfaceUserdataRegistry.release(surfaceCallbackContext) surfaceCallbackContext = nil - print("Failed to create ghostty surface") + NSLog("Failed to create ghostty surface") #if DEBUG Self.surfaceLog("createSurface FAILED surface=\(id.uuidString): ghostty_surface_new returned nil") if let cfg = GhosttyApp.shared.config { diff --git a/Sources/TerminalThemeStore.swift b/Sources/TerminalThemeStore.swift index 6e8813fa6..47769703e 100644 --- a/Sources/TerminalThemeStore.swift +++ b/Sources/TerminalThemeStore.swift @@ -166,8 +166,7 @@ struct TerminalThemeStore { return Self.directiveValue(for: "theme", in: body) } - /// Reads only the managed block (replaces `managedRawThemeValue()`'s role for backups, now - /// generalized to all five directives). + /// Reads only the managed block, for backups of all five directives. func managedRawAppearance() -> TerminalAppearanceOverrides { guard let body = managedBlockBody() else { return TerminalAppearanceOverrides() } @@ -372,8 +371,8 @@ struct TerminalThemeStore { } } - /// "Last matching line wins" scan for a single `key = value` directive, generalized from - /// the theme-only scanner this store used to have. + /// "Last matching line wins" scan for a single `key = value` directive, shared by every + /// managed directive. private static func directiveValue(for key: String, in contents: String) -> String? { var lastValue: String? diff --git a/Sources/TerminalWindowPortal.swift b/Sources/TerminalWindowPortal.swift index e672d7174..1b22bcc88 100644 --- a/Sources/TerminalWindowPortal.swift +++ b/Sources/TerminalWindowPortal.swift @@ -5,8 +5,6 @@ import Bonsplit #endif #if DEBUG -// Widened from private to internal: also called from -// TerminalWindowPortalRegistry.swift. func portalDebugToken(_ view: NSView?) -> String { guard let view else { return "nil" } let ptr = Unmanaged.passUnretained(view).toOpaque() diff --git a/Sources/TypingProfiler.swift b/Sources/TypingProfiler.swift index b0af44043..42afa1b32 100644 --- a/Sources/TypingProfiler.swift +++ b/Sources/TypingProfiler.swift @@ -2,10 +2,7 @@ import AppKit import SwiftUI import Bonsplit import CoreServices -import UserNotifications -import Combine import ObjectiveC.runtime -import Darwin #if DEBUG enum ProgramaTypingTiming { diff --git a/Sources/V2CommandCatalog.swift b/Sources/V2CommandCatalog.swift index a0a786c63..b28981f11 100644 --- a/Sources/V2CommandCatalog.swift +++ b/Sources/V2CommandCatalog.swift @@ -174,7 +174,7 @@ enum V2CommandCatalog { ] /// Full method list for the current build configuration, mirroring the - /// conditional `#if DEBUG` append that used to live inline in + /// conditional `#if DEBUG` append in /// `TerminalController.v2Capabilities()`. static var methods: [String] { #if DEBUG diff --git a/Sources/WindowSwizzles.swift b/Sources/WindowSwizzles.swift index 5e37ebe89..b71e52eb3 100644 --- a/Sources/WindowSwizzles.swift +++ b/Sources/WindowSwizzles.swift @@ -2,10 +2,7 @@ import AppKit import SwiftUI import Bonsplit import CoreServices -import UserNotifications -import Combine import ObjectiveC.runtime -import Darwin private var programaFirstResponderGuardHitViewContext: NSView? private var programaWindowFirstResponderBypassDepth = 0 @@ -24,8 +21,6 @@ func programaIsWindowFirstResponderBypassActive() -> Bool { } #if DEBUG -// Widened from `private extension` to `extension`: AppDelegate.installWindowResponderSwizzles() -// (in AppDelegate.swift) references these @objc methods via #selector(...) for method swizzling. Refs #95. // DEBUG-only: the swizzle exists solely for typing-timing instrumentation. extension NSApplication { @objc func programa_applicationSendEvent(_ event: NSEvent) { @@ -56,8 +51,6 @@ extension NSApplication { } #endif -// Widened from `private extension` to `extension`: AppDelegate.installWindowResponderSwizzles() -// (in AppDelegate.swift) references these @objc methods via #selector(...) for method swizzling. Refs #95. extension NSWindow { @objc func programa_makeFirstResponder(_ responder: NSResponder?) -> Bool { if programaIsWindowFirstResponderBypassActive() { diff --git a/Sources/Workspace+Bonsplit.swift b/Sources/Workspace+Bonsplit.swift index d56d5707b..9b815c81d 100644 --- a/Sources/Workspace+Bonsplit.swift +++ b/Sources/Workspace+Bonsplit.swift @@ -4,10 +4,7 @@ import Foundation import SwiftUI import AppKit import Bonsplit -import Combine -import CryptoKit import Darwin -import Network import CoreText /// One predicate for the workspace split cap: the pane-chrome split buttons @@ -522,7 +519,7 @@ extension Workspace: @preconcurrency BonsplitDelegate { } #if DEBUG - NSLog("[Workspace] didCloseTab panelId=\(panelId) remainingPanels=\(panels.count - 1) remainingPanes=\(controller.allPaneIds.count)") + dlog("[Workspace] didCloseTab panelId=\(panelId) remainingPanels=\(panels.count - 1) remainingPanes=\(controller.allPaneIds.count)") #endif let panel = panels[panelId] @@ -688,9 +685,9 @@ extension Workspace: @preconcurrency BonsplitDelegate { } /// Canonical per-panel metadata teardown shared by every close path. - /// The single-surface and pane-close paths previously hand-copied this - /// list and drifted (pane close leaked inheritance font points and never - /// cleared notifications). Aggregate recomputes (recomputeListeningPorts) + /// Every close path calls this one list so none can drift (a hand-copied + /// pane-close list leaked inheritance font points and never cleared + /// notifications). Aggregate recomputes (recomputeListeningPorts) /// stay at the call sites. private func removeSurfaceMetadata(panelId: UUID, isDetaching: Bool = false) { panelDirectories.removeValue(forKey: panelId) diff --git a/Sources/Workspace+FocusGeometry.swift b/Sources/Workspace+FocusGeometry.swift index 9ad41d3ff..feb370b6c 100644 --- a/Sources/Workspace+FocusGeometry.swift +++ b/Sources/Workspace+FocusGeometry.swift @@ -7,9 +7,7 @@ import SwiftUI import AppKit import Bonsplit import Combine -import CryptoKit import Darwin -import Network import CoreText extension Workspace { diff --git a/Sources/Workspace+Layout.swift b/Sources/Workspace+Layout.swift index 612d8bee4..b0afd12cc 100644 --- a/Sources/Workspace+Layout.swift +++ b/Sources/Workspace+Layout.swift @@ -4,10 +4,7 @@ import SwiftUI import AppKit import Bonsplit -import Combine -import CryptoKit import Darwin -import Network import CoreText @MainActor diff --git a/Sources/Workspace+Persistence.swift b/Sources/Workspace+Persistence.swift index 288a9c27b..f55da6098 100644 --- a/Sources/Workspace+Persistence.swift +++ b/Sources/Workspace+Persistence.swift @@ -4,10 +4,7 @@ import Foundation import SwiftUI import AppKit import Bonsplit -import Combine -import CryptoKit import Darwin -import Network import CoreText extension Workspace { @@ -129,13 +126,13 @@ extension Workspace { // before releasing any surface gated under this restore pass -- see // `TerminalSurface.isSessionRestoreSettling`'s doc comment. // - // Widened to two turns (was one): the primary-window restore path + // The gate waits two turns: the primary-window restore path // runs this restore before `window.setFrame(restoredFrame, display: // true)` (`AppDelegate.swift` restore-then-setFrame ordering), and the // terminal portal's own geometry sync is itself deferred past the // current layout turn (`TerminalWindowPortalRegistry // .scheduleExternalGeometrySynchronize`, - // `GhosttyTerminalView+SwiftUIWrapper.swift`). One turn released the + // `GhosttyTerminalView+SwiftUIWrapper.swift`). A single turn would release the // gate before the surface ever saw the final window width, which is // what let width-dependent WAL replay scatter text across the wrong // columns on restore. diff --git a/Sources/Workspace+SidebarTelemetry.swift b/Sources/Workspace+SidebarTelemetry.swift index 0f258c0fe..9eee358c2 100644 --- a/Sources/Workspace+SidebarTelemetry.swift +++ b/Sources/Workspace+SidebarTelemetry.swift @@ -5,10 +5,7 @@ import Foundation import SwiftUI import AppKit import Bonsplit -import Combine -import CryptoKit import Darwin -import Network import CoreText enum SidebarTelemetryLimits { diff --git a/Sources/Workspace+Surfaces.swift b/Sources/Workspace+Surfaces.swift index 831d226d0..c64576e01 100644 --- a/Sources/Workspace+Surfaces.swift +++ b/Sources/Workspace+Surfaces.swift @@ -5,10 +5,7 @@ import Foundation import SwiftUI import AppKit import Bonsplit -import Combine -import CryptoKit import Darwin -import Network import CoreText extension Workspace { diff --git a/Sources/Workspace+Theme.swift b/Sources/Workspace+Theme.swift index dd2cfd52b..d878924a3 100644 --- a/Sources/Workspace+Theme.swift +++ b/Sources/Workspace+Theme.swift @@ -5,10 +5,7 @@ import Foundation import SwiftUI import AppKit import Bonsplit -import Combine -import CryptoKit import Darwin -import Network import CoreText extension Workspace { diff --git a/Sources/Workspace.swift b/Sources/Workspace.swift index 1dedfcb9e..971f58457 100644 --- a/Sources/Workspace.swift +++ b/Sources/Workspace.swift @@ -3,9 +3,7 @@ import SwiftUI import AppKit import Bonsplit import Combine -import CryptoKit import Darwin -import Network import CoreText /// Workspace represents a sidebar tab. @@ -273,8 +271,7 @@ final class Workspace: Identifiable, ObservableObject { // known for this panel, there is no attached shell whose work // could be lost — never prompt. The ghostty needs-confirm // fallback can spuriously return true in the pre-attach window - // (ported from upstream cmux 2e03978ae1, adapted to our - // shell-integration telemetry signals). + // (keyed off our shell-integration telemetry signals). guard hasKnownTTY else { return false } return fallbackNeedsConfirmClose } diff --git a/Sources/WorkspaceShortcutMapper.swift b/Sources/WorkspaceShortcutMapper.swift index 534c26553..5810bd8f2 100644 --- a/Sources/WorkspaceShortcutMapper.swift +++ b/Sources/WorkspaceShortcutMapper.swift @@ -2,10 +2,7 @@ import AppKit import SwiftUI import Bonsplit import CoreServices -import UserNotifications -import Combine import ObjectiveC.runtime -import Darwin enum WorkspaceShortcutMapper { /// Maps numbered workspace shortcuts to a zero-based workspace index. diff --git a/Sources/WorkspaceSidebarModels.swift b/Sources/WorkspaceSidebarModels.swift index 2286a181c..b864f3982 100644 --- a/Sources/WorkspaceSidebarModels.swift +++ b/Sources/WorkspaceSidebarModels.swift @@ -5,10 +5,7 @@ import Foundation import SwiftUI import AppKit import Bonsplit -import Combine -import CryptoKit import Darwin -import Network import CoreText struct SidebarWorkspaceHierarchyEntry: Equatable { diff --git a/core/crates/programad/src/fdpass.rs b/core/crates/programad/src/fdpass.rs index 5bed8e845..a68caaaf6 100644 --- a/core/crates/programad/src/fdpass.rs +++ b/core/crates/programad/src/fdpass.rs @@ -470,18 +470,16 @@ mod tests { // Isolated from the default `cargo test` run and re-run alone (see the // shared-core CI step: `cargo test -p programad --locked --lib -- - // --ignored --test-threads=1`). `RecvWithFds` (above `recv_with_fds`) - // fixed the production bug this test's EPIPE check used to be exposed - // to: `MsgStream::fill_more` now closes a rejected batch while - // `recv_with_fds`'s `SPAWN_FD_LOCK` guard is still held, instead of - // after it, so a concurrent fork can no longer inherit a not-yet-closed - // duplicate from *this* rejection path. Kept isolated anyway as - // defense in depth: the EPIPE assertion below is still, structurally, - // sensitive to *any* process anywhere holding the pipe's read end, not - // just this one -- see `too_many_descriptors_are_closed_before_the_reject_returns` - // below for a regression test that instead counts this process's own - // fd table, which is robust against other processes by construction - // and is the one that would have caught the fixed bug directly. + // --ignored --test-threads=1`). `MsgStream::fill_more` closes a + // rejected batch while `recv_with_fds`'s `SPAWN_FD_LOCK` guard (see + // `RecvWithFds`) is still held, so a concurrent fork cannot inherit a + // not-yet-closed duplicate from *this* rejection path. Kept isolated + // anyway as defense in depth: the EPIPE assertion below is + // structurally sensitive to *any* process anywhere holding the pipe's + // read end, not just this one -- see + // `too_many_descriptors_are_closed_before_the_reject_returns` below, + // which instead counts this process's own fd table and is robust + // against other processes by construction. #[tokio::test] #[ignore = "must run alone: see the shared-core CI step for why"] async fn max_sendmsg_descriptors_close_without_leak() { @@ -556,10 +554,10 @@ mod tests { .unwrap_or(0) } - /// Regression test for the production bug fixed alongside this test: - /// `recv_with_fds` used to drop its `SPAWN_FD_LOCK` guard on its own - /// return, before `MsgStream::fill_more` (the caller) actually closed - /// a rejected batch -- see `RecvWithFds`'s doc comment. Unlike + /// Guards that `recv_with_fds` keeps its `SPAWN_FD_LOCK` guard alive + /// until `MsgStream::fill_more` (the caller) has closed a rejected + /// batch, rather than dropping it on return -- see `RecvWithFds`'s doc + /// comment. Unlike /// `max_sendmsg_descriptors_close_without_leak` above, this doesn't /// infer leak-freedom from a side effect (a write returning EPIPE) /// that's sensitive to what *any* process holds open; it counts this diff --git a/programaTests/AppDelegateShortcutRoutingTests.swift b/programaTests/AppDelegateShortcutRoutingTests.swift index 228288ad0..e8187b2f2 100644 --- a/programaTests/AppDelegateShortcutRoutingTests.swift +++ b/programaTests/AppDelegateShortcutRoutingTests.swift @@ -5323,7 +5323,7 @@ final class AppDelegateShortcutRoutingTests: XCTestCase { /// Regression test for the ghostty IO-thread callback-context teardown race. /// /// Root cause: `GhosttySurfaceCallbackContext` (the per-surface userdata handed to - /// ghostty) used to hold `weak var surfaceView: GhosttyNSView?` and `weak var + /// ghostty) must not hold `weak var surfaceView: GhosttyNSView?` and `weak var /// terminalSurface: TerminalSurface?`. Ghostty's IO thread invokes callbacks that /// read those weak refs SYNCHRONOUSLY, before any main-thread hop -- /// `runtimeReadClipboardCallback` read `callbackContext.runtimeSurface` directly, and @@ -5338,7 +5338,7 @@ final class AppDelegateShortcutRoutingTests: XCTestCase { /// (`PROGRAMA_FORCE_OCCLUDED=1`, honored by /// `GhosttySurfaceScrollView.applyEffectiveOcclusion()`) with a live shell in each /// window -- exactly the kind of concurrent IO-thread callback traffic vs. main-thread - /// teardown that used to race. This same rapid create/close shape is also the one the + /// teardown that could race. This same rapid create/close shape is also the one the /// `windowObserverGeneration` guard in `GhosttySurfaceScrollView.viewDidMoveToWindow` /// targets (a second, distinct teardown-race mechanism: an /// `NSNotificationCenter.addObserver(queue: .main)` block already handed off to the diff --git a/programaTests/GhosttyConfigTests.swift b/programaTests/GhosttyConfigTests.swift index 29031d255..fafa571cb 100644 --- a/programaTests/GhosttyConfigTests.swift +++ b/programaTests/GhosttyConfigTests.swift @@ -2969,7 +2969,7 @@ final class ZshShellIntegrationHandoffTests: XCTestCase { func testBashBootstrapPreservesStarshipPromptCommandAcrossPrompts() throws { // Regression test: Programa's local macOS bash bootstrap is injected - // as PROMPT_COMMAND and used to begin by unconditionally clobbering + // as PROMPT_COMMAND and must not begin by unconditionally clobbering // PROMPT_COMMAND. A user's startup files run // `eval "$(starship init bash)"` before the first prompt, which // appends starship_precmd to PROMPT_COMMAND; the bootstrap then diff --git a/programaTests/GhosttyScrollPreciseBoostTests.swift b/programaTests/GhosttyScrollPreciseBoostTests.swift index 5d9fec864..54a9ba7b2 100644 --- a/programaTests/GhosttyScrollPreciseBoostTests.swift +++ b/programaTests/GhosttyScrollPreciseBoostTests.swift @@ -1,8 +1,7 @@ import XCTest @testable import Programa_DEV -/// Regression tests for the precise-scroll 2x boost gating (ported from -/// upstream cmux da4e4bc460): only gesture-driven precise deltas (trackpad, +/// Regression tests for the precise-scroll 2x boost gating: only gesture-driven precise deltas (trackpad, /// Magic Mouse) are boosted; high-res mouse wheels report precise deltas /// with empty phases and must scroll unboosted. final class GhosttyScrollPreciseBoostTests: XCTestCase { diff --git a/programaTests/InactivePaneFirstClickFocusTests.swift b/programaTests/InactivePaneFirstClickFocusTests.swift index ae30d13e0..cec50687e 100644 --- a/programaTests/InactivePaneFirstClickFocusTests.swift +++ b/programaTests/InactivePaneFirstClickFocusTests.swift @@ -9,12 +9,12 @@ import WebKit #endif /// Clicking into an inactive Programa window activates the window without also -/// focusing the pane under the pointer. That used to be a preference -/// (`paneFirstClickFocus.enabled`); it is now the only behaviour, so these -/// tests pin it rather than exercising both sides of a toggle. +/// focusing the pane under the pointer. This is the only behaviour +/// (there is no preference), so these tests pin it rather than exercising both +/// sides of a toggle. /// -/// The three view classes are covered separately because each one used to -/// override `acceptsFirstMouse` on its own, and a future override on any of +/// The three view classes are covered separately because each one +/// overrides `acceptsFirstMouse` on its own, and a future override on any of /// them would silently reintroduce click-through. @MainActor final class InactivePaneFirstClickFocusTests: XCTestCase { diff --git a/programaTests/ProgramaConfigTests.swift b/programaTests/ProgramaConfigTests.swift index 8cb8c75e0..7a14475b9 100644 --- a/programaTests/ProgramaConfigTests.swift +++ b/programaTests/ProgramaConfigTests.swift @@ -611,7 +611,7 @@ final class ProgramaConfigDecodingTests: XCTestCase { // MARK: - JSONC (comments + trailing commas) config parsing // -// Regression coverage for a real user pain point: a project's programa.json (ported from +// Regression coverage for a real user pain point: a project's programa.json (like // ~/.config/programa/programa.json) failed to parse with "Unexpected character '/' at line 5 col 4" // because plain JSONDecoder rejects `//`/`/* */` comments and trailing commas. These tests // exercise the same JSONCParser.preprocess -> JSONDecoder pipeline ProgramaConfigStore uses @@ -970,7 +970,7 @@ final class ProgramaLayoutEncodingTests: XCTestCase { /// ran only when the file itself set `confirm: true`, so any config could opt out of being /// checked by leaving the key off, and `workspace`-type commands were never gated at all. /// -/// These pin the corrected truth table. The first case is the one that used to execute +/// These pin the corrected truth table. The first case is the one that must never execute /// arbitrary shell with no prompt at all. @MainActor final class ProgramaConfigTrustGateTests: XCTestCase { diff --git a/programaTests/ProgramaDirectoryTrustTests.swift b/programaTests/ProgramaDirectoryTrustTests.swift index b555f8524..58040e367 100644 --- a/programaTests/ProgramaDirectoryTrustTests.swift +++ b/programaTests/ProgramaDirectoryTrustTests.swift @@ -6,9 +6,9 @@ import XCTest @testable import Programa #endif -/// Issue #188: trust used to be a flat list of directory paths with no way to tell whether the -/// `programa.json` inside had changed since approval, so a later edit to an already-trusted -/// config ran with no prompt at all. These pin the fix: trust is pinned to a digest of the +/// Issue #188: a flat list of trusted directory paths cannot tell whether the +/// `programa.json` inside changed since approval, so a later edit to an already-trusted +/// config would run with no prompt at all. These pin the fix: trust is pinned to a digest of the /// config's *executable content* (JSONC comments/formatting stripped, keys canonicalized), so a /// changed command is detected while a comment edit or reformat is not. /// diff --git a/programaTests/TerminalControllerSocketSecurityTests.swift b/programaTests/TerminalControllerSocketSecurityTests.swift index 55558da8b..ea7a1337e 100644 --- a/programaTests/TerminalControllerSocketSecurityTests.swift +++ b/programaTests/TerminalControllerSocketSecurityTests.swift @@ -984,7 +984,7 @@ final class TerminalControllerSocketSecurityTests: XCTestCase { XCTAssertEqual(otherWorkspace.metadataBlocks["other-metadata"]?.markdown, "other-details") } - /// Regression for #6618: `shouldPublishShellActivity` used to record the state + /// Regression for #6618: `shouldPublishShellActivity` must not record the state /// it was queried with (write-on-read). When a report arrived before the panel /// existed, that premature write suppressed every later identical report, so the /// panel's shell state stayed `.unknown` forever. The read and the write are now @@ -1816,9 +1816,9 @@ final class TerminalControllerSocketSecurityTests: XCTestCase { XCTAssertEqual(created, [direct.id.uuidString, alias.id.uuidString], "Both creation APIs must publish exactly once") } - /// Regression for #82: `surface.report_tty`/`surface.ports_kick` used to block the socket - /// thread with `DispatchQueue.main.sync` so the response could echo the surface resolved on - /// main (e.g. falling back to the focused surface when `surface_id` is omitted). They now + /// Regression for #82: `surface.report_tty`/`surface.ports_kick` must not block the socket + /// thread with `DispatchQueue.main.sync` to echo the surface resolved on + /// main (e.g. falling back to the focused surface when `surface_id` is omitted). They /// follow the same off-main-parse + main.async-mutate telemetry shape as /// `workspace.set_status`/`workspace.report_meta_block`: the JSON-RPC response acknowledges /// immediately (echoing the request, not a value resolved on main) and the surface-resolving @@ -1903,7 +1903,7 @@ final class TerminalControllerSocketSecurityTests: XCTestCase { to: socketPath ) - // The unknown surface_id can no longer be validated synchronously (that check now + // The unknown surface_id cannot be validated synchronously (that check // happens inside the fire-and-forget main.async mutation), so the request is acked // immediately and the mutation silently no-ops once it resolves. XCTAssertEqual(reportTTYResponse["ok"] as? Bool, true, "Unexpected JSON-RPC response: \(reportTTYResponse)") diff --git a/programaTests/WorkspaceCloseConfirmationTests.swift b/programaTests/WorkspaceCloseConfirmationTests.swift index 68d29cf1a..5ee80db8e 100644 --- a/programaTests/WorkspaceCloseConfirmationTests.swift +++ b/programaTests/WorkspaceCloseConfirmationTests.swift @@ -2,8 +2,7 @@ import XCTest @testable import Programa_DEV /// Regression tests for the close-confirmation-before-tty-attach race -/// (ported from upstream cmux 2e03978ae1, adapted to our shell-integration -/// telemetry signals). +/// (keyed off our shell-integration telemetry signals). /// /// Right after a terminal is created, no shell-activity state has been /// reported and no TTY is known for the panel. In that window there is no diff --git a/scripts/gen-v2-contract.py b/scripts/gen-v2-contract.py index 7eaeca9f0..12d3efedc 100644 --- a/scripts/gen-v2-contract.py +++ b/scripts/gen-v2-contract.py @@ -82,7 +82,7 @@ def swift_array(name, items, doc): body += swift_array("debugMethods", debug, "Methods only available in DEBUG builds, appended after `baseMethods`.") body += "\n\n" body += " /// Full method list for the current build configuration, mirroring the\n" - body += " /// conditional `#if DEBUG` append that used to live inline in\n" + body += " /// conditional `#if DEBUG` append in\n" body += " /// `TerminalController.v2Capabilities()`.\n" body += " static var methods: [String] {\n" body += " #if DEBUG\n" diff --git a/vendor/bonsplit/Sources/Bonsplit/Internal/Views/TabBarView.swift b/vendor/bonsplit/Sources/Bonsplit/Internal/Views/TabBarView.swift index 02e6add7a..d129bc75c 100644 --- a/vendor/bonsplit/Sources/Bonsplit/Internal/Views/TabBarView.swift +++ b/vendor/bonsplit/Sources/Bonsplit/Internal/Views/TabBarView.swift @@ -149,13 +149,13 @@ struct TabBarView: View { // glass island so the empty drag target does not expand the island. dropZoneAfterTabs - // Reserved gutter behind the split-action icon cluster. This used to - // be plain `.padding(.trailing, 114)` — dead space with no NSView, so - // AppKit's hit-test walk fell into the ScrollView's own hosting/clip - // view here instead of reaching the tab bar's window-drag background, - // silently swallowing clicks (#window-drag-areas). A real (still - // fully transparent) drag-capture view makes window drag work in this - // strip the same way it already does in the 30pt zone before it. + // Reserved gutter behind the split-action icon cluster. Keep this a + // real view, not plain `.padding(.trailing, 114)`: padding is dead + // space with no NSView, so AppKit's hit-test walk falls into the + // ScrollView's own hosting/clip view instead of reaching the tab bar's + // window-drag background and silently swallows clicks + // (#window-drag-areas). A fully transparent drag-capture view makes + // window drag work in this strip as it does in the 30pt zone before it. if showSplitButtons { splitButtonsGutterDragZone } @@ -490,8 +490,7 @@ struct TabBarView: View { // MARK: - Split Buttons Gutter Drag Zone /// Fixed-width gutter reserved behind the split-action icon cluster (terminal/ - /// split buttons), matching the 114pt previously consumed by trailing padding. Kept as a - /// real drag-capturing view so window drag works there. + /// split buttons), 114pt wide. Kept as a real drag-capturing view so window drag works there. @ViewBuilder private var splitButtonsGutterDragZone: some View { TabBarDragZoneView() @@ -609,7 +608,6 @@ struct TabBarView: View { let fadeWidth: CGFloat = 24 let shouldShowButtons = showSplitButtons && (presentationMode != "minimal" || isHoveringTabBar) let buttonClearWidth: CGFloat = shouldShowButtons ? 90 : 0 - let buttonFadeWidth: CGFloat = shouldShowButtons ? fadeWidth : 0 HStack(spacing: 0) { // Left scroll fade diff --git a/vendor/bonsplit/Sources/Bonsplit/Public/DebugEventLog.swift b/vendor/bonsplit/Sources/Bonsplit/Public/DebugEventLog.swift index ea35a8261..0e4014f5a 100644 --- a/vendor/bonsplit/Sources/Bonsplit/Public/DebugEventLog.swift +++ b/vendor/bonsplit/Sources/Bonsplit/Public/DebugEventLog.swift @@ -64,7 +64,7 @@ public final class DebugEventLog: @unchecked Sendable { if let handle = FileHandle(forWritingAtPath: Self.logPath) { // Throwing APIs instead of the deprecated seekToEndOfFile/write/closeFile // trio, which raise ObjC exceptions (uncatchable from Swift) if the file - // disappears out from under us. Ported from upstream bonsplit cffd9a6. + // disappears out from under us. _ = try? handle.seekToEnd() try? handle.write(contentsOf: data) try? handle.close()