# Capswitch Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (\`- [ ]\`) syntax for tracking. **Goal:** Build a native Swift menu-bar app that suppresses physical Caps Lock, switches to the next enabled keyboard input source on Caps release, and preserves real Caps Lock on any Caps+Option gesture. **Architecture:** A pure Swift gesture state machine owns Caps/Option classification. A macOS adapter uses CGEventTap for global flagsChanged events, Carbon TIS APIs for input source selection, and AppKit NSStatusItem for the menu bar. A Taskfile and shell-free Swift build command assemble an unsigned/ad-hoc signed \`.app\` without an Xcode project. **Tech Stack:** Swift 5-compatible source; AppKit, CoreGraphics, ApplicationServices, Carbon; Taskfile v3; XCTest on macOS; no third-party dependencies. **Spec:** \`docs/superpowers/specs/2026-10-25-capswitch-design.md\` ## Global Constraints - Target macOS 14.0+; validate on the user's macOS 14.8.9. - Work directly on \`main\`; repository is initially empty. - Use Taskfiles for build, test, lint, bundle, and clean scripts. - No Xcode project, Storyboard, CocoaPods, Homebrew, or runtime dependency. - Plain Caps switches only on Caps release when Option was not seen during the gesture. - Caps+Option preserves real Caps Lock in either key order. - Use a stable bundle identifier and LSUIElement menu-bar app configuration. - Accessibility is required for the global event tap; show its state in the menu. - Linux verification must distinguish host-unavailable Swift tooling from actual failures. ## Review Focus - Caps release must switch exactly once and never on Caps press. - Option before Caps and Caps before Option must both toggle real Caps Lock. - Option released after Caps must complete the pending Caps toggle without switching layout. - Tap reset/sleep recovery must clear pending state safely. - Input source enumeration must ignore disabled/non-keyboard sources and keep the configured order. --- ### Task 1: Repository skeleton and plan **Files:** - Create: \`docs/superpowers/specs/2026-10-25-capswitch-design.md\` - Create: \`docs/superpowers/plans/2026-10-25-capswitch.md\` - Create: \`Taskfile.yml\` - Create: \`.gitignore\` - Create: \`README.md\` - [ ] Write the design/spec and repository workflow files. - [ ] Run Taskfile syntax/help checks. - [ ] Commit the skeleton. ### Task 2: Gesture state machine **Files:** - Create: \`Sources/Capswitch/CapsGestureMachine.swift\` - Test: \`Tests/CapswitchTests/CapsGestureMachineTests.swift\` **Interfaces:** - \`enum GestureEvent\`: \`capsDown(optionDown: Bool)\`, \`capsUp(optionDown: Bool)\`, \`optionDown\`, \`optionUp\`, \`reset\`. - \`enum GestureAction\`: \`none\`, \`switchToNextInputSource\`, \`toggleCapsLock\`. - \`struct CapsGestureMachine\` with \`mutating func handle(_ event: GestureEvent) -> GestureAction\`. - [ ] Write failing XCTest cases for plain Caps, both Option orders, delayed Option release, and reset. - [ ] Run the targeted test on macOS and verify it fails before implementation; on Linux record Swift unavailable. - [ ] Implement the minimal state machine. - [ ] Run the targeted test and the full test task. ### Task 3: macOS event tap and input source adapter **Files:** - Create: \`Sources/Capswitch/MacOSInputController.swift\` - Modify: \`Sources/Capswitch/CapsGestureMachine.swift\` only if adapter-facing visibility requires it. **Interfaces:** - \`final class MacOSInputController\` with \`start() throws\`, \`stop()\`, and \`onInputSourceChanged: (() -> Void)?\`. - Uses keycode 57, flagsChanged transitions, CGEventTap recovery, TIS selection, and synthetic Caps events. - Synthetic Caps events must be marked/guarded so they do not re-enter the gesture state machine. - [ ] Add adapter tests or compile-only checks where platform APIs are available. - [ ] Implement the event tap and Carbon input source enumeration. - [ ] Add tap timeout recovery and reset handling. - [ ] Build on macOS with Taskfile. ### Task 4: Status bar app and permissions **Files:** - Create: \`Sources/Capswitch/AppDelegate.swift\` - Create: \`Sources/Capswitch/StatusBarController.swift\` - Create: \`Resources/Info.plist\` - [ ] Add LSUIElement app delegate and status item. - [ ] Add current-layout display, permission status, pause, launch-at-login placeholder, and quit menu actions. - [ ] Add Accessibility check and System Settings URL. - [ ] Build the .app bundle. ### Task 5: CLI packaging and documentation **Files:** - Modify: \`Taskfile.yml\` - Modify: \`README.md\` - Create: \`scripts/build-app.sh\` only if Taskfile cannot express the bundle operation clearly. - [ ] Add \`task build\`, \`task bundle\`, \`task test\`, \`task lint\`, \`task clean\`, and \`task verify\`. - [ ] Document one-time Command Line Tools setup, Accessibility permission, and first launch. - [ ] Run all Linux-safe checks and report the Mac-only build limitation. ### Task 6: Final verification and delivery - [ ] Inspect diff and repository status. - [ ] Run Taskfile verification and any available tests. - [ ] Commit on \`main\`. - [ ] Push \`main\` to \`origin\`. - [ ] Report commit, remote branch, and Mac-only checks still required. ## 2026-10-25 follow-up: HID remediation The event-tap flagsChanged design is superseded for physical Caps by: - docs/superpowers/specs/2026-10-25-caps-f19-remap.md The original Caps Lock event-tap approach was not reliable during fast typing. The code now relies on a Caps-to-F19 HID mapping, direct F19 keyDown/keyUp, a managed remap lifecycle, and a small IOKit bridge for true Caps Lock state. The original spec is retained for historical debugging context.