Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Cotabby/Models/EmojiPickerModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -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)
Expand All @@ -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)
}
2 changes: 1 addition & 1 deletion Cotabby/Models/EmojiUsageModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion Cotabby/Models/FocusModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion Cotabby/Models/HuggingFaceModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Cotabby/Models/SuggestionEngineModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion Cotabby/Models/SystemMetricsStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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] = []

Expand Down
2 changes: 1 addition & 1 deletion Cotabby/Models/VisualContextModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion Cotabby/Services/Input/KeyboardInputSourceMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
}

Expand Down
6 changes: 5 additions & 1 deletion Cotabby/Services/Power/PowerSourceMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion Cotabby/Support/ConfidenceSuppressionPolicy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions Cotabby/Support/CotabbyDebugOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Cotabby/Support/DecodeStopPolicy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion Cotabby/Support/EmojiCatalog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Cotabby/Support/EmojiMatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Cotabby/Support/EmojiPopularity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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] = [
Expand Down
2 changes: 1 addition & 1 deletion Cotabby/Support/EmojiRecents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion Cotabby/Support/EmojiSynonymCatalog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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]] = [
Expand Down
2 changes: 1 addition & 1 deletion Cotabby/Support/EmojiTriggerStateMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion Cotabby/Support/FileLogHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion Cotabby/Support/FocusCapabilityFlickerGate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion Cotabby/Support/InsertionStrategySelector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Cotabby/Support/LLMIOFileHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions Cotabby/Support/MacroTriggerStateMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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)
}
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Cotabby/Support/OCRTextHygiene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down
2 changes: 1 addition & 1 deletion Cotabby/Support/OnboardingTemplateFeatureList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
4 changes: 2 additions & 2 deletions Cotabby/Support/SentenceBoundaryClassifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> = [
"mr", "mrs", "ms", "dr", "st", "vs", "eg", "ie", "etc", "no", "fig", "approx", "inc", "ltd"
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions Cotabby/Support/SuggestionSessionReconciler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Cotabby/Support/SystemResourceSampler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ struct SystemResourceSample: Equatable {
let footprintBytes: UInt64
}

enum SystemResourceSampler {
nonisolated enum SystemResourceSampler {
static func sample() -> SystemResourceSample {
SystemResourceSample(
cpuPercent: currentCPUPercent(),
Expand Down
2 changes: 1 addition & 1 deletion Cotabby/Support/TokenCountEstimator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Cotabby/Support/TypoGate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down