From 5702d712ad9fd0f1c560803d80c2263ee9566977 Mon Sep 17 00:00:00 2001 From: Jacob Fu <141651335+FuJacob@users.noreply.github.com> Date: Thu, 11 Jun 2026 18:35:48 -0700 Subject: [PATCH] fix(concurrency): eliminate all 68 Swift 6 forward-compat build warnings A clean build of main emitted 68 warnings, every one the same root cause: the target's default MainActor isolation makes pure value types, statics, and Character extensions actor-isolated, and nonisolated contexts (the llama runtime task, sort comparators, timer closures, plain XCTest classes) reference them. Each is an error in the Swift 6 language mode, so they are all future build breaks. Fixes follow the codebase's existing pattern (CaretGeometryQuality, ObservedContentEdges): annotate pure helper enums, value-type models, and the lock-guarded log writers nonisolated. The two notification-callback warnings are real isolation decisions, resolved with MainActor.assumeIsolated since both observers register on the main queue, so entering the actor is an assertion rather than a hop. Clean rebuild from scratch now emits zero project warnings (the two appintentsmetadataprocessor notices are toolchain noise about a framework the app does not use). Full suite: 1436 tests, 0 failures. --- Cotabby/Models/EmojiPickerModels.swift | 12 ++++++------ Cotabby/Models/EmojiUsageModels.swift | 2 +- Cotabby/Models/FocusModels.swift | 2 +- Cotabby/Models/HuggingFaceModels.swift | 2 +- Cotabby/Models/SuggestionEngineModels.swift | 2 +- Cotabby/Models/SystemMetricsStore.swift | 2 +- Cotabby/Models/VisualContextModels.swift | 2 +- .../Services/Input/KeyboardInputSourceMonitor.swift | 5 ++++- Cotabby/Services/Power/PowerSourceMonitor.swift | 6 +++++- Cotabby/Support/ConfidenceSuppressionPolicy.swift | 2 +- Cotabby/Support/CotabbyDebugOptions.swift | 4 ++-- Cotabby/Support/DecodeStopPolicy.swift | 2 +- Cotabby/Support/EmojiCatalog.swift | 2 +- Cotabby/Support/EmojiMatcher.swift | 2 +- Cotabby/Support/EmojiPopularity.swift | 2 +- Cotabby/Support/EmojiRecents.swift | 2 +- Cotabby/Support/EmojiSynonymCatalog.swift | 2 +- Cotabby/Support/EmojiTriggerStateMachine.swift | 2 +- Cotabby/Support/FileLogHandler.swift | 2 +- Cotabby/Support/FocusCapabilityFlickerGate.swift | 2 +- Cotabby/Support/InsertionStrategySelector.swift | 2 +- Cotabby/Support/LLMIOFileHandler.swift | 2 +- Cotabby/Support/MacroTriggerStateMachine.swift | 8 ++++---- Cotabby/Support/OCRTextHygiene.swift | 2 +- Cotabby/Support/OnboardingTemplateFeatureList.swift | 2 +- Cotabby/Support/SentenceBoundaryClassifier.swift | 4 ++-- Cotabby/Support/SuggestionSessionReconciler.swift | 4 ++-- Cotabby/Support/SystemResourceSampler.swift | 2 +- Cotabby/Support/TokenCountEstimator.swift | 2 +- Cotabby/Support/TypoGate.swift | 2 +- 30 files changed, 48 insertions(+), 41 deletions(-) diff --git a/Cotabby/Models/EmojiPickerModels.swift b/Cotabby/Models/EmojiPickerModels.swift index 377784cd..15ea238d 100644 --- a/Cotabby/Models/EmojiPickerModels.swift +++ b/Cotabby/Models/EmojiPickerModels.swift @@ -12,7 +12,7 @@ import Foundation /// /// `aliases` are the canonical `:name:` tokens a user types (for example `grinning`, `+1`), while /// `keywords` are looser synonyms used only to widen search recall. -struct EmojiEntry: Equatable, Decodable { +nonisolated struct EmojiEntry: Equatable, Decodable { let glyph: String let name: String let aliases: [String] @@ -25,7 +25,7 @@ struct EmojiEntry: Equatable, Decodable { /// /// `id` is the glyph because the bundled dataset has one record per glyph, which keeps SwiftUI list /// identity stable as the query narrows. -struct EmojiMatch: Equatable, Identifiable { +nonisolated struct EmojiMatch: Equatable, Identifiable { let entry: EmojiEntry /// The glyph to display and insert. Defaults to `entry.glyph`; the variant resolver overrides it @@ -117,14 +117,14 @@ struct EmojiVariantPreferences: Equatable, Sendable { // MARK: - Trigger state machine vocabulary /// Direction for moving the highlighted row while the picker is open. -enum EmojiSelectionMove: Equatable { +nonisolated enum EmojiSelectionMove: Equatable { case up case down } /// How a capture was committed. `.key` is a consumed Tab/Return; `.closingColon` is the /// passed-through second `:` of `:query:` (EMOJI.md Mode B). -enum EmojiCommitMode: Equatable { +nonisolated enum EmojiCommitMode: Equatable { case key case closingColon } @@ -143,7 +143,7 @@ enum EmojiTriggerInput: Equatable { /// Side effects the controller performs after a transition. The machine itself stays pure; it only /// describes what should happen. -enum EmojiTriggerAction: Equatable { +nonisolated enum EmojiTriggerAction: Equatable { case open(query: String) case updateQuery(String) case moveSelection(EmojiSelectionMove) @@ -153,7 +153,7 @@ enum EmojiTriggerAction: Equatable { /// The two lifecycle states. `idle` remembers the previously typed character so the trigger can /// require a word boundary (start of field or after whitespace) before opening a capture. -enum EmojiTriggerState: Equatable { +nonisolated enum EmojiTriggerState: Equatable { case idle(previousCharacter: Character?) case capturing(query: String) } diff --git a/Cotabby/Models/EmojiUsageModels.swift b/Cotabby/Models/EmojiUsageModels.swift index 112ae4f6..09cd547e 100644 --- a/Cotabby/Models/EmojiUsageModels.swift +++ b/Cotabby/Models/EmojiUsageModels.swift @@ -8,7 +8,7 @@ import Foundation /// Usage is keyed by an emoji's primary alias (e.g. `joy`), not its glyph, so a concept's signal is /// stable across skin-tone and gender variants: using 👍🏽 still boosts the 👍 concept, and recents /// render in the user's current variant preference at display time. -struct EmojiUsageSnapshot: Equatable, Sendable { +nonisolated struct EmojiUsageSnapshot: Equatable, Sendable { /// Primary aliases of recently committed emoji, most recent first, de-duplicated. let recentAliases: [String] /// Primary alias -> number of times committed. diff --git a/Cotabby/Models/FocusModels.swift b/Cotabby/Models/FocusModels.swift index 204a0a42..400624ff 100644 --- a/Cotabby/Models/FocusModels.swift +++ b/Cotabby/Models/FocusModels.swift @@ -10,7 +10,7 @@ import Foundation /// `elementIdentifier` is still useful because it describes the AX node we resolved, but it is not /// globally unique over time: macOS can recycle `CFHash` values after AX elements are destroyed. /// Pairing it with `focusChangeSequence` gives async consumers a stable "same focus event" key. -struct FocusedInputIdentity: Equatable, Sendable { +nonisolated struct FocusedInputIdentity: Equatable, Sendable { let elementIdentifier: String let focusChangeSequence: UInt64 } diff --git a/Cotabby/Models/HuggingFaceModels.swift b/Cotabby/Models/HuggingFaceModels.swift index 7be035ec..a2da9803 100644 --- a/Cotabby/Models/HuggingFaceModels.swift +++ b/Cotabby/Models/HuggingFaceModels.swift @@ -4,7 +4,7 @@ import Foundation /// These are pure value types with no business logic beyond URL construction. /// One result from `GET /api/models?filter=gguf&search=...&sort=downloads`. -struct HFModelSearchResult: Codable, Identifiable, Equatable { +nonisolated struct HFModelSearchResult: Codable, Identifiable, Equatable { let id: String let modelId: String let downloads: Int diff --git a/Cotabby/Models/SuggestionEngineModels.swift b/Cotabby/Models/SuggestionEngineModels.swift index 50dcf567..699cc050 100644 --- a/Cotabby/Models/SuggestionEngineModels.swift +++ b/Cotabby/Models/SuggestionEngineModels.swift @@ -58,7 +58,7 @@ enum PowerProfile: Equatable, Hashable { /// The bundle identifier is the durable identity used by the suggestion pipeline. The display name /// is saved only so Settings can show a readable list without having to resolve installed /// applications again on every launch. -struct DisabledApplicationRule: Codable, Equatable, Identifiable, Sendable { +nonisolated struct DisabledApplicationRule: Codable, Equatable, Identifiable, Sendable { let bundleIdentifier: String let displayName: String diff --git a/Cotabby/Models/SystemMetricsStore.swift b/Cotabby/Models/SystemMetricsStore.swift index 733f03d8..828f11b2 100644 --- a/Cotabby/Models/SystemMetricsStore.swift +++ b/Cotabby/Models/SystemMetricsStore.swift @@ -22,7 +22,7 @@ final class SystemMetricsStore: ObservableObject { /// Number of samples retained. At the default one-second cadence this is a rolling 60-second /// window, which is enough to watch a generation spike rise and fall without unbounded growth. static let maximumSamples = 60 - static let defaultInterval: TimeInterval = 1.0 + nonisolated static let defaultInterval: TimeInterval = 1.0 @Published private(set) var samples: [SystemMetricSample] = [] diff --git a/Cotabby/Models/VisualContextModels.swift b/Cotabby/Models/VisualContextModels.swift index c346caa3..28b76fbb 100644 --- a/Cotabby/Models/VisualContextModels.swift +++ b/Cotabby/Models/VisualContextModels.swift @@ -10,7 +10,7 @@ import Foundation /// Tunables for converting a focused-input screenshot into OCR text for prompt injection. /// These values are intentionally separate from `SuggestionConfiguration` because they govern /// screenshot capture and OCR, not normal text completion behavior. -struct VisualContextConfiguration: Equatable, Sendable { +nonisolated struct VisualContextConfiguration: Equatable, Sendable { let snapshotDimension: Int let maxImageDimension: Int let minRecognizedCharacterCount: Int diff --git a/Cotabby/Services/Input/KeyboardInputSourceMonitor.swift b/Cotabby/Services/Input/KeyboardInputSourceMonitor.swift index c4448259..1c62ed1e 100644 --- a/Cotabby/Services/Input/KeyboardInputSourceMonitor.swift +++ b/Cotabby/Services/Input/KeyboardInputSourceMonitor.swift @@ -34,7 +34,10 @@ final class KeyboardInputSourceMonitor { object: nil, queue: .main ) { [weak self] _ in - self?.handleInputSourceChanged() + // Same main-queue delivery contract as above; assumeIsolated makes it checkable. + MainActor.assumeIsolated { + self?.handleInputSourceChanged() + } } } diff --git a/Cotabby/Services/Power/PowerSourceMonitor.swift b/Cotabby/Services/Power/PowerSourceMonitor.swift index 936c9bcb..8225f7bc 100644 --- a/Cotabby/Services/Power/PowerSourceMonitor.swift +++ b/Cotabby/Services/Power/PowerSourceMonitor.swift @@ -26,7 +26,11 @@ final class PowerSourceMonitor: ObservableObject { object: nil, queue: .main ) { [weak self] _ in - self?.refreshPowerState() + // Delivered on the main queue (see `queue:` above), so entering the main actor is an + // assertion, not a hop; a queue change would trap here rather than corrupt state. + MainActor.assumeIsolated { + self?.refreshPowerState() + } } } diff --git a/Cotabby/Support/ConfidenceSuppressionPolicy.swift b/Cotabby/Support/ConfidenceSuppressionPolicy.swift index 0491ca77..7ee1f1a2 100644 --- a/Cotabby/Support/ConfidenceSuppressionPolicy.swift +++ b/Cotabby/Support/ConfidenceSuppressionPolicy.swift @@ -10,7 +10,7 @@ import Foundation /// instead of showing a confident-looking guess. The policy is pure and isolated so the threshold /// is easy to test and tune. A floor of negative infinity (the default) disables suppression, so /// this is a no-op until a caller opts in by raising the floor. -enum ConfidenceSuppressionPolicy { +nonisolated enum ConfidenceSuppressionPolicy { /// Suppress when the completion's average per-token log-probability is below `floor`. static func shouldSuppress(averageLogprob: Double, floor: Double) -> Bool { guard floor > -.infinity else { diff --git a/Cotabby/Support/CotabbyDebugOptions.swift b/Cotabby/Support/CotabbyDebugOptions.swift index b4d2c538..806a22b2 100644 --- a/Cotabby/Support/CotabbyDebugOptions.swift +++ b/Cotabby/Support/CotabbyDebugOptions.swift @@ -9,7 +9,7 @@ import os /// privacy-sensitive diagnostic path has one obvious gate. Passing `-cotabby-debug` means the /// developer intentionally opted into local debugging artifacts such as overlays, detailed service /// logs, and screenshot/OCR captures. -enum CotabbyDebugOptions { +nonisolated enum CotabbyDebugOptions { static let launchArgument = "-cotabby-debug" static var isEnabled: Bool { @@ -61,7 +61,7 @@ enum CotabbyDebugOptions { /// subsystem as a filterable column. When the `-cotabby-debug` launch argument is set we /// additionally fan out to `FileLogHandler`, which writes JSONL to /// `~/Library/Logs/Cotabby/cotabby.jsonl` for AI-assisted debugging without copy-paste. -enum CotabbyLogger { +nonisolated enum CotabbyLogger { /// Reserved label that routes only to the dedicated LLM I/O sink, never to OSLog or the main /// JSONL file. Kept out of OSLog because full prompts/completions can be many KB per request /// and would dominate Console.app; kept out of `cotabby.jsonl` because it would drown the diff --git a/Cotabby/Support/DecodeStopPolicy.swift b/Cotabby/Support/DecodeStopPolicy.swift index 81ead4a0..866a9e82 100644 --- a/Cotabby/Support/DecodeStopPolicy.swift +++ b/Cotabby/Support/DecodeStopPolicy.swift @@ -11,7 +11,7 @@ import Foundation /// A short minimum-token guard avoids degenerate instant stops (for example, the model's first token /// being a lone period). `SentenceBoundaryClassifier` already rejects decimals, abbreviations, and /// list markers, so this never truncates "e.g.", "3.14", or a numbered "1." mid-thought. -enum DecodeStopPolicy { +nonisolated enum DecodeStopPolicy { static func shouldStop( accumulated: String, tokensGenerated: Int, diff --git a/Cotabby/Support/EmojiCatalog.swift b/Cotabby/Support/EmojiCatalog.swift index debdb6ab..72993c46 100644 --- a/Cotabby/Support/EmojiCatalog.swift +++ b/Cotabby/Support/EmojiCatalog.swift @@ -14,7 +14,7 @@ import Logging /// `Resources/Emoji/emoji.json` is generated from GitHub's gemoji dataset (MIT licensed). To refresh /// it, transform the upstream `db/emoji.json` into this file's `{glyph,name,aliases,keywords,group, /// unicodeVersion}` shape. -struct EmojiCatalog { +nonisolated struct EmojiCatalog { /// An entry paired with its lowercased searchable tokens, computed once at load time. struct IndexedEntry: Equatable { let entry: EmojiEntry diff --git a/Cotabby/Support/EmojiMatcher.swift b/Cotabby/Support/EmojiMatcher.swift index 77b7d515..6e903c6e 100644 --- a/Cotabby/Support/EmojiMatcher.swift +++ b/Cotabby/Support/EmojiMatcher.swift @@ -17,7 +17,7 @@ import Foundation /// matched token (so `smile` beats `smiley`), then the curated popularity prior, then catalog order. /// Favorites and popularity only break ties inside a tier, so relevance is never sacrificed to make /// a popular or frequently-used emoji jump ahead of a genuinely better match. -struct EmojiMatcher { +nonisolated struct EmojiMatcher { let catalog: EmojiCatalog /// Default number of rows the panel shows. Bounded so a one-character query does not build a diff --git a/Cotabby/Support/EmojiPopularity.swift b/Cotabby/Support/EmojiPopularity.swift index 627c9190..a1c6a532 100644 --- a/Cotabby/Support/EmojiPopularity.swift +++ b/Cotabby/Support/EmojiPopularity.swift @@ -13,7 +13,7 @@ import Foundation /// order is roughly age-based, not usage-based. A curated list is the cheapest way to encode "what /// people reach for" until per-user history (see `EmojiUsageStore`) takes over. Aliases that are not /// present in the active catalog simply never rank or resolve, so a stale entry here is harmless. -enum EmojiPopularity { +nonisolated enum EmojiPopularity { /// Ranked aliases, most popular first. The index is the rank, so order is the contract; keep the /// highest-traffic reactions at the top. Grouped only for readability. static let ordered: [String] = [ diff --git a/Cotabby/Support/EmojiRecents.swift b/Cotabby/Support/EmojiRecents.swift index 21216146..da5ee2bd 100644 --- a/Cotabby/Support/EmojiRecents.swift +++ b/Cotabby/Support/EmojiRecents.swift @@ -8,7 +8,7 @@ import Foundation /// This is what makes the very first `:` useful instead of empty. A brand-new user with no history /// sees popular emoji; a returning user sees what they actually reach for. Variant resolution (skin /// tone / gender) is applied by the caller, the same way it is for query results. -enum EmojiRecents { +nonisolated enum EmojiRecents { /// Recents-first, popularity-padded suggestions for an empty query, capped at `limit`. static func suggestions( usage: EmojiUsageSnapshot, diff --git a/Cotabby/Support/EmojiSynonymCatalog.swift b/Cotabby/Support/EmojiSynonymCatalog.swift index fa0e9d9f..c46bdfae 100644 --- a/Cotabby/Support/EmojiSynonymCatalog.swift +++ b/Cotabby/Support/EmojiSynonymCatalog.swift @@ -14,7 +14,7 @@ import Foundation /// The matcher consumes this through `boostedAliases(for:)`: an exact key match boosts its aliases to /// the alias-prefix tier, and a prefix key match boosts to the keyword tier, so intent ranks high /// without ever overriding a literal exact-alias match the user typed. -enum EmojiSynonymCatalog { +nonisolated enum EmojiSynonymCatalog { /// Lowercased query word -> canonical aliases to boost, in rough preference order (final ordering /// among equally-boosted aliases is decided by the matcher's popularity tiebreak). static let map: [String: [String]] = [ diff --git a/Cotabby/Support/EmojiTriggerStateMachine.swift b/Cotabby/Support/EmojiTriggerStateMachine.swift index ccfcaf09..f8fd6bd0 100644 --- a/Cotabby/Support/EmojiTriggerStateMachine.swift +++ b/Cotabby/Support/EmojiTriggerStateMachine.swift @@ -12,7 +12,7 @@ import Foundation /// commit time. This keeps the active, gating event tap out of the ordinary-typing path (the /// issue #328 invariant that the base branch fixed) while still consuming navigation, commit, and /// Escape keys so the picker can be driven without the foreground app seeing them. -struct EmojiTriggerStateMachine { +nonisolated struct EmojiTriggerStateMachine { private(set) var state: EmojiTriggerState = .idle(previousCharacter: nil) var isCapturing: Bool { diff --git a/Cotabby/Support/FileLogHandler.swift b/Cotabby/Support/FileLogHandler.swift index caa58db4..32c3c2ed 100644 --- a/Cotabby/Support/FileLogHandler.swift +++ b/Cotabby/Support/FileLogHandler.swift @@ -18,7 +18,7 @@ import Logging /// /// `@unchecked Sendable`: the only mutable state is `handle` and `currentByteOffset`, /// both guarded by `lock`. FileHandle itself is not Sendable so we cannot mark it cleanly. -final class FileLogWriter: @unchecked Sendable { +nonisolated final class FileLogWriter: @unchecked Sendable { static let shared = FileLogWriter() /// Default cap of 10 MB. Large enough for hours of debug output without ballooning disk. diff --git a/Cotabby/Support/FocusCapabilityFlickerGate.swift b/Cotabby/Support/FocusCapabilityFlickerGate.swift index 96596e97..317e577b 100644 --- a/Cotabby/Support/FocusCapabilityFlickerGate.swift +++ b/Cotabby/Support/FocusCapabilityFlickerGate.swift @@ -27,7 +27,7 @@ struct FocusCapabilityFlickerGate { static let requiredConsecutiveBlockedReads = 2 /// Outcome the caller acts on. - enum Decision: Equatable { + nonisolated enum Decision: Equatable { /// Apply this snapshot as-is. case apply /// Treat as a transient flicker: pretend the previous Supported snapshot is still current. diff --git a/Cotabby/Support/InsertionStrategySelector.swift b/Cotabby/Support/InsertionStrategySelector.swift index b991d330..e07e43a6 100644 --- a/Cotabby/Support/InsertionStrategySelector.swift +++ b/Cotabby/Support/InsertionStrategySelector.swift @@ -5,7 +5,7 @@ import Foundation /// keystrokes are reliable and clipboard-free for the common short, single-line completion, but some /// apps mishandle a long or multi-line synthetic string; pasting is steadier there. Keeping the /// decision here (separate from the side-effectful inserter) makes the policy trivially testable. -enum InsertionStrategy: Equatable { +nonisolated enum InsertionStrategy: Equatable { /// Synthesize the text as a Unicode keyboard event (the default, clipboard-free path). case keystroke /// Place the text on the pasteboard and synthesize Cmd-V. Only used when paste insertion is diff --git a/Cotabby/Support/LLMIOFileHandler.swift b/Cotabby/Support/LLMIOFileHandler.swift index dcb4f17e..127a627e 100644 --- a/Cotabby/Support/LLMIOFileHandler.swift +++ b/Cotabby/Support/LLMIOFileHandler.swift @@ -13,7 +13,7 @@ import Logging /// /// Like `FileLogHandler`, this handler is only installed when `-cotabby-debug` is set, so a release /// build never touches the user's disk with prompt or completion text. -final class LLMIOFileWriter: @unchecked Sendable { +nonisolated final class LLMIOFileWriter: @unchecked Sendable { static let shared = LLMIOFileWriter() /// Same 10 MB cap as the main log. LLM I/O records are larger per line, so this corresponds to diff --git a/Cotabby/Support/MacroTriggerStateMachine.swift b/Cotabby/Support/MacroTriggerStateMachine.swift index ba1c309f..018e02f7 100644 --- a/Cotabby/Support/MacroTriggerStateMachine.swift +++ b/Cotabby/Support/MacroTriggerStateMachine.swift @@ -10,7 +10,7 @@ import Foundation /// sigil, `/` does not overlap the emoji picker's `:`, so there is no deferred hand-off and no /// shared-key race: one keystroke, one open. That directness is deliberate, it is what makes the /// preview reliably appear instead of intermittently losing the second colon to the emoji feature. -struct MacroTriggerStateMachine { +nonisolated struct MacroTriggerStateMachine { private(set) var state: MacroTriggerState = .idle(previousCharacter: nil) var isCapturing: Bool { @@ -124,7 +124,7 @@ enum MacroTriggerInput: Equatable { /// Side effects the controller performs after a transition. The machine stays pure; it only /// describes what should happen. -enum MacroTriggerAction: Equatable { +nonisolated enum MacroTriggerAction: Equatable { case open case updateQuery(String) case commit @@ -133,7 +133,7 @@ enum MacroTriggerAction: Equatable { /// The two lifecycle states. `idle` remembers the previously typed character so the trigger can /// require a word boundary; `capturing` holds the live query typed after the `/`. -enum MacroTriggerState: Equatable { +nonisolated enum MacroTriggerState: Equatable { case idle(previousCharacter: Character?) case capturing(query: String) } @@ -142,7 +142,7 @@ enum MacroTriggerState: Equatable { /// lists use `,`, conversions use `->`, and `/` is division (the leading `/` is the sigil, any later /// `/` is an operator). Only the rendered output is localized, which avoids the comma-decimal /// ambiguity a localized input grammar would create. -enum MacroQueryGrammar { +nonisolated enum MacroQueryGrammar { static func extends(_ character: Character) -> Bool { if character.isLetter || character.isNumber { return true diff --git a/Cotabby/Support/OCRTextHygiene.swift b/Cotabby/Support/OCRTextHygiene.swift index 20863709..1c12a85a 100644 --- a/Cotabby/Support/OCRTextHygiene.swift +++ b/Cotabby/Support/OCRTextHygiene.swift @@ -14,7 +14,7 @@ import Foundation /// in isolation, and so the orchestrating service (`ScreenTextExtractor` / `ScreenshotContextGenerator`) /// stays free of OCR-noise heuristics. There is no I/O, no logging, and no dependency beyond /// `Foundation`; the same input always yields the same output. -enum OCRTextHygiene { +nonisolated enum OCRTextHygiene { /// A single recognized OCR line paired with the recognizer's confidence for that line. /// diff --git a/Cotabby/Support/OnboardingTemplateFeatureList.swift b/Cotabby/Support/OnboardingTemplateFeatureList.swift index 3b97e8b3..3b9acd41 100644 --- a/Cotabby/Support/OnboardingTemplateFeatureList.swift +++ b/Cotabby/Support/OnboardingTemplateFeatureList.swift @@ -10,7 +10,7 @@ import Foundation /// `OnboardingTemplate`. If a template gains a new behavior flag, the row order here is the only /// place to extend — the UI walks whatever rows this returns. -enum OnboardingTemplateFeatureValue: Equatable, Sendable { +nonisolated enum OnboardingTemplateFeatureValue: Equatable, Sendable { /// Toggled on by the template. UI renders a positive affordance (e.g., a check). case enabled /// Explicitly off under the template. UI renders a neutral/negative affordance (e.g., a dash). diff --git a/Cotabby/Support/SentenceBoundaryClassifier.swift b/Cotabby/Support/SentenceBoundaryClassifier.swift index 11315f67..c25224ee 100644 --- a/Cotabby/Support/SentenceBoundaryClassifier.swift +++ b/Cotabby/Support/SentenceBoundaryClassifier.swift @@ -9,7 +9,7 @@ import Foundation /// "e.g.", and a numbered "1." mid-tail. A purely structural scanner cannot resolve every case, but /// it can resolve the frequent ones with a few local rules. `!` and `?` are always terminal and do /// not need this; only the period is ambiguous. -enum SentenceBoundaryClassifier { +nonisolated enum SentenceBoundaryClassifier { /// Lowercased abbreviations whose trailing period is part of the word, not a sentence end. private static let abbreviations: Set = [ "mr", "mrs", "ms", "dr", "st", "vs", "eg", "ie", "etc", "no", "fig", "approx", "inc", "ltd" @@ -101,7 +101,7 @@ enum SentenceBoundaryClassifier { } } -private extension Character { +nonisolated private extension Character { /// Closing punctuation that may follow a sentence terminator: straight and curly quotes, /// parentheses, square brackets, and braces, plus the shared CJK closer set (see /// `Character.isCJKClosingPunctuation`). `endsSentence` walks back past a run of these to find diff --git a/Cotabby/Support/SuggestionSessionReconciler.swift b/Cotabby/Support/SuggestionSessionReconciler.swift index e741d385..c8691ff0 100644 --- a/Cotabby/Support/SuggestionSessionReconciler.swift +++ b/Cotabby/Support/SuggestionSessionReconciler.swift @@ -586,7 +586,7 @@ private extension String { /// The CJK punctuation primitives, internal because they are the single source of truth shared by /// this file's acceptance policy and `SentenceBoundaryClassifier`'s sentence-end detection. Adding a /// codepoint here updates phrase boundaries, chunk binding, and the generation stop in one edit. -extension Character { +nonisolated extension Character { /// The CJK sentence terminators: ideographic full stop `。`, fullwidth `!` `?`, and the halfwidth /// ideographic stop `。`. Unlike the ASCII period these are unambiguous (they never mark decimals, /// list numbers, or abbreviations), so every consumer treats them as terminal without classifier @@ -605,7 +605,7 @@ extension Character { } } -private extension Character { +nonisolated private extension Character { /// True when the character begins a word of a space-less script (Han, Hiragana, Katakana, Hangul, /// Thai, Lao, Khmer, Myanmar, ...). These scripts write words without separating spaces, so the /// whitespace-run acceptance rule would over-accept a whole run; `nextAcceptanceChunk` switches to diff --git a/Cotabby/Support/SystemResourceSampler.swift b/Cotabby/Support/SystemResourceSampler.swift index 1f856fe6..2d59d7e3 100644 --- a/Cotabby/Support/SystemResourceSampler.swift +++ b/Cotabby/Support/SystemResourceSampler.swift @@ -20,7 +20,7 @@ struct SystemResourceSample: Equatable { let footprintBytes: UInt64 } -enum SystemResourceSampler { +nonisolated enum SystemResourceSampler { static func sample() -> SystemResourceSample { SystemResourceSample( cpuPercent: currentCPUPercent(), diff --git a/Cotabby/Support/TokenCountEstimator.swift b/Cotabby/Support/TokenCountEstimator.swift index 03191b8d..485d0e18 100644 --- a/Cotabby/Support/TokenCountEstimator.swift +++ b/Cotabby/Support/TokenCountEstimator.swift @@ -10,7 +10,7 @@ import Foundation /// global chars-per-token ratio — especially for code or short function words — while staying /// allocation-light and deterministic for tests. It is not exact, so it is used only for relative /// budgeting decisions, never to assert a hard token limit. -enum TokenCountEstimator { +nonisolated enum TokenCountEstimator { static func estimate(_ text: String) -> Int { // Split on punctuation as well as whitespace: real subword tokenizers break "can't", "end.", // and "func()" into multiple tokens, so gluing punctuation to a word would systematically diff --git a/Cotabby/Support/TypoGate.swift b/Cotabby/Support/TypoGate.swift index 30a8fae0..1eded6b7 100644 --- a/Cotabby/Support/TypoGate.swift +++ b/Cotabby/Support/TypoGate.swift @@ -5,7 +5,7 @@ import Foundation /// `SuggestionCoordinator` so the "suppress vs correct vs proceed" logic is unit-testable without /// `NSSpellChecker` or a live AX snapshot: the coordinator passes the spell-check behaviors in as /// closures, and tests pass deterministic stubs. -enum TypoGateDecision: Equatable { +nonisolated enum TypoGateDecision: Equatable { /// No actionable typo on the current word. Proceed with a normal continuation. case proceed /// The current word looks misspelled and corrections are off (or none was available). Hide the