← Back to blog

niacin 1.1 — live policy reload and a calmer menu bar

9 May 2026

niacin 1.1 is out. The headline change is for IT: managed policy now takes effect the moment your MDM pushes it, with no restart and no logout. The rest of the release is small comforts — a countdown next to the menu bar icon, an About window, a Settings window that actually opens where you’re looking, and proper unified logging when you need to figure out what the app is doing.

Live policy reload

In 1.0, applying a new managed configuration meant restarting niacin (or, more often, telling a user to). That works, but it’s the kind of friction that makes IT teams quietly stop using a tool. 1.1 watches /Library/Managed Preferences/com.oldsalt.niacin.plist directly and reacts to changes in milliseconds.

The implementation has a few sharp corners worth calling out:

  • Bypasses cfprefsd. The managed-preferences daemon aggressively caches that directory and isn’t expected to re-read on direct edits — the managed domain is designed for ingestion via mdmclient and profiles. niacin now reads the plist directly with NSDictionary(contentsOfFile:), so a JAMF push, a profile install, or a defaults write from your runbook all surface immediately.
  • kqueue with a polling safety net. A DispatchSourceFileSystemObject watcher gives near-instant reaction. A 30-second mod-date poll covers filesystems where kqueue events may not deliver. Atomic-replace deployments (cp, defaults write, JAMF) trigger .rename or .delete, and niacin re-installs the watch on the new inode.
  • Per-user plist precedence. A user-level managed plist takes precedence over the system-wide one, matching the standard managed-domain resolution order.
  • Loud parse errors. Malformed XML now logs a clear error rather than silently falling back to the previous policy. If you typo a key, you’ll see it.

Smarter handling of running sessions

If a stay-awake session is active when policy changes, niacin no longer kills it reflexively. The session is only deactivated when the new policy is genuinely incompatible:

  • The master kill switch was flipped off.
  • The session is indefinite and the new policy disallows it.
  • The remaining time exceeds a newly imposed maxDurationSeconds.

Compatible changes — for example, locking down which durations appear in the menu while the user is mid-session — leave caffeinate running. Policy also re-evaluates on app focus and on wake from sleep, so a Mac that comes back from a closed lid catches up immediately.

Menu bar countdown and tooltip

During a timed session, the menu bar icon now sits next to a compact countdown: 45m, 1h 23m, 30s. Indefinite sessions show an infinity symbol. Hovering reveals a status tooltip — Inactive, Keeping you awake, or 5m remaining — so you can check on a long backup without opening the menu.

About window

There’s now a proper About window with the app icon, current version, tagline, and links to the website and GitHub repo. It also credits KeepingYouAwake, whose menu bar UX shaped the 1.0 design.

Settings opens where you’re looking

SwiftUI’s SettingsLink has a habit of opening the window on the wrong Space or behind whatever you’re currently working in. niacin now uses openSettings with an explicit NSApp.activate, so the window comes to the front on your active screen every time.

Unified logging

For IT teams troubleshooting a deployment, niacin now emits structured logs under the com.oldsalt.niacin subsystem with four categories:

  • policy — reload events, deactivation reasons
  • policy-watcher — kqueue events with symbolic names ([write,extend], [rename], [delete]) instead of raw bitmasks
  • managed-prefs — plist parse results and errors
  • caffeinate — subprocess lifecycle

Stream them with:

log stream --predicate 'subsystem == "com.oldsalt.niacin"' --info

Or filter by category in Console.app.

Polish

  • The display name is now “Niacin” everywhere a user sees it (the Settings window title, About, the App Store listing). Bundle IDs and file paths are unchanged.
  • A proper app icon set, with all sizes from 16×16 to 1024×1024.
  • LSApplicationCategoryType is now wired at the target level, so the App Store places niacin under Utilities correctly.
  • ManagedPreferences.isManaged reads the plist files directly rather than via CFPreferences, which would occasionally return false positives by searching the user domain.

Upgrading

The Mac App Store update will roll out as soon as Apple’s review completes. If you’re running from a signed release on GitHub, replace the app and relaunch — managed policy carries over, and the new watcher picks up your existing plist on first launch. There are no breaking changes to the MDM key surface.

Source and release notes are on GitHub at github.com/just-an-oldsalt/niacin. Bug reports and MDM key requests welcome on the issue tracker or via niacin@dort.zone.