Add Open at Login toggle to General settings#450
Merged
Conversation
The LaunchAtLoginService (SMAppService-backed) was already built and wired through the app, but never surfaced in the UI, so users had no way to enable launch-at-login. Add an Open at Login toggle to the General pane's Status section. - GeneralPaneView observes LaunchAtLoginService and binds the toggle to its state/setEnabled. The toggle is disabled when macOS reports the login item as unavailable, and the row description surfaces the OS status detail (approval needed / move to Applications) or any registration error. - SettingsContainerView threads the already-available service into the pane. - Pure tests for LaunchAtLoginState presentation flags.
refresh() now clears lastErrorMessage before re-reading the login-item status, so a one-time registration failure (such as a first launch outside /Applications) no longer lingers as the row's subtext after the user fixes the cause and macOS reports the item enabled or cleanly disabled. setEnabled() re-reads OS state via a new private reloadState() instead of refresh(), so an error it just captured survives the post-mutation read and can still explain why the toggle did not take effect. GeneralPaneView's launchAtLoginDescription returns the plain explanation when the item is enabled, so a working toggle is never described by leftover detail or error text. Resolves the Greptile P1 review comment on GeneralPaneView.swift.
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
Adds an Open at Login toggle so Cotabby can start automatically when the user logs in. The SMAppService-backed
LaunchAtLoginServicealready existed and was wired through the app's dependency graph (constructed inCotabbyAppEnvironment, retained inAppDelegate, passed intoSettingsContainerView), but it was never surfaced in any settings pane, so there was no way to actually turn launch-at-login on. This exposes it in the General pane's Status section.GeneralPaneViewobservesLaunchAtLoginServiceand binds the toggle to itsstate.isEnabled/setEnabled.SettingsContainerViewthreads the already-available service into the pane.LaunchAtLoginState's presentation flags.Validation
The toggle's live registration behavior depends on a signed build installed in /Applications (SMAppService requires the app's real code identity), so the end-to-end enable/approve flow is best confirmed manually in a release-style build; the state/presentation logic is covered by tests.
Linked issues
Risk / rollout notes
Cotabby.xcodeproj/project.pbxprojis regenerated viaxcodegen generateto register the new test file (project.yml is the source of truth).Greptile Summary
This PR surfaces the already-wired
LaunchAtLoginServicein the General settings pane by adding an Open at Login toggle to the Status section, and simultaneously fixes a stale-error-message bug identified in a prior review by splittingrefresh()into two methods.LaunchAtLoginServicegains a privatereloadState()for post-mutation re-reads (preserves a just-captured error) while the publicrefresh()— called when Settings opens — clearslastErrorMessagefirst, so old failure strings no longer persist after an out-of-band fix.GeneralPaneViewadds the toggle bound tolaunchAtLoginService.state.isEnabled/setEnabled, disabled whencanToggleis false, withlaunchAtLoginDescriptioncorrectly preferringstate.detailoverlastErrorMessageand suppressing both when the item is enabled.LaunchAtLoginStatecases forisEnabled,canToggle, anddetail.Confidence Score: 5/5
Safe to merge — UI-only wiring of an existing service with no new system permissions, and the stale-error fix from the prior review is correctly addressed.
The change introduces no new entitlements or system calls beyond what SMAppService already required. The refresh()/reloadState() split is a minimal, targeted fix for the previously identified stale-error scenario, and the description logic in launchAtLoginDescription correctly handles all four LaunchAtLoginState cases. Unit tests cover all state presentation flags. No edge cases appear unhandled.
No files require special attention.
Important Files Changed
Sequence Diagram
sequenceDiagram participant User participant GeneralPaneView participant LaunchAtLoginService participant SMAppService User->>GeneralPaneView: Opens Settings window GeneralPaneView->>LaunchAtLoginService: refresh() LaunchAtLoginService->>LaunchAtLoginService: "lastErrorMessage = nil" LaunchAtLoginService->>SMAppService: read .status SMAppService-->>LaunchAtLoginService: status LaunchAtLoginService->>LaunchAtLoginService: "state = map(status)" LaunchAtLoginService-->>GeneralPaneView: "@Published state updates toggle + description" User->>GeneralPaneView: Flips toggle ON GeneralPaneView->>LaunchAtLoginService: setEnabled(true) LaunchAtLoginService->>SMAppService: register() alt success SMAppService-->>LaunchAtLoginService: (no throw) LaunchAtLoginService->>LaunchAtLoginService: "lastErrorMessage = nil" else failure SMAppService-->>LaunchAtLoginService: throws error LaunchAtLoginService->>LaunchAtLoginService: "lastErrorMessage = error.localizedDescription" end LaunchAtLoginService->>SMAppService: read .status (reloadState) SMAppService-->>LaunchAtLoginService: status LaunchAtLoginService->>LaunchAtLoginService: "state = map(status)" LaunchAtLoginService-->>GeneralPaneView: "@Published state + lastErrorMessage update UI"Reviews (2): Last reviewed commit: "Clear stale Open at Login error on out-o..." | Re-trigger Greptile