AI coding assistant context for Zman project.
macOS utility that displays an orange overlay on Calendar.app when the app's timezone differs from a configured team timezone. SwiftUI windowed app with AppKit overlay management. Runs in background after window closes. Deployment target: macOS26
ARCHITECTURE.md contains Mermaid diagrams (C4, data flow, event/state flow) for human readers. Do not load it into context — it duplicates what's below in visual form and wastes tokens. Do update it when changing architecture.
- Pattern: MVVM-like with ObservableObject managers
- Layer structure:
- UI Layer: SwiftUI views (ContentView, OverlayView)
- Manager Layer: CalendarOverlayManager, TeamTimeZoneManager (struct with static methods), CalendarTimeZoneService
- System Integration: AppKit for window management, CGWindowList API for Calendar.app window tracking
- Data flow: UserDefaults (both app and com.apple.iCal suite) → Managers → SwiftUI @Published properties → UI
- Swift: Latest
- UI Framework: SwiftUI (primary) + AppKit (overlay windows, workspace monitoring)
- Key Dependencies:
AppKit: Window management, NSWorkspace monitoringCoreGraphics: CGWindowList API for window frame trackingSwiftUI: Primary UI, NSHostingView for embedding in NSWindowCombine: Publisher/subscriber pattern for ObservableObjectFoundation: UserDefaults, Timer, DateFormatter
- No external packages: All dependencies are system frameworks
- Single target: Zman-claude (app)
- Source files:
Zman_claudeApp.swift: @main entry point, AppDelegate (owns CalendarOverlayManager), command menu setupContentView.swift: Main settings window, team timezone pickerCalendarOverlayManager.swift: Floating window overlay management, Calendar.app trackingCalendarTimeZoneService.swift: Reads Calendar.app UserDefaults, @MainActor ObservableObjectTeamTimeZoneManager.swift: Struct with static methods for team timezone storage/comparisonOverlayView.swift: Inline in CalendarOverlayManager.swift
Naming:
- Swift files: PascalCase with underscores for app name (e.g.,
Zman_claudeApp.swift) - Classes/Structs: PascalCase (e.g.,
CalendarOverlayManager) - Properties: camelCase
- Static members: camelCase
- UserDefaults keys: camelCase strings (e.g., "teamTimeZone", "lastViewsTimeZone")
- Bundle ID pattern:
com.apple.iCalfor Calendar.app
File organization:
- One primary type per file (exception: small helper views like OverlayView)
- Consistent file header with copyright, date, author
- ObservableObjects marked with
@MainActorwhere appropriate - Private members for internal manager state
Code comments:
///doc comment on every type (class/struct/enum) — role and relationships///on public/internal methods — the API surface a caller sees///on non-obvious private methods — where the name doesn't fully explain behavior (e.g. state machines, multi-path logic)- Skip
///on trivial private helpers where the name is self-documenting (e.g.hideOverlay,stopMouseMonitor) - Inline
//comments for "why", not "what" — explain decisions, tradeoffs, non-obvious constraints - No DocC or formal documentation generation — comments are for humans and AI agents
Patterns:
- ObservableObject for UI-bound services (CalendarTimeZoneService)
- AppDelegate owns app-lifetime managers (CalendarOverlayManager)
- @AppStorage for UserDefaults bindings
- Notification-driven updates with adaptive safety-net timer (1s when Calendar frontmost, 5s otherwise)
- Overlay visible whenever Calendar window is on-screen and unobstructed (not just when Calendar is frontmost)
- Occlusion detection via
CGWindowListCopyWindowInfo(.optionOnScreenAboveWindow)— hides overlay if any layer-0 window intersects Calendar's frame (filters out own overlay by window number and Calendar's own popups by PID) - Workspace notifications for Space switches, app hide/unhide to react immediately to visibility changes
- Adaptive position tracking: 1s idle poll, overlay hides on move, fades back on settle
- Global input monitor (
NSEvent.addGlobalMonitorForEvents) for instant interaction detection —leftMouseDraggedfor move/resize drags,leftMouseDownfor close/dismiss clicks, andkeyDownforCmd+W/Esc - CGWindowList API with cached window ID for single-window queries
- UserDefaults suite access for reading Calendar.app preferences
- NSAnimationContext for GPU-accelerated fade transitions
- NSHostingView bridge for SwiftUI in NSWindow
UI:
- SF Symbols for icons
- System colors/materials (controlBackgroundColor)
- Rounded corners (12pt for cards, 25pt for overlay)
- Semantic color names (.tint, .secondary, .tertiary)
- Shadow radius: 2pt for elevated cards
Memory management:
[weak self]in Timer and notification closures- Explicit cleanup in stopMonitoring/deinit
- Timer tolerance set on all timers to allow macOS coalescing
- Cached Calendar.app PID (
resolveCalendarPID()helper withkill(pid, 0)validation) to avoid repeated runningApplications scans - Cached CGWindowID and CG-coordinate frame for single-window queries and occlusion checks
- Notification observers removed on cleanup (selector-based in CalendarOverlayManager, token-based in CalendarTimeZoneService)
- Do not use EventKit: Despite README mention, no EventKit access in code. App reads Calendar.app UserDefaults, not calendar events.
- Do not switch back to Accessibility API for position tracking: CGWindowList is significantly lighter than AXUIElement cross-process IPC. AX API is no longer used.
- Do not change overlay opacity/color: Fixed at
Color.orange.opacity(0.15)- this is the core UX. - Do not remove safety-net timer: Calendar.app does not reliably post notifications for timezone preference changes. The adaptive safety-net timer (1s when Calendar frontmost, 5s otherwise) catches edge cases that UserDefaults.didChangeNotification misses for the com.apple.iCal suite.
- Do not access calendar events: This is a timezone indicator, not an event reader.
- Do not change bundle ID references:
com.apple.iCalis hardcoded for Calendar.app detection. - Do not remove AppDelegate: Prevents app quit on window close—essential for menu bar utility behavior.
- Do not show windows on all spaces: Overlay uses
.canJoinAllSpacesbut main window does not. - Do not make overlay clickable:
.ignoresMouseEvents = trueis critical—overlay must be non-intrusive. - Do not change UserDefaults suite names:
com.apple.iCalsuite is required for reading Calendar settings. - Do not simplify coordinate conversion: Screen coordinate conversion (top-left → bottom-left origin) is necessary for CGWindowList → NSWindow mapping. Must use
NSScreen.screens.first(primary screen), neverNSScreen.main(focused screen) — the CG/NS coordinate systems are defined relative to the primary screen. - Do not remove overlay fade animations: The 0.15s fade-out / 0.2s fade-in masks the 1s idle poll detection delay and makes movement feel intentional.
- Do not use leftMouseDown for drag detection: Only
leftMouseDraggedin the draggable area (top 78pt) should trigger overlay fade-out. Using mouseDown causes false triggers on toolbar button clicks. Detecting drags in the full frame area causes false triggers on in-app drags (text selection, event dragging).
- Build:
make build(wraps xcodebuild). Or directly:xcodebuild -project Zman-claude.xcodeproj -scheme Zman-claude -configuration Release SYMROOT=$(pwd)/build build - Build & run:
make run— builds, kills any running instance, launches the app - Package:
make release— builds + createsZman-claude-X.Y.Z.zip(version from pbxproj) - Clean:
make clean— removes build/ and zip artifacts - Full release:
/releaseClaude Code skill — runs pre-flight audit, build, user verification, then tags, uploads, and publishes the GitHub release. See.claude/commands/release.md. - Versioning: SemVer.
MARKETING_VERSION(user-facing, e.g.1.1) andCURRENT_PROJECT_VERSION(build number) inproject.pbxproj. Bump both in Debug and Release configs. - Changelog:
CHANGELOG.mdfollows Keep a Changelog. Use[Unreleased]for in-progress work, move to[X.Y.Z] - YYYY-MM-DDon release. - Tags:
vX.Y.Zon the version bump commit (e.g.v1.1.0). - Distribution: Homebrew Cask (
brew install plavrenko/zman/zman) or build from source. The app is not notarized — Homebrew Cask users must runxattr -crafter install. Source builds have no Gatekeeper issue. - CI/CD: GitHub Actions (
.github/workflows/):release.yml: Creates a draft GitHub release when av*tag is pushedupdate-cask.yml: Updates the Homebrew Cask formula inhomebrew-zmanwhen a release is published
- Homebrew tap: Cask formula lives in a separate
homebrew-zmanrepo. Template source of truth:homebrew/zman.rbin this repo. - Sandbox:
ENABLE_APP_SANDBOX = NO. CGWindowList API requires non-sandboxed app. No Screen Recording permission prompt — works silently for non-sandboxed apps.
- TeamTimeZoneManager is a struct with only static methods—could be enum or namespace instead.
- Timer polling rationale: Calendar.app doesn't reliably post notifications when timezone preferences change in com.apple.iCal UserDefaults suite. Primary detection uses UserDefaults.didChangeNotification and NSWorkspace.didActivateApplicationNotification, with an adaptive safety-net timer (1s when Calendar is frontmost — where timezone changes happen — 5s otherwise). Position tracking idle ticks also re-check mismatch for immediate overlay removal.
- Position tracking strategy: Adaptive two-speed polling — 1s idle to detect movement start, 0.15s while moving to detect stop. Overlay fades out on movement, fades in at final position. CGWindowList with cached window ID for single-window queries. Global input monitor triggers immediate fade/refresh on drag (
leftMouseDraggedin draggable/resize handles), close/dismiss clicks (leftMouseDown), and keyboard dismiss (Cmd+W/Esc) to avoid waiting for idle polling. - Visibility strategy: Overlay shows when Calendar window is on-screen AND unobstructed — not just when Calendar is the frontmost app.
isCalendarWindowOnScreen()checks CGWindowList with.optionOnScreenOnly(excludes minimized, other-Space, hidden windows).isCalendarWindowOccluded()uses.optionOnScreenAboveWindowto get windows above Calendar in z-order and checks geometric intersection. Filtered out: own overlay (by window number), Calendar's own popups/sheets (by PID), and non-layer-0 system UI. All CG frames use top-left origin coordinates for consistent comparison. - No error handling for CGWindowList failures—overlay silently fails if screen recording permission unavailable.
- App stays running when window closed (AppDelegate prevents termination)—typical menu bar app pattern.