NotchBar is a lightweight macOS app that turns the MacBook notch into a live dashboard for coding agents. The architecture follows three principles:
- Plugins, not providers — every coding assistant is a plugin. Adding support for a new tool is one Swift file + one line of registration.
- Capability-driven UI — the UI never checks "is this Claude?" It asks "does this plugin support live approvals?" and degrades gracefully.
- Minimal dependencies — pure Swift + Apple frameworks + SwiftTerm for embedded terminal. No node_modules, no build complexity.
┌─────────────────────────────────────────────────────────────┐
│ App Shell │
│ Infrastructure · Views · CardStack · Timeline │
│ ApprovalOverlay · Settings · Components · Shapes │
├─────────────────────────────────────────────────────────────┤
│ Plugin System │
│ ProviderCore · PluginRegistry · ProviderManager │
├────────┬────────┬────────┬─────────────────────────────────┤
│Embedded│ Claude │ Codex │ Conflict Detector │
│Terminal│ Code │ │ │
├────────┴────────┴────────┴─────────────────────────────────┤
│ Shared Services │
│ Shell · SocketServer · CoordinationEngine · FileWatcher │
│ GitIntegration · TranscriptReader · PTYSessionManager │
│ TerminalHelper · SessionHistory · FontManager · Updater │
└─────────────────────────────────────────────────────────────┘
The UI layer. Knows nothing about specific plugins.
| File | Responsibility |
|---|---|
Infrastructure.swift |
Window panels, hotkeys, menu bar, app delegate, plugin registration |
Views.swift |
Collapsed bar, expanded view, notch shape, approval routing |
ApprovalOverlay.swift |
Doorbell overlay — file preview, edit diffs, Deny/Allow buttons with disclosure chevron |
CardStack.swift |
Session cards (collapsed + expanded), card stack layout |
Timeline.swift |
Task timeline with status nodes and completion markers |
Components.swift |
Progress ring, diff views, dot progress, session state icons |
Shapes.swift |
Notch geometry, provider icons, NotchOwl branding, Nothing UI dot-matrix icons |
Settings.swift |
Plugin store, display settings, general settings |
Onboarding.swift |
First-launch setup wizard |
The bridge between plugins and the UI. Three files, no plugin-specific code.
| File | Responsibility |
|---|---|
ProviderCore.swift |
ProviderID, ProviderDescriptor, ProviderCapabilities, AgentProviderController protocol, PluginRegistry |
ProviderManager.swift |
Plugin lifecycle, action routing (approve/reject/allowAll/bypass) |
Models.swift |
AgentSession, TaskItem, PendingApproval, NotchState |
Each plugin is a single Swift file implementing AgentProviderController.
| Plugin | File | Pattern |
|---|---|---|
| Claude Code | ClaudeCodeBridge.swift |
Hook IPC via Unix socket |
| Embedded Terminal | EmbeddedTerminalProvider.swift |
PTY + SwiftTerm |
| Codex | CodexProvider.swift |
Transcript monitoring (disabled by default) |
| Conflict Detector | ConflictDetectorProvider.swift |
File watcher + MCP coordination |
| File | What it provides |
|---|---|
Shell.swift |
pgrep, cwd, process runner, JSONL parsing, file tailing |
SocketServer.swift |
Unix domain socket server for hook IPC |
TranscriptReader.swift |
Claude transcript (.jsonl) parser |
CodexTranscriptReader.swift |
Codex transcript (.jsonl) parser |
GitIntegration.swift |
Branch, status, diff parsing |
TerminalHelper.swift |
Terminal.app / iTerm2 AppleScript bridge |
CoordinationEngine.swift |
Multi-agent file lock coordination |
FileWatcher.swift |
External file modification detection |
PTYSessionManager.swift |
Pseudo-terminal lifecycle management |
SessionHistory.swift |
Past session scanning and resume |
FontManager.swift |
Custom font loading from resources |
UpdateChecker.swift |
GitHub release polling |
protocol AgentProviderController: AnyObject {
var descriptor: ProviderDescriptor { get } // Who am I, what can I do
func start() // Begin monitoring
func cleanup() // Stop monitoring
// Optional:
func installIntegration() -> Bool
func removeIntegration() -> Bool
func approveAction(requestId:sessionId:)
func rejectAction(requestId:sessionId:)
func listPastSessions() -> [PastSession]
func resumeSession(_:)
}Registration is one line in AppDelegate.applicationDidFinishLaunching:
providerManager.register(MyPlugin(state: state))| Flag | Meaning | Used by |
|---|---|---|
liveApprovals |
Can intercept tool use and show approve/reject UI | Claude Code |
liveReasoning |
Can extract reasoning from transcripts | Claude Code, Codex |
sessionHistory |
Can list and resume past sessions | Claude Code, Codex |
integrationInstall |
Has install/remove integration actions | Claude Code, Codex |
The UI checks these flags, not plugin IDs. A new plugin that sets liveApprovals: true automatically gets the full approval doorbell without any UI changes.
Claude Code hook → bash → nc -U notchbar.sock → SocketServer
→ ClaudeCodeBridge.handleSocketEvent (background thread)
→ auto-approve check → if manual: store response callback
→ dispatch to main → update AgentSession
→ SwiftUI observes @Published → re-render
→ user approves → response callback fires → hook script receives JSON
~9ms round-trip. Fail-open: if NotchBar isn't running, hook auto-approves. Your agent never gets stuck waiting for a dead app — that would be rude.
Timer (2s) → Shell.readTail(path, from: offset) → parse JSONL
→ TranscriptEntry cases → update AgentSession properties
→ SwiftUI re-renders
AgentSessionproperties are@Publishedand must be updated on the main threadClaudeCodeBridgeusesNSLock+withLockhelper forsessionMapandrunningToolsSocketServerusesNSLockfor response coordination between callback and timeout- Plugin timers run on main run loop; heavy work dispatches to
.utilityqueues