diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 596a0529..43f98409 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -2,6 +2,7 @@ @../AGENTS.md -`AGENTS.md` is the canonical project instruction file. Keep shared architecture, workflow, safety, -debugging, validation, and documentation rules there so Claude Code and other coding agents receive -the same guidance without duplicated copies drifting apart. +`AGENTS.md` is the canonical coding-agent instruction file. Keep shared workflow, safety, debugging, +validation, and architecture rules there so Claude Code and other agents receive the same guidance +without duplicated copies drifting apart. `ARCHITECTURE.md` is the ten-minute system map, while +`.internal/` contains local, gitignored deep-dive and interview-preparation documentation. diff --git a/AGENTS.md b/AGENTS.md index 2effb230..4bf27857 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -71,6 +71,12 @@ Start here when you need to understand lifecycle: and wires cross-subsystem subscriptions. SwiftUI views should observe objects from that graph rather than creating services directly. +`SuggestionSettingsModel` remains the individually `@Published` UI-facing compatibility surface. +Its `domainSettings` projection groups the same durable values into general, engine, completion, +context, correction, presentation, inline-feature, and shortcut domains. `SuggestionSettingsStore` +keeps the existing flat UserDefaults keys stable, and `SuggestionSettingsSnapshot` is the immutable +behavior boundary consumed by the suggestion pipeline. + This ownership rule prevents duplicate Accessibility observers, duplicate input monitors, runtime reload races, and mismatched settings state. @@ -84,7 +90,8 @@ Read the coordinator in this order: 4. `Cotabby/App/Coordinators/SuggestionCoordinator+Prediction.swift` 5. `Cotabby/App/Coordinators/SuggestionCoordinator+Acceptance.swift` -The coordinator owns orchestration and user-facing state. It should not absorb every rule. Prefer: +The coordinator owns orchestration plus active suggestion and presentation state. It should not +absorb every rule or state transition. Prefer: - `SuggestionRequestFactory` for pure request construction - `SuggestionAvailabilityEvaluator` for pure gating decisions @@ -92,6 +99,8 @@ The coordinator owns orchestration and user-facing state. It should not absorb e - `SuggestionTextNormalizer` for backend-independent output cleanup - `SuggestionWorkController` for generation task identity/cancellation - `SuggestionInteractionState` for active suggestion session storage +- `SuggestionStreamingState` for latest-wins partial coalescing and monotonic rendering state +- `PostExhaustionAcceptanceState` for the bounded rapid-accept window after a tail is exhausted This split matters because autocomplete is a state machine. Pure rules are easier to test and reason about than coordinator mutations. @@ -104,7 +113,8 @@ Focus and geometry live in: - `FocusSnapshotResolver`: reduces raw AX elements into Cotabby-supported focus snapshots. - `AXTextGeometryResolver`: resolves caret and input geometry. - `AXHelper`: low-level Accessibility/Core Foundation helper calls. -- `FocusModels`: pure focus values, identities, capabilities, and debug inspection data. +- `FocusModels`: pure focus values, identities, capabilities, stale-result signatures, and the + lightweight `FocusPollingEvent` used by the developer overlay. Accessibility data is eventually consistent and app-specific. Browser editors, Electron apps, native AppKit fields, and secure fields expose different AX shapes. Preserve stale-result guards, @@ -130,14 +140,15 @@ from Accessibility and Input Monitoring. Runtime generation is split by responsibility: -- `SuggestionEngineRouter`: selects Apple Intelligence vs Open Source. +- `SuggestionEngineRouter`: selects Apple Intelligence, Open Source, or the configured + OpenAI-compatible endpoint and owns the narrow Apple-to-llama language fallback. - `FoundationModelSuggestionEngine`: Apple on-device generation path. - `LlamaSuggestionEngine`: request-to-prompt, llama result handling, and cache reset handoff. +- `OpenAICompatibleSuggestionEngine`: completion/chat transport, SSE streaming, and Ollama preload + behavior for the configured endpoint. - `LlamaRuntimeManager`: UI-facing runtime state, model selection, warmup, and lifecycle control. - `LlamaRuntimeCore`: explicitly serialized native boundary around mutable llama.cpp pointers, - prompt tokenization, KV-cache reuse, sampling, an optional deterministic constrained decoder - (`runConstrainedDecode`, gated behind the default-off `cotabbyConstrainedDecoderEnabled`), and - shutdown. + prompt tokenization, KV-cache reuse, built-in sampler delegation, cancellation, and shutdown. - `BaseCompletionPromptRenderer`: prompt construction for the Open Source path. The llama models are now *base* (non-instruct) GGUFs, so this renders a pure text continuation: no instruction preamble, custom rules and context fold into a short conditioning preface (a base model conditions on diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 87e25feb..f86522b7 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -112,9 +112,12 @@ power-profile application, engine/model selection, and endpoint connection inval owns permission reactions, engine runtime start/stop, overlays, model-directory refresh, and process-lifecycle behavior. Both retain subscriptions because both own different relationships. -[SuggestionSettingsModel.swift](Cotabby/Models/Settings/SuggestionSettingsModel.swift) is the published source -of app behavior. [SuggestionSettingsStore.swift](Cotabby/Support/Settings/SuggestionSettingsStore.swift) -persists non-secret preferences in UserDefaults. Endpoint credentials live in Keychain. +[SuggestionSettingsModel.swift](Cotabby/Models/Settings/SuggestionSettingsModel.swift) is the +individually published UI-facing source of app behavior. Its +[SuggestionSettingsData.swift](Cotabby/Models/Settings/SuggestionSettingsData.swift) projection groups +the same values by product domain without replacing the existing API. The immutable snapshot used by +the pipeline is derived from those domains. [SuggestionSettingsStore.swift](Cotabby/Support/Settings/SuggestionSettingsStore.swift) +keeps the established flat UserDefaults keys stable; endpoint credentials live in Keychain. ## Suggestion State Machine @@ -126,17 +129,21 @@ Read the coordinator in this order: 4. [SuggestionCoordinator+Prediction.swift](Cotabby/App/Coordinators/SuggestionCoordinator+Prediction.swift) 5. [SuggestionCoordinator+Acceptance.swift](Cotabby/App/Coordinators/SuggestionCoordinator+Acceptance.swift) -The coordinator owns orchestration and user-facing suggestion state. It delegates rules and mutable -sub-state to smaller boundaries: +The coordinator owns orchestration plus active suggestion and presentation state. It delegates rules +and cohesive mutable sub-state to smaller boundaries: - [SuggestionAvailabilityEvaluator.swift](Cotabby/Support/Suggestion/SuggestionAvailabilityEvaluator.swift): pure permission, settings, focus, and runtime gates. - [SuggestionRequestFactory.swift](Cotabby/Support/Suggestion/SuggestionRequestFactory.swift): pure bounded - request construction and prompt preview. + request construction and the selected backend's developer-debug prompt payload. - [SuggestionWorkController.swift](Cotabby/Services/Suggestion/SuggestionWorkController.swift): debounce/generation tasks and monotonically increasing work IDs. - [SuggestionInteractionState.swift](Cotabby/Services/Suggestion/SuggestionInteractionState.swift): active session, materialized context, consumed prefix, and known post-insertion AX lag. +- [SuggestionStreamingState.swift](Cotabby/Support/Suggestion/SuggestionStreamingState.swift): latest-wins + partial coalescing, one scheduled drain, and monotonic rendered-text state. +- [PostExhaustionAcceptanceState.swift](Cotabby/Support/Suggestion/PostExhaustionAcceptanceState.swift): + pure state for the bounded Tab-ownership window while an exhausted tail regenerates. - [SuggestionSessionReconciler.swift](Cotabby/Support/Suggestion/SuggestionSessionReconciler.swift): type-through, acceptance, and live-host reconciliation. - [SuggestionTextNormalizer.swift](Cotabby/Support/Suggestion/SuggestionTextNormalizer.swift): backend-independent @@ -146,13 +153,17 @@ A native correction path runs before model generation. NSSpellChecker and bundle can suppress completion while a likely typo is forming, offer a green atomic replacement, or apply an opt-in automatic fix after Space. -Engines can stream cumulative partials. The coordinator coalesces UI work, accepts only monotonic -extensions, and allows a displayed partial to become an active accept-ready session. The final result +Engines can stream cumulative partials. SuggestionStreamingState coalesces token-rate callbacks into +latest-wins UI work, accepts only monotonic extensions, and lets a displayed partial become an active +accept-ready session. The coordinator still owns scheduling and presentation. The final result remains authoritative and can replace or suppress provisional text. Normal sessions support exact type-through, word or phrase acceptance, full-tail acceptance, CJK-aware segmentation, punctuation handling, optional trailing space, and speculative generation after final -acceptance. Corrections commit atomically rather than exposing partial acceptance. +acceptance. When rapid Tab presses cross the exhausted-tail regeneration gap, +PostExhaustionAcceptanceState can queue at most one unseen accept and has a generation-keyed backstop +that returns Tab ownership to the host. Corrections commit atomically rather than exposing partial +acceptance. ## Focus and Accessibility @@ -381,6 +392,7 @@ policies, prompt utilities, normalization, and layout logic should receive focus | Backend selection or memory issue | SuggestionEngineRouter, AppDelegate, LlamaRuntimeManager | | Native decode/cache/shutdown issue | LlamaRuntimeCore, CotabbyInference boundary | | Partial/final output mismatch | streaming policy, SuggestionTextNormalizer, seam guards | +| Rapid Tab leaks to the host after exhausting a tail | PostExhaustionAcceptanceState, acceptance coordinator | | Acceptance key passes through or is stolen | InputMonitor, acceptance validation | | Wrong or repeated inserted text | SuggestionInserter, InputSuppressionController, reconciler | | Clipboard context is irrelevant | ClipboardRelevanceFilter, ClipboardContentDistiller | @@ -388,6 +400,7 @@ policies, prompt utilities, normalization, and layout logic should receive focus | Emoji or macro conflicts with suggestions | InlineCommandCoordinator, feature trigger machine | | Permission loop or lost grant | PermissionManager, PermissionGuidanceController, app identity | | Settings/onboarding window issue | SettingsCoordinator, WelcomeCoordinator | +| Settings persistence or domain mismatch | SuggestionSettingsModel, SuggestionSettingsData, SuggestionSettingsStore | When a change crosses several rows, keep ownership at these boundaries rather than teaching one coordinator to perform every step itself.