Skip to content

How Cotabby Works

Jacob Fu edited this page Jun 1, 2026 · 4 revisions

How Cotabby Works

This page explains the core systems that make Cotabby work. For file-level reading order and layer boundaries, see ARCHITECTURE.md.

The Suggestion Pipeline

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
Loading

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

Focus Tracking

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)"]
Loading

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

Two Engines, One Interface

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

Where State Lives

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

Safe Change Order

When modifying the suggestion pipeline, prefer changes in this order:

  1. Support/ - pure helpers, no dependencies, easy to test
  2. Models/ - value types and protocol contracts
  3. Services/ - side-effectful boundaries
  4. App/Coordinators/ - orchestration
  5. UI/ - presentation

See ARCHITECTURE.md for the full reading order.

Clone this wiki locally