This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Claude Island is a macOS menu bar app (Swift 6.2, macOS 15.6+) that brings Dynamic Island-style notifications to Claude Code CLI sessions. It installs hooks into ~/.claude/hooks/ that communicate session state via a Unix socket. The app listens for events and displays them in a notch overlay with approve/deny buttons for tool permission requests.
# Build (release, ad-hoc signed)
./scripts/build.sh
# Build via Xcode directly
xcodebuild -scheme ClaudeIsland -configuration Release build
# Lint (strict mode — warnings are errors)
swiftlint lint --strict ClaudeIsland/
# Auto-format
swiftformat ClaudeIsland/
# Run all pre-commit checks
prek run --all-files
# Install pre-commit hooks (one-time setup)
prek install --hook-type pre-commit --hook-type pre-push
# Create DMG (local testing, no notarization/GitHub/website)
./scripts/create-release.sh --skip-notarization --skip-github --skip-website --skip-sparkleDependencies: brew install swiftformat swiftlint shellcheck create-dmg
All state flows through a single actor: SessionStore.shared. Every mutation enters via SessionStore.process(_ event: SessionEvent). Views subscribe via sessionsStream() -> AsyncStream<[SessionState]>.
HookSocketServer (Unix socket, receives JSON from Python hook)
→ SessionStore.process(.hookReceived(event))
→ updates SessionState (immutable struct, replaced in dict)
→ publishState() yields via AsyncStream
→ ChatHistoryManager (@Observable, subscribes to stream)
→ ClaudeSessionMonitor (@Observable, subscribes to stream)
→ SwiftUI views re-render
- App/ — Entry point (
ClaudeIslandApp@main),AppDelegate(lifecycle, hook install, window setup),WindowManager - Core/ —
NotchViewModel(@Observable UI state),ModuleRegistry/ModuleLayoutEngine(plugin-based notch modules),AppSettings(UserDefaults wrapper) - Services/ — The business logic layer:
State/SessionStore(actor) — central state, all mutations hereHooks/HookSocketServer(actor) — Unix domain socket server for hook eventsHooks/HookInstaller— auto-installs Python hook script into~/.claude/hooks/Session/ConversationParser(actor) — incremental JSONL parsing for chat historyChat/ChatHistoryManager(@Observable) — UI-facing chat dataTokenTracking/ClaudeAPIService(actor) — fetches usage from Anthropic API via OAuthTmux/ToolApprovalHandler— sends approve/deny to Claude via tmux
- Models/ —
SessionEvent(all event types),SessionState(immutable),SessionPhase(state machine),ChatMessage - UI/ — SwiftUI views + AppKit bridge (
NotchWindow/NotchPanelNSPanel subclass for borderless overlay)Modules/—NotchModuleprotocol implementations (dots, spinner, token rings, Clawd mascot, etc.)
- Hook → App: Python script (
claude-island-state.pybundled in Resources) sends JSON over Unix socket toHookSocketServer - Chat History: Parsed from JSONL files at
~/.claude/cwd/.claude-island/conversation-{sessionID}.jsonlbyConversationParser(incremental tail-based parsing for large files) - Permission Approval:
ToolApprovalHandlersends keystrokes to the correct tmux pane viaTmuxController - Token Tracking:
ClaudeAPIServicereads OAuth token from Keychain (CLIOAuthKeychainGate) and callsapi.anthropic.com
The notch UI uses a plugin architecture via the NotchModule protocol. Each module declares its side, order, visibility conditions, and renders its own view. ModuleLayoutEngine computes positions. Modules: SessionDotsModule, ActivitySpinnerModule, PermissionIndicatorModule, TokenRingsModule, ClawdModule, TimerModule, ReadyCheckmarkModule, AccessibilityWarningModule.
- Actors for mutable shared state (
SessionStore,HookSocketServer,ConversationParser,ClaudeAPIService) @Observable(not@StateObject/@Published) for all UI-facing state@MainActoron UI protocols and view-related code@concurrenton CPU-intensive nonisolated async functions (e.g.,ProcessExecutor.run)- Typed throws (SE-0413):
throws(ProcessExecutorError),throws(APIServiceError), etc. - Named tasks for debugging:
Task(name: "session-stream-register") { ... } Sendableon all value types, error enums, and cross-actor dataMutexfromSynchronizationfor thread-safe collections (not locks)
Use os.Logger exclusively — never print(). SwiftLint enforces this via no_print_statements custom rule. Each component has a static nonisolated logger:
nonisolated static let logger = Logger(subsystem: "com.engels74.ClaudeIsland", category: "ComponentName")- Large types split into extensions in separate files:
SessionStore+Subagents.swift,SessionStore+PeriodicCheck.swift // MARK: - Section Namefor organizing code within files- File names match type names exactly
- 4-space indentation, 150-char line width (200 error)
- Alphabetical imports
self.inserted explicitly (--self insert)- Type/func attributes on previous line, var attributes on same line
- Modifier order:
nonisolated, override, acl, setterACL, dynamic, mutating, nonmutating, lazy, final, required, convenience, typeMethods, owned - Acronyms preserved:
ID, URL, UUID, HTTP, JSON, API, UI, MCP, PID, JSONL, CLI, SDK
- File length: 600 warning / 1000 error
- Function body: 60 warning / 100 error
- Cyclomatic complexity: 15 warning / 25 error
- Line length: 150 warning / 200 error
HookSocketServer.swift is excluded from SwiftFormat's organizeDeclarations due to file complexity causing timeouts.
Three GitHub Actions workflows:
- code-quality.yml — Runs
prekchecks (SwiftFormat, SwiftLint, shellcheck, markdownlint, ruff) on push/PR to main - ci.yml — Builds app via
build.sh, creates DMG, optional VirusTotal scan - release.yml — Triggered by semver tag push (e.g.,
1.0.0); builds, creates DMG, creates GitHub release, updates website appcast via repository dispatch toengels74/claude-island-web
Pre-commit hooks skip SwiftFormat/SwiftLint in CI (handled separately by the code-quality workflow). The no-commit-to-branch hook prevents direct commits to main.
See .augment/rules/swift-dev-pro.md for the comprehensive Swift 6.2+ coding reference covering concurrency, typed throws, @Observable, ownership, macros, and Swift Testing framework conventions.