Minimalist macOS app that uses the physical notch to show progress on the current calendar event.
- Pick one or more calendars (checkboxes in Settings).
- If a tracked calendar is deleted or unshared, NotchBar won't auto-pick a replacement — reselect one in Settings.
- English and French, following the system language.
- At rest NotchBar draws nothing — the physical notch shows through untouched (so it never slides with the desktop during Space switches), and no information leaks until you hover.
- On hover, the panel expands and shows one of seven contextual states, computed across the events of every tracked calendar:
| State | Trigger | Shown |
|---|---|---|
| In progress | Event overlaps now | Title, start–end times, animated progress bar, elapsed / remaining + Join button when a meeting link is detected (Zoom, Meet, Teams, Webex) |
| Starting soon | Next event in ≤ 5 minutes | Starts in Xm — <title> + Join button when a meeting link is detected (Zoom, Meet, Teams, Webex) |
| Upcoming today | Next event later today | Next: <title> in Xh Ymin |
| Upcoming | Next event is beyond today (up to 7 days out) | Next event in: DD:HH:MM:SS (live countdown) |
| Empty today | No events found | No event today |
| No calendar | No calendars selected | Pick a calendar in Settings |
| Access off | Calendar access denied or revoked | Calendar access is off — re-enable in Settings |
Sources/
├── NotchBarApp.swift # @main + AppDelegate
├── NotchPanel/ # NSPanel windows, hover tracking, SwiftUI rendering
├── Calendar/ # EventKit access + event snapshot model
├── Settings/ # UserDefaults-backed preferences + settings UI
├── Utilities/ # ScreenHelper (notch geometry) + Localized helper
└── Resources/ # en.lproj/ + fr.lproj/ Localizable.strings, processed natively by SwiftPM
Tests/
└── NotchBarTests/ # XCTest target (@testable import NotchBar)
Supporting/
├── Info.plist # LSUIElement, calendars usage description, bundle metadata
└── NotchBar.entitlements # Sandbox + calendars entitlement
NotchBar is intentionally simple — no external dependencies, no framework layers.
SPM-only (no .xcodeproj)
Swift Package Manager is the sole build system. This keeps builds fully reproducible and eliminates Xcode project file merge conflicts in team workflows.
NSPanel over NSWindow
The notch overlay is an NSPanel configured as borderless + nonactivatingPanel. This combination keeps the panel visible at the correct screen layer without stealing keyboard focus from the active app.
Six-state snapshot model
EventProgressModel holds an EventProgressSnapshot — an immutable, Equatable value derived from live EventKit data. CalendarManager computes the snapshot; the view renders whatever snapshot it receives. All conditional logic is isolated in CalendarManager.computeSnapshot, making each state independently testable without a running EventKit store.
Data flow
EventKit → CalendarManager → EventProgressSnapshot → NotchPanelView
CalendarManager owns EKEventStore, publishes currentEvent / nextEvent, and polls every 30s (plus reacts to EKEventStoreChanged). If Calendar access is granted after launch — e.g. from System Settings, without restarting NotchBar — the store-changed notification and each panel open re-check authorization and pick up the change automatically. NotchPanelView observes EventProgressModel via @ObservedObject.
Idle-first performance
At rest the notch shows nothing from the snapshot, so NotchBar does no live work while collapsed: the 1-second refresh tick and the 60fps progress-bar shimmer both run only while the panel is open (hover). Snapshots are Equatable, so redundant recomputes never trigger a SwiftUI invalidation. Result: ~0% CPU when collapsed, even during an in-progress event.
LSUIElement
Set in Info.plist, this flag hides the app from the Dock and the Cmd-Tab app switcher. NotchBar runs as a pure background UI layer with no Dock presence.
- Grab it from the NotchBar website, or download the latest
NotchBar.dmgfrom the Releases page. - Open the
.dmgand drag NotchBar into your Applications folder. - Open NotchBar. The first time, macOS blocks it because the app isn't notarized yet.
- Open System Settings → Privacy & Security, scroll down, and click Open Anyway, then confirm.
Why the extra step? macOS blocks it only because NotchBar isn't notarized by Apple yet — it's safe, just unsigned. You do this once; afterwards it opens with a normal double-click. (Notarization is on the roadmap.)
On first launch, grant Calendar access when prompted, then hover over the notch and click the settings icon to choose which calendars to track.
- If you turned on Launch at login, hover the notch → open settings → toggle it off first (this removes the login item cleanly). You can also remove it later under System Settings → General → Login Items.
- Quit NotchBar: click the NotchBar icon in the menu bar → Quit, or hover the notch → open settings → Quit NotchBar.
- Drag NotchBar from Applications to the Trash.
- Optional — remove leftover settings: delete
~/Library/Containers/com.periicles.NotchBar/. - Optional — revoke Calendar access: System Settings → Privacy & Security → Calendars.
Prerequisites
- macOS 14 or later
- Xcode full install (required for XCTest and the Swift 6.1 toolchain)
Clone, build, and run
git clone https://github.com/Periicles/Notchapp.git
cd Notchapp
swift build
swift runOn first launch, grant Calendar access when the system prompt appears (or later via System Settings → Privacy & Security → Calendars). Then hover over the physical notch and click the settings icon to choose which calendars to track.
Running tests
The test target depends on XCTest, which ships with full Xcode (not Command Line Tools). If xcode-select -p points at /Library/Developer/CommandLineTools, set DEVELOPER_DIR for the test invocation:
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer swift testLinting
Install SwiftLint and run it from the project root:
brew install swiftlint
swiftlintThe CI pipeline runs swiftlint on every PR. Fix all errors before pushing.
Branch naming
| Prefix | Use for |
|---|---|
feature/<topic> |
New functionality |
fix/<topic> |
Bug fixes |
docs/<topic> |
Documentation-only changes |
refactor/<topic> |
Code changes with no behavior change |
Commit style — Conventional Commits
feat: add weekly agenda view
fix: correct progress bar overflow at event boundary
refactor: extract snapshot helpers into static methods
test: cover upcomingToday state with same-day boundary
docs: document five-state model in README
Before opening a PR
swift test— all tests passswiftlint— zero errors- New non-trivial logic is covered by tests
- One concern per PR — avoid mixing features with refactors
The CI pipeline (lint → build → test) runs automatically on every PR. A PR cannot be merged with a failing CI.
Releases are cut by pushing a tag. A GitHub Actions workflow (.github/workflows/release.yml) then lints, builds, tests, packages the .dmg, and publishes a GitHub Release automatically.
git tag v0.2.0
git push origin v0.2.0The version comes from the tag (vX.Y.Z → X.Y.Z) and is injected into the app at build time — no need to edit Info.plist. Releases are currently marked pre-release and are ad-hoc signed (not notarized), so macOS shows the one-time "Open Anyway" step.
Enabling notarization (later). Once an Apple Developer ID is available, add these repository secrets and follow the commented hooks in release.yml / scripts/package.sh, then drop --prerelease:
| Secret | For |
|---|---|
MACOS_CERT_P12_BASE64 |
Developer ID Application cert (.p12), base64-encoded |
MACOS_CERT_PASSWORD |
the .p12 password |
APPLE_ID, APPLE_TEAM_ID, APPLE_APP_PASSWORD |
notarytool credentials |
NotchBar is released under the MIT License.