Skip to content

Disable survives a quit, the way switching one display off already did - #113

Merged
PathGao merged 1 commit into
mainfrom
feat/disable-survives-a-quit
Aug 29, 2026
Merged

Disable survives a quit, the way switching one display off already did#113
PathGao merged 1 commit into
mainfrom
feat/disable-survives-a-quit

Conversation

@PathGao

@PathGao PathGao commented Aug 29, 2026

Copy link
Copy Markdown
Owner

What this is

Press Disable, quit Nifro, launch it again: the wallpapers are back. Switch a single display off from the panel and do the same, and it stays off. The two halves of "off" disagreed about whether they were worth keeping, and the half that lost is the one that covers the whole Mac.

The independent step the front/back alignment audit left last, and it was left last on purpose — see Scope.

Mechanism

disabledDisplays is a Defaults key. isManuallyDisabled was a plain property on AppState, so it died with the process.

Constants.swift sorts everything the app stores by one question — can this be asked again? — and already named this as the one member out of place. The lock screen and the battery rule answer themselves the moment anybody wants to know. A press does not.

It gets a key, and the property becomes that key rather than a value beside it, so there is one answer and not two.

No didSet on the property. Events recomputes isEnabled from a change to the key, next to the line that does the same for deactivateOnBattery and for the same reason: the first delivery is what applies it on launch. That also gives the key its third writer for free — Defaults.removeAll deletes one key at a time, so Restore Defaults now switches the app back on. Before this change there was nothing to restore; after it, with a didSet, there would have been a wiped key over a still-disabled app and nothing on disk saying why.

Which is the decision RestoreDefaultsTests demands of any new key, and it is recorded there: this is one of the app's settings, so the wipe takes it. An app left switched off by a setting that has just been reset is the one state a user cannot read anywhere.

Scope

The menu bar icon needed no change, and that is why this step came last. It has read isShowingWallpaper since #107, which asks isSwitchedOff, which asks both switches — so the icon was already honest about the state this makes reachable. Landed the other way round, this would have made a cold start with the app disabled a thing that happens while the icon still drew it as fully on.

Nothing else about Disable changes: the menu item, the keyboard shortcut and the Shortcuts action all write the same property they wrote before.

Verification

224 tests in 44 suites pass; xcodebuild Debug, swiftlint --strict and periphery --strict all clean. macOS 26, Apple silicon.

Three new assertions in Tests/DisableSurvivesAQuitTests.swift, one per piece of the path, each checked by breaking the thing it names — dropping the key, making the property stored again, pointing the subscription at another key. All three go red.

Worth running by hand, since App compiles into no SwiftPM target:

  • Disable from the menu, quit, relaunch — should come back disabled, with the icon dimmed.
  • Same, then Restore Defaults from Settings — should come back on without a relaunch.
  • Disable, then switch a display off from the panel, then Enable — that display should stay off, which is isSwitchedOff doing its job and not this change.
  • On battery with "Deactivate while on battery" set: the battery rule and a stored Disable should not fight; plugging in should not undo a press.

Press Disable, quit Nifro, launch it again: the wallpapers are back. Switch a single display off
from the panel and do the same, and it stays off. The two halves of "off" disagreed about
whether they were worth keeping, and the half that lost is the one that covers the whole Mac.

`disabledDisplays` is a `Defaults` key. `isManuallyDisabled` was a plain property on `AppState`,
so it died with the process. `Constants.swift` sorts everything the app stores by one question —
can this be asked again? — and names this as the one member out of place: the lock screen and
the battery rule answer themselves the moment anybody wants to know, and a press does not.

It gets a key. The property becomes that key rather than a value beside it, so there is one
answer and not two.

No `didSet` on the property. `Events` recomputes `isEnabled` from a change to the *key*, next to
the line that does the same for `deactivateOnBattery` and for the same reason — the first
delivery is what applies it on launch. That also gives the key its third writer for free:
`Defaults.removeAll` deletes one key at a time, so Restore Defaults now switches the app back
on. Before this change there was nothing to restore, and after it with a `didSet` there would
have been a wiped key over a still-disabled app, with nothing on disk saying why.

Which is the decision `RestoreDefaultsTests` demands of a new key, recorded there: this is one
of the app's settings, so the wipe takes it. An app left switched off by a setting that has just
been reset is the one state a user cannot read anywhere.

The menu bar icon needed no change. It has read `isShowingWallpaper` since #107, which asks
`isSwitchedOff`, which asks both switches — so the icon was already telling the truth about the
state this makes reachable across a quit. That ordering was deliberate: this change makes a
cold start with the app disabled a thing that happens, and the icon had to be honest first.

224 tests in 44 suites pass; build, `swiftlint --strict` and `periphery --strict` clean. Each of
the three new assertions was checked by breaking the thing it names: dropping the key, making
the property stored again, and pointing the subscription at another key. All three go red.
@PathGao
PathGao merged commit d0b91a4 into main Aug 29, 2026
0 of 5 checks passed
@PathGao
PathGao deleted the feat/disable-survives-a-quit branch August 29, 2026 16:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant