Imported from kim-raaschou/aerospace-lens-wip (
AGENTS.md). Install upstream withnpx skills add kim-raaschou/aerospace-lens-wip. Copyright stays with the author.
Development Guide for AerospaceLens
Quick Start
make dev # Build and run GUI (default: preview mode)
make dev ARGS="--live" # With live streaming
make dev ARGS="--icons" # Icons only mode
make terminal # Terminal mode
make test # Run all tests
make test ARGS="-t GUIStateEventTests" # Single test suite
CRITICAL RULES
- NEVER commit changes without explicit user approval
- Always ask for review before creating commits
- Never run
git commitwithout user permission - Never push to remote without user approval
- Always verify tests pass before asking for review
- Changes should be staged but NOT committed until user approves
Test Commands
- Run all tests:
make testorswift test - Run specific test file:
swift test --filter GUIStateEventTests - Run specific test:
swift test --filter GUIStateEventTests/initialLoad - Test with retries:
swift test --retry-failing-tests 3
Linting & Formatting
- No external linting tools configured (swift-format/swiftlint not in use)
- Manual formatting: Follow inline style guidelines below
- Pre-commit: Run tests before committing:
make test
swift-code-expert Agent
When to Use
Use this agent for high-quality Swift code, test suites, or implementation improvements.
Trigger phrases: "Write Swift code", "Create tests", "Improve implementation", "Add tests", "Refactor"
Responsibilities
- Write idiomatic Swift code following Apple patterns
- Create comprehensive test suites (happy paths + edge cases)
- Ensure SOLID principles and Swift best practices
- Verify code compiles without warnings
Code Standards
- Naming: Clear, descriptive names (camelCase vars, PascalCase types)
- Structure: Functions <30 lines, value types by default
- Swift idioms: guard statements, enums for states, let over var
- Error handling: Custom enum errors, explicit handling
- Access: Most restrictive appropriate, mark internals private
Testing
- XCTest framework with descriptive test names
- Cover happy paths, errors, edge cases
- Aim for ≥80% coverage for critical code
- Mock protocols for testability
Quality Checklist
- Compiles without warnings
- All tests pass
- No force unwrapping (!) unless documented
- Error cases handled explicitly
- Follows naming conventions
- Functions focused and reasonably sized
- Tests cover all scenarios
- Public APIs documented
Code Style
Swift Language
- Version: Swift 6.2 (Swift v5 language mode)
- File encoding: UTF-8
- Line endings: LF (Unix)
Imports
- Group imports by module:
System→Foundation→AppKit/SwiftUI→Common→@testable import - Sort alphabetically within groups
- Use
importnot@testable importunless testing
Naming Conventions
- Types:
PascalCase(e.g.,GUIState,WindowInfo) - Functions/Variables:
camelCase(e.g.,focusWindow,focusedWindowId) - Constants/Properties:
camelCase(e.g.,maxThumbnailRows) - Protocols:
PascalCase+-ing/Sourcesuffix (e.g.,ImageCapturing,AerospaceEventSource) - Implementations: Noun suffix differentiates from protocols (e.g.,
IconCapture,AerospaceCLIAdapter) - Enums:
PascalCase(e.g.,CaptureMode,CLIError,AerospaceEvent)
File Organization
Sources/
├── Common/ # Foundation-only (no AppKit/SwiftUI)
│ ├── Models.swift # Domain models (WindowInfo, WorkspaceInfo, OverviewResult)
│ ├── OverviewUpdate.swift # Pure state reducer (OverviewModel, OverviewEvent)
│ ├── LayoutCalculator.swift
│ ├── CaptureMode.swift
│ ├── ThemeColors.swift
│ └── cli/ # CLI adapters + event parsing
│ ├── AerospaceCLI.swift / AerospaceCLIAdapter.swift
│ ├── AerospaceEventSource.swift / AerospaceSubscribeAdapter.swift
│ └── AerospaceEvent.swift
├── GUIBundle/ # GUI (AppKit/SwiftUI/ScreenCaptureKit)
│ ├── AppDelegate.swift
│ ├── GUIState.swift # Main GUI state with @Observable
│ ├── capture/ # ImageCapturing protocol + implementations
│ │ ├── ImageCapturing.swift # Protocol
│ │ ├── IconCapture.swift, LiveCapture.swift, PreviewCapture.swift
│ └── ui/ # SwiftUI views
│ ├── OverlayView.swift, WorkspaceCard.swift, FloatingPanel.swift
├── TUIBundle/ # Terminal UI
│ ├── TUIState.swift
│ └── TUIRenderer.swift
├── GUIEntry/ # GUI entry point (main.swift)
└── TUIEntry/ # TUI entry point (main.swift)
Key Project Patterns
- Monolithic libs: Shared domain logic in
Common, platform-specific in bundles - DI: Protocols for testability (
AerospaceCLI,AerospaceEventSource,ImageCapturing) - Reactive state:
OverviewModel→GUIStatewith@Observable - Event-driven:
aerospace subscribe --allis source of truth - Async streams:
AsyncThrowingStreamfor events and CLI responses - MainActor: All UI state on
@MainActor - Sendable: Core protocols/models conform to
Sendable
Error Handling
- Use
enumfor errors:CLIError,ThumbnailError - Use
async throwsfor all IO operations - Log errors with
print()for CLI errors,self.error =for UI errors - Don't silently swallow errors—display to user or log
- Handle TCC permission errors for Screen Recording gracefully
Concurrency
- GUI:
@MainActorfor all UI state - CLI/Events: Background tasks via
Task.detached - Cancellation: Check
Task.isCancelledbefore long operations - Async streams: Use
AsyncThrowingStreamfor event sources and CLI responses - MainActor.run: Use for posting background tasks to MainActor
Testing
- Location:
Tests/*.swift - Framework:
Testing(Swift 5.9+) - Test suite naming:
describe what it does(e.g.,GUIStateEventTests) - Test naming:
underscore_separated(e.g.,focusSameWorkspace) - Mock pattern: Inject mocks via init, use
MockCLI,MockEventSource - Async tests: Use
drainUntil { condition }for event-driven assertions - Window validation: Use
liveWindowIdsclosure to simulate closed windows
Formatting
- No trailing whitespace
- 4 spaces for indentation (no tabs)
- Line length: ~120 chars (prefer wrapping over truncation)
- Blank lines: One between top-level declarations, two between file and first declaration
- Trailing commas: Enable in array/dict literals
- MARK comments: Use
// MARK: - Sectionfor organization
Code Patterns
- Value types:
structoverclasswhere possible - Immutability:
letby default,varonly when needed - Type inference: Use where obvious, explicit where helpful
- Comments: Add for non-obvious logic, prefer clarity over comments
ScreenCaptureKit/Capture Modes
- Live:
SCStreamat 10fps, streams focused window only - Preview: One-shot
SCScreenshotManager.captureScreenshot(), all windows - Icons: No SCK, only
NSWorkspace.icon(forFile:)
CLI Binary
- Default path:
/opt/homebrew/bin/aerospace - Use
aerospace subscribe --allfor live events - Use
aerospace list-workspaces --all --json/list-windowsfor initial load - Timeout: 2 seconds for CLI calls