-
-
Notifications
You must be signed in to change notification settings - Fork 39
How Cotabby Works
This page explains the core systems that make Cotabby work. For file-level reading order and layer boundaries, see ARCHITECTURE.md.
Every suggestion follows this path:
sequenceDiagram
participant User
participant InputMonitor
participant Coordinator as SuggestionCoordinator
participant Factory as SuggestionRequestFactory
participant Router as SuggestionEngineRouter
participant Engine as LLM Engine
participant Normalizer as SuggestionTextNormalizer
participant Overlay as OverlayController
User->>InputMonitor: keyDown
InputMonitor->>Coordinator: CapturedInputEvent
Coordinator->>Coordinator: debounce (settle period)
Coordinator->>Factory: build request from focus snapshot + settings
Factory-->>Coordinator: SuggestionRequest + prompt preview
Coordinator->>Router: generateSuggestion(for: request)
Router->>Engine: dispatch to selected engine
Engine-->>Router: raw completion text
Router-->>Coordinator: SuggestionResult
Coordinator->>Normalizer: clean output
Normalizer-->>Coordinator: normalized text
Coordinator->>Overlay: show ghost text at caret
User->>InputMonitor: Tab
InputMonitor->>Coordinator: accept event
Coordinator->>Coordinator: insert via Accessibility API
Key files for this flow:
-
Cotabby/Services/Input/InputMonitor.swift- global key event tap -
Cotabby/App/Coordinators/SuggestionCoordinator+Input.swift- event routing -
Cotabby/App/Coordinators/SuggestionCoordinator+Prediction.swift- generation lifecycle -
Cotabby/Support/SuggestionRequestFactory.swift- pure request construction -
Cotabby/Services/Runtime/SuggestionEngineRouter.swift- engine dispatch -
Cotabby/Support/SuggestionTextNormalizer.swift- output cleanup -
Cotabby/App/Coordinators/SuggestionCoordinator+Acceptance.swift- Tab insertion
Cotabby needs to know which text field is focused, what text is in it, and where the caret is. This all comes from the macOS Accessibility tree.
flowchart LR
A["FocusTracker<br/>(polling timer)"] --> B["FocusSnapshotResolver<br/>(AX tree walk)"]
B --> C["FocusCapabilityResolver<br/>(score candidates)"]
C --> D["AXTextGeometryResolver<br/>(caret rect)"]
D --> E["FocusSnapshot<br/>(published)"]
Why polling, not events? AXObserver notification delivery is inconsistent across host apps. A single polling loop gives predictable eventual consistency: every tick re-reads the frontmost focused element and repairs stale state within one interval.
Key files:
-
Cotabby/Services/Focus/FocusTracker.swift- the polling loop -
Cotabby/Services/Focus/FocusSnapshotResolver.swift- AX tree traversal -
Cotabby/Support/FocusCapabilityResolver.swift- field scoring -
Cotabby/Services/Focus/AXTextGeometryResolver.swift- caret position
Cotabby supports two generation backends behind the SuggestionGenerating protocol:
| Engine | Backend | Requires |
|---|---|---|
| Apple Intelligence |
FoundationModels framework |
macOS 26+, supported hardware |
| Open Source | llama.cpp via CotabbyInference
|
Any Apple Silicon Mac, downloaded GGUF model |
SuggestionEngineRouter dispatches to the selected engine. Both engines implement SuggestionGenerating:
protocol SuggestionGenerating: AnyObject {
func generateSuggestion(for request: SuggestionRequest) async throws -> SuggestionResult
func resetCachedGenerationContext() async
}If Apple Intelligence fails (e.g., unsupported locale), the router automatically falls back to the open-source engine.
Key files:
-
Cotabby/Models/SuggestionSubsystemContracts.swift- protocol definitions -
Cotabby/Services/Runtime/SuggestionEngineRouter.swift- routing logic -
Cotabby/Services/Runtime/LlamaSuggestionEngine.swift- llama.cpp path -
Cotabby/Services/Runtime/FoundationModelSuggestionEngine.swift- Apple Intelligence path
| Owner | What it holds | File |
|---|---|---|
CotabbyAppEnvironment |
Composition root - builds all services once | Cotabby/App/Core/CotabbyAppEnvironment.swift |
SuggestionCoordinator |
Pipeline orchestration, published debug state | Cotabby/App/Coordinators/SuggestionCoordinator.swift |
SuggestionSettingsModel |
User preferences, publishes via Combine | Cotabby/Models/SuggestionSettingsModel.swift |
FocusTracker |
Latest focus snapshot | Cotabby/Services/Focus/FocusTracker.swift |
When modifying the suggestion pipeline, prefer changes in this order:
-
Support/- pure helpers, no dependencies, easy to test -
Models/- value types and protocol contracts -
Services/- side-effectful boundaries -
App/Coordinators/- orchestration -
UI/- presentation
See ARCHITECTURE.md for the full reading order.