Repository navigation
fix(macos): handle capture commands while the main window is closed - #15
Merged
Merged
Conversation
Start/Stop Capture from the Window menu, the status bar item and the ⌃⌥⌘S hotkey only posted .toggleCapture, whose sole observer was MainView. After the user closed the window (the app keeps running in the menu bar) nothing observed it and the command was silently dropped. Closing the window also destroyed MainView's @StateObject view model, so reopening started a fresh session next to any overlay still bound to the old one. Move ownership to an app-lifetime AppCoordinator on the app delegate: it creates the single TranscriptionViewModel (lazily, after onboarding) and the overlay controller, and observes the capture, pause, overlay and settings-change notifications. A capture command that arrives while the main window is closed or minimized first activates the app and reopens the window, then goes through the unchanged start()/stop()/toggleCapture() state machine; with the window on screen nothing steals focus, as before. The main scene becomes a single-instance Window so closing only hides it and SwiftUI's openWindow (captured from a Commands environment) reopens the same window from AppKit callers. Model prewarm now runs once per view model instead of on every window appearance. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve the AppDelegate conflict: keep master's removal of the startup screen-recording preflight and route the launch-time window activation through AppCoordinator.showMainWindow(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PCcoding666
force-pushed
the
fix/macos-capture-commands-window-closed
branch
from
October 10, 2026 10:37
5f94b3e to
25ce079
Compare
Resolve the MainView conflict with P2: keep the coordinator owning the start/stop, pause and settings-change observers, and keep master's refreshLanguageAvailability() in MainView's .task (the model prewarm already runs once in AppCoordinator.makeAppViewModel). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The session now outlives the main window (AppCoordinator owns the view model), but the .exportSession observer still lived in MainView, so File ▸ Export (⌘E) did nothing after the window was closed. Move it to the coordinator alongside the other app-level commands: it brings the window back so the result notice is visible, then exports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 of 3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
With the main window closed, Start/Stop Capture from the Window menu, the status bar item, and the ⌃⌥⌘S hotkey did nothing. Each of them only posts
.toggleCapture, and the only observer wasMainView. After the window closed (the app keeps running in the menu bar), nothing was listening. Reproduced on 2026-10-10: nostart() calledline appeared until the window was reopened.Closing the window also destroyed
MainView's@StateObjectTranscriptionViewModel. Reopening started a fresh, idle session next to an overlay that could still be bound to the old one.Changes
AppCoordinator(macos/LCTMac/App/AppCoordinator.swift), owned byAppDelegateso it lives for the whole run:TranscriptionViewModel, created lazily after onboarding so it still loads the settings onboarding saved, plus theOverlayWindowController;.toggleCapture,.togglePause,.toggleOverlayand.settingsDidChange;start()/stop()/toggleCapture()state machine;WindowGroup→ single-instanceWindow("LCT", id: "main"). Closing aWindowonly hides it. SwiftUI'sopenWindow(id:)reopens the same window, is idempotent when it's already open, and restores it when minimized. Before this,openWindowon aWindowGroupalways spawned a new window, and ⌘N could create severalMainViews that each started their own capture.MainWindowCommandscapturesopenWindowfrom the Commands environment so AppKit callers (status bar, hotkey) can use it.MainViewtakes the view model as@ObservedObjectand handles only window-scoped commands (Copy Translation, Show History).prepareModelOnLaunch) runs once when the coordinator creates the view model, not on every window appearance. Reopening the window mid-capture no longer re-warms the model.NSApp.windows.first, which after a close isn't the main window.Pause, overlay and settings-change moved along with start/stop because they target the same view model, which now outlives the window. Without the move, the ⌃⌥⌘P / ⌃⌥⌘O hotkeys would also do nothing with the window closed. Settings saved from the ⌘, window while the main window was closed would also be lost, because the old code picked them up by rebuilding the view model on reopen.
Test plan
AppCoordinatorTests(10 tests):.toggleCapturenotification with no window present is delivered;.toggleCapturesubscription removed, the two notification tests fail.cd macos && ./Scripts/run_tests.shafter merging currentmaster(feat(macos): capture system audio with a Core Audio tap — no screen recording permission #16): debug, release and-warnings-as-errorsbuilds pass, andswift testpasses all 454 tests with 0 failures.dev-run.sh, 2026-10-10), log lines from~/Library/Logs/LCTMac.log:[AppCoordinator] Capture command toggle — main window not visible, showing it, then▶️ start() called; the window came back and captions appeared.Merged with master (P2, #17–#20)
MainView: the coordinator keeps the start/stop, pause and settings-change observers; master'srefreshLanguageAvailability()stays in MainView's.task(the model prewarm already runs once inAppCoordinator.makeAppViewModel).AppCoordinatorTestsfor this../Scripts/run_tests.shafter the merge: debug, release and-warnings-as-errorsbuilds pass, andswift testpasses all 522 tests with 0 failures. No new warnings, including the build withoutDEVELOPER_DIR.🤖 Generated with Claude Code