Swift alt-tab & alttab repository by @lwouis. AI agent rules & system prompts with 16k stars.
# macOS development
- Don't use xcode directly to develop
- Use pure swift 5.8 code to make the app. No interface builder. No SwiftUI.
- Aim for compact code. Within methods, don't have groups of statements separated with newlines. No inline comments for simple code. Instead, split statements into sub-methods.
- Use guard closes as much as possible to separate the happy-path under them
- Organize source files into folders. Folders should group files that change together, at the same pace (e.g. one feature)
- When possible, follow the triad pattern: specs in *Specs.md, unit-tests in *Tests.swift, and *swift for the implementation. Document features and their edge-cases this way
- Favor low latency and responsiveness. Reuse objects, avoid wasting memory or I/O. Use observer APIs; don't poll.
# Main-thread IPC
Ordinary AppKit calls can be a synchronous XPC round trip, so they can hang for as long as the other process takes. `searchField.stringValue = ""` froze a user's main thread for 3.0s (#5981): it resigned first responder, which deactivates the system text-input context, which timed out. Assume any AppKit call that changes first responder, orders a window, or resizes one talks to another process.
- Everything under `NSResponder` and `NSCell` (so `NSWindow`, `NSView`, `NSControl`, `NSTextField`, `NSApplication`) plus `NSTextInputContext` is main-thread-only, and AppKit asserts on it at runtime. For those the levers are ordering and deferral, not dispatch: the three off-main schedulers (`AXCallScheduler`, `CGSCallScheduler`, `ProcessCallScheduler`) can't take them.
- `NSWorkspace` and `NSRunningApplication` are the documented exception: their headers state they are thread safe, so LaunchServices reads (`runningApplications`, `frontmostApplication`, `bundleURL`, `icon`, `activate`) may run off-main, and `Application.fetchAppIcon` already relies on that. Don't "fix" them back onto main. `src/main-thread-ipc.md` has the audited call sites and the evidence for both halves of this.
- On the latency-critical paths (summoning, a keystroke while cycling or searching, dismissal with focus), nothing that can stall may run before the work the user is waiting for. Put the visible work first; put the bookkeeping after it.
- A CoreAnimation transaction commits when the runloop turn ENDS. Setting `alphaValue` or calling `orderOut` does not reach the screen before that, so blocking later in the same turn keeps the old frame up. Defer with `DispatchQueue.main.async` and re-check the state in the block.
- `MainThreadStall.step()` at the top of a main-thread function names it in the log when it runs long. Use it to measure rather than guessing; temporarily lower `thresholdInMs` and add marks around single statements to attribute a cost.
# Comments
A wrong comment costs several times more than a missing one, for humans and agents alike. So write for low drift, not for low line count. A correct, non-obvious comment can be as long as it needs to be.
- Comment what the code cannot show: OS/API behaviour (macOS, CGS, SkyLight, AppKit), measured timings, private-API notes, invariants, and why a guard that looks removable isn't. Prefer measured evidence over recollection.
- Don't comment what the code already says. Don't narrate history: no "this used to be X", no reverted approaches, no commentary on earlier comments. If a past bug is the reason a constraint exists, name the test that pins it instead.
- State each rule once, at the widest scope it applies to. Restating it further down is what makes two copies drift apart.
- One intent per comment, and keep it short. Split a paragraph that mixes a rule, its history, and an aside.
- When you change code, re-read the comments around it. Updating them is part of the change, not a follow-up.
- Run `/rework-comments` to audit or rework comments across a file, a folder, or all of `src/`.
# Workflow
- Copy commands from ai/build.sh and run them, to confirm compilation works after you're done with implementing a change
- Run the unit tests with ai/test.sh. Don't use scripts/run_tests.sh: that's the CI entrypoint and its Release config rebuilds the whole test target on every edit (34s vs 1.3s)
- In a git worktree, copy `config/local.xcconfig` over from the main checkout before building. It's gitignored, so a fresh worktree doesn't have it, and `debug.xcconfig` / `release.xcconfig` `#include?` it. Without it the build dies at CodeSign ("no identity found") on a dependency framework, BEFORE the app target's own Swift compile β so it looks like the code compiled when nothing of `src/` was checked. `ai/test.sh` doesn't cover that either: the `unit-tests` target has its own source list and never builds the app target
- Git commit messages must respect our pre-hook conventions, and must be clear and high-level, written for end-users (changelog)
# License / Keychain invariant
- The app's Developer ID, TeamID, and bundle ID must remain stable across builds. Keychain items are tied to the code signature; changing any of these orphans every user's stored license key and forces mass re-activation. If a rotation is unavoidable, plan a migration first (e.g., a backup-restore handler, or `kSecAttrAccessGroup` with a stable group identifier).
- Do not introduce legacy `SecKeychain*` API or `kSecAccessControl` (biometric/PIN gating) into license code β both can trigger Keychain password prompts, which is bad UX for license activation.