5.7 KiB
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.