See More Details

SideNotch

A native macOS side rail and notch bar: ten glanceable modules that expand in place, tear off into floating windows, and mirror across every connected display.

2026
macOS 15+

Overview

SideNotch puts a persistent, glanceable surface at the edge of the screen — the idea behind the Dynamic Island, applied to a Mac. Collapsed, it's a thin rail showing status at a glance. Hovered, it grows. Clicked, a module expands in place with its full view. It runs as an accessory app: no Dock icon, menu bar item only.

It's Swift, SwiftUI and AppKit, macOS only — no Electron, no web views, no cross-platform layer. Around 12,000 lines across 123 source files, with the deliberate convention that AppKit-owning controllers stay thin and every decision worth making moves into a pure, injectable type sitting next to them.

Because the repository holds a single squashed commit, git history isn't a usable record of how the app got here. An architecture document is maintained in its place as a current snapshot of what is actually implemented — updated when the shape of the app changes, rather than left to drift the way the README's module list once did.

Highlights

  • Two presentation modes sharing one state machine: a vertical rail docked to either screen edge, and a horizontal bar that hugs the MacBook's measured hardware notch.
  • Ten modules — system stats, battery, clipboard, file shelf, controls, calendar, notes, weather, timer, now playing — each with its own compact and expanded view, reorderable and individually placeable from Settings.
  • Drag a module off the rail and it detaches into its own floating panel, reattaching automatically at quit or if it's disabled while open.
  • Multi-display mirroring: a second panel on every other screen renders the same view, sized from that display's own geometry, with hover aggregated across panels.
  • Clipboard history with a privacy pause, pinning, and per-kind previews for text, URLs, files and images; a drag-and-drop file shelf with Quick Look.
  • A transient Live Activity slot that briefly takes over the collapsed surface for battery charging, a clipboard capture, a dropped file, a calendar reminder or a finished timer.
  • 329 XCTest cases, almost all against pure logic types rather than the AppKit controllers.

Design Notes

01

One pure type decides every frame, so the panels can't disagree

Sizing, positioning and notch geometry for a display all route through SideNotchDisplayPresentation — a pure type that both the docked window controller and the mirrored panels call. Without a shared authority, a second monitor is where the two code paths quietly diverge. Mirrors stay display-and-hover only: dragging, tearing off a module, and the expanded card's measured height belong to the docked panel, because they're tied to a physical position.

02

Hover is aggregated across panels, not taken from the last event

With a panel on every screen, moving the pointer from one display to another delivers an exit on the old panel after the enter on the new one. Taking the most recent event collapses the bar the pointer just arrived at, so hover state is computed across all panels instead.

03

The notch bar refuses to invent a notch

On a MacBook the top bar hugs the camera notch's actual measured rectangle, read from the screen's auxiliary top-left and top-right areas. On an external display there is no notch to hug, so it falls back to a fixed collapsed size and centers on the top edge rather than pretending hardware exists that doesn't.

04

Idle cost measured, then removed

A profiling pass on a Release build measured 0.0% CPU across five idle observations and a 22.0 MB physical footprint, with the main thread parked in the normal AppKit event wait for every captured sample. Getting there meant giving modules a lifecycle hook so only enabled, onscreen modules sample at all; splitting fast CPU/memory sampling (3s) from slow disk-capacity sampling (60s); and holding weak references across suspension points in the long-lived task loops, which had been keeping their owners alive past deinit.

05

Pausing the clipboard actually stops the work

A privacy pause that keeps waking every 600 ms to check a boolean is not a pause. Pausing cancels the polling task outright; resuming first resynchronizes the pasteboard change count, so anything copied while paused is never retroactively captured.

06

Hover tracking that works while the app isn't active

SwiftUI's onHover is unreliable on a borderless non-activating panel, and doubly so while its content is being swapped during a state transition. The auto-hide reveal sliver uses an AppKit NSTrackingArea with activeAlways instead, keeping the pointer over stable bounds that don't change as the SwiftUI view hierarchy does.

07

Previews render the production views, and can't touch a live resource

There is no preview-only copy of the root view: previews host the real one inside a stand-in for the window controller that computes panel size from the same pure types the real controller uses, so a preview container has the real NSPanel's dimensions and clipping shows up honestly. The fixtures are structurally incapable of side effects — a throwaway UserDefaults suite, a private NSPasteboard, day-long poll intervals — and the AppDelegate refuses to boot its services under previews, the same way it already refuses under XCTest.