Skip to content

Nocturne 1.3.0: follow your Focus

Choose a tag to compare

@dotcomjack dotcomjack released this 30 Aug 15:52
· 6 commits to main since this release
3ba68f5

Follow Focus

Turn on Do Not Disturb on your phone. Your Mac's menu bar goes dark too.

Nocturne is now a Focus filter. Set it up once under System Settings >
Focus > pick a Focus > Add Filter > Nocturne, and choose a mode.
Turn that Focus on from anywhere signed into your iCloud account and this Mac's
menu bar goes with it, because Focus already syncs across your devices. End the
Focus and the mode you were on comes back.

Per Focus, so put it on Do Not Disturb only, or on Work and Sleep too, whichever
ones you actually want it for.

It costs no permission. Not Full Disk Access, not Accessibility, not Screen
Recording, not a Focus prompt. macOS hands the event to Nocturne the same way it
hands it to Mail and Safari. Measured, 18ms from the system recording the Focus
to Nocturne's code running.

It gives the mode back, and it knows when not to. The mode you were on
before the Focus is remembered across a quit or a crash, because the app can be
killed while a Focus is running. But if you pick a different mode by hand while
the Focus is on, that choice is left alone when the Focus ends.

The part worth stealing even if you never install this

Two implementations were built and thrown away before this one. Both failures
are undocumented and both cost a rewrite.

Reading ~/Library/DoNotDisturb/DB/Assertions.json needs Full Disk Access.
It is the answer every search result gives. It is plain JSON and it names the
exact Focus. The whole feature was finished on top of it. The directory is TCC
protected, and the identical binary behaves differently depending on how it is
launched:

launched as isReadableFile open(O_EVTONLY)
bare binary from Terminal true fd 3
.app via open false -1, EPERM
System Policy: Nocturne(99186) deny(1) file-read-data
  /Users/.../Library/DoNotDisturb/DB/Assertions.json

It reads fine from a shell only because Terminal already holds Full Disk Access.
That is the same trap this project already documents for kCGWindowName,
walked into a second time in the same codebase, and caught only by running the
installed app instead of the build.

INFocusStatusCenter lies when it is not authorised. Apple's public Focus
API prompts for permission, reports a single optional boolean so it can never
say which Focus is on, and unauthorised it does not fail. Polled every two
seconds while Do Not Disturb was genuinely on, it returned Optional(false)
every time.

A Focus filter cannot tell "started" from "ended" unless its parameter is
optional.
Apple's own dialog promises that selected apps are notified "when
this Focus turns on or off" and nothing says how to tell the two apart, because
both arrive as the same perform() with the same values:

@Parameter(title: "Mode", default: .hideEverything) var mode: FocusFilterMode
Focus ON    perform() mode=hideEverything
Focus OFF   perform() mode=hideEverything     // identical, useless

@Parameter(title: "Mode") var mode: FocusFilterMode?
Focus ON    perform() mode=Optional(hideEverything)
Focus OFF   perform() mode=nil                // the only signal there is

Give the parameter a default value to tidy up the System Settings picker and you
lose the ability to detect the Focus ending: the app engages on both edges,
never releases, and the effect stays on forever. macOS also calls perform()
twice per transition, 588ms apart, so the handler has to be idempotent.

One defect the review caught, worth naming

"Restore clock to how it was" used to be undone within 30 seconds.

Restore ends Nocturne's engagement with the running Focus, but it does not end
the Focus. The backstop sweep kept re-reading the same live Focus, found nothing
engaged, and could not tell an hours-old Do Not Disturb from a brand new one, so
it re-hid the menu bar the user had just explicitly un-hidden. For an all-day
Focus, that is every 30 seconds all day.

The whole test suite passed while this existed, because the bug lived in the
seam between the state machine and the controller feeding it, and every test
pointed at the state machine alone. A green suite is evidence about what you
thought to check.

Tests

The project had none. It now has 74 checks over the Follow Focus state machine
including a 50,000 operation fuzz pass, run with ./Tests/run.sh, no XCTest
target, one swiftc invocation on a clean clone. Validated by mutation testing
rather than by a green bar: 16 deliberate defects introduced one at a time, all
16 caught.

Install

Download the DMG below, or:

brew install --cask dotcomjack/tap/nocturne

Signed with a Developer ID, notarized and stapled, app and disk image both.
Universal, Apple silicon and Intel. macOS 14 or later. MIT.