diff --git a/AGENTS.md b/AGENTS.md index f4317b2ad..1cb70e52f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -126,7 +126,23 @@ Focus and geometry live in: 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, -`focusChangeSequence`, and capability checks unless the change explicitly replaces them. +`focusChangeSequence`, and capability checks unless the change explicitly replaces them. Known +shapes worth keeping in mind: CodeMirror (Obsidian) exposes a wrapped paragraph as one static-text +run plus a single-space spacer run per line, and its value runs paragraphs together with no +separator, so a paragraph is found from where its run was anchored in the value, never from the +last line break (`WrappedRunAnchor` hands both that run and a single-line paragraph run to +`PixelCaretLocator`, which reads the caret from the run's pixels, a one-line run in its own line +box, and carries it forward by the typed advance while the ghost covers the run); +Chromium's address bar keeps its inline completion selected after the caret (stripped by the +resolver and held as the host's marked text, never treated as a user selection) and answers every +bounds query with a zero rect (the caret comes from its pixels, `PixelCaretLocator`); Mail's compose +header rows are `AXTextField`s with identifiers `Mail.toField`, `Mail.ccField`, `Mail.subjectField` +where Tab moves to the next field, so they are blocked (`MailHeaderFieldDetector`), and the body is +an `AXWebArea` described "message body". A Chromium or Electron host reports its font size in CSS +pixels and nothing of its zoom (the Claude desktop composer reports 14 and paints 15.4 at 110%), +and a contenteditable answers no width query: the ghost's size there follows the advance measured +from the caret's own movement (`CaretAdvanceSampler`), and the faces such a host bundles in its +own `Contents/Resources` are registered for the pixel match (`HostBundledFontRegistry`). ## Visual Context And OCR @@ -174,7 +190,13 @@ The Swift generation loop owns the maximum output-token budget. ## UI And Overlays -- `OverlayController` owns the ghost-text panel lifecycle and positioning. +- `OverlayController` owns the ghost-text panel lifecycle and positioning. Ghost glyphs are laid + out by `GhostTextLayout` in the font `GhostFontResolver` resolves, on the baseline + `GhostBaselinePolicy` (or a `HostBaselineCalibrator` measurement) gives, wrapping onto the host's + next lines at the measured pitch. A caret with text after it on its line gets the card under the + caret instead (`CompletionRenderModePolicy`): a ghost painted over the host's own characters + reads as overwriting them. Change those pure helpers and their tests rather than nudging offsets + in the controller. - `SuggestionOverlayPresenter` decides whether a suggestion should be shown or hidden. - `ActivationIndicatorController` owns the optional caret/field-edge indicator. - `FocusDebugOverlayController` is for developer visibility and should stay gated behind debug @@ -237,6 +259,10 @@ Console.app stream. - `~/Desktop/cotabby-ax-dump.txt` — most recent Chrome AX tree snapshot. Overwritten on each Chrome focus change (debounced by focused-element identity). - Rotated previous logs: `*.jsonl.1` (one-step rotation when a file exceeds 10 MB). +- `~/Library/Logs//strips/` — every calibration strip the overlay captured, as PNG plus a JSON + sidecar (caret column, size, line text), written only while + `defaults write cotabbyDumpCalibrationStrips -bool YES` is set at launch. Replay one + through `TypefaceMatcher` offline to see why a face was or was not matched. **Correlation IDs.** Every prediction gets a `request_id` like `req_a3f9k2lq`, stamped on every log line touching that request (coordinator state transitions, router selection, engine generation, LLM @@ -297,6 +323,52 @@ Keep DerivedData in `build/DerivedData`, avoid concurrent builds in the same che it after validation. Verify cleanup before reporting completion. Never move DerivedData to `~/Library/Developer/Xcode/DerivedData/Cotabby-*`. +```bash +xcodebuild -project Cotabby.xcodeproj -scheme Cotabby -destination 'platform=macOS' build \ + -derivedDataPath build/DerivedData +xcodebuild -project Cotabby.xcodeproj -scheme Cotabby -destination 'platform=macOS' build-for-testing \ + -derivedDataPath build/DerivedData +``` + +Always pass `-derivedDataPath build/DerivedData` so the output lands in the repo-scoped `build/` +directory (already gitignored) instead of accumulating under +`~/Library/Developer/Xcode/DerivedData/Cotabby-*`, where every build leaves a fresh multi-GB module +cache and SwiftPM checkout that nothing trims. When a task is done and the artifacts are no longer +needed, `rm -rf build/DerivedData` before reporting completion. + +Two model-backed eval suites measure suggestion quality and are local-only (they need a downloaded +GGUF and are gated behind the `RUN_LLAMA_EVAL` compile flag). `test_reportEvalSuite` scores ordinary +continuations; `test_reportRecallSuite` scores whether context the user did not type (earlier field +text, screen OCR, clipboard) actually reaches the completion. Run either with: + +```bash +xcodebuild test -project Cotabby.xcodeproj -scheme Cotabby -destination 'platform=macOS' \ + -only-testing:CotabbyTests/LlamaSuggestionEvalTests/test_reportRecallSuite \ + SWIFT_ACTIVE_COMPILATION_CONDITIONS='$(inherited) RUN_LLAMA_EVAL' \ + CODE_SIGNING_ALLOWED=NO -configuration Release ENABLE_TESTABILITY=YES \ + -derivedDataPath build/DerivedData +``` + +Quote latency only from a Release build; Debug inflates per-token Swift work by an order of magnitude. +Any change to prompt content, context budgets, sampling, or the model should be justified with a +before/after on both suites over identical cases. + +The dev app is a SEPARATE target and scheme, `Cotabby Dev` (product `Cotabby Dev.app`). Building the +`Cotabby` scheme leaves the dev bundle stale, so build the dev app explicitly and confirm the binary +timestamp moved before testing against it: + +```bash +xcodebuild -project Cotabby.xcodeproj -scheme "Cotabby Dev" -configuration Release \ + -destination 'platform=macOS' build -derivedDataPath build/DerivedData +``` + +Ghost placement changes are verified against the pixels the user sees, not against log counts: type +a fragment at the end of a field, capture the ghost, accept it, capture the host's own rendering of the +same words, and compare the two, the ghost's dx/dy in points per line. A placement fix is done when +every host checked (TextEdit, a Chrome contenteditable, an Obsidian note with one wrapped paragraph and +with several) stays within about 0.2pt on every line; a change that helps one host and moves another +is a regression even when its own log counters improve. + Run targeted tests for changed pure logic when available. If validation fails, report the exact failure and distinguish a successful build-for-testing step from actual test execution. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 7be31eb27..f21a38373 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -52,8 +52,10 @@ These rules explain most of the structure: reconciliation, layout, and low-level bridging helpers. - [CotabbyTests](CotabbyTests): unit tests and microbenchmarks, with emphasis on pure Support and Models behavior. -- CotabbyInference: the llama.cpp Swift wrapper consumed from an external SwiftPM package pinned to - its main branch; native code is not vendored here. +- CotabbyInference: the llama.cpp Swift wrapper consumed from an external SwiftPM package; native + code is not vendored here. The mid-word anchoring needs a required-prefix constraint that is not in + `FuJacob/cotabbyinference` main yet, so the package resolves from the `feat/required-prefix` branch + of the `Mason363/cotabbyinference` fork that adds it; the pin returns to main once it lands there. [SOURCE_LAYOUT.md](SOURCE_LAYOUT.md) expands this map into the canonical nested source and test layout. Child folders name stable responsibilities inside a subsystem; they do not create Swift @@ -139,7 +141,15 @@ and cohesive mutable sub-state to smaller boundaries: - [SuggestionAvailabilityEvaluator.swift](Cotabby/Support/Suggestion/Request/SuggestionAvailabilityEvaluator.swift): pure permission, settings, focus, and runtime gates. - [SuggestionRequestFactory.swift](Cotabby/Support/Suggestion/Request/SuggestionRequestFactory.swift): pure bounded - request construction and the selected backend's developer-debug prompt payload. + request construction and the selected backend's developer-debug prompt payload. Nothing is generated + with the caret inside a token ([CaretTokenPosition.swift](Cotabby/Support/Suggestion/Request/CaretTokenPosition.swift)), + and on the llama path a request made mid-word is anchored at the word boundary + ([WordBoundaryAnchorPolicy.swift](Cotabby/Support/Suggestion/Request/WordBoundaryAnchorPolicy.swift)): the partial + word leaves the prompt (so its last token is a whole word) and the engine is handed the boundary whitespace plus the + typed letters as a required prefix it masks every inconsistent token against, so the model finishes the word the + user started; the normalizer shows only the untyped remainder. The word-count preset also bounds the result + ([SuggestionLengthPolicy.swift](Cotabby/Support/Suggestion/Output/SuggestionLengthPolicy.swift) trims to the + preset's upper bound at a clause boundary; the decoder's sentence stop waits for its lower bound). - [SuggestionWorkController.swift](Cotabby/Services/Suggestion/State/SuggestionWorkController.swift): debounce/generation tasks and monotonically increasing work IDs. - [SuggestionInteractionState.swift](Cotabby/Services/Suggestion/State/SuggestionInteractionState.swift): @@ -151,11 +161,16 @@ and cohesive mutable sub-state to smaller boundaries: - [SuggestionSessionReconciler.swift](Cotabby/Support/Suggestion/Session/SuggestionSessionReconciler.swift): type-through, acceptance, and live-host reconciliation. - [SuggestionTextNormalizer.swift](Cotabby/Support/Suggestion/Output/SuggestionTextNormalizer.swift): backend-independent - cleanup, echo removal, whitespace policy, trailing-text deduplication, and unsafe-output rejection. + cleanup, echo removal, whitespace policy, trailing-text deduplication, word-boundary reconciliation, and + unsafe-output rejection. [CompletionContentPolicy.swift](Cotabby/Support/Suggestion/Output/CompletionContentPolicy.swift) + then drops punctuation-only output, closing punctuation after a typed space, forum/chat scaffolding or + meta-responses about the prompt, a word sequence looping back to back, and text lifted verbatim from what the + user just wrote. A native correction path runs before model generation. NSSpellChecker and bundled SymSpell indexes can suppress completion while a likely typo is forming, offer a green atomic replacement, or apply -an opt-in automatic fix after Space. +an opt-in automatic fix after Space. A word the checker can still complete ("apprec") is treated as +in progress rather than misspelled, so the continuation finishes it. 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 @@ -184,7 +199,9 @@ across AppKit, browsers, Electron, and custom editors. Activity resets the caden state backs it off. Input and acceptance paths may request an explicit fresh capture, but do not trust event payloads as complete field state. -FocusSnapshotResolver finds a usable editable candidate, blocks secure/unsupported surfaces, bounds +FocusSnapshotResolver finds a usable editable candidate, blocks secure/unsupported surfaces (and +Mail's compose header rows, [MailHeaderFieldDetector.swift](Cotabby/Support/Accessibility/MailHeaderFieldDetector.swift): +Tab is the way from To to Subject to body there, not an accept), bounds text on both sides of the caret, resolves the focused process, and publishes stable domain values. Chromium/Electron require accessibility priming, cursor hit-test recovery, and out-of-process iframe handling. All fallbacks are revalidated and yield to a valid system-focused element. @@ -268,9 +285,20 @@ cancel from aborting a later request that reused the same sequence. [BaseCompletionPromptRenderer.swift](Cotabby/Support/Prompting/BaseCompletionPromptRenderer.swift) renders a base-model text continuation with optional budgeted context and the caret prefix last. It does not -wrap a base GGUF in an instruction conversation. [FoundationModelPromptRenderer.swift](Cotabby/Support/Prompting/FoundationModelPromptRenderer.swift) +wrap a base GGUF in an instruction conversation. The writer's name enters that preface only when +the caret follows a valediction ([SignOffCue.swift](Cotabby/Support/Prompting/SignOffCue.swift)): +named in every prompt, a base model introduced the writer at openings ("Hi, I'm Jacob"), addressed +them as the recipient, and copied the preface wording into the ghost; at a sign-off the name is the +one token wanted. The first-launch name is the Mac account's full name, never a placeholder. [FoundationModelPromptRenderer.swift](Cotabby/Support/Prompting/FoundationModelPromptRenderer.swift) keeps Apple's instruction-shaped prompt separate. +The llama context window is 4096 tokens. `SuggestionConfiguration.derivedLlamaPromptTokenBudget` +subtracts the output ceiling and a safety margin from it, so the prompt budget (3982 tokens) can never +silently drift from the KV capacity the model actually has. The prefix caps (14000 characters / 2400 +words) are sized to bind at roughly the same point: raising one without the other does nothing, +because whichever is smaller truncates the prefix to its tail and drops the early text that carries +the setup. Both were doubled together after the recall eval showed facts stated more than ~1900 +tokens before the caret were invisible to the model. The request window and section allocator preserve the prefix's spaces, line breaks, and indentation. Multiline output cleanup also preserves the leading space needed to join a new word at the caret. Bounded right-of-caret text is descriptive reference material before the prefix, not invented @@ -282,6 +310,38 @@ Prewarm is opportunistic and goes only to the selected backend. Context reset re The local runtime is loaded only for the Open Source engine and is released when switching to Apple or endpoint mode so mapped weights and Metal buffers do not stay resident unnecessarily. +Two measurements happen at presentation time rather than in the focus resolver, because they read +the host's pixels (Screen Recording) and are asynchronous: + +- [HostBaselineCalibrator.swift](Cotabby/Services/Presentation/HostBaselineCalibrator.swift) finds + the painted baseline on the caret's line. A reading is accepted only if the letter bodies it was + read from are the right size to be a line of this font (`describesPlausibleBodies`: body rows + within 0.45x-1.35x the font's ascent) and the answer sits within the policy window; otherwise the + policy baseline, the font's own metric, stands. Measured in a real session, one field's lines read + 12.0 and 15.0 for the same font: both passed the policy window, and the 12.0 lines put the ghost + three points high. The gate is on the measurement, not a vote across lines: an earlier attempt to + let one line's reading speak for the whole field adopted a bad first reading and made every line + wrong instead of one. +- [PixelCaretLocator.swift](Cotabby/Services/Presentation/PixelCaretLocator.swift) places the caret + inside a paragraph the host exposes only as one union-framed run with no answer to any bounds + query (Obsidian's CodeMirror), and in a single-line field whose caret Accessibility could only + estimate (Chrome's address bar answers every bounds query with a zero rect): there the field's + frame is the line and the caret is where its ink ends, so the ghost goes inline instead of to the + card. A single-line paragraph run gets the same treatment, read in its own line box widened to + where its text now ends: its proportional caret landed 3pt off in Obsidian, and the full + padding read its neighbours' ink as lines of its own. A capture cannot see the run under + Cotabby's own ghost (the excluded window + comes back black), so while the ghost is up a re-anchor for text typed since the run's last + capture carries that caret forward by the typed advance (`extrapolatedMeasurement`), and + anything else takes the ghost down for the read; re-anchoring to the Accessibility estimate + instead put the ghost four lines up on every other keystroke. + [InkCaretAnalyzer.swift](Cotabby/Support/Presentation/Geometry/InkCaretAnalyzer.swift) + finds the inked lines in a capture of the run's frame; the caret is the end of the last line, the + pitch is the distance between line tops, and the line box is the frame height less the pitch per + extra line. Only a caret at the end of its paragraph is measured; a caret inside one keeps the + card. `OverlayController` holds the first presentation for a paragraph text until the capture + answers (tens of milliseconds) and reuses it for every later presentation of that text. + ## Context, Privacy, and Permissions [PermissionManager.swift](Cotabby/Services/Permission/PermissionManager.swift) tracks: @@ -295,6 +355,11 @@ metadata, user rules/extended context, relevant clipboard content, visual OCR, l [PromptContextSanitizer.swift](Cotabby/Support/Context/PromptContextSanitizer.swift) sanitizes optional text, and prompt renderers apply per-section budgets. +Whether any of that context survives into the completion is measured, not assumed: +`CotabbyTests/Fixtures/llama-recall-cases.json` hides a fact in each context source (same-field text, +screen OCR, clipboard) and requires the completion to reproduce it. It is reported separately from the +continuation suite because averaging the two lets fluent prose hide a total failure to use context. + [ClipboardContextProvider.swift](Cotabby/Services/Context/ClipboardContextProvider.swift) reads a fresh bounded value at request time rather than recording clipboard history. Relevance and distillation policies drop unrelated or excessive content. @@ -342,6 +407,64 @@ estimated/layout-estimated geometry, mid-line editing, or explicit user preferen match host font/color, render corrections distinctly, respect right-to-left and multiline layout, show an acceptance hint, and advance a partial tail without waiting for noisy AX geometry. +Inline ghost text is built to occupy the pixels the accepted text will occupy: + +- [GhostFontResolver.swift](Cotabby/Support/Presentation/Style/GhostFontResolver.swift) picks the + host's face and size from the field's reported style, from a measured width sample + ([HostTextMetricsProbe.swift](Cotabby/Services/Focus/Resolution/HostTextMetricsProbe.swift); a host + that answers no width query gets one from how far its caret moves as the user types, + [CaretAdvanceSampler.swift](Cotabby/Support/Focus/CaretAdvanceSampler.swift), because a Chromium + size is CSS pixels that know nothing of page or Electron zoom: the Claude composer reported 14 + and painted 15.4), or from the host's own pixels + ([TypefaceMatcher.swift](Cotabby/Support/Presentation/Style/TypefaceMatcher.swift)) + when the host names no face. Every capture is snapped to whole device pixels first: a fractional + edge makes ScreenCaptureKit resample the image and the blurred glyphs correlate with nothing. The pixel match searches size as well as face (a caret-box size is + a guess: Obsidian's 16px body arrived as 17 and 20, and a face matched at the wrong size is + confidently wrong), carries every candidate to its exact size (the correlation drops from 0.95 + to 0.66 a sixth of a point away, and a wrong face at a lucky size outscores the true face at a + grid point), marks down a candidate whose letter bodies are not the height the host + painted, prefers the system face on a near tie, and declines on too little ink. The size it + keeps is then fitted to the host's glyph positions alone, the chosen face rendered once and + stretched about the caret (`advanceFitted`): shape scoring picked Chrome's 18px Georgia at + 18.054, a ghost two device pixels long by a line's end, while the positions put it at 18. An + Electron host's own bundled faces join the candidates + ([HostBundledFontRegistry.swift](Cotabby/Services/Presentation/HostBundledFontRegistry.swift) + registers its TrueType/OpenType files for this process alone), which is how Claude's composer + can be drawn in Anthropic Sans rather than a stand-in. The calibrator + keeps one record per field, replaced only when a later strip's winner beats the recorded face on + that same strip; scores from different strips are not comparable. A reported size stands when the + caret box is shorter than its glyphs (VS Code's hidden textarea reports 8.5pt boxes for 14pt text). +- [GhostBaselinePolicy.swift](Cotabby/Support/Presentation/Geometry/GhostBaselinePolicy.swift) places + the baseline the way TextKit or Blink/WebKit would inside the caret box; + [HostBaselineCalibrator.swift](Cotabby/Services/Presentation/HostBaselineCalibrator.swift) measures a + web host's painted baseline from a small screen capture (Screen Recording permitting) to recover + the sub-point line position Accessibility rounds away. +- [GhostTextLayout.swift](Cotabby/Support/Presentation/Geometry/GhostTextLayout.swift) lays rows out + from the caret with CTTypesetter inside the band + [GhostWrapBandPolicy.swift](Cotabby/Support/Presentation/Geometry/GhostWrapBandPolicy.swift) derives + from the element's real frame, one row per host line at the measured line pitch (the probe scans + single-character bounds for the nearest other line when a host's line APIs give none, as Chromium's + do; sibling text runs give it for CodeMirror; the caret box height stands in until a field has a + second line). A row is never placed over the host's own text: with the host's lines below the + caret the ghost keeps to the caret row and reveals the rest as it is accepted, and a caret with + characters after it on its line gets the card under the caret from + [CompletionRenderModePolicy.swift](Cotabby/Support/Presentation/Policy/CompletionRenderModePolicy.swift) + (an opaque band in the field's background color was tried and read as the suggestion overwriting + the user's text). Accepted or typed-through text only advances a consumed offset, so remaining + glyphs never move. [GhostTextPanelView.swift](Cotabby/Services/Presentation/GhostTextPanelView.swift) + draws the rows with CoreText on a whole-point panel origin. +- While the host shows uncommitted text of its own (macOS inline predictive text, an IME + composition; [HostMarkedTextPolicy.swift](Cotabby/Support/Input/HostMarkedTextPolicy.swift)) the + coordinator holds: no generation, no ghost, session kept. Chromium's address bar completes inline + and leaves the completion selected; the resolver strips that selection from the text and treats + the completion as the host's marked text, so the hold covers it too and its ink is never read as + the caret line's end. +- Editors that expose a whole wrapped paragraph as one text run (CodeMirror in Obsidian) get their + caret from a layout of that paragraph inside the run's own frame at the sibling runs' pitch + (`WrappedRunAnchor`, laid out by + [TextLayoutCaretEstimator.swift](Cotabby/Support/Presentation/Geometry/TextLayoutCaretEstimator.swift) + in the coordinator's repair step); whitespace-only spacer runs never anchor the caret mapping. + [ActivationIndicatorController.swift](Cotabby/Services/Presentation/ActivationIndicatorController.swift) owns the optional field/caret indicator. [FocusDebugOverlayController.swift](Cotabby/Services/Presentation/FocusDebugOverlayController.swift) is developer-only and gated by -cotabby-debug. diff --git a/Cotabby.xcodeproj/project.pbxproj b/Cotabby.xcodeproj/project.pbxproj index 61265fed5..59550455b 100644 --- a/Cotabby.xcodeproj/project.pbxproj +++ b/Cotabby.xcodeproj/project.pbxproj @@ -10,12 +10,14 @@ 000C6FB630E84073B89BF926 /* SpellingDictionaryPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72B8156EEF23664F775190AB /* SpellingDictionaryPicker.swift */; }; 00B988D789E7FC55653191DB /* SentenceBoundaryClassifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FC72BD286ABA5B3CEF82F7 /* SentenceBoundaryClassifier.swift */; }; 0144ECDA06A8BDEFBB8DACE3 /* RuntimeBootstrapModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF8AA184C5F9CFFDF97A070 /* RuntimeBootstrapModel.swift */; }; + 01527F11AEDBE20935A254B6 /* TypefaceMatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10107090891BE11E2B34BC9E /* TypefaceMatcherTests.swift */; }; 0172EECCAFB0D648FF91FA17 /* ShortcutModifierMaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D34FEC77713B1DFED0834E2 /* ShortcutModifierMaskTests.swift */; }; 01945ABF02DEFD8FE470C3F3 /* InsertionStrategySelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D239BFA9C9061C04956C591 /* InsertionStrategySelector.swift */; }; 019EFBC6CD5189676D8B035F /* SuggestionTextNormalizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A19E3F68A42D2FAC95E000 /* SuggestionTextNormalizer.swift */; }; 01D6FF389B0E14C5BFDE4FBB /* LowPowerModeMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2CC3E0A24F4608350657D80 /* LowPowerModeMonitor.swift */; }; 01EA87DB89817609EC02BD93 /* FocusCapabilityFlickerGate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79AA39C97E8A959E9A5B6F04 /* FocusCapabilityFlickerGate.swift */; }; 0203A1A205162551A22FAC98 /* PermissionGuidanceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C41A249D9538B91B7FA8A9 /* PermissionGuidanceController.swift */; }; + 0209B7FDA016D12F91C1D197 /* BlockBreakAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82420F9505E69AE2ADE9F583 /* BlockBreakAlignment.swift */; }; 023A1A2A26B192E244192D8D /* TrailingDuplicationFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E824A5614BC7E7FCA05AD29 /* TrailingDuplicationFilterTests.swift */; }; 02A837BD3861FD72B3E99D0F /* BaseCompletionPromptRendererTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 612CA2988BF19FAE0114165A /* BaseCompletionPromptRendererTests.swift */; }; 02C80969B8D6A0DBDC619FCA /* FocusSessionScopedCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 487D8C5521411FC6845D4A51 /* FocusSessionScopedCacheTests.swift */; }; @@ -27,9 +29,9 @@ 034B9FAFB0C719798D49BA0C /* SystemResourceSampler.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B7FAD5A5E141AD9926D4A8 /* SystemResourceSampler.swift */; }; 037C23A24EF4BD5E21F526CA /* KeyRecorderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1D44E50290A78AFAE7753DB /* KeyRecorderView.swift */; }; 039BC6D14C80C15841FE8019 /* OnboardingTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF1C918E49607E99AE50C80 /* OnboardingTemplate.swift */; }; - 03C210D6D0BCC7DB9C4F46F0 /* GhostSuggestionLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F1EE1DBFDC6CDC288DB98D /* GhostSuggestionLayout.swift */; }; 03FBB478CF7C51BDFB9AEB6D /* ScreenTextExtractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 445BDC778CCC14AC909D659D /* ScreenTextExtractorTests.swift */; }; 050750A0343235F426D8B47B /* ScreenshotContextGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 023BA8AA7DF3F8A45F9CE8FA /* ScreenshotContextGeneratorTests.swift */; }; + 0569D0ECE7AA0F70B2436B1A /* GhostCaretRefinement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A39920BD5E23E4D2E9AF037 /* GhostCaretRefinement.swift */; }; 05CC25E51682528CE2E73446 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BC4F887528AE74AC0DD30314 /* Assets.xcassets */; }; 05CFA72F4FF44A0CDBEFED3C /* PostExhaustionAcceptanceState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22FA75F9AD318B9084B23ECF /* PostExhaustionAcceptanceState.swift */; }; 067371AD898788392F158EDE /* PhrasePredictionScreenContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6A9735EBD163A2E8AD67A5 /* PhrasePredictionScreenContext.swift */; }; @@ -46,14 +48,18 @@ 0A08A88B773845EF9A27DF0F /* SymSpellCorrector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E1E72C09460390583D98D1 /* SymSpellCorrector.swift */; }; 0A15FAD03A93D57372D267E0 /* VisualContextCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A854CAFB1F557BC4CAED8819 /* VisualContextCoordinator.swift */; }; 0A2D9EC761FDAEF15F547C4E /* ScreenFrameReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B49898B64D4DB50736CBC46 /* ScreenFrameReader.swift */; }; + 0A3063CCE08DCD90DA1650D8 /* HostZoomLadderResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEDA42939A0406D27EB799A9 /* HostZoomLadderResolver.swift */; }; 0A3443AEE6540F11E5E6BF8F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3E8E86A14090BC7BD13BA76 /* AppDelegate.swift */; }; 0A87210653A6C00EA62DC005 /* SuggestionCoordinatorPredictionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA290A7B1BF37F3D125DC8F /* SuggestionCoordinatorPredictionTests.swift */; }; 0A88D5638E98A41AE324AED7 /* EmojiQueryRun.swift in Sources */ = {isa = PBXBuildFile; fileRef = 264C3F355F2F82D1964DD263 /* EmojiQueryRun.swift */; }; 0A9F8963DABEDBFFD8D5A239 /* DeepGeometryWalkThrottle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CCBD29CF7B0BA5A9CFDEEA3 /* DeepGeometryWalkThrottle.swift */; }; + 0B0789C330063A0536A68C7A /* SignOffCue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C896CEDC632FB738D3A42BF /* SignOffCue.swift */; }; 0B7C1A4F515F63462CCCE9EA /* HuggingFaceAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D6D4D7CAD20C94DF9EF21D2 /* HuggingFaceAPIClient.swift */; }; 0BC6F3ED46B28033B80355BF /* PerformanceMetricsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 024A80AEB32CC1AE8C0FF69E /* PerformanceMetricsStore.swift */; }; 0C0087684909C4A4F067BA9B /* OCRTextHygiene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7736E6B390C822F91CC1DA99 /* OCRTextHygiene.swift */; }; 0C06CAD62975E87B2C852191 /* ScreenTextExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59E299BE2E9D42A33D5D2F5D /* ScreenTextExtractor.swift */; }; + 0C6DCE04EF0D1EE132B59B73 /* GhostCaretRefinementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF9A081715185297A31EB8F /* GhostCaretRefinementTests.swift */; }; + 0C89AF5CF2114DF57DA794CF /* MailHeaderFieldDetectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68F9EDFE2903996F0E5524E /* MailHeaderFieldDetectorTests.swift */; }; 0C8D3E1DC9D7B0FC7664055A /* GhostTextColorPreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74AB26FDFBBAC0C6375C4192 /* GhostTextColorPreset.swift */; }; 0CF057842A15F79FB3C69462 /* ModelDownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 050DC9D858A124FD1CFA4D99 /* ModelDownloadManager.swift */; }; 0CF6C912A1B25718CE090195 /* FocusSnapshotResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BEFDDFF24B78CDED4EB4206 /* FocusSnapshotResolver.swift */; }; @@ -62,13 +68,18 @@ 0DBDD71AB17F87D6558DC07D /* VisualContextExcerptSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47457FED99065966328047EB /* VisualContextExcerptSelector.swift */; }; 0DE687D08BE8AA33F0A91787 /* TokenCountEstimatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA97052545B05BA82450A6D /* TokenCountEstimatorTests.swift */; }; 0E6692D2A7AEEDF72FCF1AE2 /* ExtendedContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA0447A27B293394D9379C48 /* ExtendedContextTests.swift */; }; + 1010A070B4E0F0A2FDF45112 /* CompletionContentPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA5F007F5185BD597550607 /* CompletionContentPolicyTests.swift */; }; 102E1EDC04636AFB607A8249 /* ActiveSuggestionSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04F95814016D80E0A23974F0 /* ActiveSuggestionSession.swift */; }; 105187F185A579BC67CA2A93 /* AXHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5D1A7D938F633C6EE094F7 /* AXHelper.swift */; }; + 1092E77D9D92DAC4808ABF9A /* GhostFontResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB26526298A85454330591 /* GhostFontResolver.swift */; }; + 109F9BD7E1AE3E7FB0C712AA /* GhostTextLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EB202C5AD6079174A14330C /* GhostTextLayoutTests.swift */; }; 1104ABD01283774EDF249EF0 /* LLMIOFileHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2F75C66BECA61A722D3FB /* LLMIOFileHandler.swift */; }; 111905666B5F1D0B65B87D44 /* CapturedInputEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 353191D1D8A1C655E1B5F562 /* CapturedInputEventTests.swift */; }; + 113D4A18C8782C578463A6C4 /* GhostTextLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5C0792BF971B702DE0A095E /* GhostTextLayout.swift */; }; 11C548C2B50D9B27BD1B7293 /* SymSpell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2E9AF341F8F7043D5344B20 /* SymSpell.swift */; }; 11D3A5C8DA17BCDB79969FC4 /* FileLogHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C90C9EBCB70327D215EAE07 /* FileLogHandler.swift */; }; 12995E5DDB11E3395E6AF82F /* ShortcutsPaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB630F9814388203DD1CA2EC /* ShortcutsPaneView.swift */; }; + 1380A12C2A092F3B2334B5F3 /* InkCaretAnalyzer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC19809AAB1314BD6CC61B20 /* InkCaretAnalyzer.swift */; }; 1415DED5362F6F343AF63123 /* ControlTokenMarkers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33D4F378E5C70CAF7827A90A /* ControlTokenMarkers.swift */; }; 1417914B85A8B6BBD91AD68D /* SpeculativeAcceptanceContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEF8670AD74BAD1D5EFFD2D0 /* SpeculativeAcceptanceContext.swift */; }; 1419979A0649B00DACF75C61 /* OCRTextHygieneTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B0584FA16465453F04CA8C /* OCRTextHygieneTests.swift */; }; @@ -92,6 +103,7 @@ 1741F4AFFEA90CD6F81959F3 /* ArithmeticEvaluatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D381F1752705DC80E491B025 /* ArithmeticEvaluatorTests.swift */; }; 1816D3D625F561209590D45D /* TagChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = A04DFC5BD7821D73DA21D7DB /* TagChip.swift */; }; 181DB7463BA225142F02DEFD /* InsertionStrategySelectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12BB15F7F2E595129B190E89 /* InsertionStrategySelectorTests.swift */; }; + 186879EFBD85CB07E40C4B3F /* MailHeaderFieldDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F1A6BCCA20EBE7314B79D3 /* MailHeaderFieldDetector.swift */; }; 188DE63AA5C77C740781A1AF /* RuntimeBootstrapModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF8AA184C5F9CFFDF97A070 /* RuntimeBootstrapModel.swift */; }; 18954C4C3266A068BA339C48 /* SettingsQuickLinkCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1E69CFA319C5F9E4C30F699 /* SettingsQuickLinkCard.swift */; }; 1899BC5A35DC96B4D04B18A5 /* es.txt in Resources */ = {isa = PBXBuildFile; fileRef = 0B6816DF5D33863F966240B4 /* es.txt */; }; @@ -104,14 +116,16 @@ 19EB4D1C2D18C0ADC70BAD29 /* EngineAndModelPaneView+OpenSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B2C3A2CEC6E0A13E2102D15 /* EngineAndModelPaneView+OpenSource.swift */; }; 1A0EEE893EA683AE79EA76C3 /* WordPrefixIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6428AF104998876A60AC602C /* WordPrefixIndex.swift */; }; 1A4A2FCA8F08258A8A70A057 /* LGPL-3.0.txt in Resources */ = {isa = PBXBuildFile; fileRef = 7513810E78F3C94FE972EB07 /* LGPL-3.0.txt */; }; + 1A9FB5C832377E951C572606 /* HostBaselineCalibratorTypefaceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 147D8EAD035A66FCA1D07E37 /* HostBaselineCalibratorTypefaceTests.swift */; }; 1AAC3A35721942F618E5C37F /* ApplicationBundleMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1E28CF46BF59ABDC3056BF /* ApplicationBundleMetadata.swift */; }; 1AB037C6DC95769F4765B5CA /* SettingsAttentionEvaluator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79AAAB1C46A0E5A9C6FC7D3D /* SettingsAttentionEvaluator.swift */; }; - 1AF051175E382193908AE73E /* GhostFontMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C68A9C7C77AF6B111782FD /* GhostFontMetrics.swift */; }; 1B1DDCEEA435F7DAB6C68B92 /* LlamaSuggestionEngineCancellationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC812721A3E5327154D6CD4E /* LlamaSuggestionEngineCancellationTests.swift */; }; 1B66CA97E53D1ADB9F4E9A4A /* InputMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB36177FC6C26F5E4CD1AD6E /* InputMonitorTests.swift */; }; 1B80604D77F1AF1DF96DDD45 /* MarkerSelectionSynthesizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F7E66CFA5AB733CE1F793A8 /* MarkerSelectionSynthesizerTests.swift */; }; 1C30344A181A9F6EBC4A9C8F /* DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6586D6D735F287F43D42520A /* DeviceInfo.swift */; }; + 1C40E805671D6EFC890113AA /* HostTextMetricsCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8B99181BF74C1F1DC5F6A7 /* HostTextMetricsCache.swift */; }; 1C776D5BC2DECFAE133B9A57 /* AppSurfaceClassifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21047E3C6ACA1035669A7E06 /* AppSurfaceClassifierTests.swift */; }; + 1C853F8F3BE4DFE89F90BF35 /* CaretAdvanceSampler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A879A0AD69BCA2F60D2735 /* CaretAdvanceSampler.swift */; }; 1CCDD5419039F1151AF7A7B9 /* SuggestionOverlayViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9720913FECB22A9A28C96333 /* SuggestionOverlayViews.swift */; }; 1D5389E9562AF6315BFCDCE1 /* NOTICE.md in Resources */ = {isa = PBXBuildFile; fileRef = 66CF2A70D4699421AC9BD849 /* NOTICE.md */; }; 1D54C941ED086C6427CFD773 /* MacroEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 974A8708D2006767BD76862A /* MacroEngine.swift */; }; @@ -120,10 +134,12 @@ 1EB5B904DB17460D839AD57F /* SpellingLanguageResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61CCBF33813CE5CD3B9EA37B /* SpellingLanguageResolver.swift */; }; 1EB90D3D8A5BA028B86E4D9F /* SettingsContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0CE9AB1286367BA2E82392 /* SettingsContainerView.swift */; }; 1ED8785EB680045FF13270B4 /* MacroTriggerStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CDB11D96F343194B390321 /* MacroTriggerStateMachine.swift */; }; + 1F1131A36C6808948C6341AA /* SystemUIFocusShadowPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EC384A90F3D8B71584B3449 /* SystemUIFocusShadowPolicy.swift */; }; 1F3171D691605978D51E1368 /* Aria2LocatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 924714B1688E884D9AB2A79D /* Aria2LocatorTests.swift */; }; 1F39EE1D5FA0F5D32AFFB028 /* CurrencyEvaluator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67332497BB5448AEDA700BA7 /* CurrencyEvaluator.swift */; }; 1F728AD7AED5908FAA087EA9 /* EngineAndModelPaneView+AppleIntelligence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B891BA80475B61900A1D3C /* EngineAndModelPaneView+AppleIntelligence.swift */; }; 1F8CC88AFFE67C08944CF506 /* WindowScreenshotService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77B0121E7BB173F8A2B0B108 /* WindowScreenshotService.swift */; }; + 1FCDFBFCA35BF3108E476042 /* HostBundledFontRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 734608EBE2319F927C4EDDD4 /* HostBundledFontRegistry.swift */; }; 1FEB7ADDE569DE8C6E13920B /* GPL-3.0.txt in Resources */ = {isa = PBXBuildFile; fileRef = BEF60972B2D88E4EC4841AB0 /* GPL-3.0.txt */; }; 2089CC6E33B24D5CD3DD1E57 /* DownloadOutcomeClassifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F324C645A4E53FB6E90061A /* DownloadOutcomeClassifier.swift */; }; 2089E4C64C1BFDCDA9F2F721 /* ContextPaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89497C35D1825BAE9625EE06 /* ContextPaneView.swift */; }; @@ -143,8 +159,10 @@ 24D5BE34BE7B7B72A93C6A9F /* BundledRuntimeLocator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1AB5EBB44EE64EB3EB1426F /* BundledRuntimeLocator.swift */; }; 24E0F4F0EC9148FB409FD112 /* InlinePreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76BC705A82669AEFBC704F59 /* InlinePreviewView.swift */; }; 257302D78C5AE9951C63FCEE /* SuggestionAnchorCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE7DB9EE77511823EDA7B52E /* SuggestionAnchorCacheTests.swift */; }; + 25F7E6EC713F8F71DEEEAAA3 /* SystemUIFocusShadowPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EC384A90F3D8B71584B3449 /* SystemUIFocusShadowPolicy.swift */; }; 26067524E60D738791E983CD /* SOURCES.md in Resources */ = {isa = PBXBuildFile; fileRef = 054987E76CA9D1FA4F81EA8F /* SOURCES.md */; }; 26EA96EB13B94A68276FA15E /* MenuBarRecoveryPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E267F3BB7FC8DEAEB5E841B /* MenuBarRecoveryPolicy.swift */; }; + 2740742B866BC12043B81268 /* TypefaceEvidence.swift in Sources */ = {isa = PBXBuildFile; fileRef = B616CB46A8624BC4E4FBB01A /* TypefaceEvidence.swift */; }; 27A09D81E47FA601F279EF11 /* FocusCapabilityResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B8D2232F271197468CBC11 /* FocusCapabilityResolver.swift */; }; 27A7976690FC7FDA7EE364D8 /* SuggestionTextColorCodec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11CF768650A90705FC0D2730 /* SuggestionTextColorCodec.swift */; }; 27B1A99D61398752863DB251 /* RequestIDTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E80071E4A5D844CEF5B7C487 /* RequestIDTests.swift */; }; @@ -167,7 +185,6 @@ 2CF9087AE6FDB2A182BA9C85 /* MirrorOverlayLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3732CD6886AEAF3C01F543E /* MirrorOverlayLayout.swift */; }; 2D33BFF41BDE6CE7464885B8 /* BrowserAppDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 266C8BF167118855C56FC7F4 /* BrowserAppDetector.swift */; }; 2D83CECB0FF076323226449D /* SuggestionSettingsModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D253D06D941F60EDAF112625 /* SuggestionSettingsModelTests.swift */; }; - 2D845761E78C30D8BE11E2B7 /* GhostFontMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C68A9C7C77AF6B111782FD /* GhostFontMetrics.swift */; }; 2DD6D14058C7FD05DBE1AD60 /* UnitConversionEvaluator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6B0048AAD1B2A4EA09DCEE0 /* UnitConversionEvaluator.swift */; }; 2DF5A3826AAB99C279EBB8DE /* InputMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B81DD30EB657368AACE9625A /* InputMonitor.swift */; }; 2E0AEA2199864F46F86FC0C0 /* TextDirectionDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD33A18BECBD7292ACD9F8B8 /* TextDirectionDetector.swift */; }; @@ -177,6 +194,7 @@ 2F7C69992620D669D794A6F0 /* CaretWordContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D274F82A32C7FB404964428 /* CaretWordContextTests.swift */; }; 2FC40D4BFDD05C2401D7A5E9 /* SuggestionInserter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3D1125B962CBE0269EEDDB /* SuggestionInserter.swift */; }; 306836875282844C05C35B2C /* ClipboardContentDistillerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3680E1B8FA712A888F509640 /* ClipboardContentDistillerTests.swift */; }; + 306DC10E9B442E40C814FDEA /* HostMarkedTextPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9CFD041A13DACE5612DC991 /* HostMarkedTextPolicy.swift */; }; 308CFECF065626DD62EB64BC /* FocusedInputPollingSignature.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1891267512941C84BEA4172 /* FocusedInputPollingSignature.swift */; }; 310C533E572BC2C5FF12CF69 /* EmojiTriggerStateMachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0D3B0DE3EF3F31AE1E01828 /* EmojiTriggerStateMachineTests.swift */; }; 311A4FDEFFEB67B337FEE47A /* EmojiCatalogMatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E8BE4574F1770808AA848FE /* EmojiCatalogMatcherTests.swift */; }; @@ -189,9 +207,12 @@ 332F2AE6277FB6E871589E4D /* MacroControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C64BFD7273D1B637AD880AA /* MacroControllerTests.swift */; }; 3368DFBAA36527B7BB09EA67 /* ApplicationBundleMetadataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C151BF4D39485E5CFACFECB /* ApplicationBundleMetadataTests.swift */; }; 33EC776945F8CE612A8E85A3 /* CustomRulesEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7D1184F0BC707F0A9148DD /* CustomRulesEditor.swift */; }; + 35173B2E2394D942043FB086 /* CompletionContentPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 384D85534F3C2F40E2FD8781 /* CompletionContentPolicy.swift */; }; 35F1681148A324CBEC797BAF /* MenuBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE038A6DF4A16B3EB5CEBD3 /* MenuBarView.swift */; }; + 3615284684EBC0F0E8843BE6 /* MailHeaderFieldDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F1A6BCCA20EBE7314B79D3 /* MailHeaderFieldDetector.swift */; }; 36DBF7CA5EC371E4AA860843 /* SpellingDictionaryPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72B8156EEF23664F775190AB /* SpellingDictionaryPicker.swift */; }; 37029DDB055255BA5419C4FA /* WelcomePermissionStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F952E54538EF82D1B1E672D8 /* WelcomePermissionStepView.swift */; }; + 3744E92A7A36B0958F9DB488 /* CaretAdvanceSampleStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D86D5157D7E3F4447D2DE7F7 /* CaretAdvanceSampleStore.swift */; }; 37625DC44E2228CC897222B7 /* ru-100k.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6CF1FBAABEF545B620AF8D78 /* ru-100k.txt */; }; 37644CB48FCDF44AFA5EBA6E /* EmojiCatalog.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACD93D7DE258C8E5367A433D /* EmojiCatalog.swift */; }; 3767E8C175376A784D5B3E80 /* TokenCountEstimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A502BFB13373D1931B082CD /* TokenCountEstimator.swift */; }; @@ -210,14 +231,15 @@ 39B6842EF482AFB6CBEE6816 /* GhostTextColorPreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74AB26FDFBBAC0C6375C4192 /* GhostTextColorPreset.swift */; }; 39E68CFF981F61E262AE4D5F /* LlamaRuntimeCoreIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB9F15B92FB496D2F8D5845 /* LlamaRuntimeCoreIntegrationTests.swift */; }; 39ED9975387552307851A625 /* THIRD_PARTY_LICENSES.md in Resources */ = {isa = PBXBuildFile; fileRef = 5FF4FF5C43E650FF46F4BE76 /* THIRD_PARTY_LICENSES.md */; }; - 39F954287497C8B1253119C7 /* GhostSuggestionLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA6E6BC47B781B63640E25DA /* GhostSuggestionLayoutTests.swift */; }; 3A2A9FE7D529DDA2D471C8F5 /* Aria2StagingDirectoryResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAA0ED5F9A319942CA024F93 /* Aria2StagingDirectoryResolver.swift */; }; 3A3B49975BCF1F648F8C1782 /* SuggestionEngineModelsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B494C76056D9B01D6A3ECABD /* SuggestionEngineModelsTests.swift */; }; 3A5BA316620E502DAED81EE1 /* LlamaSuggestionEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = B19F5B58A9F2F20E7FA94AF8 /* LlamaSuggestionEngine.swift */; }; + 3A68F284E2C24093B115EC77 /* InkBaselineAnalyzer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 665A55CA2E7975CBFC63B4E2 /* InkBaselineAnalyzer.swift */; }; 3AB45217DFC86AFC98C374D6 /* WelcomeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21CB3008986BE7FD2A4D9132 /* WelcomeCoordinator.swift */; }; 3AC2F7F221F7C59242B06DFC /* InputSuppressionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D1F9CEBAB0F330F8E7B61D8 /* InputSuppressionController.swift */; }; 3AD39D992243A7AA541A43C8 /* SecureFieldDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EE99C84483D3A58D561C61D /* SecureFieldDetector.swift */; }; 3AFDA4A5BE9486A1B367815B /* SettingsNavigationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1AC5EC06664F49A6AE2B17 /* SettingsNavigationModel.swift */; }; + 3B23D06D21B57E0A8F04645D /* PixelCaretLocator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A65BCABF4B23690AE3DBE93 /* PixelCaretLocator.swift */; }; 3B3E08D1204E85F3776D8853 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = BAADA69C6172DD7F4A642E93 /* Sparkle */; }; 3BB065B62ABD544C30EF7E89 /* SuggestionCoordinatorTypingPredictionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B6924817244D5A909B9624 /* SuggestionCoordinatorTypingPredictionTests.swift */; }; 3BB16F9F9F84C7A8D87DF044 /* es-100l.txt in Resources */ = {isa = PBXBuildFile; fileRef = 620D393D3B7E687A08FA9446 /* es-100l.txt */; }; @@ -249,11 +271,10 @@ 41ACF0EDFE4E69328E4EA292 /* Aria2Locator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A29EA4138BBF767BA8D27223 /* Aria2Locator.swift */; }; 41F36D158005A69CE87921FA /* PermissionOverlayTrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD003D4EBE530DC0E2B87C24 /* PermissionOverlayTrackerTests.swift */; }; 430EB30535A955C3904C2AD9 /* PerDomainDisableSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F49C55FFF4352A6763BAE1A /* PerDomainDisableSettings.swift */; }; - 43E923211DAFB9728B56A6D8 /* GhostHostScaleTrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A4D17B2105C51112B19B2C2 /* GhostHostScaleTrackerTests.swift */; }; 44265797C6EC7798A5E61EF3 /* DateMacroEvaluator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94A26D86030FC5FA67371C52 /* DateMacroEvaluator.swift */; }; + 442999B123DFBAD112231987 /* SuggestionLengthPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA2EA95C425C3EAF246C66D /* SuggestionLengthPolicy.swift */; }; 44B7F10676606D11D0B1D5D0 /* SuggestionCoordinator+Acceptance.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA85693671D2A705F544B566 /* SuggestionCoordinator+Acceptance.swift */; }; 4503F64E33DBB1FF92E1799A /* SuggestionConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8F55E55BBA67E4BF500832D /* SuggestionConfigurationTests.swift */; }; - 45172DA4CBEC1BD40126D190 /* GhostFontSizeStabilizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F2C4ED070BBE86378229BB5 /* GhostFontSizeStabilizer.swift */; }; 4564DFEF1670EA473D9AE335 /* LlamaSuggestionEngineStreamingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1FC547BE2E96D2ABB55DF4 /* LlamaSuggestionEngineStreamingTests.swift */; }; 45E72FDF94C0510AFCAA6C3E /* LlamaRuntimeCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019029E24E67812E3FF63286 /* LlamaRuntimeCore.swift */; }; 4666D3B3A81CCB4CC68FC395 /* FocusModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B778713FE1C9481CE0DF065 /* FocusModels.swift */; }; @@ -272,7 +293,9 @@ 4A460D26B773DC213B0E6471 /* DeviceInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14D5E77EB08030489DAFA4DC /* DeviceInfoTests.swift */; }; 4A69EB9EEA4620D2EC35B9B2 /* ClipboardContextProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B70455CA75A08FAD0F7409 /* ClipboardContextProvider.swift */; }; 4AC255BE2D0CCC67B8882C7A /* WelcomeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21CB3008986BE7FD2A4D9132 /* WelcomeCoordinator.swift */; }; + 4B0153F0BBF4A4343FF0C778 /* HostZoomLadderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52675961C22C88675CC4B107 /* HostZoomLadderTests.swift */; }; 4B1FD27F44D60A3B7CEF03D1 /* DebouncePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B630D1F95FD08BC30E624672 /* DebouncePolicy.swift */; }; + 4BDE48F1CCFAA097E95B53F6 /* GhostFontResolverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FA6A8D05AEEBE3501B478F /* GhostFontResolverTests.swift */; }; 4C117D5B93C36D6C502AAC05 /* SuggestionAvailabilityEvaluator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12082948AEBC0DFE5ADC6961 /* SuggestionAvailabilityEvaluator.swift */; }; 4D2C1A0B4FBEF95CBFB9F481 /* ModelFileValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0259095D0A3E9B8DC26CB4B0 /* ModelFileValidatorTests.swift */; }; 4D32F003F78EAAA984E77D83 /* LLMIOFileHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2F75C66BECA61A722D3FB /* LLMIOFileHandler.swift */; }; @@ -284,6 +307,7 @@ 4F3CE71B97663B8025779F9C /* fr.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5DCA3E46621815A19300365F /* fr.txt */; }; 507E7BCCD189A64C3F8ECB79 /* HomePaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1123AB515110BD0CBA39490 /* HomePaneView.swift */; }; 50CDF4E6CD754A74DDCF67FC /* MacroTriggerStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CDB11D96F343194B390321 /* MacroTriggerStateMachine.swift */; }; + 50D12741A639F8A367239FBE /* TypefaceEvidenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94B45E958CF9BE63A728E273 /* TypefaceEvidenceTests.swift */; }; 50D33B1B862171CB00CB7526 /* PhrasePredictionEvalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74369DE9087CB47CC566657A /* PhrasePredictionEvalTests.swift */; }; 50E1247BE6A952AB1B12C444 /* PowerSourceMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB235F0DEA53295DAF8B4FA0 /* PowerSourceMonitor.swift */; }; 513066CA00F0E9A5E7358A4E /* PermissionReminderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 168A0DBF62BEF674B96CEBD2 /* PermissionReminderView.swift */; }; @@ -296,6 +320,7 @@ 526097DCFB81E01FD066915E /* Aria2Provisioner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D2F4ED8C7493EF4B967BFF9 /* Aria2Provisioner.swift */; }; 52DA855997BCA533B0643E31 /* SpellingDictionaryCatalogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEC5EC41F1677027D46E2B02 /* SpellingDictionaryCatalogTests.swift */; }; 52E6B0B61DA8AC33EF60BA04 /* FoundationModelSuggestionEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17794AE5A5D5A93A7919E6BA /* FoundationModelSuggestionEngine.swift */; }; + 5313CEB1330FED9569FF55EF /* GhostBaselinePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08EC70547439E840417F5A5 /* GhostBaselinePolicy.swift */; }; 5392CB32A4E8BD0A4EC0B7AB /* SuggestionPauseModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0ACD725A16F26E9F29124E4 /* SuggestionPauseModels.swift */; }; 53BA549ABCA25E9F44DF6341 /* DisplayCoordinateConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D881FED12A85FFC20F2C9D7 /* DisplayCoordinateConverterTests.swift */; }; 53F946FF09038C1C751C6E53 /* FoundationModelPromptRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A051152D3C503A2339BF0FA /* FoundationModelPromptRenderer.swift */; }; @@ -311,9 +336,13 @@ 556197D64680237CBCCCDD9F /* OCRTextHygiene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7736E6B390C822F91CC1DA99 /* OCRTextHygiene.swift */; }; 55DAFC9D06629C23A6868565 /* WelcomeTemplateStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53ED6F2F3354FB3E6065804 /* WelcomeTemplateStepView.swift */; }; 56471188E5F78EE0785D7BF7 /* CotabbyInference-MIT.txt in Resources */ = {isa = PBXBuildFile; fileRef = 2472C76E96009300EBF038D4 /* CotabbyInference-MIT.txt */; }; + 564BA8F230F45E378D26B36C /* GhostSpaceBoundary.swift in Sources */ = {isa = PBXBuildFile; fileRef = D99C3973D57C7F53416CFBF6 /* GhostSpaceBoundary.swift */; }; 56D2FDE72FE21C35E8BDD06C /* ActivationIndicatorController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E379AF08CDFF65D6EFC565E /* ActivationIndicatorController.swift */; }; 56D7E792922B83393A578E5A /* CompletionRenderModePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705E9119B1661BF464496D39 /* CompletionRenderModePolicy.swift */; }; + 5732858F8FF26E50AE4BFF8C /* DebugForcedSuggestionEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8D50115291212D03414CBE /* DebugForcedSuggestionEngine.swift */; }; + 57857C599039465ADB0EC3AD /* GhostCaretRefinement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A39920BD5E23E4D2E9AF037 /* GhostCaretRefinement.swift */; }; 57BCDFE786675C9793F3E08E /* ControlTokenMarkersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D6234872B84ECB56A707945 /* ControlTokenMarkersTests.swift */; }; + 583FB683A2CA8516F96BE682 /* HostBundledFontRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 734608EBE2319F927C4EDDD4 /* HostBundledFontRegistry.swift */; }; 590122DC2702DFEBEC1DB3A4 /* Gemoji-MIT.txt in Resources */ = {isa = PBXBuildFile; fileRef = CF63186E4DB1E071F6A3C58B /* Gemoji-MIT.txt */; }; 597743F49ECF16C765AE9162 /* SuggestionContinuationPlan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8FE1B9BEA0B35C6910E534 /* SuggestionContinuationPlan.swift */; }; 599A5F23125EE32592AA180E /* PerAppShortcutOverrideTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86E1C98557E3E592AF8B6735 /* PerAppShortcutOverrideTests.swift */; }; @@ -340,10 +369,12 @@ 5E506F0CD6FA82B323264C88 /* RandomMacroEvaluatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0DF8F53EDD401B6A9EE06E /* RandomMacroEvaluatorTests.swift */; }; 5EA37C462E8CF319B8748A40 /* EmojiCatalog.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACD93D7DE258C8E5367A433D /* EmojiCatalog.swift */; }; 5ED4272F22F0FB0DF2AE0AE1 /* MPL-2.0.txt in Resources */ = {isa = PBXBuildFile; fileRef = 4E283DF8948B10268B46811F /* MPL-2.0.txt */; }; + 5F1F159BF6E776F3A85F1442 /* CaretLagPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4963D7DB8B810CD0C4E8CF2 /* CaretLagPolicyTests.swift */; }; 5F2BFD0D7718CC774F03FBE0 /* FoundationModelAvailabilityServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FACB1195A78B524AB65D2EF4 /* FoundationModelAvailabilityServiceTests.swift */; }; 5F2F0475865445A777B5E3F0 /* CompletionSeamGuardTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC0C7FE7FB134C225EA7B71 /* CompletionSeamGuardTests.swift */; }; 5F3E299F27F63F8F9D9FBABF /* SpellingLanguageResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61CCBF33813CE5CD3B9EA37B /* SpellingLanguageResolver.swift */; }; 5FA377F90142B8FC53FD9F61 /* OllamaPreloadWorkControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 338EE4D92F8BE01CF43FE876 /* OllamaPreloadWorkControllerTests.swift */; }; + 5FACCF66D0D46ABCE3668EE2 /* InkCaretAnalyzerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3153FB925190B70E0502C43D /* InkCaretAnalyzerTests.swift */; }; 6004FCACD5F5F27FA3319B76 /* KeyCodeLabelsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97F14FC0E8E0A5D23FA2F54C /* KeyCodeLabelsTests.swift */; }; 604D3D3F1D8966C6A976746C /* EngineAndModelPaneView+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DFA08E857DB36A7876FFEA /* EngineAndModelPaneView+Actions.swift */; }; 60636D92D12FED132250D8D2 /* PerformancePaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEBD6113A3C1038BECC99245 /* PerformancePaneView.swift */; }; @@ -359,6 +390,7 @@ 644EEF959D07D54CC779BBF6 /* SettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3350EDE01ED5125520C79D53 /* SettingsCoordinator.swift */; }; 6471AEB3C75714538D78DBA3 /* SuggestionSettingsModelDisabledAppsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC07B7CE715C69A63E6123DD /* SuggestionSettingsModelDisabledAppsTests.swift */; }; 647F30C40CE57EDFDA95958F /* MacroController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A38CFCDC00DC4DB86D91BB2 /* MacroController.swift */; }; + 6481A54A8B9C1285125225E1 /* HostFaceMemory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7103021AEC287186CE25415D /* HostFaceMemory.swift */; }; 64A3D79B01983CAD4EFDD1BC /* SuggestionTextNormalizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55C8ED5A6E0648BAE6C4DC55 /* SuggestionTextNormalizerTests.swift */; }; 64F9AE9BBCA9D09623246A14 /* SuggestionCoordinator+Prediction.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED7B674D586540F35682C573 /* SuggestionCoordinator+Prediction.swift */; }; 65CD25FF9051B75DAF0464F6 /* StreamedGhostTextPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC3DF6826CD5E4EF695EF163 /* StreamedGhostTextPolicy.swift */; }; @@ -372,6 +404,7 @@ 66F8D175E7F36D269C6E0659 /* SuggestionInsertionChunkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E437EAF9A2296BCE358EF257 /* SuggestionInsertionChunkTests.swift */; }; 67545937A4CAA6B1A1B4B86A /* AXTextGeometryResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98423150126CC5969C2886BA /* AXTextGeometryResolver.swift */; }; 676533C6555BAC0C4A263576 /* HuggingFaceSearchServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9DE35F3B6F0DFEB9538692 /* HuggingFaceSearchServiceTests.swift */; }; + 67A5529348C03A5B97BEFFC5 /* BlockBreakAlignmentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D681DDF8E81F868C1BC92CB4 /* BlockBreakAlignmentTests.swift */; }; 67C36958132793C76843F875 /* LlamaTypingSessionEvalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A936CA937FA629F828938516 /* LlamaTypingSessionEvalTests.swift */; }; 68DA5F93B7185B4F5E6DB4C3 /* it.txt in Resources */ = {isa = PBXBuildFile; fileRef = 0397F1DACB094A0F6A66BC0E /* it.txt */; }; 6952E5B030740443C684F263 /* SettingsSearchRanker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57B29DAC600626783649475A /* SettingsSearchRanker.swift */; }; @@ -396,11 +429,11 @@ 6DB11ABBCB996178084FF5A2 /* WelcomeKeybindStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2576B762374F4D3CD8347AD4 /* WelcomeKeybindStepView.swift */; }; 6DFF14EEC5838BC309D03B09 /* CompletionRenderMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F93E4EC64A8773A1A7F3E24 /* CompletionRenderMode.swift */; }; 6E666BEE80ECE65CF71C7F56 /* EmojiPickerPanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 872C8DDC2E86A1C4C4BBD99F /* EmojiPickerPanelController.swift */; }; + 6EC1C0311B57074487F9658F /* GhostFontSizeLimits.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC61CA8FEFB45FB1CCA453A /* GhostFontSizeLimits.swift */; }; 6F2FE689BCA50BEAE80AC6F4 /* ShortcutsPaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB630F9814388203DD1CA2EC /* ShortcutsPaneView.swift */; }; 6F3B43828817C2C303E8D16E /* InlineCommandCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65AF0FF1CE97A0761ADAFEB8 /* InlineCommandCoordinatorTests.swift */; }; 6FA8CBA189210C9821B03656 /* TokenHealingBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50AECDF57800BBB6617620D5 /* TokenHealingBuffer.swift */; }; 7008657841ECD4F06A46A9DE /* PromptPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC4D7129517D678D95D0BC2 /* PromptPolicyTests.swift */; }; - 70825EA97C7AD576B3ED899E /* GhostHostScaleTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D40D1FC651686EF76ECADE3 /* GhostHostScaleTracker.swift */; }; 7097797640547007DF552F4E /* SuggestionOverlayStabilityGate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DB247AACE1B28D45DB52B0 /* SuggestionOverlayStabilityGate.swift */; }; 70A40CE02EF0AF361263BE7E /* EmojiPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 871C7A27BAB5A510273D0A19 /* EmojiPickerView.swift */; }; 70E282036479782EE76A59C4 /* PermissionOverlayWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4213A267AE55FE56A071646F /* PermissionOverlayWindowController.swift */; }; @@ -408,6 +441,7 @@ 715BD5081873EF5FF070D3B0 /* CalendarAccessibilityCapturePolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 863B5A1DC3C4B9668F620245 /* CalendarAccessibilityCapturePolicyTests.swift */; }; 71676E29789ACF4FE7925220 /* TypingPredictionCandidateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81C4E4D11C9BE3B53F79FD36 /* TypingPredictionCandidateTests.swift */; }; 71DA26FE8F6758CAFE6F7042 /* EmojiPickerModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8736D4321E8BF847D1F7338E /* EmojiPickerModels.swift */; }; + 71E039A6A5EF33DE3EB40328 /* HostTextMetricsProbe.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE4180FC6B2E085CEE93F23 /* HostTextMetricsProbe.swift */; }; 71E95FF86D5B3399FBD618A9 /* CompletionRenderModePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705E9119B1661BF464496D39 /* CompletionRenderModePolicy.swift */; }; 7207434A045B4B372981BE2C /* CaretRunPlacementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A8962FE08E1C76F0EEF423 /* CaretRunPlacementTests.swift */; }; 72672FBC57236EF484340659 /* ActivationIndicatorController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E379AF08CDFF65D6EFC565E /* ActivationIndicatorController.swift */; }; @@ -416,7 +450,7 @@ 7324AD21F85FF6DDDD27EC84 /* SuggestionOverlayStabilityGate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DB247AACE1B28D45DB52B0 /* SuggestionOverlayStabilityGate.swift */; }; 733A13BF72DCBBDC37952DD9 /* TypingPredictionCandidate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C59EC159778E4802683A190E /* TypingPredictionCandidate.swift */; }; 735C2E64CA51F58098B30A0D /* it.txt in Resources */ = {isa = PBXBuildFile; fileRef = 0397F1DACB094A0F6A66BC0E /* it.txt */; }; - 73B39F0BC9FFA95059AA7C7F /* GhostFontSizeStabilizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F2C4ED070BBE86378229BB5 /* GhostFontSizeStabilizer.swift */; }; + 73A1C073056E07657992AE5A /* CaretTokenPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBE00782EE6813AF3224CD5 /* CaretTokenPosition.swift */; }; 73B7C4D5143F7AEC321BC873 /* PermissionAndContextModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEBBBAB9588AA8EDBA41520 /* PermissionAndContextModelTests.swift */; }; 744B06C2488156B178675615 /* PermissionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BF316556FDA64CB8AD07B6 /* PermissionManager.swift */; }; 749C4953D3AFE86AFABA968C /* OnboardingTemplateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4C5E3A714F5A7818337101C /* OnboardingTemplateTests.swift */; }; @@ -425,6 +459,7 @@ 7527A1523BFCCC1D418B44FD /* MenuBarPopoverDismisser.swift in Sources */ = {isa = PBXBuildFile; fileRef = DADE6EEA8248DA6700E11844 /* MenuBarPopoverDismisser.swift */; }; 753DC144B9394A35A3F395DA /* SettingsSidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BADB38D0160B47637572FC5E /* SettingsSidebarView.swift */; }; 75432BB598E96902A799AA10 /* OpenAICompatibleCredentialStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B23950F38B1B3C8A5CC5BE5 /* OpenAICompatibleCredentialStore.swift */; }; + 757696BA01AE112E3B1241EC /* CaretLagPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40016262B83C6C55B6593C7 /* CaretLagPolicy.swift */; }; 764F157C87F3155FBE698A3A /* SuggestionConversationIsolationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022734DBBC384802C7CDB97D /* SuggestionConversationIsolationTests.swift */; }; 765A4E8212962910AA3FBA87 /* InputSuppressionControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBD75CAF6CAC849D5ED9FA4 /* InputSuppressionControllerTests.swift */; }; 76933444A4EF1F1CE5039E9E /* ContextLivePreviewField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8368940C6C15CC3F932B8DFC /* ContextLivePreviewField.swift */; }; @@ -436,21 +471,24 @@ 77B57484667F71F7EFA380D1 /* fr-100k.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6DB982BF30B3601F57277776 /* fr-100k.txt */; }; 77C0F245013D52FF4F830575 /* ModelDownloadSessionDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8922EEFC979DE2C2E480FB0D /* ModelDownloadSessionDelegateTests.swift */; }; 77C40ACA872F20CFAB2BDD7F /* TagChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = A04DFC5BD7821D73DA21D7DB /* TagChip.swift */; }; - 77FF70B3EFA14CDCA4FAF5A3 /* GhostHostScaleTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D40D1FC651686EF76ECADE3 /* GhostHostScaleTracker.swift */; }; + 780565E1249321B688BF206C /* CaretLagPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40016262B83C6C55B6593C7 /* CaretLagPolicy.swift */; }; 7807D6217618D982CD301875 /* MenuBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE038A6DF4A16B3EB5CEBD3 /* MenuBarView.swift */; }; 78ADD24191489318BCC99372 /* SettingsRowLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44768629365FAD1A66DA3A51 /* SettingsRowLabel.swift */; }; 78C0604EAA5B6BB7C9CBFCF0 /* Gemoji-MIT.txt in Resources */ = {isa = PBXBuildFile; fileRef = CF63186E4DB1E071F6A3C58B /* Gemoji-MIT.txt */; }; 7955ED6ECA506D4EDA204BB1 /* AppUpdateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7A9C39A0713BD3B442C9AC /* AppUpdateManager.swift */; }; 7A31E6395C535FF017A1EFE1 /* CotabbyDebugOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F47CD9DF50E7192A70D1870E /* CotabbyDebugOptions.swift */; }; + 7AC032855FD65F67B93E172D /* CaretAdvanceSamplerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 627E02913F35A5802D48A207 /* CaretAdvanceSamplerTests.swift */; }; 7B6A63F5DCC2C163CDFD2A5C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BC4F887528AE74AC0DD30314 /* Assets.xcassets */; }; 7BE110312F7E8E845763D6A5 /* InsertionSafetyGateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EA130AF8073D90CA89AAC6 /* InsertionSafetyGateTests.swift */; }; 7BEA76E69707BC760B0D2394 /* LlamaRuntimeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45161160AC94229A630FC3A /* LlamaRuntimeManager.swift */; }; 7C03CF993AFF3B1BCD61F31C /* HostFontRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F328B43743D803A16045110 /* HostFontRegistry.swift */; }; + 7C56674EF8DA8869D5116EE6 /* GhostSpaceBoundaryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD9262617968D8D1EF1EA45 /* GhostSpaceBoundaryTests.swift */; }; 7C5BD8FDACC491EF62665FB2 /* TerminalAppDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6DC2CCFDB2BF1F1E1375620 /* TerminalAppDetector.swift */; }; 7CD9B73CE933F15B490B3605 /* SettingsSearchResultRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DB1F528070F8F9108720DB /* SettingsSearchResultRow.swift */; }; 7D87C0AEF85FA0653C9C6031 /* PerformanceMetricsStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA991392A72658B758551BBA /* PerformanceMetricsStoreTests.swift */; }; 7DE4F91CADF4E65BCCF4DB17 /* SettingsRowLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44768629365FAD1A66DA3A51 /* SettingsRowLabel.swift */; }; 7E11BEAD32E9FF170C92000A /* HuggingFaceModelBrowserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C696204AAEBA357129F77646 /* HuggingFaceModelBrowserView.swift */; }; + 7EA9216A909247A4F722C9C1 /* HostTextMetricsCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2BBA3187237680274BF80E /* HostTextMetricsCacheTests.swift */; }; 7F5DD6592FAA0BC2866634AB /* AboutPaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2559228BB102022DC48193D9 /* AboutPaneView.swift */; }; 7FC103944F4EF39DB965F469 /* InMemoryLogging in Frameworks */ = {isa = PBXBuildFile; productRef = 88921938DC814625ED57D605 /* InMemoryLogging */; }; 7FEF9048069CF8032FFC45F7 /* FocusedInputPollingSignatureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B2BA5E0AD5E3F1D89531B9 /* FocusedInputPollingSignatureTests.swift */; }; @@ -458,6 +496,7 @@ 806EDB078881E5DA147E8811 /* LaunchAtLogin-MIT.txt in Resources */ = {isa = PBXBuildFile; fileRef = 150AB28A463F8CDA01E0D5B7 /* LaunchAtLogin-MIT.txt */; }; 80D394FC224DE82F3A9057BC /* LanguageSupportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE29E54D78A9DD7CD87626DD /* LanguageSupportTests.swift */; }; 81073963BC57B5CA9151B0EC /* CustomRulesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ADC3A68EF5FD9A1E6B5D249 /* CustomRulesTests.swift */; }; + 8129ED85DFCF718BFF855D7C /* PixelCaretLocatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52263602D578987A7ADC776 /* PixelCaretLocatorTests.swift */; }; 81870F2D46C12CA1E6B19523 /* CurrentWordExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1AAC6367483952AB7D59A62 /* CurrentWordExtractor.swift */; }; 81FCE906A26ED284E8DD46D5 /* ModelDownloadSessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEFBEB715AF043D3315B6656 /* ModelDownloadSessionDelegate.swift */; }; 82330263D4919D89C293F737 /* DebouncePolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61134664FF616732705A89B /* DebouncePolicyTests.swift */; }; @@ -477,8 +516,10 @@ 85178E15A85EB772554220C6 /* WebContentFieldDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 178C5C54E1CC7E906DEA1D51 /* WebContentFieldDetector.swift */; }; 852D882AA3AF54EE73BA5306 /* SpellingDictionaryCatalog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25D41CC179EC04093971A0D0 /* SpellingDictionaryCatalog.swift */; }; 853C19A148DE6D5A59AA2C87 /* ClipboardRelevanceFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04364C3242A25358E7C85FD6 /* ClipboardRelevanceFilterTests.swift */; }; + 8553CEBF40318B0D6DD36B0C /* ChromiumInlineAutocompleteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A166301D0A276214F895A963 /* ChromiumInlineAutocompleteTests.swift */; }; 8586BBF24A7F21AF84F59D2C /* CompletionRenderMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F93E4EC64A8773A1A7F3E24 /* CompletionRenderMode.swift */; }; 85BB2FAD4BFE9077AEE2A262 /* MidWordContinuationPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30AB3A99BEB2EA978298FC6F /* MidWordContinuationPolicyTests.swift */; }; + 85DE2B1A62BA24DA615A628E /* HostBundledFontRegistryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA2CFC322EE56151646E745 /* HostBundledFontRegistryTests.swift */; }; 85EDFFAB94CB8577CDBAF49B /* SuggestionDismissalMemoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6809474A7C1B57F1D13AA6F /* SuggestionDismissalMemoryTests.swift */; }; 85FCDC35AC29F4A89E30F559 /* BundledRuntimeLocatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBD7B94FF42D8C538C328E80 /* BundledRuntimeLocatorTests.swift */; }; 8756E3050C45106D7AD188AA /* MacroTriggerStateMachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1C9DBC2A94B6E805EDAFEA5 /* MacroTriggerStateMachineTests.swift */; }; @@ -486,9 +527,12 @@ 87CE40ADBFCDBFD266BA9E3F /* InlinePreviewPanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 924CAA5E25C596A9FAB7602B /* InlinePreviewPanelController.swift */; }; 888FF8B27F8545F1EE9BBA93 /* OpenAICompatibleSuggestionEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = B241105A4F9C29DD8C8D4A91 /* OpenAICompatibleSuggestionEngine.swift */; }; 88AAFB21F06EE37A392081E1 /* SuggestionSessionReconciler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CADFBB03C10309B9048C8E9 /* SuggestionSessionReconciler.swift */; }; + 88B5413CB02F1C53E81DC683 /* HostFaceMemoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 623D8B3C927836D7C4002AB8 /* HostFaceMemoryTests.swift */; }; 89329024F050602EFBC7CC6B /* FocusTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9FDF029F7828CAF3FE8850 /* FocusTracker.swift */; }; 89B0199B226B7B6FCEBBBAC3 /* SuggestionCoordinatorRigTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2471F967A05EB4C8BF7CD242 /* SuggestionCoordinatorRigTestCase.swift */; }; + 8AE7B4DE4650D0DA8F6EFFE7 /* GhostTextLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5C0792BF971B702DE0A095E /* GhostTextLayout.swift */; }; 8AE7DC96D3DC615410A47B81 /* FoundationModelAvailabilityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA97ED2A10499771F9C32B64 /* FoundationModelAvailabilityService.swift */; }; + 8AFA546C09EE51078EFBEDAE /* GhostBaselinePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08EC70547439E840417F5A5 /* GhostBaselinePolicy.swift */; }; 8B36A3FDDBB803C72A1E521B /* PerDomainDisableSettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A306C7B2B0790A0B98D8F353 /* PerDomainDisableSettingsTests.swift */; }; 8B8EA667506EB67DBCA35D57 /* EngineAndModelPaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C35A0A68DA5D2B59460A67E /* EngineAndModelPaneView.swift */; }; 8C090BA472D90D9682F329BA /* KeyCodeLabels.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF4D6CEDF032A25EF72D7228 /* KeyCodeLabels.swift */; }; @@ -500,8 +544,8 @@ 8E209BC819EBBCA0E7F10775 /* FoundationModelAvailabilityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA97ED2A10499771F9C32B64 /* FoundationModelAvailabilityService.swift */; }; 8E460A307B1ED0DA783CB247 /* FocusPollBackoff.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD5C8648740E7050CCA657F /* FocusPollBackoff.swift */; }; 8E4DFB1338AE3BDA8FF16B98 /* SuggestionContinuationPlan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8FE1B9BEA0B35C6910E534 /* SuggestionContinuationPlan.swift */; }; + 8E5D6584BA4168C7E381EF91 /* SuggestionLengthPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA2EA95C425C3EAF246C66D /* SuggestionLengthPolicy.swift */; }; 8ED4542BF4F6BC1620D47A20 /* FocusDebugOverlayController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B4F6E70B8A242F7BDE5361A /* FocusDebugOverlayController.swift */; }; - 8EFD4154069432EB43955033 /* GhostFontSizeStabilizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3AC792296D57BE1AD0D308 /* GhostFontSizeStabilizerTests.swift */; }; 8F216FA9E4DD8C0317E28286 /* BundleVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A951C04DF783B1F1EFDD1E5 /* BundleVersion.swift */; }; 8F6B554D210B15AD99511D4D /* OnboardingTemplateRecommender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F86D2716CBE36970BB462E8 /* OnboardingTemplateRecommender.swift */; }; 902175A80CC3585095BE7D58 /* InlineCommandCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D68B5ABA427D3E87000E78 /* InlineCommandCoordinator.swift */; }; @@ -509,6 +553,7 @@ 907D4369413BBD3715DB3436 /* PermissionHostApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C32F6A7622BB9D537D157FA2 /* PermissionHostApp.swift */; }; 908D99B538976C07833157E8 /* OnboardingFeatureShowcase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 926B332E7B4CFEE42C4CAA75 /* OnboardingFeatureShowcase.swift */; }; 90D53306BE8369534BE8ED4F /* CaretWordContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 976E3D6790D2A3594BCEEA23 /* CaretWordContext.swift */; }; + 9116AADE58B526162FBF4676 /* DebugForcedSuggestionEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8D50115291212D03414CBE /* DebugForcedSuggestionEngine.swift */; }; 917E9217724C62C8C8E4E1C9 /* FocusCapabilityFlickerGate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79AA39C97E8A959E9A5B6F04 /* FocusCapabilityFlickerGate.swift */; }; 91979FC94BCE7A80F74EC47F /* Aria2StagingDirectoryResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAA0ED5F9A319942CA024F93 /* Aria2StagingDirectoryResolver.swift */; }; 920DE073BEE1DC78B73108D0 /* OnboardingFlowSteps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AEF94A48DF96D6BEE3048C /* OnboardingFlowSteps.swift */; }; @@ -519,6 +564,7 @@ 9301B309462E4CF7F554578F /* OnboardingStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ABC808DE01AF9AD04E38943 /* OnboardingStyle.swift */; }; 930BA578E742D96FD9D340ED /* ContextPaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89497C35D1825BAE9625EE06 /* ContextPaneView.swift */; }; 931D40674AF1CB11BE27327A /* OnboardingTemplateFeatureList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7174EF88BA41ACC0A74CA582 /* OnboardingTemplateFeatureList.swift */; }; + 93483A25EA4434A05C6CCB2C /* GhostTextPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29163A3D0BB0F11EEAC2985A /* GhostTextPanelView.swift */; }; 935C8D87322CE57F91B673B6 /* FocusSnapshotExternalApplicationIdentityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E97C4BFAF5EAF6792170AC /* FocusSnapshotExternalApplicationIdentityTests.swift */; }; 936F87925A22DAC58525B60F /* DownloadFileRescuer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC756DF8E0032B39099D2BF8 /* DownloadFileRescuer.swift */; }; 940E71A409B4FCDD63642471 /* EmojiVariantResolverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCD0F17E1E2BB8FE75A6E75E /* EmojiVariantResolverTests.swift */; }; @@ -528,12 +574,14 @@ 948AD3BD25C113539DF12FD7 /* VisualContextModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A51B1559F4CE6728D39C959 /* VisualContextModels.swift */; }; 949409B5B133BF4960691AE8 /* SuggestionRequestFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD834BCFF812935889E16D5A /* SuggestionRequestFactory.swift */; }; 94A93C9AECB84D56E3347601 /* FocusPollBackoff.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD5C8648740E7050CCA657F /* FocusPollBackoff.swift */; }; + 94FA4632A562E454472BE1C6 /* HostBaselineCalibrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC1ECBE9D1EDE855B57A913 /* HostBaselineCalibrator.swift */; }; 952729C6514FBC40D6E7D1D4 /* CotabbyInference in Frameworks */ = {isa = PBXBuildFile; productRef = D11AB0F962CCC5BD79B630CA /* CotabbyInference */; }; 95B1C40207F57D7E059B9965 /* de.txt in Resources */ = {isa = PBXBuildFile; fileRef = C648EBB10D7F8E0B904DEC91 /* de.txt */; }; 96117B3DC1292771EE6B8316 /* PerAppShortcutOverride.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A9F3C0F7B53B7F0D3BA82A /* PerAppShortcutOverride.swift */; }; 9698DED033367C1E6F48D43E /* ModelFileValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36652DB88C5948AA4A31524A /* ModelFileValidator.swift */; }; 96CC39E6D6DF3F27ABA8019B /* SuggestionSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB17696CE79982039061F29 /* SuggestionSettingsStore.swift */; }; 973171C8C049F8CC15F589F2 /* ShortcutConflictTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA02598F555696782E180DA5 /* ShortcutConflictTests.swift */; }; + 976835E71042F5755616F678 /* SuggestionCoordinator+HostMarkedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DAD5637347E83DDCF515568 /* SuggestionCoordinator+HostMarkedText.swift */; }; 97EF76E6B7A1AFB3FA4879D1 /* LGPL-3.0.txt in Resources */ = {isa = PBXBuildFile; fileRef = 7513810E78F3C94FE972EB07 /* LGPL-3.0.txt */; }; 980E02175E2D83D4B60FE519 /* SuggestionQualityMetricsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50119F67E7BB9FBC8EA94BF2 /* SuggestionQualityMetricsStore.swift */; }; 985C583A41DEEE040F48D661 /* FocusModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B778713FE1C9481CE0DF065 /* FocusModels.swift */; }; @@ -553,6 +601,7 @@ 9C11CED7C78707EE8C3E0E10 /* SettingsAttentionEvaluatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E53B16C1E0CE667E9666C71 /* SettingsAttentionEvaluatorTests.swift */; }; 9C31C64224E710040F23CEB2 /* LanguageTagsEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB53B865B0FA4378DEF2054 /* LanguageTagsEditor.swift */; }; 9C41C817DE1785040E3628CE /* LLMIOFileHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB500911B047A5363D51E71 /* LLMIOFileHandlerTests.swift */; }; + 9C8E4F3EC59DB628C99F500A /* CompletionContentPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 384D85534F3C2F40E2FD8781 /* CompletionContentPolicy.swift */; }; 9CD0DEFE83D73972985D2F1C /* UnitConversionEvaluatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45227E5F07E5BC72D2FC8254 /* UnitConversionEvaluatorTests.swift */; }; 9D12E6CDAA9CE87FC512D5CC /* CotabbyInference-MIT.txt in Resources */ = {isa = PBXBuildFile; fileRef = 2472C76E96009300EBF038D4 /* CotabbyInference-MIT.txt */; }; 9D31509FA34AE79C0EEAC95B /* ConfidenceSuppressionPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 122298AE151ECEEC175878BF /* ConfidenceSuppressionPolicy.swift */; }; @@ -561,14 +610,19 @@ 9EB8E3DC796A0C8BFDE8E683 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3E8E86A14090BC7BD13BA76 /* AppDelegate.swift */; }; 9F2F4AC4D8B575143D044C25 /* SuggestionPresentationTiming.swift in Sources */ = {isa = PBXBuildFile; fileRef = 210D477AA213B1EF8B3AC7F1 /* SuggestionPresentationTiming.swift */; }; 9F2FDCABCC941CBECAA3B4AB /* CotabbyInference in Frameworks */ = {isa = PBXBuildFile; productRef = 48A46AD6B613CF06072603E4 /* CotabbyInference */; }; + 9F8656C10B45DA6699A43787 /* TypefaceMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E5BA02F079AD5EB20CDB215 /* TypefaceMatcher.swift */; }; A061CB3EEB9ABEF16CC93422 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = AC03B99F8F0C435522D44238 /* LICENSE */; }; A06D86F7475984F7AD810EFC /* InputModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A29DF1C68154C21E6943D38E /* InputModels.swift */; }; A145F5E9E5BEC53EB46A596E /* PhrasePredictionScreenContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74D4296C2F9A14E96AD86D7F /* PhrasePredictionScreenContextTests.swift */; }; + A16DEE62FB45155A10E5DF74 /* SuggestionCoordinator+HostMarkedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DAD5637347E83DDCF515568 /* SuggestionCoordinator+HostMarkedText.swift */; }; A1712224D1E7273EDA047544 /* SuggestionCoordinatorWordCompletionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 867188F95EB41AB11AA8862B /* SuggestionCoordinatorWordCompletionTests.swift */; }; + A18051D870C0BEF082053D2C /* GhostFontResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB26526298A85454330591 /* GhostFontResolver.swift */; }; A1A612C90221E0FE1195754A /* SettingsCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D0AEFF86F8210CBE7CFCBAD /* SettingsCategory.swift */; }; + A1C4151AAE1CC55282C136BB /* GhostSpaceBoundary.swift in Sources */ = {isa = PBXBuildFile; fileRef = D99C3973D57C7F53416CFBF6 /* GhostSpaceBoundary.swift */; }; A1D47F271E70B4FF24FA9E77 /* PermissionHostAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EB3DF187FA7AE5A61B035F6 /* PermissionHostAppTests.swift */; }; A262EF1BD871C12840DA75F2 /* SentenceBoundaryClassifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FC72BD286ABA5B3CEF82F7 /* SentenceBoundaryClassifier.swift */; }; A275EDDC0B43865C587C33AA /* TrailingDuplicationFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD851386E85D3ED0A1F17E47 /* TrailingDuplicationFilter.swift */; }; + A29869C9E187C7EEF756E08F /* CaretTokenPositionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A7F2B20877E19BD2B27D1E /* CaretTokenPositionTests.swift */; }; A2E77B6BE41060407B2C0508 /* SwiftLog-Apache-2.0.txt in Resources */ = {isa = PBXBuildFile; fileRef = 19B257134CCCD0D4370E5287 /* SwiftLog-Apache-2.0.txt */; }; A2FCC90388ABD94CB3C9F9AA /* SuggestionOverlayAcceptanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4850AE68E84879254CBFEDE6 /* SuggestionOverlayAcceptanceTests.swift */; }; A30F28CB21A2DDE9E44FC10A /* BundleVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A951C04DF783B1F1EFDD1E5 /* BundleVersion.swift */; }; @@ -578,6 +632,7 @@ A43DC0A76DFCD221A7DA6A3F /* SuggestionWorkController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A66389B78743ABACE7CD4D82 /* SuggestionWorkController.swift */; }; A468F55B53FCE7C34D3A7F22 /* AXTextGeometryResolverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FC7ADFF0012541EFC762D0F /* AXTextGeometryResolverTests.swift */; }; A54337B0B12D1FE2F332C7E1 /* MenuBarRecoveryPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66B5FF2D1DCC846AABC134CF /* MenuBarRecoveryPolicyTests.swift */; }; + A5D55F0B61671D752CC75AC7 /* SignOffCue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C896CEDC632FB738D3A42BF /* SignOffCue.swift */; }; A5D76116479357C29E2D8405 /* CotabbyDebugOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F47CD9DF50E7192A70D1870E /* CotabbyDebugOptions.swift */; }; A614AD79BE724ABA3721613B /* MacroModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B41F06FEF208B30ECCF23A6F /* MacroModels.swift */; }; A62575C4326C9C961A82436F /* TokenHealingBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50AECDF57800BBB6617620D5 /* TokenHealingBuffer.swift */; }; @@ -596,6 +651,8 @@ A8854697A9EB29DB737C4A26 /* SelfCaptureGateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 571B221ACAFED86DABB10F48 /* SelfCaptureGateTests.swift */; }; A88F3C7039E8DDB71C5D6246 /* TypoGate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FCE0E0C38A776383B11809 /* TypoGate.swift */; }; A8DCC8CFAD1B698A32E1B077 /* SuggestionClientError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 743B7207A91EB1BFADD0C5E7 /* SuggestionClientError.swift */; }; + A966DCE17C5160F15F371372 /* HostBaselineCalibratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F23A711EA8EF13B480FF34DE /* HostBaselineCalibratorTests.swift */; }; + A974EF146F549AD4DFD11D66 /* GhostWrapBandPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B753DA96CA6BC168E675E61E /* GhostWrapBandPolicy.swift */; }; A9B01E476E483F2A712F1B90 /* HostFontRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F328B43743D803A16045110 /* HostFontRegistry.swift */; }; AA00D42DFD1EE094E01A7EEA /* AcknowledgementsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7024D32C43EAD2C5F8689B2D /* AcknowledgementsView.swift */; }; AA2E09FF7E430D66ECA8ECD5 /* CotabbyApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC1EDFB535AAA2EE0D67828A /* CotabbyApp.swift */; }; @@ -609,15 +666,16 @@ AC716016F8E26C1711C39F6D /* FileLogHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C90C9EBCB70327D215EAE07 /* FileLogHandler.swift */; }; ACC1D8C50007AA193271F977 /* PerformancePaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEBD6113A3C1038BECC99245 /* PerformancePaneView.swift */; }; AD0FE3F0F75A40B827109589 /* SettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3350EDE01ED5125520C79D53 /* SettingsCoordinator.swift */; }; + AD1BCDC0809B413FEC5F115B /* GhostTextPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29163A3D0BB0F11EEAC2985A /* GhostTextPanelView.swift */; }; AD7940C54015532D2E8973DD /* CompositionInputModeClassifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9316289DD11A72D3EAC0A929 /* CompositionInputModeClassifier.swift */; }; ADB10BCA76295920FE4A9FE1 /* SuggestionFadeInPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B351B5FDF077F8FF49A9A7 /* SuggestionFadeInPolicy.swift */; }; + AE5D7E6E784F22EC71060E7E /* HostZoomLadder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B09274BE9500E3C41C18B2 /* HostZoomLadder.swift */; }; AE739989AC401113B99018BC /* SuggestionCoordinator+Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFA31F5FA8D164CB6C30D06 /* SuggestionCoordinator+Input.swift */; }; AE79541055252CA69CFD5DD7 /* CaretWordContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 976E3D6790D2A3594BCEEA23 /* CaretWordContext.swift */; }; AED18FC69D7ECC5A0A80B8FB /* CustomRulesCatalog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D93E3819F3BB0957FF3DF6 /* CustomRulesCatalog.swift */; }; AEF6FC272F0B1B20B43FD0EE /* SuggestionTextColorCodecTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 655F56C230F45CAC13EEE59C /* SuggestionTextColorCodecTests.swift */; }; AF55F1ABEDEA10C76C307CEC /* CotabbyAppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 711293EA57808B9428C7B908 /* CotabbyAppEnvironment.swift */; }; AF5AF5B7D32EDC0AF1A31557 /* InsertedTextAdvance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C8C1A4EC935BB837432BAF2 /* InsertedTextAdvance.swift */; }; - AFA6E84BC8C637CDBDF8BA9D /* GhostFontMetricsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E3785ACD1BBD9D866ADE8DC /* GhostFontMetricsTests.swift */; }; AFFF05025897116EA6E58EA7 /* Aria2OutputParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83457CCF1A50CE83428C363D /* Aria2OutputParser.swift */; }; B00FDD3DEE0B73FF5136C91C /* FocusTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9FDF029F7828CAF3FE8850 /* FocusTracker.swift */; }; B0D7A0BC5621C8D23A9145B7 /* CustomRulesEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7D1184F0BC707F0A9148DD /* CustomRulesEditor.swift */; }; @@ -631,6 +689,7 @@ B3EB0A706B7510CBB4AB23BB /* SettingsQuickLinkCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1E69CFA319C5F9E4C30F699 /* SettingsQuickLinkCard.swift */; }; B413327721F83D9B5C8D4BCE /* SuggestionInteractionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6017E8AEDF88042933C61DFD /* SuggestionInteractionState.swift */; }; B422256933F7BEAEF2FC4176 /* es-100l.txt in Resources */ = {isa = PBXBuildFile; fileRef = 620D393D3B7E687A08FA9446 /* es-100l.txt */; }; + B42C9D3DCDE6E50DC75D0342 /* HostAdvanceFit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A5DC5FF5BD2995FB6709A65 /* HostAdvanceFit.swift */; }; B4A2F4CF9253A927961A8E08 /* SuggestionPresentationTimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D89680F74D4E1097F81FC02A /* SuggestionPresentationTimingTests.swift */; }; B4D36F5D03E3143CE74582F9 /* AppearancePaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFBE491B3CA04FE9069B7B0F /* AppearancePaneView.swift */; }; B50D696D40BB500C5CD39F00 /* Aria2DownloadService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE6E2A19BF6B87A0C6FA9D49 /* Aria2DownloadService.swift */; }; @@ -641,10 +700,16 @@ B6BA7DF77DCA55F9EF090AEE /* ScreenTextExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59E299BE2E9D42A33D5D2F5D /* ScreenTextExtractor.swift */; }; B6C2570DB8D17C8626D50F76 /* SuggestionCoordinatorContinuityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5830E18C10728B7AF1DB2CCF /* SuggestionCoordinatorContinuityTests.swift */; }; B6F12EFB0AA3CC6BA8773476 /* CaretLinePosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE5E9B2A196282738F8D4BAD /* CaretLinePosition.swift */; }; + B71D51572125A69F279B3D79 /* llama-recall-cases.json in Resources */ = {isa = PBXBuildFile; fileRef = 78721FCF04E4D6F0C7C5DDCF /* llama-recall-cases.json */; }; + B76A1739BB3A656D6417C5D3 /* InkBaselineAnalyzerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC7FA0146AC6B78725105BA4 /* InkBaselineAnalyzerTests.swift */; }; B803D0491F5CF19735F23B65 /* CurrencyEvaluator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67332497BB5448AEDA700BA7 /* CurrencyEvaluator.swift */; }; B8B71602DFA0E6B7E61E2692 /* SuggestionSessionTypingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A61DC16ED76D694A59BF14 /* SuggestionSessionTypingTests.swift */; }; B9348E5D08C50B82D2C62BCF /* ContextBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7B8727111208CB506C25C4F /* ContextBuffer.swift */; }; + B97F5A32DC8C9CE048F231D7 /* InkBaselineAnalyzer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 665A55CA2E7975CBFC63B4E2 /* InkBaselineAnalyzer.swift */; }; B9C7A729C0D0A8F7ED032A8E /* EmojiPickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC8599C00D6C2CA5FB7A7E7 /* EmojiPickerController.swift */; }; + BA1EC288248A2E4508E4FE9A /* HostBaselineCalibrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC1ECBE9D1EDE855B57A913 /* HostBaselineCalibrator.swift */; }; + BACE17862C9D65881E0D5710 /* HostTextMetricsCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8B99181BF74C1F1DC5F6A7 /* HostTextMetricsCache.swift */; }; + BB9CA5E517347B478B4D52E9 /* GhostTextPixelAlignmentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F3107DC345254BF028C63 /* GhostTextPixelAlignmentTests.swift */; }; BBBD7B4628BA1160DD6B4BDB /* MacroModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B41F06FEF208B30ECCF23A6F /* MacroModels.swift */; }; BBC7F32FC99DBC41B0102E54 /* OpenAICompatibleEndpointModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ABCE733783332BE52E43D67 /* OpenAICompatibleEndpointModels.swift */; }; BBCF59701C5E1E77E7A3F62D /* AXTreeDumpWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB07C5F0319D124B6266BCF3 /* AXTreeDumpWriter.swift */; }; @@ -675,6 +740,7 @@ C27B7576CDA64AEE7558F700 /* ShortcutResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = E83C6E965A3978D0EBFB1EA2 /* ShortcutResolver.swift */; }; C2CD418FED8EB332C6417AF9 /* TokenCountEstimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A502BFB13373D1931B082CD /* TokenCountEstimator.swift */; }; C2D5D224A3B1954E198A9F9C /* FieldStyleCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C8D2853B43F01B285FB44C /* FieldStyleCacheTests.swift */; }; + C304E4802C31CD1BCD2B8743 /* HostFaceMemory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7103021AEC287186CE25415D /* HostFaceMemory.swift */; }; C317134A15FC4388B3163632 /* Aria2DownloadServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA0A1D64275BC85AEBBB852 /* Aria2DownloadServiceTests.swift */; }; C3B79350EEAAC688F89A4E6D /* WelcomePersonalizeStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F26F50467C83B202FB4BAC2A /* WelcomePersonalizeStepView.swift */; }; C3C347E9485B70C59BF5EFC8 /* SuggestionQualityMetricsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50119F67E7BB9FBC8EA94BF2 /* SuggestionQualityMetricsStore.swift */; }; @@ -708,8 +774,11 @@ C8434622BFCE324E7044E581 /* TickMarkSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BB42779285A1BA40B7848C8 /* TickMarkSlider.swift */; }; C8682DF05F0B3C3950FB64B1 /* SystemMetricsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B966EA885CB107161FC3006 /* SystemMetricsStore.swift */; }; C8A3B724BB54A1209583B78B /* VisualContextStartCoalescer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF8F1A40EAA82773C07F8B9 /* VisualContextStartCoalescer.swift */; }; + C8A48355F3821EBD3D964749 /* SignOffCueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F19230243E3B5DE5A76244C /* SignOffCueTests.swift */; }; C8B5E45A6F45B307C067CF61 /* SurfaceContextComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27B03589D82D0ADCF41B9CD0 /* SurfaceContextComposer.swift */; }; C95343A5A8496627F44233F0 /* InsertedTextAdvance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C8C1A4EC935BB837432BAF2 /* InsertedTextAdvance.swift */; }; + CA959F082DB2C6E95C5743C8 /* GhostBaselinePolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25782BF21045F20B9721DB90 /* GhostBaselinePolicyTests.swift */; }; + CB2D43A2B7568173A8E82CE4 /* GhostWrapBandPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39AD68E34CF598F2C83D7C73 /* GhostWrapBandPolicyTests.swift */; }; CC54CAD96A348364989132D3 /* ActiveSuggestionSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04F95814016D80E0A23974F0 /* ActiveSuggestionSession.swift */; }; CC615916E478E98D403F5714 /* SettingsAttentionEvaluator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79AAAB1C46A0E5A9C6FC7D3D /* SettingsAttentionEvaluator.swift */; }; CC6BE0B13CCF9B1DE0466DC2 /* DecodeStopPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C319B4FF652512A69377DD5C /* DecodeStopPolicy.swift */; }; @@ -720,16 +789,20 @@ CD5740A7732F4ACA73CB3F64 /* SystemSettingsWindowLocator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 177B69917649DAD33BC90388 /* SystemSettingsWindowLocator.swift */; }; CDDEF3C29AEACA986034C01C /* SuggestionEngineModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB1A3F7D6C0FF28717E6D4C /* SuggestionEngineModels.swift */; }; CE218D30F69D215095E68456 /* AXHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2AA5BD865E0BCFB53DC699 /* AXHelperTests.swift */; }; + CE2D1E5B1F987FC76024DB46 /* InkCaretAnalyzer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC19809AAB1314BD6CC61B20 /* InkCaretAnalyzer.swift */; }; CEB972329AA694F8483433C2 /* FocusSnapshotResolverSelectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C01F11F7F29EFC3A27A1EE5D /* FocusSnapshotResolverSelectionTests.swift */; }; CED430DD80E4769920E3C0E5 /* FocusModelsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7E1691BC93836AE2FA8931 /* FocusModelsTests.swift */; }; CEDE9C238AD33258B36DB509 /* WelcomeHeroDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = F155556C751FB074B31CCC64 /* WelcomeHeroDemo.swift */; }; CF04C28598AF9A437990C8A4 /* SurfaceContextComposerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 652973AF18C20D51F81F76C5 /* SurfaceContextComposerTests.swift */; }; CF2EADEEEF5AA63FB9B9EA8E /* SuggestionInserter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3D1125B962CBE0269EEDDB /* SuggestionInserter.swift */; }; CF391BC4A3C80C41048D64D1 /* TypoCaseTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B293615FC411551C3FC15528 /* TypoCaseTransfer.swift */; }; + CFCBA5D14335960CF994F40D /* GhostFontSizeLimits.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC61CA8FEFB45FB1CCA453A /* GhostFontSizeLimits.swift */; }; D0152B3DB7CF0A52A9A66DAB /* SOURCES.md in Resources */ = {isa = PBXBuildFile; fileRef = 054987E76CA9D1FA4F81EA8F /* SOURCES.md */; }; D01A732060A64634619980B1 /* DateMacroEvaluator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94A26D86030FC5FA67371C52 /* DateMacroEvaluator.swift */; }; + D060C3A636D395CE9DE6BDAB /* HostMarkedTextPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBF9FDA4F652DCC91A724314 /* HostMarkedTextPolicyTests.swift */; }; D07ED62B4BB3EEBB6566C1ED /* FocusedInputContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFB06531A54FDCDBAAD14E8B /* FocusedInputContext.swift */; }; D0BA4677F47C6943310B8A28 /* PopupChrome.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87C6335EED41343595F17A08 /* PopupChrome.swift */; }; + D18F11732EA94EA16CD8101F /* SystemUIFocusShadowPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 209089420F2EE43C312657A7 /* SystemUIFocusShadowPolicyTests.swift */; }; D1B42567FEECD406CECFEC45 /* BrowserDomainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD829565558CB1EA8CA69D5 /* BrowserDomainTests.swift */; }; D1CB2B2D33E5B6C76DDB2A9C /* FocusTrackingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 480596411FBB38BA3B993EDC /* FocusTrackingModel.swift */; }; D1D7D50E5C620042CEA3A77E /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = E0AA9F90B83B823132880E6F /* LaunchAtLogin */; }; @@ -741,6 +814,7 @@ D2B271E0FAE4F65FC2287930 /* SymSpellCorrector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E1E72C09460390583D98D1 /* SymSpellCorrector.swift */; }; D2BB9B47E0E1619021931B49 /* WritingPaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D48B95B6665109B6C6A63B42 /* WritingPaneView.swift */; }; D2DB4238276DD73A8D5F588C /* ClipboardRelevanceFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BF37C1A0F734BB940E371C2 /* ClipboardRelevanceFilter.swift */; }; + D3044E73646472B70D754F3D /* BlockBreakAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82420F9505E69AE2ADE9F583 /* BlockBreakAlignment.swift */; }; D338A02B462BCE3A9BA978ED /* SuggestionAvailabilityEvaluatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED76F12EBF9C7E7B9AA95142 /* SuggestionAvailabilityEvaluatorTests.swift */; }; D38D295DB3155EFB001B3F5C /* Aria2Provisioner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D2F4ED8C7493EF4B967BFF9 /* Aria2Provisioner.swift */; }; D3909BBB80781B0CE1982A8E /* DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6586D6D735F287F43D42520A /* DeviceInfo.swift */; }; @@ -752,9 +826,12 @@ D58B73ED080E5D253A838FAC /* SpellingDictionaryResourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDBD55F205C609303913423 /* SpellingDictionaryResourceTests.swift */; }; D5929DF79BA02EE9B170B4D5 /* FieldEdgeIconIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F35CD1B715FCBB12BEB6CE62 /* FieldEdgeIconIndicatorView.swift */; }; D59584461B4A854572C331D2 /* CompletionRenderModeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 816106CA251AFDE69A804762 /* CompletionRenderModeTests.swift */; }; + D5C6524646C545CFA08D00C9 /* PixelCaretLocator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A65BCABF4B23690AE3DBE93 /* PixelCaretLocator.swift */; }; D5F36CA1A9400432CB1FDAFC /* ArithmeticEvaluator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5897D28F4DBEAF4792FB4D24 /* ArithmeticEvaluator.swift */; }; D66623DB320CB93A7EBA9B86 /* FocusCapabilityResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B8D2232F271197468CBC11 /* FocusCapabilityResolver.swift */; }; D67368E11F48E78414525077 /* SwiftLog-NOTICE.txt in Resources */ = {isa = PBXBuildFile; fileRef = 1788B4543337A9E8388B8F6F /* SwiftLog-NOTICE.txt */; }; + D6C4E1E75B8DA8F6471D6E09 /* HostZoomLadderResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEDA42939A0406D27EB799A9 /* HostZoomLadderResolver.swift */; }; + D720D99A50108D0D44E63902 /* SuggestionLengthPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF584D56E44086925BC0E345 /* SuggestionLengthPolicyTests.swift */; }; D76772E12E03CAFF0999F672 /* Aria2DownloadService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE6E2A19BF6B87A0C6FA9D49 /* Aria2DownloadService.swift */; }; D795377F4F24F49740808960 /* SuggestionInteractionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6017E8AEDF88042933C61DFD /* SuggestionInteractionState.swift */; }; D79C2A5DCDAFB9741E208103 /* WordCountFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14E22DCC1852B8E4B235D256 /* WordCountFormatter.swift */; }; @@ -766,7 +843,9 @@ D98538D3C1344422354B0B2E /* SuggestionDebugLoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A02F9151AEBBD825D64CEFA /* SuggestionDebugLoggerTests.swift */; }; D9BF921EDFAE90D4D15DA82F /* GhostTextColorPresetPaletteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F6000E673651AE72243F8A /* GhostTextColorPresetPaletteTests.swift */; }; D9CE1B135CA6FB766426BD05 /* SuggestionRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E23EF696D34CFB8EF320668 /* SuggestionRequest.swift */; }; + D9CE5079C27826E94A29C744 /* HostAdvanceFitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A236731DA3FFF8FEECB67167 /* HostAdvanceFitTests.swift */; }; DA00477BB00EB3E95F204CB2 /* EmojiUsageStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846A1C3328B3B1CB42E2E46B /* EmojiUsageStore.swift */; }; + DA4693E09A0790D12F531CCA /* TypefaceMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E5BA02F079AD5EB20CDB215 /* TypefaceMatcher.swift */; }; DA80B804C506B427A663302D /* ContextLivePreviewField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8368940C6C15CC3F932B8DFC /* ContextLivePreviewField.swift */; }; DA8D967DFA3AFDE469D1FAE7 /* DebouncePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B630D1F95FD08BC30E624672 /* DebouncePolicy.swift */; }; DC0394C83D334B92A512A775 /* NOTICE.md in Resources */ = {isa = PBXBuildFile; fileRef = 66CF2A70D4699421AC9BD849 /* NOTICE.md */; }; @@ -780,6 +859,7 @@ DE84AC117C1A0C04C5F60ECD /* ContextPaneViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A01BF1613C95C767A2E93D8F /* ContextPaneViewTests.swift */; }; DEB745B8A3920512A87A260D /* AXTextGeometryResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98423150126CC5969C2886BA /* AXTextGeometryResolver.swift */; }; DED8731A5A34A10349AA05A8 /* EmojiPickerModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8736D4321E8BF847D1F7338E /* EmojiPickerModels.swift */; }; + DF38049917DC33C6235DB3E6 /* CaretTokenPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBE00782EE6813AF3224CD5 /* CaretTokenPosition.swift */; }; E04950B45EF6C6CE47EE5081 /* MacroController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A38CFCDC00DC4DB86D91BB2 /* MacroController.swift */; }; E1A8EA5F00C6B2E228FC9ACB /* EmojiPopularityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C28F67F089B66EC18C24182 /* EmojiPopularityTests.swift */; }; E2A0998154C6BFFC69B54038 /* SuggestionStreamingStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0C2FF2D19C8280B8A65DC3 /* SuggestionStreamingStateTests.swift */; }; @@ -790,6 +870,7 @@ E40142EDA2CFD1F1B1B41C86 /* SuggestionStreamingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 157C86303F9B51B6F648E5B3 /* SuggestionStreamingState.swift */; }; E42469F1225220932384F342 /* ClipboardContentDistiller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2229037BCDF7EFD3D47C15F /* ClipboardContentDistiller.swift */; }; E43B0EA04ED0B747CC8D9EBC /* SystemMetricsStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A25AC9344DBA93EFA90788D /* SystemMetricsStoreTests.swift */; }; + E4B3CF7EB30D22DABFF46F33 /* CaretAdvanceSampleStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D86D5157D7E3F4447D2DE7F7 /* CaretAdvanceSampleStore.swift */; }; E4C03DC18410E3F0EBB1C16B /* EmojiSynonymCatalog.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7FBE9101AF744A4D205B32D /* EmojiSynonymCatalog.swift */; }; E5022E7BF72591C0FEE80CD7 /* DownloadFileRescuerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E94A6036F4B703A2FF7F68EF /* DownloadFileRescuerTests.swift */; }; E511E9B1143E6FDECB4456FA /* Llama.cpp-MIT.txt in Resources */ = {isa = PBXBuildFile; fileRef = 617B396FAD84E6F4A270B5EA /* Llama.cpp-MIT.txt */; }; @@ -812,14 +893,17 @@ E96817EE505817F9E616B31C /* PerAppShortcutOverrideStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9227DE16CA5CE2E085C1A891 /* PerAppShortcutOverrideStoreTests.swift */; }; E9B34664449F7451581C6908 /* CustomRulesCatalog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D93E3819F3BB0957FF3DF6 /* CustomRulesCatalog.swift */; }; E9B7E353B233F842AC859542 /* MenuBarRecoveryPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E267F3BB7FC8DEAEB5E841B /* MenuBarRecoveryPolicy.swift */; }; + E9D65F5D5452B595886F43DC /* GhostWrapBandPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B753DA96CA6BC168E675E61E /* GhostWrapBandPolicy.swift */; }; E9E4CC657771DF9F4C56183C /* VisualContextCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A854CAFB1F557BC4CAED8819 /* VisualContextCoordinator.swift */; }; EA353CCECBFB4D297C865447 /* CurrentWordExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1AAC6367483952AB7D59A62 /* CurrentWordExtractor.swift */; }; EA6E9DD40B45A48FCBED4BCB /* SuggestionCoordinator+TypingPrediction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57D3DEF846A089D523BB8BC0 /* SuggestionCoordinator+TypingPrediction.swift */; }; + EA79F5362D5CDBC3B4F3701A /* HostMarkedTextPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9CFD041A13DACE5612DC991 /* HostMarkedTextPolicy.swift */; }; EA7D6ECD050AC621B02E35F2 /* RequestID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05F173F93FFA5FFBC169DA25 /* RequestID.swift */; }; EA89011A4D7ADD2A716D886E /* EmojiPaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A28B4A4368DB33C25E3AB5F3 /* EmojiPaneView.swift */; }; EA91121F23229B9BA8474680 /* LlamaSuggestionEngineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30452E10F080145EAD9F3667 /* LlamaSuggestionEngineTests.swift */; }; EB57614CAD69582324EE8264 /* WelcomeHeroDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = F155556C751FB074B31CCC64 /* WelcomeHeroDemo.swift */; }; EB9DC100AADED49A684632FC /* SuggestionWordAcceptanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F4A8BCFC1F9F09B10A4D6F1 /* SuggestionWordAcceptanceTests.swift */; }; + EBE0052259FC97E5C29BD330 /* HostAdvanceFit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A5DC5FF5BD2995FB6709A65 /* HostAdvanceFit.swift */; }; EC4975F707689CEA77A636BB /* PromptSectionBudgetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A73D95F2BF5CA59705653931 /* PromptSectionBudgetTests.swift */; }; ED560EA132AF1EEA0FE66DAA /* WordPrefixIndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D549854AD86485DF61F3B75 /* WordPrefixIndexTests.swift */; }; ED5E57512F532DDA48DF3360 /* SurfaceContextComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27B03589D82D0ADCF41B9CD0 /* SurfaceContextComposer.swift */; }; @@ -829,17 +913,20 @@ EE73E5D254C575F692D4D3B4 /* SecureFieldDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EE99C84483D3A58D561C61D /* SecureFieldDetector.swift */; }; EEC06CE37957B44E2B258DEE /* PromptContextSanitizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA66559D50874865032EE8C /* PromptContextSanitizerTests.swift */; }; EF0DE5E045F328F1E912A02A /* AppsPaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C1C921A1CDA2ADFC39EA01 /* AppsPaneView.swift */; }; + EF3A6587D39F1541BF5551E7 /* HostZoomLadder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B09274BE9500E3C41C18B2 /* HostZoomLadder.swift */; }; EF4C27A7BEB47AFC6635BD38 /* SuggestionPresentationModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3003832CAC19ED7AE371E845 /* SuggestionPresentationModels.swift */; }; - EF572E116EAD1FC1901B1BDA /* GhostSuggestionLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F1EE1DBFDC6CDC288DB98D /* GhostSuggestionLayout.swift */; }; EFEA8845584EC9AE7D3B123D /* SelfCaptureGate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13838E1372956FF2CFC5ECCC /* SelfCaptureGate.swift */; }; F00030AE79BA5E78B5615100 /* PermissionGuidanceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C41A249D9538B91B7FA8A9 /* PermissionGuidanceController.swift */; }; F02C29667DEA43507521C87B /* LlamaRuntimeCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019029E24E67812E3FF63286 /* LlamaRuntimeCore.swift */; }; F07B21A61B8B0C0E3B3147F6 /* TextLayoutCaretEstimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BF6C6A11D5B5A7B987AFB9C /* TextLayoutCaretEstimator.swift */; }; + F09B3B8B1E61818BCE539358 /* CaretAdvanceSampler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A879A0AD69BCA2F60D2735 /* CaretAdvanceSampler.swift */; }; + F0A97143C565D2C2BD2339DF /* OverlayControllerTypefaceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB446CE06049A4F958C9DC91 /* OverlayControllerTypefaceTests.swift */; }; F0DCEFED640B453A5ECEB810 /* HuggingFaceModelBrowserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C696204AAEBA357129F77646 /* HuggingFaceModelBrowserView.swift */; }; F0E47B759322F911BB54EFCB /* EmojiMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA8311FAC345FE431FA89855 /* EmojiMatcher.swift */; }; F10E203703E57C0EF2C0F98E /* StaticTextRunWalkThrottle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21E26C899DE4FE7AA9CC7869 /* StaticTextRunWalkThrottle.swift */; }; F13B85DD24398CD22C3560C1 /* InputModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A29DF1C68154C21E6943D38E /* InputModels.swift */; }; F140C1894C54E0A94EAE70BC /* BundledRuntimeLocator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1AB5EBB44EE64EB3EB1426F /* BundledRuntimeLocator.swift */; }; + F229353A4D1AD42CBDFD5F8F /* HostTextMetricsProbe.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE4180FC6B2E085CEE93F23 /* HostTextMetricsProbe.swift */; }; F22FAA3449506ABBACB12BA0 /* WordCountFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14E22DCC1852B8E4B235D256 /* WordCountFormatter.swift */; }; F25F1800B79BF85AF07048C3 /* TypoCaseTransferTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C5953E5EB042ABAF6F35A69 /* TypoCaseTransferTests.swift */; }; F26DD9AEAFECAAAD13F3A0A0 /* DownloadFileRescuer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC756DF8E0032B39099D2BF8 /* DownloadFileRescuer.swift */; }; @@ -858,6 +945,7 @@ F50F54FB1B3B2B2E464B8683 /* LlamaRuntimeModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16967F782C95CEB09B9C8D49 /* LlamaRuntimeModels.swift */; }; F54E69831254219DCA97C1F3 /* AboutPaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2559228BB102022DC48193D9 /* AboutPaneView.swift */; }; F5E397207AFEB2AA97C264A0 /* PerformanceMetricsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 024A80AEB32CC1AE8C0FF69E /* PerformanceMetricsStore.swift */; }; + F60A94747732F706FD666925 /* TypefaceEvidence.swift in Sources */ = {isa = PBXBuildFile; fileRef = B616CB46A8624BC4E4FBB01A /* TypefaceEvidence.swift */; }; F619A3C5BD770B230A78530D /* ChromiumAccessibilityEnabler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB13342992EEF2BDEEAB1236 /* ChromiumAccessibilityEnabler.swift */; }; F61CFA7A600E266D5A05E26E /* TokenHealingPlan.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDAA9ADF6C0BD9D16016AA9B /* TokenHealingPlan.swift */; }; F6A88AAC1A3FE7FB284BFC34 /* TypingCadence.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7014E6E969FD4579028BAC4 /* TypingCadence.swift */; }; @@ -930,9 +1018,9 @@ 0C48F57D741CE10C195AF167 /* ClipboardContextProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipboardContextProviderTests.swift; sourceTree = ""; }; 0C90C9EBCB70327D215EAE07 /* FileLogHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileLogHandler.swift; sourceTree = ""; }; 0D239BFA9C9061C04956C591 /* InsertionStrategySelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsertionStrategySelector.swift; sourceTree = ""; }; - 0D40D1FC651686EF76ECADE3 /* GhostHostScaleTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostHostScaleTracker.swift; sourceTree = ""; }; 0DA66559D50874865032EE8C /* PromptContextSanitizerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromptContextSanitizerTests.swift; sourceTree = ""; }; 0F328B43743D803A16045110 /* HostFontRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostFontRegistry.swift; sourceTree = ""; }; + 10107090891BE11E2B34BC9E /* TypefaceMatcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypefaceMatcherTests.swift; sourceTree = ""; }; 110F737140F015E1A18E5A58 /* FocusSnapshotResolverLiveTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusSnapshotResolverLiveTests.swift; sourceTree = ""; }; 11CF768650A90705FC0D2730 /* SuggestionTextColorCodec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionTextColorCodec.swift; sourceTree = ""; }; 12082948AEBC0DFE5ADC6961 /* SuggestionAvailabilityEvaluator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionAvailabilityEvaluator.swift; sourceTree = ""; }; @@ -941,6 +1029,7 @@ 12E1E72C09460390583D98D1 /* SymSpellCorrector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymSpellCorrector.swift; sourceTree = ""; }; 12F6FDB6D09851CC5C6649EF /* MenuBarSections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarSections.swift; sourceTree = ""; }; 13838E1372956FF2CFC5ECCC /* SelfCaptureGate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfCaptureGate.swift; sourceTree = ""; }; + 147D8EAD035A66FCA1D07E37 /* HostBaselineCalibratorTypefaceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostBaselineCalibratorTypefaceTests.swift; sourceTree = ""; }; 14D5E77EB08030489DAFA4DC /* DeviceInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceInfoTests.swift; sourceTree = ""; }; 14E22DCC1852B8E4B235D256 /* WordCountFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordCountFormatter.swift; sourceTree = ""; }; 150AB28A463F8CDA01E0D5B7 /* LaunchAtLogin-MIT.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "LaunchAtLogin-MIT.txt"; sourceTree = ""; }; @@ -975,7 +1064,9 @@ 1EE99DFA6D7D4B925AFAD1B4 /* OpenAICompatibleAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAICompatibleAPIClient.swift; sourceTree = ""; }; 1F4A8BCFC1F9F09B10A4D6F1 /* SuggestionWordAcceptanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionWordAcceptanceTests.swift; sourceTree = ""; }; 1FB0AF4FEC5A3FD07652F718 /* CotabbyDebugOptionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CotabbyDebugOptionsTests.swift; sourceTree = ""; }; + 1FD9262617968D8D1EF1EA45 /* GhostSpaceBoundaryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostSpaceBoundaryTests.swift; sourceTree = ""; }; 202086735E28B96BBCE55E6B /* LlamaEvalScoring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LlamaEvalScoring.swift; sourceTree = ""; }; + 209089420F2EE43C312657A7 /* SystemUIFocusShadowPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemUIFocusShadowPolicyTests.swift; sourceTree = ""; }; 21047E3C6ACA1035669A7E06 /* AppSurfaceClassifierTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSurfaceClassifierTests.swift; sourceTree = ""; }; 210D477AA213B1EF8B3AC7F1 /* SuggestionPresentationTiming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionPresentationTiming.swift; sourceTree = ""; }; 21A5285E99934D0045CB1918 /* CompletionRenderModePolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionRenderModePolicyTests.swift; sourceTree = ""; }; @@ -986,18 +1077,22 @@ 22FA75F9AD318B9084B23ECF /* PostExhaustionAcceptanceState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostExhaustionAcceptanceState.swift; sourceTree = ""; }; 2471F967A05EB4C8BF7CD242 /* SuggestionCoordinatorRigTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionCoordinatorRigTestCase.swift; sourceTree = ""; }; 2472C76E96009300EBF038D4 /* CotabbyInference-MIT.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CotabbyInference-MIT.txt"; sourceTree = ""; }; + 24A7F2B20877E19BD2B27D1E /* CaretTokenPositionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaretTokenPositionTests.swift; sourceTree = ""; }; 2559228BB102022DC48193D9 /* AboutPaneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutPaneView.swift; sourceTree = ""; }; 2576B762374F4D3CD8347AD4 /* WelcomeKeybindStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeKeybindStepView.swift; sourceTree = ""; }; + 25782BF21045F20B9721DB90 /* GhostBaselinePolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostBaselinePolicyTests.swift; sourceTree = ""; }; 25C4859A89B59938DB905377 /* SuggestionCoordinatorLifecycleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionCoordinatorLifecycleTests.swift; sourceTree = ""; }; 25D41CC179EC04093971A0D0 /* SpellingDictionaryCatalog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpellingDictionaryCatalog.swift; sourceTree = ""; }; 262BE2F1E97389FE8D7A5FB9 /* Cotabby.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Cotabby.app; sourceTree = BUILT_PRODUCTS_DIR; }; 264C3F355F2F82D1964DD263 /* EmojiQueryRun.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiQueryRun.swift; sourceTree = ""; }; 266C8BF167118855C56FC7F4 /* BrowserAppDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserAppDetector.swift; sourceTree = ""; }; 27B03589D82D0ADCF41B9CD0 /* SurfaceContextComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceContextComposer.swift; sourceTree = ""; }; + 29163A3D0BB0F11EEAC2985A /* GhostTextPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostTextPanelView.swift; sourceTree = ""; }; 2B23950F38B1B3C8A5CC5BE5 /* OpenAICompatibleCredentialStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAICompatibleCredentialStore.swift; sourceTree = ""; }; 2BF37C1A0F734BB940E371C2 /* ClipboardRelevanceFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipboardRelevanceFilter.swift; sourceTree = ""; }; 2C5953E5EB042ABAF6F35A69 /* TypoCaseTransferTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypoCaseTransferTests.swift; sourceTree = ""; }; 2C693D7C76486C97DCB9FC56 /* AcceptanceModePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcceptanceModePickerView.swift; sourceTree = ""; }; + 2C8D50115291212D03414CBE /* DebugForcedSuggestionEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugForcedSuggestionEngine.swift; sourceTree = ""; }; 2CADFBB03C10309B9048C8E9 /* SuggestionSessionReconciler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionSessionReconciler.swift; sourceTree = ""; }; 2CC8599C00D6C2CA5FB7A7E7 /* EmojiPickerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerController.swift; sourceTree = ""; }; 2D1F9CEBAB0F330F8E7B61D8 /* InputSuppressionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputSuppressionController.swift; sourceTree = ""; }; @@ -1009,14 +1104,15 @@ 2E379AF08CDFF65D6EFC565E /* ActivationIndicatorController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivationIndicatorController.swift; sourceTree = ""; }; 2E7C883990AE384602BA9AF6 /* SuggestionSettingsDomainTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionSettingsDomainTests.swift; sourceTree = ""; }; 2EB3DF187FA7AE5A61B035F6 /* PermissionHostAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionHostAppTests.swift; sourceTree = ""; }; + 2EC384A90F3D8B71584B3449 /* SystemUIFocusShadowPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemUIFocusShadowPolicy.swift; sourceTree = ""; }; 2F93E4EC64A8773A1A7F3E24 /* CompletionRenderMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionRenderMode.swift; sourceTree = ""; }; 2FCD2572B33EB6DDF1F2110A /* EmojiCatalogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiCatalogTests.swift; sourceTree = ""; }; 3003832CAC19ED7AE371E845 /* SuggestionPresentationModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionPresentationModels.swift; sourceTree = ""; }; 30452E10F080145EAD9F3667 /* LlamaSuggestionEngineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LlamaSuggestionEngineTests.swift; sourceTree = ""; }; 308F8660F70ED64511BDB59F /* RandomMacroEvaluator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomMacroEvaluator.swift; sourceTree = ""; }; 30AB3A99BEB2EA978298FC6F /* MidWordContinuationPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MidWordContinuationPolicyTests.swift; sourceTree = ""; }; + 3153FB925190B70E0502C43D /* InkCaretAnalyzerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InkCaretAnalyzerTests.swift; sourceTree = ""; }; 31A62CE4DA3E55B5E117DBD8 /* LlamaSuggestionEvalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LlamaSuggestionEvalTests.swift; sourceTree = ""; }; - 32F1EE1DBFDC6CDC288DB98D /* GhostSuggestionLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostSuggestionLayout.swift; sourceTree = ""; }; 3350EDE01ED5125520C79D53 /* SettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCoordinator.swift; sourceTree = ""; }; 338EE4D92F8BE01CF43FE876 /* OllamaPreloadWorkControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OllamaPreloadWorkControllerTests.swift; sourceTree = ""; }; 33D4F378E5C70CAF7827A90A /* ControlTokenMarkers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlTokenMarkers.swift; sourceTree = ""; }; @@ -1028,22 +1124,26 @@ 36652DB88C5948AA4A31524A /* ModelFileValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelFileValidator.swift; sourceTree = ""; }; 3680E1B8FA712A888F509640 /* ClipboardContentDistillerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipboardContentDistillerTests.swift; sourceTree = ""; }; 377A0BBB59988043005A138A /* FoundationModelSuggestionEngineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FoundationModelSuggestionEngineTests.swift; sourceTree = ""; }; + 384D85534F3C2F40E2FD8781 /* CompletionContentPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionContentPolicy.swift; sourceTree = ""; }; 384FBCF5D7A3A446C5BE2B8D /* SuggestionEngineRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionEngineRouter.swift; sourceTree = ""; }; 38D10022BEFABAD14D16EBF2 /* SuggestionOverlayStabilityGateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionOverlayStabilityGateTests.swift; sourceTree = ""; }; 38FCE0E0C38A776383B11809 /* TypoGate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypoGate.swift; sourceTree = ""; }; + 39AD68E34CF598F2C83D7C73 /* GhostWrapBandPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostWrapBandPolicyTests.swift; sourceTree = ""; }; 39B891BA80475B61900A1D3C /* EngineAndModelPaneView+AppleIntelligence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EngineAndModelPaneView+AppleIntelligence.swift"; sourceTree = ""; }; 3A051152D3C503A2339BF0FA /* FoundationModelPromptRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FoundationModelPromptRenderer.swift; sourceTree = ""; }; + 3A5DC5FF5BD2995FB6709A65 /* HostAdvanceFit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostAdvanceFit.swift; sourceTree = ""; }; 3A7A9C39A0713BD3B442C9AC /* AppUpdateManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdateManager.swift; sourceTree = ""; }; 3D6234872B84ECB56A707945 /* ControlTokenMarkersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlTokenMarkersTests.swift; sourceTree = ""; }; 3DB1A3F7D6C0FF28717E6D4C /* SuggestionEngineModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionEngineModels.swift; sourceTree = ""; }; 3DBD75CAF6CAC849D5ED9FA4 /* InputSuppressionControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputSuppressionControllerTests.swift; sourceTree = ""; }; - 3E3785ACD1BBD9D866ADE8DC /* GhostFontMetricsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostFontMetricsTests.swift; sourceTree = ""; }; 3E8BE4574F1770808AA848FE /* EmojiCatalogMatcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiCatalogMatcherTests.swift; sourceTree = ""; }; + 3EB202C5AD6079174A14330C /* GhostTextLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostTextLayoutTests.swift; sourceTree = ""; }; 3ED4E38B9FE65164C3ABEA12 /* LlamaEvalScoringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LlamaEvalScoringTests.swift; sourceTree = ""; }; 3FE69F822DE85087BCA01DF5 /* SymSpellCorrectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymSpellCorrectorTests.swift; sourceTree = ""; }; 3FE7D19D22434E3E24804999 /* CalendarAccessibilityCapturePolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarAccessibilityCapturePolicy.swift; sourceTree = ""; }; 3FFA31F5FA8D164CB6C30D06 /* SuggestionCoordinator+Input.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SuggestionCoordinator+Input.swift"; sourceTree = ""; }; 3FFC1D1322005305C7D482D2 /* SuggestionCoordinator+Continuation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SuggestionCoordinator+Continuation.swift"; sourceTree = ""; }; + 406F3107DC345254BF028C63 /* GhostTextPixelAlignmentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostTextPixelAlignmentTests.swift; sourceTree = ""; }; 409BB6D0152A1A9B37DE12F3 /* supported-model-regressions.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "supported-model-regressions.json"; sourceTree = ""; }; 40D912ACDEDD1EE3589180CB /* VisualContextCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualContextCoordinatorTests.swift; sourceTree = ""; }; 415E80FEA7C1330D502CE97F /* DownloadableModelCatalogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadableModelCatalogView.swift; sourceTree = ""; }; @@ -1055,6 +1155,7 @@ 45207F82F22BAE1E89286E4B /* SuggestionInteractionStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionInteractionStateTests.swift; sourceTree = ""; }; 45227E5F07E5BC72D2FC8254 /* UnitConversionEvaluatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitConversionEvaluatorTests.swift; sourceTree = ""; }; 47457FED99065966328047EB /* VisualContextExcerptSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualContextExcerptSelector.swift; sourceTree = ""; }; + 47F1A6BCCA20EBE7314B79D3 /* MailHeaderFieldDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailHeaderFieldDetector.swift; sourceTree = ""; }; 47F6000E673651AE72243F8A /* GhostTextColorPresetPaletteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostTextColorPresetPaletteTests.swift; sourceTree = ""; }; 480596411FBB38BA3B993EDC /* FocusTrackingModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusTrackingModel.swift; sourceTree = ""; }; 4850AE68E84879254CBFEDE6 /* SuggestionOverlayAcceptanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionOverlayAcceptanceTests.swift; sourceTree = ""; }; @@ -1062,24 +1163,27 @@ 487D8C5521411FC6845D4A51 /* FocusSessionScopedCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusSessionScopedCacheTests.swift; sourceTree = ""; }; 49DB247AACE1B28D45DB52B0 /* SuggestionOverlayStabilityGate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionOverlayStabilityGate.swift; sourceTree = ""; }; 4A51B1559F4CE6728D39C959 /* VisualContextModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualContextModels.swift; sourceTree = ""; }; + 4A65BCABF4B23690AE3DBE93 /* PixelCaretLocator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelCaretLocator.swift; sourceTree = ""; }; 4B0DF8F53EDD401B6A9EE06E /* RandomMacroEvaluatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomMacroEvaluatorTests.swift; sourceTree = ""; }; 4B8665A5495891F9E3DDA48B /* de-100k.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "de-100k.txt"; sourceTree = ""; }; 4BA1D28712B262ABD96F8399 /* ModelAndPresentationValueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelAndPresentationValueTests.swift; sourceTree = ""; }; 4BC92317837813ACA5051177 /* Cotabby Dev.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Cotabby Dev.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 4BF9A081715185297A31EB8F /* GhostCaretRefinementTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostCaretRefinementTests.swift; sourceTree = ""; }; 4CE9156494BFC0A1E12E0B6C /* OpenAICompatibleSuggestionEngineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAICompatibleSuggestionEngineTests.swift; sourceTree = ""; }; 4D2BDE0DF26393F326F7C44A /* SuggestionCoordinator+WordCompletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SuggestionCoordinator+WordCompletion.swift"; sourceTree = ""; }; 4D6D4D7CAD20C94DF9EF21D2 /* HuggingFaceAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HuggingFaceAPIClient.swift; sourceTree = ""; }; 4DF8401D3859F00CC94F8893 /* HuggingFaceSearchService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HuggingFaceSearchService.swift; sourceTree = ""; }; 4E1EBD88A434C325CC6EFBDF /* SuggestionCoordinatorAcceptanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionCoordinatorAcceptanceTests.swift; sourceTree = ""; }; 4E283DF8948B10268B46811F /* MPL-2.0.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "MPL-2.0.txt"; sourceTree = ""; }; - 4F3AC792296D57BE1AD0D308 /* GhostFontSizeStabilizerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostFontSizeStabilizerTests.swift; sourceTree = ""; }; 4F86D2716CBE36970BB462E8 /* OnboardingTemplateRecommender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingTemplateRecommender.swift; sourceTree = ""; }; 4FFD3AFACAAC963DAF59D153 /* OnboardingTemplateRecommenderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingTemplateRecommenderTests.swift; sourceTree = ""; }; 50119F67E7BB9FBC8EA94BF2 /* SuggestionQualityMetricsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionQualityMetricsStore.swift; sourceTree = ""; }; + 50A879A0AD69BCA2F60D2735 /* CaretAdvanceSampler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaretAdvanceSampler.swift; sourceTree = ""; }; 50AECDF57800BBB6617620D5 /* TokenHealingBuffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenHealingBuffer.swift; sourceTree = ""; }; 50CDB11D96F343194B390321 /* MacroTriggerStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacroTriggerStateMachine.swift; sourceTree = ""; }; 518453163741E02E053860E1 /* DeepGeometryWalkThrottleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepGeometryWalkThrottleTests.swift; sourceTree = ""; }; 51D93E3819F3BB0957FF3DF6 /* CustomRulesCatalog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomRulesCatalog.swift; sourceTree = ""; }; + 52675961C22C88675CC4B107 /* HostZoomLadderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostZoomLadderTests.swift; sourceTree = ""; }; 52C557203A5A5F08E68BB11B /* TerminalAppDetectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalAppDetectorTests.swift; sourceTree = ""; }; 534F1297DEF3547D0DE56FB2 /* KeyboardInputSourceMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardInputSourceMonitor.swift; sourceTree = ""; }; 544C25CEF5C997F920C7BF0F /* FieldStyleCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldStyleCache.swift; sourceTree = ""; }; @@ -1094,8 +1198,8 @@ 5830E18C10728B7AF1DB2CCF /* SuggestionCoordinatorContinuityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionCoordinatorContinuityTests.swift; sourceTree = ""; }; 589029534F91D2BE26A65263 /* BrowserAppDetectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserAppDetectorTests.swift; sourceTree = ""; }; 5897D28F4DBEAF4792FB4D24 /* ArithmeticEvaluator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArithmeticEvaluator.swift; sourceTree = ""; }; + 58FA6A8D05AEEBE3501B478F /* GhostFontResolverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostFontResolverTests.swift; sourceTree = ""; }; 59E299BE2E9D42A33D5D2F5D /* ScreenTextExtractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenTextExtractor.swift; sourceTree = ""; }; - 5A4D17B2105C51112B19B2C2 /* GhostHostScaleTrackerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostHostScaleTrackerTests.swift; sourceTree = ""; }; 5A9DE35F3B6F0DFEB9538692 /* HuggingFaceSearchServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HuggingFaceSearchServiceTests.swift; sourceTree = ""; }; 5ADC3A68EF5FD9A1E6B5D249 /* CustomRulesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomRulesTests.swift; sourceTree = ""; }; 5B5D1A7D938F633C6EE094F7 /* AXHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AXHelper.swift; sourceTree = ""; }; @@ -1107,7 +1211,6 @@ 5D0AEFF86F8210CBE7CFCBAD /* SettingsCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCategory.swift; sourceTree = ""; }; 5D549854AD86485DF61F3B75 /* WordPrefixIndexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPrefixIndexTests.swift; sourceTree = ""; }; 5DCA3E46621815A19300365F /* fr.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = fr.txt; sourceTree = ""; }; - 5F2C4ED070BBE86378229BB5 /* GhostFontSizeStabilizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostFontSizeStabilizer.swift; sourceTree = ""; }; 5FF4FF5C43E650FF46F4BE76 /* THIRD_PARTY_LICENSES.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = THIRD_PARTY_LICENSES.md; sourceTree = ""; }; 6017E8AEDF88042933C61DFD /* SuggestionInteractionState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionInteractionState.swift; sourceTree = ""; }; 601C120D3DB41535E1D90E24 /* WebContentFieldDetectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebContentFieldDetectorTests.swift; sourceTree = ""; }; @@ -1118,7 +1221,9 @@ 61CCBF33813CE5CD3B9EA37B /* SpellingLanguageResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpellingLanguageResolver.swift; sourceTree = ""; }; 61DFA08E857DB36A7876FFEA /* EngineAndModelPaneView+Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EngineAndModelPaneView+Actions.swift"; sourceTree = ""; }; 620D393D3B7E687A08FA9446 /* es-100l.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "es-100l.txt"; sourceTree = ""; }; + 623D8B3C927836D7C4002AB8 /* HostFaceMemoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostFaceMemoryTests.swift; sourceTree = ""; }; 626861231138509BCD7BB6EA /* EmojiQueryRunTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiQueryRunTests.swift; sourceTree = ""; }; + 627E02913F35A5802D48A207 /* CaretAdvanceSamplerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaretAdvanceSamplerTests.swift; sourceTree = ""; }; 6428AF104998876A60AC602C /* WordPrefixIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPrefixIndex.swift; sourceTree = ""; }; 64AEF94A48DF96D6BEE3048C /* OnboardingFlowSteps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingFlowSteps.swift; sourceTree = ""; }; 64B0584FA16465453F04CA8C /* OCRTextHygieneTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OCRTextHygieneTests.swift; sourceTree = ""; }; @@ -1127,6 +1232,7 @@ 6586D6D735F287F43D42520A /* DeviceInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceInfo.swift; sourceTree = ""; }; 65AF0FF1CE97A0761ADAFEB8 /* InlineCommandCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineCommandCoordinatorTests.swift; sourceTree = ""; }; 65E97C4BFAF5EAF6792170AC /* FocusSnapshotExternalApplicationIdentityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusSnapshotExternalApplicationIdentityTests.swift; sourceTree = ""; }; + 665A55CA2E7975CBFC63B4E2 /* InkBaselineAnalyzer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InkBaselineAnalyzer.swift; sourceTree = ""; }; 66B5FF2D1DCC846AABC134CF /* MenuBarRecoveryPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarRecoveryPolicyTests.swift; sourceTree = ""; }; 66CF2A70D4699421AC9BD849 /* NOTICE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = NOTICE.md; sourceTree = ""; }; 66D6D153C80A5BDE4905B81E /* MacroEngineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacroEngineTests.swift; sourceTree = ""; }; @@ -1144,6 +1250,7 @@ 6C034C1C9E51031A845D1184 /* SuggestionEngineRouterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionEngineRouterTests.swift; sourceTree = ""; }; 6C28F67F089B66EC18C24182 /* EmojiPopularityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPopularityTests.swift; sourceTree = ""; }; 6C74D10CCC6CB008695F5C0A /* SentenceBoundaryClassifierTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentenceBoundaryClassifierTests.swift; sourceTree = ""; }; + 6C896CEDC632FB738D3A42BF /* SignOffCue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignOffCue.swift; sourceTree = ""; }; 6C8C1A4EC935BB837432BAF2 /* InsertedTextAdvance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsertedTextAdvance.swift; sourceTree = ""; }; 6CF1FBAABEF545B620AF8D78 /* ru-100k.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "ru-100k.txt"; sourceTree = ""; }; 6D8FE1B9BEA0B35C6910E534 /* SuggestionContinuationPlan.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionContinuationPlan.swift; sourceTree = ""; }; @@ -1151,6 +1258,7 @@ 6E2AA5BD865E0BCFB53DC699 /* AXHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AXHelperTests.swift; sourceTree = ""; }; 6E3BA84BBFD8B2501767516C /* SuggestionConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionConfiguration.swift; sourceTree = ""; }; 6E53B16C1E0CE667E9666C71 /* SettingsAttentionEvaluatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAttentionEvaluatorTests.swift; sourceTree = ""; }; + 6E5BA02F079AD5EB20CDB215 /* TypefaceMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypefaceMatcher.swift; sourceTree = ""; }; 6E6A9735EBD163A2E8AD67A5 /* PhrasePredictionScreenContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhrasePredictionScreenContext.swift; sourceTree = ""; }; 6E824A5614BC7E7FCA05AD29 /* TrailingDuplicationFilterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrailingDuplicationFilterTests.swift; sourceTree = ""; }; 6F0EE728C0B1A7AD6B19CD0C /* AGPL-3.0.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "AGPL-3.0.txt"; sourceTree = ""; }; @@ -1162,6 +1270,7 @@ 705E9119B1661BF464496D39 /* CompletionRenderModePolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionRenderModePolicy.swift; sourceTree = ""; }; 70C489EC7EA50C2B103A6B09 /* Sparkle-LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "Sparkle-LICENSE.txt"; sourceTree = ""; }; 70F68E9A9255F607C4439ED5 /* VisualContextStartCoalescerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualContextStartCoalescerTests.swift; sourceTree = ""; }; + 7103021AEC287186CE25415D /* HostFaceMemory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostFaceMemory.swift; sourceTree = ""; }; 711293EA57808B9428C7B908 /* CotabbyAppEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CotabbyAppEnvironment.swift; sourceTree = ""; }; 7113D3373525113CA69E7597 /* PermissionsPaneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionsPaneView.swift; sourceTree = ""; }; 7174EF88BA41ACC0A74CA582 /* OnboardingTemplateFeatureList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingTemplateFeatureList.swift; sourceTree = ""; }; @@ -1169,6 +1278,7 @@ 72B8156EEF23664F775190AB /* SpellingDictionaryPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpellingDictionaryPicker.swift; sourceTree = ""; }; 733BF6287BDE599B02A12271 /* CurrentWordSpellChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentWordSpellChecker.swift; sourceTree = ""; }; 7344003A6327C5FBDA581028 /* HostFontRegistryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostFontRegistryTests.swift; sourceTree = ""; }; + 734608EBE2319F927C4EDDD4 /* HostBundledFontRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostBundledFontRegistry.swift; sourceTree = ""; }; 74369DE9087CB47CC566657A /* PhrasePredictionEvalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhrasePredictionEvalTests.swift; sourceTree = ""; }; 743B7207A91EB1BFADD0C5E7 /* SuggestionClientError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionClientError.swift; sourceTree = ""; }; 74AB26FDFBBAC0C6375C4192 /* GhostTextColorPreset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostTextColorPreset.swift; sourceTree = ""; }; @@ -1180,6 +1290,7 @@ 77B0121E7BB173F8A2B0B108 /* WindowScreenshotService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowScreenshotService.swift; sourceTree = ""; }; 77DB52780A112A686C06D7E3 /* EmojiPopularity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPopularity.swift; sourceTree = ""; }; 7814C85DA992C8124474FD81 /* SuggestionCoordinator+Lifecycle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SuggestionCoordinator+Lifecycle.swift"; sourceTree = ""; }; + 78721FCF04E4D6F0C7C5DDCF /* llama-recall-cases.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "llama-recall-cases.json"; sourceTree = ""; }; 790082EFCDEF94454A3B805F /* SuggestionContinuationPlanTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionContinuationPlanTests.swift; sourceTree = ""; }; 79AA39C97E8A959E9A5B6F04 /* FocusCapabilityFlickerGate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusCapabilityFlickerGate.swift; sourceTree = ""; }; 79AAAB1C46A0E5A9C6FC7D3D /* SettingsAttentionEvaluator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAttentionEvaluator.swift; sourceTree = ""; }; @@ -1191,6 +1302,7 @@ 7D2E806C1915514651343055 /* HuggingFaceModelsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HuggingFaceModelsTests.swift; sourceTree = ""; }; 7D2F4ED8C7493EF4B967BFF9 /* Aria2Provisioner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Aria2Provisioner.swift; sourceTree = ""; }; 7DE038A6DF4A16B3EB5CEBD3 /* MenuBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarView.swift; sourceTree = ""; }; + 7F19230243E3B5DE5A76244C /* SignOffCueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignOffCueTests.swift; sourceTree = ""; }; 7F7E66CFA5AB733CE1F793A8 /* MarkerSelectionSynthesizerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkerSelectionSynthesizerTests.swift; sourceTree = ""; }; 80588CA72AB588807BE1C499 /* TokenHealingBufferTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenHealingBufferTests.swift; sourceTree = ""; }; 8159F4DD2F03935DD17BD907 /* EmojiTriggerStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiTriggerStateMachine.swift; sourceTree = ""; }; @@ -1198,6 +1310,7 @@ 81B57905DE20AD5E88BAE0B0 /* BrowserDomain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserDomain.swift; sourceTree = ""; }; 81C4E4D11C9BE3B53F79FD36 /* TypingPredictionCandidateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingPredictionCandidateTests.swift; sourceTree = ""; }; 81D7F428477976398CFC5CCF /* DecodeStopPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecodeStopPolicyTests.swift; sourceTree = ""; }; + 82420F9505E69AE2ADE9F583 /* BlockBreakAlignment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockBreakAlignment.swift; sourceTree = ""; }; 83457CCF1A50CE83428C363D /* Aria2OutputParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Aria2OutputParser.swift; sourceTree = ""; }; 8368940C6C15CC3F932B8DFC /* ContextLivePreviewField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextLivePreviewField.swift; sourceTree = ""; }; 846A1C3328B3B1CB42E2E46B /* EmojiUsageStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiUsageStore.swift; sourceTree = ""; }; @@ -1222,6 +1335,7 @@ 8BE5F414704A8264C2946A50 /* TypoGateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypoGateTests.swift; sourceTree = ""; }; 8C151BF4D39485E5CFACFECB /* ApplicationBundleMetadataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationBundleMetadataTests.swift; sourceTree = ""; }; 8D881FED12A85FFC20F2C9D7 /* DisplayCoordinateConverterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayCoordinateConverterTests.swift; sourceTree = ""; }; + 8DAD5637347E83DDCF515568 /* SuggestionCoordinator+HostMarkedText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SuggestionCoordinator+HostMarkedText.swift"; sourceTree = ""; }; 8E542E57459488F3D39A9053 /* PhrasePredictionScoringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhrasePredictionScoringTests.swift; sourceTree = ""; }; 8E89746E8CE7E9487337EE6F /* InsertionSafetyGate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsertionSafetyGate.swift; sourceTree = ""; }; 9227DE16CA5CE2E085C1A891 /* PerAppShortcutOverrideStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerAppShortcutOverrideStoreTests.swift; sourceTree = ""; }; @@ -1232,6 +1346,7 @@ 928A884292355FEF8B605B64 /* SuggestionFadeInPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionFadeInPolicyTests.swift; sourceTree = ""; }; 9316289DD11A72D3EAC0A929 /* CompositionInputModeClassifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionInputModeClassifier.swift; sourceTree = ""; }; 94A26D86030FC5FA67371C52 /* DateMacroEvaluator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateMacroEvaluator.swift; sourceTree = ""; }; + 94B45E958CF9BE63A728E273 /* TypefaceEvidenceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypefaceEvidenceTests.swift; sourceTree = ""; }; 95C62A4403E4815004C6FCB8 /* SettingsSearchRankerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSearchRankerTests.swift; sourceTree = ""; }; 961A47BBFDBF7C233BC4DDC4 /* TextLayoutCaretEstimatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextLayoutCaretEstimatorTests.swift; sourceTree = ""; }; 9720913FECB22A9A28C96333 /* SuggestionOverlayViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionOverlayViews.swift; sourceTree = ""; }; @@ -1241,6 +1356,7 @@ 98423150126CC5969C2886BA /* AXTextGeometryResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AXTextGeometryResolver.swift; sourceTree = ""; }; 99FBB636008490B66CF26772 /* frequency_dictionary_en_82_765.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = frequency_dictionary_en_82_765.txt; sourceTree = ""; }; 9A02F9151AEBBD825D64CEFA /* SuggestionDebugLoggerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionDebugLoggerTests.swift; sourceTree = ""; }; + 9A39920BD5E23E4D2E9AF037 /* GhostCaretRefinement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostCaretRefinement.swift; sourceTree = ""; }; 9A3C5A66AA93E50E4A64ED46 /* OverlayController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlayController.swift; sourceTree = ""; }; 9AA97052545B05BA82450A6D /* TokenCountEstimatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenCountEstimatorTests.swift; sourceTree = ""; }; 9ABC808DE01AF9AD04E38943 /* OnboardingStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStyle.swift; sourceTree = ""; }; @@ -1252,15 +1368,19 @@ 9DE86A49339BB507D197629D /* MenuBarPresentationObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarPresentationObserver.swift; sourceTree = ""; }; 9E76C76AEE756F43787EEECE /* SuggestionCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionCoordinator.swift; sourceTree = ""; }; 9E7D1184F0BC707F0A9148DD /* CustomRulesEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomRulesEditor.swift; sourceTree = ""; }; + 9EBE00782EE6813AF3224CD5 /* CaretTokenPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaretTokenPosition.swift; sourceTree = ""; }; 9EEDCE7B9BC5B22080B3A8DF /* SuggestionSessionReconciliationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionSessionReconciliationTests.swift; sourceTree = ""; }; 9F63A5EF992329FB9F6C4C26 /* HardwareCapabilityProbe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HardwareCapabilityProbe.swift; sourceTree = ""; }; 9F8418BB40BEE4BCAFB4516B /* EmojiPickerPanelLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerPanelLayout.swift; sourceTree = ""; }; + 9FA2CFC322EE56151646E745 /* HostBundledFontRegistryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostBundledFontRegistryTests.swift; sourceTree = ""; }; A01BF1613C95C767A2E93D8F /* ContextPaneViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextPaneViewTests.swift; sourceTree = ""; }; A04DFC5BD7821D73DA21D7DB /* TagChip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagChip.swift; sourceTree = ""; }; A061B0B064B469180B2F1D63 /* TypingExperienceEvalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingExperienceEvalTests.swift; sourceTree = ""; }; A0D3B0DE3EF3F31AE1E01828 /* EmojiTriggerStateMachineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiTriggerStateMachineTests.swift; sourceTree = ""; }; + A166301D0A276214F895A963 /* ChromiumInlineAutocompleteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChromiumInlineAutocompleteTests.swift; sourceTree = ""; }; A1C9DBC2A94B6E805EDAFEA5 /* MacroTriggerStateMachineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacroTriggerStateMachineTests.swift; sourceTree = ""; }; A1D44E50290A78AFAE7753DB /* KeyRecorderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyRecorderView.swift; sourceTree = ""; }; + A236731DA3FFF8FEECB67167 /* HostAdvanceFitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostAdvanceFitTests.swift; sourceTree = ""; }; A25B3A7C488A6D863FE56113 /* ModelDownloadTransferControls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelDownloadTransferControls.swift; sourceTree = ""; }; A28B4A4368DB33C25E3AB5F3 /* EmojiPaneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPaneView.swift; sourceTree = ""; }; A29DF1C68154C21E6943D38E /* InputModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputModels.swift; sourceTree = ""; }; @@ -1268,6 +1388,7 @@ A2E9AF341F8F7043D5344B20 /* SymSpell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymSpell.swift; sourceTree = ""; }; A306C7B2B0790A0B98D8F353 /* PerDomainDisableSettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerDomainDisableSettingsTests.swift; sourceTree = ""; }; A3E8E86A14090BC7BD13BA76 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + A5B09274BE9500E3C41C18B2 /* HostZoomLadder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostZoomLadder.swift; sourceTree = ""; }; A66389B78743ABACE7CD4D82 /* SuggestionWorkController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionWorkController.swift; sourceTree = ""; }; A6B0048AAD1B2A4EA09DCEE0 /* UnitConversionEvaluator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitConversionEvaluator.swift; sourceTree = ""; }; A73D95F2BF5CA59705653931 /* PromptSectionBudgetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromptSectionBudgetTests.swift; sourceTree = ""; }; @@ -1280,6 +1401,7 @@ A9805B5D25A6E261D92E0ADD /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; }; A9FD2044CDEA95EC02C9B2FE /* MarkerSelectionSynthesizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkerSelectionSynthesizer.swift; sourceTree = ""; }; AAD02052E51AFE56BECB92E4 /* EmojiPickerControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerControllerTests.swift; sourceTree = ""; }; + AAE4180FC6B2E085CEE93F23 /* HostTextMetricsProbe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostTextMetricsProbe.swift; sourceTree = ""; }; AAE89BEAF57DF3A287DF56F1 /* PromptSectionBudget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromptSectionBudget.swift; sourceTree = ""; }; AB84C175EA5B59E118F58C49 /* llama-eval-cases.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "llama-eval-cases.json"; sourceTree = ""; }; AC03B99F8F0C435522D44238 /* LICENSE */ = {isa = PBXFileReference; path = LICENSE; sourceTree = ""; }; @@ -1291,6 +1413,7 @@ AEFBEB715AF043D3315B6656 /* ModelDownloadSessionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelDownloadSessionDelegate.swift; sourceTree = ""; }; AF02469BF40CC6BAF2675E37 /* EmojiRecentsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiRecentsTests.swift; sourceTree = ""; }; AF12E007D04AAC60FE14651B /* FocusCapabilityFlickerGateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusCapabilityFlickerGateTests.swift; sourceTree = ""; }; + AF584D56E44086925BC0E345 /* SuggestionLengthPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionLengthPolicyTests.swift; sourceTree = ""; }; AFBE491B3CA04FE9069B7B0F /* AppearancePaneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearancePaneView.swift; sourceTree = ""; }; B0ACD725A16F26E9F29124E4 /* SuggestionPauseModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionPauseModels.swift; sourceTree = ""; }; B0C41A249D9538B91B7FA8A9 /* PermissionGuidanceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionGuidanceController.swift; sourceTree = ""; }; @@ -1304,25 +1427,31 @@ B2CAE5772E4C748960219DA0 /* SettingsNavigationModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsNavigationModelTests.swift; sourceTree = ""; }; B3732CD6886AEAF3C01F543E /* MirrorOverlayLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MirrorOverlayLayout.swift; sourceTree = ""; }; B3FA4E40E8DF352247E1374B /* SystemResourceSamplerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemResourceSamplerTests.swift; sourceTree = ""; }; + B40016262B83C6C55B6593C7 /* CaretLagPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaretLagPolicy.swift; sourceTree = ""; }; B41F06FEF208B30ECCF23A6F /* MacroModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacroModels.swift; sourceTree = ""; }; B494C76056D9B01D6A3ECABD /* SuggestionEngineModelsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionEngineModelsTests.swift; sourceTree = ""; }; B4C5E3A714F5A7818337101C /* OnboardingTemplateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingTemplateTests.swift; sourceTree = ""; }; B53ED6F2F3354FB3E6065804 /* WelcomeTemplateStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeTemplateStepView.swift; sourceTree = ""; }; B61134664FF616732705A89B /* DebouncePolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebouncePolicyTests.swift; sourceTree = ""; }; + B616CB46A8624BC4E4FBB01A /* TypefaceEvidence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypefaceEvidence.swift; sourceTree = ""; }; B630D1F95FD08BC30E624672 /* DebouncePolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebouncePolicy.swift; sourceTree = ""; }; + B68F9EDFE2903996F0E5524E /* MailHeaderFieldDetectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailHeaderFieldDetectorTests.swift; sourceTree = ""; }; + B753DA96CA6BC168E675E61E /* GhostWrapBandPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostWrapBandPolicy.swift; sourceTree = ""; }; B81DD30EB657368AACE9625A /* InputMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputMonitor.swift; sourceTree = ""; }; B8EBC9F1890DD2FFC4884A5C /* SecureFieldDetectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureFieldDetectorTests.swift; sourceTree = ""; }; B9F5D16600EE9D4B76D04A26 /* CaretGeometrySelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaretGeometrySelector.swift; sourceTree = ""; }; - BA6E6BC47B781B63640E25DA /* GhostSuggestionLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostSuggestionLayoutTests.swift; sourceTree = ""; }; + BA2BBA3187237680274BF80E /* HostTextMetricsCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostTextMetricsCacheTests.swift; sourceTree = ""; }; BA991392A72658B758551BBA /* PerformanceMetricsStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerformanceMetricsStoreTests.swift; sourceTree = ""; }; BADB38D0160B47637572FC5E /* SettingsSidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSidebarView.swift; sourceTree = ""; }; BB36177FC6C26F5E4CD1AD6E /* InputMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputMonitorTests.swift; sourceTree = ""; }; BC07B7CE715C69A63E6123DD /* SuggestionSettingsModelDisabledAppsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionSettingsModelDisabledAppsTests.swift; sourceTree = ""; }; BC4F887528AE74AC0DD30314 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + BC7FA0146AC6B78725105BA4 /* InkBaselineAnalyzerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InkBaselineAnalyzerTests.swift; sourceTree = ""; }; BC812721A3E5327154D6CD4E /* LlamaSuggestionEngineCancellationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LlamaSuggestionEngineCancellationTests.swift; sourceTree = ""; }; BCF2889446A21BC8AC773A81 /* CotabbyTestFixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CotabbyTestFixtures.swift; sourceTree = ""; }; BD1E28CF46BF59ABDC3056BF /* ApplicationBundleMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationBundleMetadata.swift; sourceTree = ""; }; BD33A18BECBD7292ACD9F8B8 /* TextDirectionDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextDirectionDetector.swift; sourceTree = ""; }; + BDA5F007F5185BD597550607 /* CompletionContentPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionContentPolicyTests.swift; sourceTree = ""; }; BE354770CC59461EE0E976D3 /* SettingsPaneScaffold.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPaneScaffold.swift; sourceTree = ""; }; BE5E9B2A196282738F8D4BAD /* CaretLinePosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaretLinePosition.swift; sourceTree = ""; }; BE6EA89178F52F6D5B90CBFE /* LanguageCatalog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageCatalog.swift; sourceTree = ""; }; @@ -1331,6 +1460,7 @@ BFB53B865B0FA4378DEF2054 /* LanguageTagsEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageTagsEditor.swift; sourceTree = ""; }; C01F11F7F29EFC3A27A1EE5D /* FocusSnapshotResolverSelectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusSnapshotResolverSelectionTests.swift; sourceTree = ""; }; C0894DA7EA3E157596DA0E66 /* SuggestionStaleAcceptanceEchoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionStaleAcceptanceEchoTests.swift; sourceTree = ""; }; + C08EC70547439E840417F5A5 /* GhostBaselinePolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostBaselinePolicy.swift; sourceTree = ""; }; C1337F5C94FA9A6388841805 /* FocusPollBackoffTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusPollBackoffTests.swift; sourceTree = ""; }; C1A8962FE08E1C76F0EEF423 /* CaretRunPlacementTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaretRunPlacementTests.swift; sourceTree = ""; }; C1D68B5ABA427D3E87000E78 /* InlineCommandCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineCommandCoordinator.swift; sourceTree = ""; }; @@ -1345,6 +1475,7 @@ C4B70455CA75A08FAD0F7409 /* ClipboardContextProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipboardContextProvider.swift; sourceTree = ""; }; C57BCCAF434D44772948B696 /* FileLogHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileLogHandlerTests.swift; sourceTree = ""; }; C59EC159778E4802683A190E /* TypingPredictionCandidate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingPredictionCandidate.swift; sourceTree = ""; }; + C5C0792BF971B702DE0A095E /* GhostTextLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostTextLayout.swift; sourceTree = ""; }; C648EBB10D7F8E0B904DEC91 /* de.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = de.txt; sourceTree = ""; }; C696204AAEBA357129F77646 /* HuggingFaceModelBrowserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HuggingFaceModelBrowserView.swift; sourceTree = ""; }; C6A19E3F68A42D2FAC95E000 /* SuggestionTextNormalizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionTextNormalizer.swift; sourceTree = ""; }; @@ -1361,13 +1492,13 @@ CC1EDFB535AAA2EE0D67828A /* CotabbyApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CotabbyApp.swift; sourceTree = ""; }; CC5CD24D2FF35FD36B8D78DE /* SuggestionQualityMetricsStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionQualityMetricsStoreTests.swift; sourceTree = ""; }; CD1FC547BE2E96D2ABB55DF4 /* LlamaSuggestionEngineStreamingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LlamaSuggestionEngineStreamingTests.swift; sourceTree = ""; }; + CE8B99181BF74C1F1DC5F6A7 /* HostTextMetricsCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostTextMetricsCache.swift; sourceTree = ""; }; CEDD364CC7C995916BD1E4B0 /* TokenHealingPlanTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenHealingPlanTests.swift; sourceTree = ""; }; CF63186E4DB1E071F6A3C58B /* Gemoji-MIT.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "Gemoji-MIT.txt"; sourceTree = ""; }; CF96999230079A768BEFD0FC /* KeycapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeycapView.swift; sourceTree = ""; }; CFB06531A54FDCDBAAD14E8B /* FocusedInputContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusedInputContext.swift; sourceTree = ""; }; CFB3F1AA4007F0B44CA70E6F /* SymSpellTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymSpellTests.swift; sourceTree = ""; }; D07B9AB842C16672AC490EFB /* FocusCapabilityResolverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusCapabilityResolverTests.swift; sourceTree = ""; }; - D0C68A9C7C77AF6B111782FD /* GhostFontMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostFontMetrics.swift; sourceTree = ""; }; D0DB1F528070F8F9108720DB /* SettingsSearchResultRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSearchResultRow.swift; sourceTree = ""; }; D1123AB515110BD0CBA39490 /* HomePaneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomePaneView.swift; sourceTree = ""; }; D1499B80A697DAF5ABD8298E /* GhostTextPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostTextPreview.swift; sourceTree = ""; }; @@ -1379,17 +1510,23 @@ D3C1C9A9BF1F74C16E4401EC /* CotabbyBrand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CotabbyBrand.swift; sourceTree = ""; }; D47E1471C110614F0D07E399 /* PermissionModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionModels.swift; sourceTree = ""; }; D48B95B6665109B6C6A63B42 /* WritingPaneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WritingPaneView.swift; sourceTree = ""; }; + D4963D7DB8B810CD0C4E8CF2 /* CaretLagPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaretLagPolicyTests.swift; sourceTree = ""; }; D4BA53E8B4DBF1612C8F70BC /* SuggestionFocusFreshnessTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionFocusFreshnessTests.swift; sourceTree = ""; }; D4DB988251D2B907F316A3B8 /* EmojiSynonymCatalogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiSynonymCatalogTests.swift; sourceTree = ""; }; + D52263602D578987A7ADC776 /* PixelCaretLocatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelCaretLocatorTests.swift; sourceTree = ""; }; D5E16CB478D3F015EE0C5D8F /* LlamaPromptCacheHintTrackerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LlamaPromptCacheHintTrackerTests.swift; sourceTree = ""; }; + D681DDF8E81F868C1BC92CB4 /* BlockBreakAlignmentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockBreakAlignmentTests.swift; sourceTree = ""; }; D71686BC29B7E0FACFF0B19C /* SpellingLanguageResolverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpellingLanguageResolverTests.swift; sourceTree = ""; }; D72CC98CFDA795F8210198E8 /* RuntimeBootstrapModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeBootstrapModelTests.swift; sourceTree = ""; }; D841ADCE3832A39042F21A9F /* CurrencyEvaluatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyEvaluatorTests.swift; sourceTree = ""; }; D84A5FB8D9F0333B0B8E9C5F /* SuggestionCoordinatorTestSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionCoordinatorTestSupport.swift; sourceTree = ""; }; + D86D5157D7E3F4447D2DE7F7 /* CaretAdvanceSampleStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaretAdvanceSampleStore.swift; sourceTree = ""; }; D89680F74D4E1097F81FC02A /* SuggestionPresentationTimingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionPresentationTimingTests.swift; sourceTree = ""; }; D8C2663427BC5219EA415D00 /* ru.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ru.txt; sourceTree = ""; }; D92D3845798F3EF8F24853AE /* FocusedInputContextTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusedInputContextTests.swift; sourceTree = ""; }; + D99C3973D57C7F53416CFBF6 /* GhostSpaceBoundary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostSpaceBoundary.swift; sourceTree = ""; }; D9C1C921A1CDA2ADFC39EA01 /* AppsPaneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppsPaneView.swift; sourceTree = ""; }; + D9CFD041A13DACE5612DC991 /* HostMarkedTextPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostMarkedTextPolicy.swift; sourceTree = ""; }; DA02598F555696782E180DA5 /* ShortcutConflictTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutConflictTests.swift; sourceTree = ""; }; DA0447A27B293394D9379C48 /* ExtendedContextTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtendedContextTests.swift; sourceTree = ""; }; DAC4D7129517D678D95D0BC2 /* PromptPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromptPolicyTests.swift; sourceTree = ""; }; @@ -1402,6 +1539,8 @@ DD834BCFF812935889E16D5A /* SuggestionRequestFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionRequestFactory.swift; sourceTree = ""; }; DDA0A1D64275BC85AEBBB852 /* Aria2DownloadServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Aria2DownloadServiceTests.swift; sourceTree = ""; }; DDA17364BAFFA56E7DDF682D /* OpenAICompatibleAPIClientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAICompatibleAPIClientTests.swift; sourceTree = ""; }; + DDA2EA95C425C3EAF246C66D /* SuggestionLengthPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionLengthPolicy.swift; sourceTree = ""; }; + DDC1ECBE9D1EDE855B57A913 /* HostBaselineCalibrator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostBaselineCalibrator.swift; sourceTree = ""; }; DE29E54D78A9DD7CD87626DD /* LanguageSupportTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageSupportTests.swift; sourceTree = ""; }; DE6E2A19BF6B87A0C6FA9D49 /* Aria2DownloadService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Aria2DownloadService.swift; sourceTree = ""; }; DEBD6113A3C1038BECC99245 /* PerformancePaneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerformancePaneView.swift; sourceTree = ""; }; @@ -1423,11 +1562,14 @@ E94A6036F4B703A2FF7F68EF /* DownloadFileRescuerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadFileRescuerTests.swift; sourceTree = ""; }; EA8311FAC345FE431FA89855 /* EmojiMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiMatcher.swift; sourceTree = ""; }; EAA290A7B1BF37F3D125DC8F /* SuggestionCoordinatorPredictionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionCoordinatorPredictionTests.swift; sourceTree = ""; }; + EAC61CA8FEFB45FB1CCA453A /* GhostFontSizeLimits.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostFontSizeLimits.swift; sourceTree = ""; }; EB07C5F0319D124B6266BCF3 /* AXTreeDumpWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AXTreeDumpWriter.swift; sourceTree = ""; }; EB13342992EEF2BDEEAB1236 /* ChromiumAccessibilityEnabler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChromiumAccessibilityEnabler.swift; sourceTree = ""; }; + EB446CE06049A4F958C9DC91 /* OverlayControllerTypefaceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlayControllerTypefaceTests.swift; sourceTree = ""; }; EB4A5E6B82A38B9A579E5AA2 /* PostExhaustionAcceptanceStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostExhaustionAcceptanceStateTests.swift; sourceTree = ""; }; EB630F9814388203DD1CA2EC /* ShortcutsPaneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsPaneView.swift; sourceTree = ""; }; EBD7B94FF42D8C538C328E80 /* BundledRuntimeLocatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundledRuntimeLocatorTests.swift; sourceTree = ""; }; + EBF9FDA4F652DCC91A724314 /* HostMarkedTextPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostMarkedTextPolicyTests.swift; sourceTree = ""; }; EC3DF6826CD5E4EF695EF163 /* StreamedGhostTextPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamedGhostTextPolicy.swift; sourceTree = ""; }; ECC1416A95346CB72BE036FE /* DisplayCoordinateConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayCoordinateConverter.swift; sourceTree = ""; }; ECD5C8648740E7050CCA657F /* FocusPollBackoff.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusPollBackoff.swift; sourceTree = ""; }; @@ -1435,6 +1577,7 @@ ED76F12EBF9C7E7B9AA95142 /* SuggestionAvailabilityEvaluatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionAvailabilityEvaluatorTests.swift; sourceTree = ""; }; ED7B674D586540F35682C573 /* SuggestionCoordinator+Prediction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SuggestionCoordinator+Prediction.swift"; sourceTree = ""; }; ED8672B87CEC72BE3978C6BB /* CotabbyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CotabbyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + EEDA42939A0406D27EB799A9 /* HostZoomLadderResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostZoomLadderResolver.swift; sourceTree = ""; }; EF21767F255146EA968B267D /* InlineFeatureTestDoubles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineFeatureTestDoubles.swift; sourceTree = ""; }; EF4D6CEDF032A25EF72D7228 /* KeyCodeLabels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyCodeLabels.swift; sourceTree = ""; }; F000304686E4573BC351AD3F /* PermissionDragSourceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionDragSourceView.swift; sourceTree = ""; }; @@ -1442,9 +1585,11 @@ F155556C751FB074B31CCC64 /* WelcomeHeroDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeHeroDemo.swift; sourceTree = ""; }; F1A4C5DEA32868F99E953D99 /* CompletionSeamGuard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionSeamGuard.swift; sourceTree = ""; }; F1E69CFA319C5F9E4C30F699 /* SettingsQuickLinkCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsQuickLinkCard.swift; sourceTree = ""; }; + F23A711EA8EF13B480FF34DE /* HostBaselineCalibratorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostBaselineCalibratorTests.swift; sourceTree = ""; }; F26F50467C83B202FB4BAC2A /* WelcomePersonalizeStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomePersonalizeStepView.swift; sourceTree = ""; }; F2CC3E0A24F4608350657D80 /* LowPowerModeMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LowPowerModeMonitor.swift; sourceTree = ""; }; F35CD1B715FCBB12BEB6CE62 /* FieldEdgeIconIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldEdgeIconIndicatorView.swift; sourceTree = ""; }; + F3BB26526298A85454330591 /* GhostFontResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostFontResolver.swift; sourceTree = ""; }; F3D991D3536ED989C89AF44D /* EmojiUsageStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiUsageStoreTests.swift; sourceTree = ""; }; F47CD9DF50E7192A70D1870E /* CotabbyDebugOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CotabbyDebugOptions.swift; sourceTree = ""; }; F4B98B1ABB36E600D7ECA185 /* SpeculativeAcceptanceContextTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpeculativeAcceptanceContextTests.swift; sourceTree = ""; }; @@ -1456,6 +1601,7 @@ FA4364402391C8A8D9B3FCEC /* PhrasePredictionScoring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhrasePredictionScoring.swift; sourceTree = ""; }; FA97ED2A10499771F9C32B64 /* FoundationModelAvailabilityService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FoundationModelAvailabilityService.swift; sourceTree = ""; }; FACB1195A78B524AB65D2EF4 /* FoundationModelAvailabilityServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FoundationModelAvailabilityServiceTests.swift; sourceTree = ""; }; + FC19809AAB1314BD6CC61B20 /* InkCaretAnalyzer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InkCaretAnalyzer.swift; sourceTree = ""; }; FCD0F17E1E2BB8FE75A6E75E /* EmojiVariantResolverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiVariantResolverTests.swift; sourceTree = ""; }; FCE4F5CA49ED9988AC4F4C6C /* CaretLinePositionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaretLinePositionTests.swift; sourceTree = ""; }; FDAA9ADF6C0BD9D16016AA9B /* TokenHealingPlan.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenHealingPlan.swift; sourceTree = ""; }; @@ -1608,11 +1754,14 @@ 0F63ADF8231C373295DC37FD /* Style */ = { isa = PBXGroup; children = ( - 3E3785ACD1BBD9D866ADE8DC /* GhostFontMetricsTests.swift */, - 4F3AC792296D57BE1AD0D308 /* GhostFontSizeStabilizerTests.swift */, - 5A4D17B2105C51112B19B2C2 /* GhostHostScaleTrackerTests.swift */, + 58FA6A8D05AEEBE3501B478F /* GhostFontResolverTests.swift */, 47F6000E673651AE72243F8A /* GhostTextColorPresetPaletteTests.swift */, + A236731DA3FFF8FEECB67167 /* HostAdvanceFitTests.swift */, + 623D8B3C927836D7C4002AB8 /* HostFaceMemoryTests.swift */, + 52675961C22C88675CC4B107 /* HostZoomLadderTests.swift */, 655F56C230F45CAC13EEE59C /* SuggestionTextColorCodecTests.swift */, + 94B45E958CF9BE63A728E273 /* TypefaceEvidenceTests.swift */, + 10107090891BE11E2B34BC9E /* TypefaceMatcherTests.swift */, ); path = Style; sourceTree = ""; @@ -1649,7 +1798,12 @@ B9F5D16600EE9D4B76D04A26 /* CaretGeometrySelector.swift */, BE5E9B2A196282738F8D4BAD /* CaretLinePosition.swift */, ECC1416A95346CB72BE036FE /* DisplayCoordinateConverter.swift */, - 32F1EE1DBFDC6CDC288DB98D /* GhostSuggestionLayout.swift */, + C08EC70547439E840417F5A5 /* GhostBaselinePolicy.swift */, + 9A39920BD5E23E4D2E9AF037 /* GhostCaretRefinement.swift */, + C5C0792BF971B702DE0A095E /* GhostTextLayout.swift */, + B753DA96CA6BC168E675E61E /* GhostWrapBandPolicy.swift */, + 665A55CA2E7975CBFC63B4E2 /* InkBaselineAnalyzer.swift */, + FC19809AAB1314BD6CC61B20 /* InkCaretAnalyzer.swift */, B3732CD6886AEAF3C01F543E /* MirrorOverlayLayout.swift */, 4861CC2533F3EA222A0833A9 /* SuggestionAnchorCache.swift */, BD33A18BECBD7292ACD9F8B8 /* TextDirectionDetector.swift */, @@ -1687,6 +1841,7 @@ 1D306A6A810731674F649D0C /* Policy */ = { isa = PBXGroup; children = ( + B40016262B83C6C55B6593C7 /* CaretLagPolicy.swift */, 705E9119B1661BF464496D39 /* CompletionRenderModePolicy.swift */, C2B351B5FDF077F8FF49A9A7 /* SuggestionFadeInPolicy.swift */, 49DB247AACE1B28D45DB52B0 /* SuggestionOverlayStabilityGate.swift */, @@ -1715,8 +1870,11 @@ 20F159786DD8DE3219469EE4 /* Output */ = { isa = PBXGroup; children = ( + BDA5F007F5185BD597550607 /* CompletionContentPolicyTests.swift */, 7CC0C7FE7FB134C225EA7B71 /* CompletionSeamGuardTests.swift */, E2F60BE5EDABE8882CA2F63B /* ConfidenceSuppressionPolicyTests.swift */, + 1FD9262617968D8D1EF1EA45 /* GhostSpaceBoundaryTests.swift */, + AF584D56E44086925BC0E345 /* SuggestionLengthPolicyTests.swift */, 55C8ED5A6E0648BAE6C4DC55 /* SuggestionTextNormalizerTests.swift */, 6E824A5614BC7E7FCA05AD29 /* TrailingDuplicationFilterTests.swift */, ); @@ -1933,7 +2091,12 @@ 3EA5A1BC99AA99A0A910F324 /* Presentation */ = { isa = PBXGroup; children = ( + F23A711EA8EF13B480FF34DE /* HostBaselineCalibratorTests.swift */, + 147D8EAD035A66FCA1D07E37 /* HostBaselineCalibratorTypefaceTests.swift */, + 9FA2CFC322EE56151646E745 /* HostBundledFontRegistryTests.swift */, 7344003A6327C5FBDA581028 /* HostFontRegistryTests.swift */, + EB446CE06049A4F958C9DC91 /* OverlayControllerTypefaceTests.swift */, + D52263602D578987A7ADC776 /* PixelCaretLocatorTests.swift */, ); path = Presentation; sourceTree = ""; @@ -1990,11 +2153,15 @@ 4F37A3A455E254F04FB255D6 /* Style */ = { isa = PBXGroup; children = ( - D0C68A9C7C77AF6B111782FD /* GhostFontMetrics.swift */, - 5F2C4ED070BBE86378229BB5 /* GhostFontSizeStabilizer.swift */, - 0D40D1FC651686EF76ECADE3 /* GhostHostScaleTracker.swift */, + F3BB26526298A85454330591 /* GhostFontResolver.swift */, + EAC61CA8FEFB45FB1CCA453A /* GhostFontSizeLimits.swift */, 74AB26FDFBBAC0C6375C4192 /* GhostTextColorPreset.swift */, + 3A5DC5FF5BD2995FB6709A65 /* HostAdvanceFit.swift */, + 7103021AEC287186CE25415D /* HostFaceMemory.swift */, + A5B09274BE9500E3C41C18B2 /* HostZoomLadder.swift */, 11CF768650A90705FC0D2730 /* SuggestionTextColorCodec.swift */, + B616CB46A8624BC4E4FBB01A /* TypefaceEvidence.swift */, + 6E5BA02F079AD5EB20CDB215 /* TypefaceMatcher.swift */, ); path = Style; sourceTree = ""; @@ -2099,6 +2266,7 @@ 3D6234872B84ECB56A707945 /* ControlTokenMarkersTests.swift */, DAC4D7129517D678D95D0BC2 /* PromptPolicyTests.swift */, A73D95F2BF5CA59705653931 /* PromptSectionBudgetTests.swift */, + 7F19230243E3B5DE5A76244C /* SignOffCueTests.swift */, 9AA97052545B05BA82450A6D /* TokenCountEstimatorTests.swift */, ); path = Prompting; @@ -2185,6 +2353,7 @@ children = ( 6FC7ADFF0012541EFC762D0F /* AXTextGeometryResolverTests.swift */, C1A8962FE08E1C76F0EEF423 /* CaretRunPlacementTests.swift */, + A166301D0A276214F895A963 /* ChromiumInlineAutocompleteTests.swift */, 518453163741E02E053860E1 /* DeepGeometryWalkThrottleTests.swift */, 110F737140F015E1A18E5A58 /* FocusSnapshotResolverLiveTests.swift */, C01F11F7F29EFC3A27A1EE5D /* FocusSnapshotResolverSelectionTests.swift */, @@ -2241,7 +2410,9 @@ isa = PBXGroup; children = ( 5B5D1A7D938F633C6EE094F7 /* AXHelper.swift */, + 82420F9505E69AE2ADE9F583 /* BlockBreakAlignment.swift */, 3FE7D19D22434E3E24804999 /* CalendarAccessibilityCapturePolicy.swift */, + 47F1A6BCCA20EBE7314B79D3 /* MailHeaderFieldDetector.swift */, E286B9A912808088FDA6B4C4 /* PermissionOverlayTracker.swift */, 1EE99C84483D3A58D561C61D /* SecureFieldDetector.swift */, 13838E1372956FF2CFC5ECCC /* SelfCaptureGate.swift */, @@ -2311,6 +2482,7 @@ 74BD6723AA7FC884CE224E73 /* Request */ = { isa = PBXGroup; children = ( + 24A7F2B20877E19BD2B27D1E /* CaretTokenPositionTests.swift */, 1D274F82A32C7FB404964428 /* CaretWordContextTests.swift */, B61134664FF616732705A89B /* DebouncePolicyTests.swift */, 65E97C4BFAF5EAF6792170AC /* FocusSnapshotExternalApplicationIdentityTests.swift */, @@ -2347,6 +2519,7 @@ 7A890FAF8663115937A3DB86 /* Policy */ = { isa = PBXGroup; children = ( + D4963D7DB8B810CD0C4E8CF2 /* CaretLagPolicyTests.swift */, 21A5285E99934D0045CB1918 /* CompletionRenderModePolicyTests.swift */, 928A884292355FEF8B605B64 /* SuggestionFadeInPolicyTests.swift */, 38D10022BEFABAD14D16EBF2 /* SuggestionOverlayStabilityGateTests.swift */, @@ -2439,7 +2612,9 @@ isa = PBXGroup; children = ( 6E2AA5BD865E0BCFB53DC699 /* AXHelperTests.swift */, + D681DDF8E81F868C1BC92CB4 /* BlockBreakAlignmentTests.swift */, 863B5A1DC3C4B9668F620245 /* CalendarAccessibilityCapturePolicyTests.swift */, + B68F9EDFE2903996F0E5524E /* MailHeaderFieldDetectorTests.swift */, AD003D4EBE530DC0E2B87C24 /* PermissionOverlayTrackerTests.swift */, B8EBC9F1890DD2FFC4884A5C /* SecureFieldDetectorTests.swift */, 571B221ACAFED86DABB10F48 /* SelfCaptureGateTests.swift */, @@ -2463,6 +2638,7 @@ 98423150126CC5969C2886BA /* AXTextGeometryResolver.swift */, 9CCBD29CF7B0BA5A9CFDEEA3 /* DeepGeometryWalkThrottle.swift */, 6BEFDDFF24B78CDED4EB4206 /* FocusSnapshotResolver.swift */, + AAE4180FC6B2E085CEE93F23 /* HostTextMetricsProbe.swift */, 21E26C899DE4FE7AA9CC7869 /* StaticTextRunWalkThrottle.swift */, ); path = Resolution; @@ -2524,7 +2700,13 @@ 1BB44E9DE070D5EFC52B968A /* CaretGeometrySelectorTests.swift */, FCE4F5CA49ED9988AC4F4C6C /* CaretLinePositionTests.swift */, 8D881FED12A85FFC20F2C9D7 /* DisplayCoordinateConverterTests.swift */, - BA6E6BC47B781B63640E25DA /* GhostSuggestionLayoutTests.swift */, + 25782BF21045F20B9721DB90 /* GhostBaselinePolicyTests.swift */, + 4BF9A081715185297A31EB8F /* GhostCaretRefinementTests.swift */, + 3EB202C5AD6079174A14330C /* GhostTextLayoutTests.swift */, + 406F3107DC345254BF028C63 /* GhostTextPixelAlignmentTests.swift */, + 39AD68E34CF598F2C83D7C73 /* GhostWrapBandPolicyTests.swift */, + BC7FA0146AC6B78725105BA4 /* InkBaselineAnalyzerTests.swift */, + 3153FB925190B70E0502C43D /* InkCaretAnalyzerTests.swift */, 2D962AC9D24D00957562CA7B /* MirrorOverlayLayoutTests.swift */, BE7DB9EE77511823EDA7B52E /* SuggestionAnchorCacheTests.swift */, 00BD4DE5D9D8C9BB66B78633 /* TextDirectionDetectorTests.swift */, @@ -2595,8 +2777,10 @@ 9B8306AE4E44B4B443C32EEF /* Caching */ = { isa = PBXGroup; children = ( + D86D5157D7E3F4447D2DE7F7 /* CaretAdvanceSampleStore.swift */, 544C25CEF5C997F920C7BF0F /* FieldStyleCache.swift */, 43EBAC4CC8B8B8266F3E4DA1 /* FocusSessionScopedCache.swift */, + CE8B99181BF74C1F1DC5F6A7 /* HostTextMetricsCache.swift */, ); path = Caching; sourceTree = ""; @@ -2627,6 +2811,7 @@ 21047E3C6ACA1035669A7E06 /* AppSurfaceClassifierTests.swift */, 589029534F91D2BE26A65263 /* BrowserAppDetectorTests.swift */, CBD829565558CB1EA8CA69D5 /* BrowserDomainTests.swift */, + 209089420F2EE43C312657A7 /* SystemUIFocusShadowPolicyTests.swift */, 52C557203A5A5F08E68BB11B /* TerminalAppDetectorTests.swift */, ); path = Applications; @@ -2673,6 +2858,7 @@ 33D4F378E5C70CAF7827A90A /* ControlTokenMarkers.swift */, 3A051152D3C503A2339BF0FA /* FoundationModelPromptRenderer.swift */, AAE89BEAF57DF3A287DF56F1 /* PromptSectionBudget.swift */, + 6C896CEDC632FB738D3A42BF /* SignOffCue.swift */, 8A502BFB13373D1931B082CD /* TokenCountEstimator.swift */, ); path = Prompting; @@ -2693,6 +2879,7 @@ isa = PBXGroup; children = ( 1EE61CD2E8D2399E23D48818 /* CompositionInputModeClassifierTests.swift */, + EBF9FDA4F652DCC91A724314 /* HostMarkedTextPolicyTests.swift */, 97F14FC0E8E0A5D23FA2F54C /* KeyCodeLabelsTests.swift */, 7F7E66CFA5AB733CE1F793A8 /* MarkerSelectionSynthesizerTests.swift */, AE1EC85F13B5FC8E35C25638 /* ShortcutResolverTests.swift */, @@ -2704,6 +2891,7 @@ isa = PBXGroup; children = ( AB84C175EA5B59E118F58C49 /* llama-eval-cases.json */, + 78721FCF04E4D6F0C7C5DDCF /* llama-recall-cases.json */, 225D56594DCCBEAE2BD25E8D /* phrase-prediction-1337.json */, 409BB6D0152A1A9B37DE12F3 /* supported-model-regressions.json */, ); @@ -2811,6 +2999,7 @@ 9E76C76AEE756F43787EEECE /* SuggestionCoordinator.swift */, CA85693671D2A705F544B566 /* SuggestionCoordinator+Acceptance.swift */, 3FFC1D1322005305C7D482D2 /* SuggestionCoordinator+Continuation.swift */, + 8DAD5637347E83DDCF515568 /* SuggestionCoordinator+HostMarkedText.swift */, 3FFA31F5FA8D164CB6C30D06 /* SuggestionCoordinator+Input.swift */, 7814C85DA992C8124474FD81 /* SuggestionCoordinator+Lifecycle.swift */, ED7B674D586540F35682C573 /* SuggestionCoordinator+Prediction.swift */, @@ -2861,9 +3050,14 @@ 2E379AF08CDFF65D6EFC565E /* ActivationIndicatorController.swift */, 872C8DDC2E86A1C4C4BBD99F /* EmojiPickerPanelController.swift */, 8B4F6E70B8A242F7BDE5361A /* FocusDebugOverlayController.swift */, + 29163A3D0BB0F11EEAC2985A /* GhostTextPanelView.swift */, + DDC1ECBE9D1EDE855B57A913 /* HostBaselineCalibrator.swift */, + 734608EBE2319F927C4EDDD4 /* HostBundledFontRegistry.swift */, 0F328B43743D803A16045110 /* HostFontRegistry.swift */, + EEDA42939A0406D27EB799A9 /* HostZoomLadderResolver.swift */, 924CAA5E25C596A9FAB7602B /* InlinePreviewPanelController.swift */, 9A3C5A66AA93E50E4A64ED46 /* OverlayController.swift */, + 4A65BCABF4B23690AE3DBE93 /* PixelCaretLocator.swift */, ); path = Presentation; sourceTree = ""; @@ -2882,6 +3076,7 @@ children = ( B1C8D2853B43F01B285FB44C /* FieldStyleCacheTests.swift */, 487D8C5521411FC6845D4A51 /* FocusSessionScopedCacheTests.swift */, + BA2BBA3187237680274BF80E /* HostTextMetricsCacheTests.swift */, ); path = Caching; sourceTree = ""; @@ -2944,6 +3139,7 @@ C1052555A838E3AFF55EE6DC /* Request */ = { isa = PBXGroup; children = ( + 9EBE00782EE6813AF3224CD5 /* CaretTokenPosition.swift */, 976E3D6790D2A3594BCEEA23 /* CaretWordContext.swift */, B630D1F95FD08BC30E624672 /* DebouncePolicy.swift */, C3F56B79BF030507CC7FAE4D /* MidWordContinuationPolicy.swift */, @@ -3011,6 +3207,7 @@ isa = PBXGroup; children = ( 9316289DD11A72D3EAC0A929 /* CompositionInputModeClassifier.swift */, + D9CFD041A13DACE5612DC991 /* HostMarkedTextPolicy.swift */, EF4D6CEDF032A25EF72D7228 /* KeyCodeLabels.swift */, A9FD2044CDEA95EC02C9B2FE /* MarkerSelectionSynthesizer.swift */, E83C6E965A3978D0EBFB1EA2 /* ShortcutResolver.swift */, @@ -3025,6 +3222,7 @@ 17A45844F33B55A3A58E9C71 /* AppSurfaceClassifier.swift */, 266C8BF167118855C56FC7F4 /* BrowserAppDetector.swift */, 81B57905DE20AD5E88BAE0B0 /* BrowserDomain.swift */, + 2EC384A90F3D8B71584B3449 /* SystemUIFocusShadowPolicy.swift */, E6DC2CCFDB2BF1F1E1375620 /* TerminalAppDetector.swift */, ); path = Applications; @@ -3056,6 +3254,7 @@ children = ( 9D5B9D87B5D7E7B8A42FEBDE /* Applications */, F2C5C6E620DD62A4496A539E /* Capability */, + 627E02913F35A5802D48A207 /* CaretAdvanceSamplerTests.swift */, C8B2BA5E0AD5E3F1D89531B9 /* FocusedInputPollingSignatureTests.swift */, C1337F5C94FA9A6388841805 /* FocusPollBackoffTests.swift */, ); @@ -3090,6 +3289,7 @@ children = ( CFA86D8DC87D79FD23899608 /* Applications */, 30BB2A9D0CE9D4B10795D789 /* Capability */, + 50A879A0AD69BCA2F60D2735 /* CaretAdvanceSampler.swift */, D1891267512941C84BEA4172 /* FocusedInputPollingSignature.swift */, ECD5C8648740E7050CCA657F /* FocusPollBackoff.swift */, ); @@ -3396,8 +3596,11 @@ FA338804A6BCDE4F4CCAE922 /* Output */ = { isa = PBXGroup; children = ( + 384D85534F3C2F40E2FD8781 /* CompletionContentPolicy.swift */, F1A4C5DEA32868F99E953D99 /* CompletionSeamGuard.swift */, 122298AE151ECEEC175878BF /* ConfidenceSuppressionPolicy.swift */, + D99C3973D57C7F53416CFBF6 /* GhostSpaceBoundary.swift */, + DDA2EA95C425C3EAF246C66D /* SuggestionLengthPolicy.swift */, C6A19E3F68A42D2FAC95E000 /* SuggestionTextNormalizer.swift */, AD851386E85D3ED0A1F17E47 /* TrailingDuplicationFilter.swift */, ); @@ -3410,6 +3613,7 @@ 9DE7214E640C6F55297A245B /* AppleIntelligence */, F12953939DDAE88E16FF7509 /* Llama */, D1FD19C28ED1EA45A68ABE16 /* OpenAICompatible */, + 2C8D50115291212D03414CBE /* DebugForcedSuggestionEngine.swift */, 384FBCF5D7A3A446C5BE2B8D /* SuggestionEngineRouter.swift */, ); path = Runtime; @@ -3578,6 +3782,7 @@ buildActionMask = 2147483647; files = ( 8320BEF361D8EA55E5CC7D95 /* llama-eval-cases.json in Resources */, + B71D51572125A69F279B3D79 /* llama-recall-cases.json in Resources */, C559712BC6669FCACF24D841 /* phrase-prediction-1337.json in Resources */, 87C0A4F8038F912CDFF5C071 /* supported-model-regressions.json in Resources */, ); @@ -3648,19 +3853,25 @@ 91979FC94BCE7A80F74EC47F /* Aria2StagingDirectoryResolver.swift in Sources */, D5F36CA1A9400432CB1FDAFC /* ArithmeticEvaluator.swift in Sources */, 9B6C176547D2B6D118572E41 /* BaseCompletionPromptRenderer.swift in Sources */, + 0209B7FDA016D12F91C1D197 /* BlockBreakAlignment.swift in Sources */, CCE3A8C0CDB6F493D61131F6 /* BrowserAppDetector.swift in Sources */, 51A2818103753C731384D948 /* BrowserDomain.swift in Sources */, A30F28CB21A2DDE9E44FC10A /* BundleVersion.swift in Sources */, 24D5BE34BE7B7B72A93C6A9F /* BundledRuntimeLocator.swift in Sources */, 7291020840F2164EBB764FAF /* CalendarAccessibilityCaptureGuard.swift in Sources */, BFD3F43E9C83F3D0F7BF9ADE /* CalendarAccessibilityCapturePolicy.swift in Sources */, + 3744E92A7A36B0958F9DB488 /* CaretAdvanceSampleStore.swift in Sources */, + 1C853F8F3BE4DFE89F90BF35 /* CaretAdvanceSampler.swift in Sources */, 3D62542845E1048A93A48749 /* CaretGeometrySelector.swift in Sources */, + 780565E1249321B688BF206C /* CaretLagPolicy.swift in Sources */, C1D40B6EE4EFB064F3F64958 /* CaretLinePosition.swift in Sources */, + DF38049917DC33C6235DB3E6 /* CaretTokenPosition.swift in Sources */, 90D53306BE8369534BE8ED4F /* CaretWordContext.swift in Sources */, 32F8F271C53D0F3782C7375D /* ChromiumAccessibilityEnabler.swift in Sources */, E42469F1225220932384F342 /* ClipboardContentDistiller.swift in Sources */, 4A69EB9EEA4620D2EC35B9B2 /* ClipboardContextProvider.swift in Sources */, D2DB4238276DD73A8D5F588C /* ClipboardRelevanceFilter.swift in Sources */, + 9C8E4F3EC59DB628C99F500A /* CompletionContentPolicy.swift in Sources */, 8586BBF24A7F21AF84F59D2C /* CompletionRenderMode.swift in Sources */, 56D7E792922B83393A578E5A /* CompletionRenderModePolicy.swift in Sources */, BEAFF8C487D70EA3EA4A1E0D /* CompletionSeamGuard.swift in Sources */, @@ -3681,6 +3892,7 @@ B0D7A0BC5621C8D23A9145B7 /* CustomRulesEditor.swift in Sources */, 44265797C6EC7798A5E61EF3 /* DateMacroEvaluator.swift in Sources */, 4B1FD27F44D60A3B7CEF03D1 /* DebouncePolicy.swift in Sources */, + 5732858F8FF26E50AE4BFF8C /* DebugForcedSuggestionEngine.swift in Sources */, 29E20FF0F413E723D2A526C4 /* DecodeStopPolicy.swift in Sources */, 0A9F8963DABEDBFFD8D5A239 /* DeepGeometryWalkThrottle.swift in Sources */, 1C30344A181A9F6EBC4A9C8F /* DeviceInfo.swift in Sources */, @@ -3728,19 +3940,34 @@ 6B9B1630E1F30EAE429B5B60 /* FoundationModelPromptRenderer.swift in Sources */, 52E6B0B61DA8AC33EF60BA04 /* FoundationModelSuggestionEngine.swift in Sources */, 4882DF865737ECDC4925F44B /* GeneralPaneView.swift in Sources */, - 1AF051175E382193908AE73E /* GhostFontMetrics.swift in Sources */, - 45172DA4CBEC1BD40126D190 /* GhostFontSizeStabilizer.swift in Sources */, - 70825EA97C7AD576B3ED899E /* GhostHostScaleTracker.swift in Sources */, - 03C210D6D0BCC7DB9C4F46F0 /* GhostSuggestionLayout.swift in Sources */, + 8AFA546C09EE51078EFBEDAE /* GhostBaselinePolicy.swift in Sources */, + 0569D0ECE7AA0F70B2436B1A /* GhostCaretRefinement.swift in Sources */, + 1092E77D9D92DAC4808ABF9A /* GhostFontResolver.swift in Sources */, + CFCBA5D14335960CF994F40D /* GhostFontSizeLimits.swift in Sources */, + A1C4151AAE1CC55282C136BB /* GhostSpaceBoundary.swift in Sources */, 39B6842EF482AFB6CBEE6816 /* GhostTextColorPreset.swift in Sources */, + 113D4A18C8782C578463A6C4 /* GhostTextLayout.swift in Sources */, + 93483A25EA4434A05C6CCB2C /* GhostTextPanelView.swift in Sources */, 9210DC383F2D181F617A2D74 /* GhostTextPreview.swift in Sources */, + E9D65F5D5452B595886F43DC /* GhostWrapBandPolicy.swift in Sources */, 31FE2E49FD1491E90B32B956 /* HardwareCapabilityProbe.swift in Sources */, 507E7BCCD189A64C3F8ECB79 /* HomePaneView.swift in Sources */, + EBE0052259FC97E5C29BD330 /* HostAdvanceFit.swift in Sources */, + 94FA4632A562E454472BE1C6 /* HostBaselineCalibrator.swift in Sources */, + 1FCDFBFCA35BF3108E476042 /* HostBundledFontRegistry.swift in Sources */, + 6481A54A8B9C1285125225E1 /* HostFaceMemory.swift in Sources */, 7C03CF993AFF3B1BCD61F31C /* HostFontRegistry.swift in Sources */, + EA79F5362D5CDBC3B4F3701A /* HostMarkedTextPolicy.swift in Sources */, + BACE17862C9D65881E0D5710 /* HostTextMetricsCache.swift in Sources */, + F229353A4D1AD42CBDFD5F8F /* HostTextMetricsProbe.swift in Sources */, + EF3A6587D39F1541BF5551E7 /* HostZoomLadder.swift in Sources */, + D6C4E1E75B8DA8F6471D6E09 /* HostZoomLadderResolver.swift in Sources */, 09B092E8A682D127FC9872A7 /* HuggingFaceAPIClient.swift in Sources */, 7E11BEAD32E9FF170C92000A /* HuggingFaceModelBrowserView.swift in Sources */, FF46903861BA67AED8C24EF8 /* HuggingFaceModels.swift in Sources */, 6AF17537A3A91BAA875DEA0F /* HuggingFaceSearchService.swift in Sources */, + B97F5A32DC8C9CE048F231D7 /* InkBaselineAnalyzer.swift in Sources */, + 1380A12C2A092F3B2334B5F3 /* InkCaretAnalyzer.swift in Sources */, 902175A80CC3585095BE7D58 /* InlineCommandCoordinator.swift in Sources */, 87CE40ADBFCDBFD266BA9E3F /* InlinePreviewPanelController.swift in Sources */, 2C44C5142ED914BF99A70EC6 /* InlinePreviewView.swift in Sources */, @@ -3768,6 +3995,7 @@ A614AD79BE724ABA3721613B /* MacroModels.swift in Sources */, 5161D2D6BE6ADEAC91A7F0D4 /* MacroReferenceSheet.swift in Sources */, 50CDF4E6CD754A74DDCF67FC /* MacroTriggerStateMachine.swift in Sources */, + 186879EFBD85CB07E40C4B3F /* MailHeaderFieldDetector.swift in Sources */, FCB060F562093AACA90CAA21 /* MarkerSelectionSynthesizer.swift in Sources */, 7527A1523BFCCC1D418B44FD /* MenuBarPopoverDismisser.swift in Sources */, 84E9E1F4D55CEB8A9692734E /* MenuBarPresentationObserver.swift in Sources */, @@ -3806,6 +4034,7 @@ 70E282036479782EE76A59C4 /* PermissionOverlayWindowController.swift in Sources */, 513066CA00F0E9A5E7358A4E /* PermissionReminderView.swift in Sources */, D553BAA6C9F478533BD4A221 /* PermissionsPaneView.swift in Sources */, + 3B23D06D21B57E0A8F04645D /* PixelCaretLocator.swift in Sources */, D0BA4677F47C6943310B8A28 /* PopupChrome.swift in Sources */, 14816A290847D599769532E3 /* PostExhaustionAcceptanceState.swift in Sources */, C607A624A0FB697486C56B8E /* PowerSourceMonitor.swift in Sources */, @@ -3835,6 +4064,7 @@ 753DC144B9394A35A3F395DA /* SettingsSidebarView.swift in Sources */, C27B7576CDA64AEE7558F700 /* ShortcutResolver.swift in Sources */, 6F2FE689BCA50BEAE80AC6F4 /* ShortcutsPaneView.swift in Sources */, + A5D55F0B61671D752CC75AC7 /* SignOffCue.swift in Sources */, 1417914B85A8B6BBD91AD68D /* SpeculativeAcceptanceContext.swift in Sources */, 852D882AA3AF54EE73BA5306 /* SpellingDictionaryCatalog.swift in Sources */, 36DBF7CA5EC371E4AA860843 /* SpellingDictionaryPicker.swift in Sources */, @@ -3848,6 +4078,7 @@ 8E4DFB1338AE3BDA8FF16B98 /* SuggestionContinuationPlan.swift in Sources */, 44B7F10676606D11D0B1D5D0 /* SuggestionCoordinator+Acceptance.swift in Sources */, 608D525DDFC28645CF79772C /* SuggestionCoordinator+Continuation.swift in Sources */, + A16DEE62FB45155A10E5DF74 /* SuggestionCoordinator+HostMarkedText.swift in Sources */, AE739989AC401113B99018BC /* SuggestionCoordinator+Input.swift in Sources */, 5CA62E4891EDC6F4E36B1F32 /* SuggestionCoordinator+Lifecycle.swift in Sources */, 148FE12B749A51872F54C8F8 /* SuggestionCoordinator+Prediction.swift in Sources */, @@ -3861,6 +4092,7 @@ CCA8847AD15933EC1EB2C238 /* SuggestionFadeInPolicy.swift in Sources */, CF2EADEEEF5AA63FB9B9EA8E /* SuggestionInserter.swift in Sources */, B413327721F83D9B5C8D4BCE /* SuggestionInteractionState.swift in Sources */, + 442999B123DFBAD112231987 /* SuggestionLengthPolicy.swift in Sources */, EE2C9177CE615298595215A8 /* SuggestionOverlayPresenter.swift in Sources */, 7097797640547007DF552F4E /* SuggestionOverlayStabilityGate.swift in Sources */, 996786038E31C671B650DFE4 /* SuggestionOverlayViews.swift in Sources */, @@ -3886,6 +4118,7 @@ C8682DF05F0B3C3950FB64B1 /* SystemMetricsStore.swift in Sources */, 034B9FAFB0C719798D49BA0C /* SystemResourceSampler.swift in Sources */, CD5740A7732F4ACA73CB3F64 /* SystemSettingsWindowLocator.swift in Sources */, + 1F1131A36C6808948C6341AA /* SystemUIFocusShadowPolicy.swift in Sources */, 77C40ACA872F20CFAB2BDD7F /* TagChip.swift in Sources */, 7C5BD8FDACC491EF62665FB2 /* TerminalAppDetector.swift in Sources */, D1F372A869DB29A8AC78C0CB /* TextDirectionDetector.swift in Sources */, @@ -3895,6 +4128,8 @@ A62575C4326C9C961A82436F /* TokenHealingBuffer.swift in Sources */, E6A062DEDF942C57A77A1AB7 /* TokenHealingPlan.swift in Sources */, A275EDDC0B43865C587C33AA /* TrailingDuplicationFilter.swift in Sources */, + F60A94747732F706FD666925 /* TypefaceEvidence.swift in Sources */, + DA4693E09A0790D12F531CCA /* TypefaceMatcher.swift in Sources */, 555501CEB124DFDF657EACA4 /* TypingCadence.swift in Sources */, BDF13BDA6D30BB8FF755AC32 /* TypingPredictionCandidate.swift in Sources */, C0468063222C5FEA7C1CFAB3 /* TypoCaseTransfer.swift in Sources */, @@ -3944,19 +4179,25 @@ 3A2A9FE7D529DDA2D471C8F5 /* Aria2StagingDirectoryResolver.swift in Sources */, 2CA5740E5D8A1167043D6045 /* ArithmeticEvaluator.swift in Sources */, DC6C087A7C0E973967BB38D5 /* BaseCompletionPromptRenderer.swift in Sources */, + D3044E73646472B70D754F3D /* BlockBreakAlignment.swift in Sources */, 2D33BFF41BDE6CE7464885B8 /* BrowserAppDetector.swift in Sources */, A35366FD201FE8747BAE0C09 /* BrowserDomain.swift in Sources */, 8F216FA9E4DD8C0317E28286 /* BundleVersion.swift in Sources */, F140C1894C54E0A94EAE70BC /* BundledRuntimeLocator.swift in Sources */, 77823BCFB29F63615C514927 /* CalendarAccessibilityCaptureGuard.swift in Sources */, 51F0B064F34BA3F88196C39F /* CalendarAccessibilityCapturePolicy.swift in Sources */, + E4B3CF7EB30D22DABFF46F33 /* CaretAdvanceSampleStore.swift in Sources */, + F09B3B8B1E61818BCE539358 /* CaretAdvanceSampler.swift in Sources */, 6D088E2762DBDE195463DE47 /* CaretGeometrySelector.swift in Sources */, + 757696BA01AE112E3B1241EC /* CaretLagPolicy.swift in Sources */, B6F12EFB0AA3CC6BA8773476 /* CaretLinePosition.swift in Sources */, + 73A1C073056E07657992AE5A /* CaretTokenPosition.swift in Sources */, AE79541055252CA69CFD5DD7 /* CaretWordContext.swift in Sources */, F619A3C5BD770B230A78530D /* ChromiumAccessibilityEnabler.swift in Sources */, 2B4049F81730E41A1274E57F /* ClipboardContentDistiller.swift in Sources */, 6A3C43437DEECBCF935C1F12 /* ClipboardContextProvider.swift in Sources */, A6EC0817EEF776BAC49D9E93 /* ClipboardRelevanceFilter.swift in Sources */, + 35173B2E2394D942043FB086 /* CompletionContentPolicy.swift in Sources */, 6DFF14EEC5838BC309D03B09 /* CompletionRenderMode.swift in Sources */, 71E95FF86D5B3399FBD618A9 /* CompletionRenderModePolicy.swift in Sources */, 2C4480F23602E7F7FAB50A5D /* CompletionSeamGuard.swift in Sources */, @@ -3977,6 +4218,7 @@ 33EC776945F8CE612A8E85A3 /* CustomRulesEditor.swift in Sources */, D01A732060A64634619980B1 /* DateMacroEvaluator.swift in Sources */, DA8D967DFA3AFDE469D1FAE7 /* DebouncePolicy.swift in Sources */, + 9116AADE58B526162FBF4676 /* DebugForcedSuggestionEngine.swift in Sources */, CC6BE0B13CCF9B1DE0466DC2 /* DecodeStopPolicy.swift in Sources */, FECBB40B75788B449719170B /* DeepGeometryWalkThrottle.swift in Sources */, D3909BBB80781B0CE1982A8E /* DeviceInfo.swift in Sources */, @@ -4024,19 +4266,34 @@ 53F946FF09038C1C751C6E53 /* FoundationModelPromptRenderer.swift in Sources */, 54F87EB87E46B987E3679514 /* FoundationModelSuggestionEngine.swift in Sources */, 4134ADBE464D00BB748BD9AE /* GeneralPaneView.swift in Sources */, - 2D845761E78C30D8BE11E2B7 /* GhostFontMetrics.swift in Sources */, - 73B39F0BC9FFA95059AA7C7F /* GhostFontSizeStabilizer.swift in Sources */, - 77FF70B3EFA14CDCA4FAF5A3 /* GhostHostScaleTracker.swift in Sources */, - EF572E116EAD1FC1901B1BDA /* GhostSuggestionLayout.swift in Sources */, + 5313CEB1330FED9569FF55EF /* GhostBaselinePolicy.swift in Sources */, + 57857C599039465ADB0EC3AD /* GhostCaretRefinement.swift in Sources */, + A18051D870C0BEF082053D2C /* GhostFontResolver.swift in Sources */, + 6EC1C0311B57074487F9658F /* GhostFontSizeLimits.swift in Sources */, + 564BA8F230F45E378D26B36C /* GhostSpaceBoundary.swift in Sources */, 0C8D3E1DC9D7B0FC7664055A /* GhostTextColorPreset.swift in Sources */, + 8AE7B4DE4650D0DA8F6EFFE7 /* GhostTextLayout.swift in Sources */, + AD1BCDC0809B413FEC5F115B /* GhostTextPanelView.swift in Sources */, C657A9C35E432D0A21D96F9F /* GhostTextPreview.swift in Sources */, + A974EF146F549AD4DFD11D66 /* GhostWrapBandPolicy.swift in Sources */, 8D380BEC82C2969F3ED2161A /* HardwareCapabilityProbe.swift in Sources */, B65B49F24F59154A7611FD22 /* HomePaneView.swift in Sources */, + B42C9D3DCDE6E50DC75D0342 /* HostAdvanceFit.swift in Sources */, + BA1EC288248A2E4508E4FE9A /* HostBaselineCalibrator.swift in Sources */, + 583FB683A2CA8516F96BE682 /* HostBundledFontRegistry.swift in Sources */, + C304E4802C31CD1BCD2B8743 /* HostFaceMemory.swift in Sources */, A9B01E476E483F2A712F1B90 /* HostFontRegistry.swift in Sources */, + 306DC10E9B442E40C814FDEA /* HostMarkedTextPolicy.swift in Sources */, + 1C40E805671D6EFC890113AA /* HostTextMetricsCache.swift in Sources */, + 71E039A6A5EF33DE3EB40328 /* HostTextMetricsProbe.swift in Sources */, + AE5D7E6E784F22EC71060E7E /* HostZoomLadder.swift in Sources */, + 0A3063CCE08DCD90DA1650D8 /* HostZoomLadderResolver.swift in Sources */, 0B7C1A4F515F63462CCCE9EA /* HuggingFaceAPIClient.swift in Sources */, F0DCEFED640B453A5ECEB810 /* HuggingFaceModelBrowserView.swift in Sources */, AB1B25E213AE0A79C6993239 /* HuggingFaceModels.swift in Sources */, 39910B6812663A61FC13D6BD /* HuggingFaceSearchService.swift in Sources */, + 3A68F284E2C24093B115EC77 /* InkBaselineAnalyzer.swift in Sources */, + CE2D1E5B1F987FC76024DB46 /* InkCaretAnalyzer.swift in Sources */, AAC519AC668EF430F68B06CA /* InlineCommandCoordinator.swift in Sources */, E8768B22E683F35098DF4CCF /* InlinePreviewPanelController.swift in Sources */, 24E0F4F0EC9148FB409FD112 /* InlinePreviewView.swift in Sources */, @@ -4064,6 +4321,7 @@ BBBD7B4628BA1160DD6B4BDB /* MacroModels.swift in Sources */, 15F3DFEF14B748F5083A2517 /* MacroReferenceSheet.swift in Sources */, 1ED8785EB680045FF13270B4 /* MacroTriggerStateMachine.swift in Sources */, + 3615284684EBC0F0E8843BE6 /* MailHeaderFieldDetector.swift in Sources */, 61932DE224F103A22C8E4EF7 /* MarkerSelectionSynthesizer.swift in Sources */, 24A2D498222EB81B64EC36C9 /* MenuBarPopoverDismisser.swift in Sources */, E846FC1E29526242117F126F /* MenuBarPresentationObserver.swift in Sources */, @@ -4102,6 +4360,7 @@ 84C7C981F9243D1DB1BC90A4 /* PermissionOverlayWindowController.swift in Sources */, 610650A14AC5E8EDDC81B4E9 /* PermissionReminderView.swift in Sources */, 39571AB31481959CD5C223AE /* PermissionsPaneView.swift in Sources */, + D5C6524646C545CFA08D00C9 /* PixelCaretLocator.swift in Sources */, 986C1339A8FBA3569FC129D6 /* PopupChrome.swift in Sources */, 05CFA72F4FF44A0CDBEFED3C /* PostExhaustionAcceptanceState.swift in Sources */, 50E1247BE6A952AB1B12C444 /* PowerSourceMonitor.swift in Sources */, @@ -4131,6 +4390,7 @@ 27D4F5CACADE171F142178B4 /* SettingsSidebarView.swift in Sources */, 3C9B747BD1947C4D513EFB2E /* ShortcutResolver.swift in Sources */, 12995E5DDB11E3395E6AF82F /* ShortcutsPaneView.swift in Sources */, + 0B0789C330063A0536A68C7A /* SignOffCue.swift in Sources */, 32CB610713A790160C20C5F4 /* SpeculativeAcceptanceContext.swift in Sources */, 2AF12199E0FD9B802BF35A10 /* SpellingDictionaryCatalog.swift in Sources */, 000C6FB630E84073B89BF926 /* SpellingDictionaryPicker.swift in Sources */, @@ -4144,6 +4404,7 @@ 597743F49ECF16C765AE9162 /* SuggestionContinuationPlan.swift in Sources */, CC6E722F33A2CCE2DEC6AA9D /* SuggestionCoordinator+Acceptance.swift in Sources */, 09112D213315AA1C70E303CE /* SuggestionCoordinator+Continuation.swift in Sources */, + 976835E71042F5755616F678 /* SuggestionCoordinator+HostMarkedText.swift in Sources */, 830E59C6940485511EE3FA39 /* SuggestionCoordinator+Input.swift in Sources */, 5D6BF62973E33472E20E82B5 /* SuggestionCoordinator+Lifecycle.swift in Sources */, 64F9AE9BBCA9D09623246A14 /* SuggestionCoordinator+Prediction.swift in Sources */, @@ -4157,6 +4418,7 @@ ADB10BCA76295920FE4A9FE1 /* SuggestionFadeInPolicy.swift in Sources */, 2FC40D4BFDD05C2401D7A5E9 /* SuggestionInserter.swift in Sources */, D795377F4F24F49740808960 /* SuggestionInteractionState.swift in Sources */, + 8E5D6584BA4168C7E381EF91 /* SuggestionLengthPolicy.swift in Sources */, 02DA43985CDAE6859014F14F /* SuggestionOverlayPresenter.swift in Sources */, 7324AD21F85FF6DDDD27EC84 /* SuggestionOverlayStabilityGate.swift in Sources */, 1CCDD5419039F1151AF7A7B9 /* SuggestionOverlayViews.swift in Sources */, @@ -4182,6 +4444,7 @@ 513BD87C18DAFB7EDC45B2FF /* SystemMetricsStore.swift in Sources */, C6EAB2503D7186C0B2CF417D /* SystemResourceSampler.swift in Sources */, 54290B1569A7568809014A23 /* SystemSettingsWindowLocator.swift in Sources */, + 25F7E6EC713F8F71DEEEAAA3 /* SystemUIFocusShadowPolicy.swift in Sources */, 1816D3D625F561209590D45D /* TagChip.swift in Sources */, 401409603DE521880F75C779 /* TerminalAppDetector.swift in Sources */, 2E0AEA2199864F46F86FC0C0 /* TextDirectionDetector.swift in Sources */, @@ -4191,6 +4454,8 @@ 6FA8CBA189210C9821B03656 /* TokenHealingBuffer.swift in Sources */, F61CFA7A600E266D5A05E26E /* TokenHealingPlan.swift in Sources */, 407D80367A0243FBCC9865D6 /* TrailingDuplicationFilter.swift in Sources */, + 2740742B866BC12043B81268 /* TypefaceEvidence.swift in Sources */, + 9F8656C10B45DA6699A43787 /* TypefaceMatcher.swift in Sources */, F6A88AAC1A3FE7FB284BFC34 /* TypingCadence.swift in Sources */, 733A13BF72DCBBDC37952DD9 /* TypingPredictionCandidate.swift in Sources */, CF391BC4A3C80C41048D64D1 /* TypoCaseTransfer.swift in Sources */, @@ -4230,19 +4495,25 @@ A638F12BABC343E76EA948C6 /* Aria2StagingDirectoryResolverTests.swift in Sources */, 1741F4AFFEA90CD6F81959F3 /* ArithmeticEvaluatorTests.swift in Sources */, 02A837BD3861FD72B3E99D0F /* BaseCompletionPromptRendererTests.swift in Sources */, + 67A5529348C03A5B97BEFFC5 /* BlockBreakAlignmentTests.swift in Sources */, 5461DD0133FE632B2EB4936A /* BrowserAppDetectorTests.swift in Sources */, D1B42567FEECD406CECFEC45 /* BrowserDomainTests.swift in Sources */, A6878832F0F19C3C70108185 /* BundleVersionTests.swift in Sources */, 85FCDC35AC29F4A89E30F559 /* BundledRuntimeLocatorTests.swift in Sources */, 715BD5081873EF5FF070D3B0 /* CalendarAccessibilityCapturePolicyTests.swift in Sources */, 111905666B5F1D0B65B87D44 /* CapturedInputEventTests.swift in Sources */, + 7AC032855FD65F67B93E172D /* CaretAdvanceSamplerTests.swift in Sources */, C4511DF96F8D3A88DB6161DE /* CaretGeometrySelectorTests.swift in Sources */, + 5F1F159BF6E776F3A85F1442 /* CaretLagPolicyTests.swift in Sources */, C25DF7FAB2DE87680FDFD6EE /* CaretLinePositionTests.swift in Sources */, 7207434A045B4B372981BE2C /* CaretRunPlacementTests.swift in Sources */, + A29869C9E187C7EEF756E08F /* CaretTokenPositionTests.swift in Sources */, 2F7C69992620D669D794A6F0 /* CaretWordContextTests.swift in Sources */, + 8553CEBF40318B0D6DD36B0C /* ChromiumInlineAutocompleteTests.swift in Sources */, 306836875282844C05C35B2C /* ClipboardContentDistillerTests.swift in Sources */, A76F1AFB944C10A9EB06DBF9 /* ClipboardContextProviderTests.swift in Sources */, 853C19A148DE6D5A59AA2C87 /* ClipboardRelevanceFilterTests.swift in Sources */, + 1010A070B4E0F0A2FDF45112 /* CompletionContentPolicyTests.swift in Sources */, A6EB92BAE29EF2546084558A /* CompletionRenderModePolicyTests.swift in Sources */, D59584461B4A854572C331D2 /* CompletionRenderModeTests.swift in Sources */, 5F2F0475865445A777B5E3F0 /* CompletionSeamGuardTests.swift in Sources */, @@ -4293,15 +4564,28 @@ 5F2BFD0D7718CC774F03FBE0 /* FoundationModelAvailabilityServiceTests.swift in Sources */, AC3A30CDDFE6ED0A0D87005F /* FoundationModelDriftEvalTests.swift in Sources */, F3DA1B242F30332AA91C00C5 /* FoundationModelSuggestionEngineTests.swift in Sources */, - AFA6E84BC8C637CDBDF8BA9D /* GhostFontMetricsTests.swift in Sources */, - 8EFD4154069432EB43955033 /* GhostFontSizeStabilizerTests.swift in Sources */, - 43E923211DAFB9728B56A6D8 /* GhostHostScaleTrackerTests.swift in Sources */, - 39F954287497C8B1253119C7 /* GhostSuggestionLayoutTests.swift in Sources */, + CA959F082DB2C6E95C5743C8 /* GhostBaselinePolicyTests.swift in Sources */, + 0C6DCE04EF0D1EE132B59B73 /* GhostCaretRefinementTests.swift in Sources */, + 4BDE48F1CCFAA097E95B53F6 /* GhostFontResolverTests.swift in Sources */, + 7C56674EF8DA8869D5116EE6 /* GhostSpaceBoundaryTests.swift in Sources */, D9BF921EDFAE90D4D15DA82F /* GhostTextColorPresetPaletteTests.swift in Sources */, + 109F9BD7E1AE3E7FB0C712AA /* GhostTextLayoutTests.swift in Sources */, + BB9CA5E517347B478B4D52E9 /* GhostTextPixelAlignmentTests.swift in Sources */, + CB2D43A2B7568173A8E82CE4 /* GhostWrapBandPolicyTests.swift in Sources */, 2240B5EC1DB9902E2D2E2CCD /* HardwareCapabilityProbeTests.swift in Sources */, + D9CE5079C27826E94A29C744 /* HostAdvanceFitTests.swift in Sources */, + A966DCE17C5160F15F371372 /* HostBaselineCalibratorTests.swift in Sources */, + 1A9FB5C832377E951C572606 /* HostBaselineCalibratorTypefaceTests.swift in Sources */, + 85DE2B1A62BA24DA615A628E /* HostBundledFontRegistryTests.swift in Sources */, + 88B5413CB02F1C53E81DC683 /* HostFaceMemoryTests.swift in Sources */, 380D6D04743E5F0A8A71228E /* HostFontRegistryTests.swift in Sources */, + D060C3A636D395CE9DE6BDAB /* HostMarkedTextPolicyTests.swift in Sources */, + 7EA9216A909247A4F722C9C1 /* HostTextMetricsCacheTests.swift in Sources */, + 4B0153F0BBF4A4343FF0C778 /* HostZoomLadderTests.swift in Sources */, C080D50FA0ED1F32C92BCC36 /* HuggingFaceModelsTests.swift in Sources */, 676533C6555BAC0C4A263576 /* HuggingFaceSearchServiceTests.swift in Sources */, + B76A1739BB3A656D6417C5D3 /* InkBaselineAnalyzerTests.swift in Sources */, + 5FACCF66D0D46ABCE3668EE2 /* InkCaretAnalyzerTests.swift in Sources */, 6F3B43828817C2C303E8D16E /* InlineCommandCoordinatorTests.swift in Sources */, 9447C6F6A30C51877236EC01 /* InlineFeatureTestDoubles.swift in Sources */, 1B66CA97E53D1ADB9F4E9A4A /* InputMonitorTests.swift in Sources */, @@ -4327,6 +4611,7 @@ 332F2AE6277FB6E871589E4D /* MacroControllerTests.swift in Sources */, AB8F186A7CB13779DAF729A0 /* MacroEngineTests.swift in Sources */, 8756E3050C45106D7AD188AA /* MacroTriggerStateMachineTests.swift in Sources */, + 0C89AF5CF2114DF57DA794CF /* MailHeaderFieldDetectorTests.swift in Sources */, 1B80604D77F1AF1DF96DDD45 /* MarkerSelectionSynthesizerTests.swift in Sources */, A54337B0B12D1FE2F332C7E1 /* MenuBarRecoveryPolicyTests.swift in Sources */, 85BB2FAD4BFE9077AEE2A262 /* MidWordContinuationPolicyTests.swift in Sources */, @@ -4344,6 +4629,7 @@ 477488B0FDAD0A9B76DA9848 /* OpenAICompatibleAPIClientTests.swift in Sources */, 24A641C63FB5DB47C2116A5C /* OpenAICompatibleEndpointConfigurationTests.swift in Sources */, FCEE05402A708C33F9719D7F /* OpenAICompatibleSuggestionEngineTests.swift in Sources */, + F0A97143C565D2C2BD2339DF /* OverlayControllerTypefaceTests.swift in Sources */, E96817EE505817F9E616B31C /* PerAppShortcutOverrideStoreTests.swift in Sources */, 599A5F23125EE32592AA180E /* PerAppShortcutOverrideTests.swift in Sources */, 8B36A3FDDBB803C72A1E521B /* PerDomainDisableSettingsTests.swift in Sources */, @@ -4356,6 +4642,7 @@ B5C255E58C128C4042FA7F66 /* PhrasePredictionScoringTests.swift in Sources */, 067371AD898788392F158EDE /* PhrasePredictionScreenContext.swift in Sources */, A145F5E9E5BEC53EB46A596E /* PhrasePredictionScreenContextTests.swift in Sources */, + 8129ED85DFCF718BFF855D7C /* PixelCaretLocatorTests.swift in Sources */, C68E65AF917EB511AC604C80 /* PostExhaustionAcceptanceStateTests.swift in Sources */, EEC06CE37957B44E2B258DEE /* PromptContextSanitizerTests.swift in Sources */, 7008657841ECD4F06A46A9DE /* PromptPolicyTests.swift in Sources */, @@ -4376,6 +4663,7 @@ 2EF09816678369E9CB1B63B8 /* ShortcutModifierMaskNSEventFlagsTests.swift in Sources */, 0172EECCAFB0D648FF91FA17 /* ShortcutModifierMaskTests.swift in Sources */, D491F5757551D6A69A94968D /* ShortcutResolverTests.swift in Sources */, + C8A48355F3821EBD3D964749 /* SignOffCueTests.swift in Sources */, C72537496242A0A90F4442B3 /* SpeculativeAcceptanceContextTests.swift in Sources */, 52DA855997BCA533B0643E31 /* SpellingDictionaryCatalogTests.swift in Sources */, D58B73ED080E5D253A838FAC /* SpellingDictionaryResourceTests.swift in Sources */, @@ -4405,6 +4693,7 @@ 16A1C0C9B93983231FAD809B /* SuggestionFocusFreshnessTests.swift in Sources */, 66F8D175E7F36D269C6E0659 /* SuggestionInsertionChunkTests.swift in Sources */, FF65453AC3797BBF2F39EBBB /* SuggestionInteractionStateTests.swift in Sources */, + D720D99A50108D0D44E63902 /* SuggestionLengthPolicyTests.swift in Sources */, A2FCC90388ABD94CB3C9F9AA /* SuggestionOverlayAcceptanceTests.swift in Sources */, 6A36F49DE520844FC971F704 /* SuggestionOverlayStabilityGateTests.swift in Sources */, 298EDBCC7D1778EAC2FDF004 /* SuggestionPauseModelsTests.swift in Sources */, @@ -4430,6 +4719,7 @@ 833A5A25C0F5B0E9A46DA04D /* SyntheticReplacePlannerTests.swift in Sources */, E43B0EA04ED0B747CC8D9EBC /* SystemMetricsStoreTests.swift in Sources */, FBC44557CFBA826392930ABD /* SystemResourceSamplerTests.swift in Sources */, + D18F11732EA94EA16CD8101F /* SystemUIFocusShadowPolicyTests.swift in Sources */, 76D834C8C72F8AFD737D8433 /* TerminalAppDetectorTests.swift in Sources */, 74C07F4568C4FAE1C344E789 /* TextDirectionDetectorTests.swift in Sources */, 696514ADA5A7D085C333BE35 /* TextLayoutCaretEstimatorTests.swift in Sources */, @@ -4437,6 +4727,8 @@ C2085C2B2C6F0926A1266867 /* TokenHealingBufferTests.swift in Sources */, 82BEAFC0C5DCE607D45335D3 /* TokenHealingPlanTests.swift in Sources */, 023A1A2A26B192E244192D8D /* TrailingDuplicationFilterTests.swift in Sources */, + 50D12741A639F8A367239FBE /* TypefaceEvidenceTests.swift in Sources */, + 01527F11AEDBE20935A254B6 /* TypefaceMatcherTests.swift in Sources */, FDA59446E91261744C6DDFDA /* TypingCadenceTests.swift in Sources */, BD09E1744CEF3EA688E6BB1D /* TypingExperienceEvalTests.swift in Sources */, 71676E29789ACF4FE7925220 /* TypingPredictionCandidateTests.swift in Sources */, diff --git a/Cotabby/App/Coordinators/Suggestion/SuggestionCoordinator+Acceptance.swift b/Cotabby/App/Coordinators/Suggestion/SuggestionCoordinator+Acceptance.swift index fb346faa4..6e70ee3a3 100644 --- a/Cotabby/App/Coordinators/Suggestion/SuggestionCoordinator+Acceptance.swift +++ b/Cotabby/App/Coordinators/Suggestion/SuggestionCoordinator+Acceptance.swift @@ -591,9 +591,21 @@ extension SuggestionCoordinator { suggestionStreamingState.recordRendered(advancedSession.remainingText) } state = .ready(text: advancedSession.remainingText, latency: advancedSession.latency) - // Same slide as Tab acceptance; the user typed the next characters, so the caret traveled - // by exactly them. Fall back to the (session-start) caret anchor only if the slide can't apply. - if !overlayController.advanceInline(to: advancedSession.remainingText, insertedText: typedCharacters) { + // Nearly typed through: fetch what comes next into the cache now, so exhausting this + // suggestion lands on a ready one instead of a blank gap (see `prefetchContinuation`). + if let rawContext = focusModel.snapshot.context { + prefetchContinuation(after: advancedSession, rawContext: rawContext) + } + if isHoldingForHostMarkedText { + // The host's own prediction still occupies the ghost's spot; the advanced tail stays + // hidden until a snapshot without marked text reconciles and re-presents it. + if overlayState.isVisible { + hideOverlay(reason: Self.hostMarkedTextHoldReason) + } + } else if !overlayController.advanceInline(to: advancedSession.remainingText, insertedText: typedCharacters) { + // Same slide as Tab acceptance; the user typed the next characters, so the caret + // traveled by exactly them. Fall back to the (session-start) caret anchor only if the + // slide can't apply. presentOverlay( text: advancedSession.remainingText, at: session.baseContext.caretRect, @@ -789,6 +801,7 @@ extension SuggestionCoordinator { inputFrameRect: context.inputFrameRect, caretQuality: anchor.quality, bundleIdentifier: context.bundleIdentifier, + focusedURLString: context.focusedURLString, isCaretAtEndOfLine: context.isCaretAtEndOfLine, observedCharWidth: context.observedCharWidth, isRightToLeft: isRightToLeft, @@ -796,6 +809,13 @@ extension SuggestionCoordinator { focusedInputIdentityKey: context.focusedInputIdentityKey, isCorrection: isCorrection, resolvedFieldStyle: context.resolvedFieldStyle, + hostTextMetrics: context.hostTextMetrics, + isWebContentField: context.isWebContentField, + hasTrailingContent: context.hasTrailingContent, + isSingleLineField: context.isSingleLineField, + elementFrameRect: context.elementFrameRect, + lineTextBeforeCaret: GhostCaretRefinement.paragraphTextBeforeCaret(in: context.precedingText), + wrappedRun: context.observedContentEdges?.wrappedRun, observedContentEdges: context.observedContentEdges ) let presentationMessage = overlayPresenter.present( @@ -874,6 +894,20 @@ extension SuggestionCoordinator { return LayoutRepairedAnchor(rect: fallbackRect, quality: quality, outcome: nil, skipReason: nil) } + // A caret inside a wrapped paragraph run (CodeMirror in Obsidian, see `WrappedRunAnchor`): + // the paragraph is laid out in the run's own frame at the sibling runs' pitch, and the + // line the caret lands on is placed from the union's top. That geometry is measured (frame + // and pitch) except for the x inside the line, which is the host font's advance. + // A one-line run is not laid out again: its frame is the host's own line box, so the + // Accessibility caret already has the right line and only its x is approximate (the pixel + // read corrects that at presentation). Laying it out put the caret fourteen lines up. + if let edges = context.observedContentEdges, let wrapped = edges.wrappedRun, !wrapped.spansOneLine { + return wrappedRunAnchor( + edges: edges, context: context, fallbackRect: fallbackRect, + pendingInsertion: pendingInsertion, isRightToLeft: isRightToLeft + ) + } + // Derived rects carry a real AX measurement, so whether the estimate may second-guess // them depends on who produced the measurement. Both bypasses skip the estimator // entirely rather than computing a diagnostic they can never act on: this path runs @@ -893,8 +927,11 @@ extension SuggestionCoordinator { // real line positions, including blank lines some hosts omit from the AX text. The // provenance check matters because content edges can now also come from the host's // line-query attributes, which describe a left margin but carry no line information — - // letting those skip the repair would leave a wrong-line web caret uncorrected. - if context.observedContentEdges?.isRunMeasured == true { + // letting those skip the repair would leave a wrong-line web caret uncorrected. A + // one-line run (`WrappedRunAnchor.spansOneLine`) is such a frame too: it is the host's + // own line box, and the pixel read corrects its x at presentation. + if context.observedContentEdges?.isRunMeasured == true + || context.observedContentEdges?.wrappedRun?.spansOneLine == true { return LayoutRepairedAnchor( rect: fallbackRect, quality: .derived, outcome: nil, skipReason: .runMeasuredGeometry ) @@ -934,6 +971,51 @@ extension SuggestionCoordinator { } } + /// Lays the wrapped paragraph out inside its run frame and returns the caret's line box there. + /// The estimator judges a field's shape from its frame height and rejects content taller than + /// the frame, but a union frame grows with its paragraph and the throttled run walk can report + /// it a few lines short of the live text (measured: every estimate rejected for vertical + /// overflow while a paragraph was being typed), so the layout frame is opened far below the + /// run: only its top edge and width place the caret. The caret rect is the sibling runs' line + /// box at the laid-out line's top, which is where the host draws that line. + static func wrappedRunAnchor( + edges: ObservedContentEdges, + context: FocusedInputContext, + fallbackRect: CGRect, + pendingInsertion: String, + isRightToLeft: Bool + ) -> LayoutRepairedAnchor { + guard let wrapped = edges.wrappedRun, let pitch = edges.linePitch ?? edges.lineBoxHeight, pitch > 0, + wrapped.frame.width > 0 + else { + return LayoutRepairedAnchor(rect: fallbackRect, quality: .estimated, outcome: nil, skipReason: nil) + } + let frame = wrapped.frame + guard frame.width > 0 else { + return LayoutRepairedAnchor(rect: fallbackRect, quality: .estimated, outcome: nil, skipReason: nil) + } + let layoutHeight = max(frame.height, pitch * 60) + let layoutFrame = CGRect(x: frame.minX, y: frame.maxY - layoutHeight, width: frame.width, height: layoutHeight) + let input = TextLayoutCaretEstimator.Input( + precedingText: wrapped.paragraphTextBeforeCaret + pendingInsertion, + fieldFrame: layoutFrame, + fieldStyle: context.resolvedFieldStyle, + isRightToLeft: isRightToLeft, + prefixMayBeTruncated: false, + observedLineHeight: pitch, + observedCharWidth: context.observedCharWidth, + observedContentEdges: ObservedContentEdges(leftX: frame.minX, topY: frame.maxY) + ) + let outcome = TextLayoutCaretEstimator.estimate(for: input) + guard case .estimate(let estimate) = outcome else { + return LayoutRepairedAnchor(rect: fallbackRect, quality: .estimated, outcome: outcome, skipReason: nil) + } + let lineBox = edges.lineBoxHeight ?? pitch + let lineTop = frame.maxY - CGFloat(estimate.lineIndex) * pitch + let rect = CGRect(x: estimate.caretRect.minX, y: lineTop - lineBox, width: 2, height: lineBox) + return LayoutRepairedAnchor(rect: rect, quality: .derived, outcome: outcome, skipReason: nil) + } + /// Vertical agreement test between the AX-derived caret and the layout estimate. Tolerance is /// three-quarters of a line: same-line measurements differ by baseline and padding subtleties, /// while the failure this repairs (the caret mapped into a neighboring visual line) is off by diff --git a/Cotabby/App/Coordinators/Suggestion/SuggestionCoordinator+HostMarkedText.swift b/Cotabby/App/Coordinators/Suggestion/SuggestionCoordinator+HostMarkedText.swift new file mode 100644 index 000000000..cb3f3c726 --- /dev/null +++ b/Cotabby/App/Coordinators/Suggestion/SuggestionCoordinator+HostMarkedText.swift @@ -0,0 +1,60 @@ +import Foundation + +/// File overview: +/// The hold the coordinator enters while the host shows uncommitted text of its own: macOS inline +/// predictive text after the caret, or an IME composition before it (see `HostMarkedTextPolicy`). +/// +/// While the hold is on, Cotabby generates nothing and paints nothing, because the host's gray +/// prediction already occupies the spot a ghost would take and a composition is text the user has +/// not finished. The live session is kept, not invalidated: the moment the host commits or drops +/// its span, the next focus snapshot reconciles the same session against the real text and the +/// ghost returns (or advances, when the user typed through it meanwhile) without a regeneration. +extension SuggestionCoordinator { + static let hostMarkedTextHoldReason = + "Overlay hidden while the app shows its own inline prediction or composes text." + + /// Enters or leaves the hold according to `snapshot`. Returns true when the snapshot carries + /// marked text, in which case the caller must not reconcile, generate, or present from it. + func updateHostMarkedTextHold(for snapshot: FocusSnapshot) -> Bool { + guard snapshot.context?.hasHostMarkedText == true else { + if isHoldingForHostMarkedText { + isHoldingForHostMarkedText = false + logStage( + "host-marked-text-released", + workID: currentWorkID, + generation: latestGenerationNumber, + message: "The app committed or dropped its own inline text; resuming." + ) + } + return false + } + holdForHostMarkedText() + return true + } + + /// Pauses generation and hides the ghost without touching the active session. + func holdForHostMarkedText() { + cancelPredictionWork() + let wasHolding = isHoldingForHostMarkedText + isHoldingForHostMarkedText = true + if overlayState.isVisible { + hideOverlay(reason: Self.hostMarkedTextHoldReason) + } + if interactionState.activeSession == nil { + switch state { + case .disabled, .idle: + break + default: + state = .idle + } + } + if !wasHolding { + logStage( + "host-marked-text-hold", + workID: currentWorkID, + generation: latestGenerationNumber, + message: "Holding while the app shows its own inline prediction or composes text." + ) + } + } +} diff --git a/Cotabby/App/Coordinators/Suggestion/SuggestionCoordinator+Input.swift b/Cotabby/App/Coordinators/Suggestion/SuggestionCoordinator+Input.swift index c63beb7c1..c0730e75e 100644 --- a/Cotabby/App/Coordinators/Suggestion/SuggestionCoordinator+Input.swift +++ b/Cotabby/App/Coordinators/Suggestion/SuggestionCoordinator+Input.swift @@ -110,6 +110,12 @@ extension SuggestionCoordinator { return } + // The host is showing its own inline prediction or composing text: keep any live session, + // but neither reconcile against, generate from, nor paint over the host-owned span. + if updateHostMarkedTextHold(for: snapshot) { + return + } + // After accepting a correction there may be no visible session, but its following words // can still be generating. Retire that work on focus changes too; active-tail reconciliation // cannot protect this interval because the source offer has already been consumed. @@ -159,6 +165,12 @@ extension SuggestionCoordinator { _ = interactionState.materializeContext(from: focusedContext) hideOverlay(reason: "Overlay hidden because the focused field changed.") state = .idle + // Adopt the new field now. The comparison above reads the context the last generation + // materialized, so without this every snapshot in the new process kept reading as a + // field change and the cancel above killed each pending generation before it could + // run (measured live: switching from Chrome to Obsidian left Cotabby silent until the + // next app switch). + _ = interactionState.materializeContext(from: focusedContext) // The user is now on a new editable surface and is likely to type soon. Prime the // selected engine in the background so weight loading and instruction tokenization // happen before the first real `respond` instead of inside its critical path. The @@ -514,6 +526,15 @@ extension SuggestionCoordinator { return false } + CotabbyLogger.suggestion.debug( + "Typed text did not match the suggestion", + metadata: [ + "stage": .string("typed-mismatch"), + "typed": .string(event.characters), + "expected": .string(String(session.remainingText.prefix(24))), + "consumed": .stringConvertible(session.consumedCharacterCount) + ] + ) invalidateActiveSuggestion( reason: SuggestionSessionReconciler.overlayHideReason(for: event), clearDiagnostics: false diff --git a/Cotabby/App/Coordinators/Suggestion/SuggestionCoordinator+Prediction.swift b/Cotabby/App/Coordinators/Suggestion/SuggestionCoordinator+Prediction.swift index 562b7d6b9..3df7bcc27 100644 --- a/Cotabby/App/Coordinators/Suggestion/SuggestionCoordinator+Prediction.swift +++ b/Cotabby/App/Coordinators/Suggestion/SuggestionCoordinator+Prediction.swift @@ -91,21 +91,7 @@ extension SuggestionCoordinator { return } - guard SuggestionRequestFactory.shouldGenerateSuggestion( - for: rawContext.precedingText, suggestWithinWords: settingsSnapshot.suggestWithinWords - ) else { - // A visual-context refresh may ask for new work while a valid tail is visible. - // The boundary preference quiets new suggestions without interrupting type-through. - if interactionState.activeSession != nil { - reconcileActiveSession(with: snapshot) - } else { - clearSuggestion() - let isEmpty = rawContext.precedingText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty - hideOverlay(reason: isEmpty - ? "Overlay hidden because the field has no typed text yet." - : "Overlay hidden while waiting for a word boundary.") - state = .idle - } + guard passesPreGenerationGates(rawContext: rawContext, snapshot: snapshot) else { return } @@ -145,8 +131,12 @@ extension SuggestionCoordinator { latestGenerationNumber = context.generation let request = requestBuildResult.request latestRequestID = request.requestID + latestRequestPrecedingText = request.context.precedingText state = .generating + // The model needs tens to hundreds of milliseconds; the overlay uses that time to measure + // the host's baseline so the ghost lands right the first time. + overlayController.prepareInlinePresentation(for: context) logStage( "generating", workID: workID, @@ -158,6 +148,39 @@ extension SuggestionCoordinator { dispatchGeneration(request: request, workID: workID) } + /// The gates that run before any request is built: the host's own marked text (an inline + /// prediction or IME composition) holds everything, and a field with no typed text, a caret + /// inside a token, or an unfinished word under the boundary preference gets no request. Returns + /// false when it handled the cycle, so `generateFromCurrentFocus` stays within the project's + /// complexity budget. + private func passesPreGenerationGates(rawContext: FocusedInputSnapshot, snapshot: FocusSnapshot) -> Bool { + // No generation while the host shows its own inline prediction or composes text. + if rawContext.hasHostMarkedText { + holdForHostMarkedText() + return false + } + guard SuggestionRequestFactory.shouldGenerateSuggestion( + for: rawContext.precedingText, + trailingText: rawContext.trailingText, + suggestWithinWords: settingsSnapshot.suggestWithinWords + ) else { + // A visual-context refresh may ask for new work while a valid tail is visible. + // The boundary preference quiets new suggestions without interrupting type-through. + if interactionState.activeSession != nil { + reconcileActiveSession(with: snapshot) + } else { + clearSuggestion() + let isEmpty = rawContext.precedingText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + hideOverlay(reason: isEmpty + ? "Overlay hidden because the field has no typed text yet." + : "Overlay hidden while waiting for a word boundary or because the caret is inside a word.") + state = .idle + } + return false + } + return true + } + /// Re-shows the freshest cached suggestion consistent with the live text, if any survives the /// same display guards a fresh generation passes. Returns true when a suggestion was restored /// (the caller skips generation entirely). The win is exactly the common editing moments: @@ -238,7 +261,8 @@ extension SuggestionCoordinator { // little text) or suppress (typo gate). The post-publish regeneration still runs the full // gate with its correction semantics; declining here only skips the speculation. guard SuggestionRequestFactory.shouldGenerateSuggestion( - for: optimistic.precedingText, suggestWithinWords: settingsSnapshot.suggestWithinWords + for: optimistic.precedingText, trailingText: optimistic.trailingText, + suggestWithinWords: settingsSnapshot.suggestWithinWords ) else { return } @@ -269,6 +293,7 @@ extension SuggestionCoordinator { latestGenerationNumber = context.generation let request = requestBuildResult.request latestRequestID = request.requestID + latestRequestPrecedingText = request.context.precedingText let workID = workController.replaceDebouncedWork(delayMilliseconds: 0) { [weak self] workID in guard let self else { return } @@ -284,6 +309,83 @@ extension SuggestionCoordinator { ) } + /// Longest remaining ghost text that still counts as "about to run out", in characters. + /// A short word plus its space: past this the user has enough ahead of them that the + /// continuation can wait for the ordinary cycle. + static let continuationPrefetchRemainingCharacters = 9 + + /// Generates the suggestion that comes AFTER the visible one and files it in the anchor cache, + /// so typing through the last word lands on a ready suggestion instead of a blank gap. + /// + /// Why the cache rather than the session: the result arrives while the user is still typing + /// through the visible ghost, and anything that touched the session or the overlay then would + /// either replace text the user is mid-way through or be dropped as stale (which hides the + /// ghost). Writing only to `suggestionAnchorCache` cannot disturb what is on screen; when the + /// type-through exhausts the suggestion, the regeneration that follows finds this entry through + /// `restoreSuggestionFromAnchorCache` and shows it with no model round-trip at all. + /// + /// This matters most at short word-count presets: a 2-4 word suggestion is typed through in a + /// second or two, and the gap that followed it was most of the time the ghost was absent. + /// + /// Deliberately outside `workController`: this generation must not become "the current work", + /// or it would retire the in-flight cycle and its own result would be judged as the visible + /// suggestion. It is fire-and-forget, and a stale answer is simply a cache entry the live text + /// never matches. + func prefetchContinuation(after session: ActiveSuggestionSession, rawContext: FocusedInputSnapshot) { + guard !userDefaults.bool(forKey: Self.continuationPrefetchDisabledDefaultsKey) else { return } + guard !hasPrefetchedContinuation, case .continuation = session.kind else { return } + let remaining = session.remainingText + guard !remaining.isEmpty, remaining.count <= Self.continuationPrefetchRemainingCharacters else { return } + // The field once the ghost is typed through, from the session's own text: not the live + // snapshot plus the remaining tail. The typed-through advance runs on the keystroke, before + // the host publishes it, so the snapshot can still lack the characters just typed, and the + // model was prompted with "The budget" + "is $100," read as "The budgetis $100," (and + // "the pilot " + "s $250,00" as "pilot s", 2026-09-11). + let optimistic = SpeculativeAcceptanceContext.optimisticSnapshot( + after: rawContext, precedingText: session.precedingTextOnceTypedThrough + ) + guard SuggestionRequestFactory.shouldGenerateSuggestion( + for: optimistic.precedingText, trailingText: optimistic.trailingText + ) else { return } + hasPrefetchedContinuation = true + + let context = interactionState.materializeContext(from: optimistic) + let identityKey = context.focusedInputIdentityKey + let precedingText = context.precedingText + let requestBuildResult = SuggestionRequestFactory.buildRequest( + context: context, + settings: settingsSnapshot, + configuration: configuration, + clipboardContext: pinnedClipboardContext(rawContext: optimistic), + visualContextSummary: permissionManager.screenRecordingGranted + ? visualContextCoordinator.excerpt(for: context) + : nil + ) + let request = requestBuildResult.request + let suggestionEngine = suggestionEngine + Task { @MainActor [weak self] in + let result = try? await suggestionEngine.generateSuggestion(for: request) + guard let self, let result, !result.text.isEmpty else { return } + // Space-corrected against the text this continuation will follow, so the cached entry + // is already right when it is restored (see `GhostSpaceBoundary`). + let text = GhostSpaceBoundary.liveAdjusted( + result.text, + precedingText: precedingText, + requestPrecedingText: result.spacingIsExact ? request.context.precedingText : nil + ) + self.suggestionAnchorCache.record(identityKey: identityKey, precedingText: precedingText, fullText: text) + CotabbyLogger.suggestion.debug( + "Prefetched the continuation of the visible suggestion", + metadata: [ + "stage": .string("continuation-prefetch"), + "request_id": .string(request.requestID), + "after": .string(String(remaining.suffix(12))), + "cached": .string(String(text.prefix(28))) + ] + ) + } + } + /// Runs the engine generation for `request` as the replaceable work for `workID`, applying the /// result (or failure) only while it is still the current work. Extracted from /// `generateFromCurrentFocus` so that function stays within the project's complexity budget. @@ -417,7 +519,15 @@ extension SuggestionCoordinator { let liveContext = interactionState.materializeContext(from: rawContext) guard let presentedPartial = rebasedStreamedPartial(partial, rawContext: rawContext, liveContext: liveContext) else { return } - guard case let .show(text, wordOnly) = completionPresentation(text: presentedPartial.text, context: liveContext, isFinal: false), + // A partial that attaches to a word the user has since ended with a space is stale, and the + // leading space of the rest is decided against the live text (see `GhostSpaceBoundary`). + guard !GhostSpaceBoundary.isStaleAfterTypedSpace(presentedPartial.text, precedingText: liveContext.precedingText) else { return } + let spacedText = GhostSpaceBoundary.liveAdjusted( + presentedPartial.text, + precedingText: liveContext.precedingText, + requestPrecedingText: presentedPartial.spacingIsExact ? latestRequestPrecedingText : nil + ) + guard case let .show(text, wordOnly) = completionPresentation(text: spacedText, context: liveContext, isFinal: false), !wasDismissed(text, context: liveContext) else { return } let delay = presentationDelay(context: liveContext) if delay > 0 { @@ -428,7 +538,7 @@ extension SuggestionCoordinator { } return } - let buffered = bufferedCompletionText(presentedPartial.text, visibleText: text, context: liveContext, isFinal: false) + let buffered = bufferedCompletionText(spacedText, visibleText: text, context: liveContext, isFinal: false) let candidate = ActiveSuggestionSession(baseContext: liveContext, fullText: buffered, initialVisibleCharacterCount: wordOnly ? text.count : nil, showFollowingWords: settingsSnapshot.showFollowingWords, latency: partial.latency) @@ -437,7 +547,7 @@ extension SuggestionCoordinator { $0.remainingText == visible && buffered.count > $0.fullText.count && buffered.hasPrefix($0.fullText) } ?? false guard suggestionStreamingState.canRender(visible) || growsBuffer else { return } - let session = startCompletionSession(prediction: presentedPartial.text, visibleText: text, context: liveContext, + let session = startCompletionSession(prediction: spacedText, visibleText: text, context: liveContext, latency: partial.latency, isFinal: false, wordEndingOnly: wordOnly) if growsBuffer { return } suggestionStreamingState.recordRendered(session.remainingText) @@ -830,14 +940,40 @@ extension SuggestionCoordinator { liveContext: FocusedInputContext, rawContext: FocusedInputSnapshot ) { - let decision = completionPresentation(text: result.text, context: liveContext, isFinal: true) + // A completion that attaches to a word the user has since ended with a space is stale + // (see `GhostSpaceBoundary.isStaleAfterTypedSpace`). + if GhostSpaceBoundary.isStaleAfterTypedSpace(result.text, precedingText: liveContext.precedingText) { + clearSuggestion() + hideOverlay(reason: "Overlay hidden because the completion attached to a word the user had already ended.") + state = .idle + qualityMetricsStore.recordSuppressed(reason: "punctuationAfterTypedSpace") + logStage( + "stale-punctuation", + workID: workID, + generation: result.generation, + message: "Dropped a completion that opened with punctuation after the user typed a space.", + rawOutput: result.rawText, + normalizedOutput: result.text + ) + return + } + // The leading space is decided here, against the text that is in the field now, not against + // the snapshot the request was built from: the user keeps typing while the model runs, so a + // space that arrived meanwhile would otherwise leave the ghost a space too far right (and + // insert two), and a completion the model returned without one would glue to the last word. + let spacedText = GhostSpaceBoundary.liveAdjusted( + result.text, + precedingText: liveContext.precedingText, + requestPrecedingText: result.spacingIsExact ? latestRequestPrecedingText : nil + ) + let decision = completionPresentation(text: spacedText, context: liveContext, isFinal: true) let visibleText: String let prediction: String let wordEndingOnly: Bool switch decision { case let .show(text, wordOnly): visibleText = text - prediction = result.text + prediction = spacedText wordEndingOnly = wordOnly case .wait, .suppress: if let fallback = localWordCompletion(context: liveContext) { @@ -880,14 +1016,10 @@ extension SuggestionCoordinator { precedingText: liveContext.precedingText, fullText: session.fullText ) + hasPrefetchedContinuation = false state = .ready(text: session.remainingText, latency: session.latency) - presentOverlay( - text: session.remainingText, - at: liveContext.caretRect, - context: liveContext, - isRightToLeft: TextDirectionDetector.isRightToLeft(liveContext.precedingText) - ) + presentFreshSession(session, liveContext: liveContext) logStage( "ready", workID: workID, @@ -904,6 +1036,21 @@ extension SuggestionCoordinator { flushQueuedPostExhaustionAcceptIfNeeded() } + /// Shows a fresh session's ghost. A host prediction that appeared while the model was thinking + /// owns the spot right now; the session is then kept and shows itself once the host span clears. + private func presentFreshSession(_ session: ActiveSuggestionSession, liveContext: FocusedInputContext) { + if liveContext.hasHostMarkedText { + holdForHostMarkedText() + } else { + presentOverlay( + text: session.remainingText, + at: liveContext.caretRect, + context: liveContext, + isRightToLeft: TextDirectionDetector.isRightToLeft(liveContext.precedingText) + ) + } + } + /// Converts a runtime or engine failure into visible coordinator state and clears stale UI. func applyFailure(_ message: String, workID: UInt64) async { guard workController.isCurrent(workID) else { @@ -976,10 +1123,45 @@ extension SuggestionCoordinator { ) case let .invalid(reason): + logReconciliationMismatch(session: activeSession, rawContext: rawContext, reason: reason) invalidateActiveSuggestion(reason: reason) } } + /// Debug-only evidence for a reconciliation that killed the session: the exact text on both + /// sides of the comparison, escaped so invisible differences (non-breaking spaces, line breaks, + /// zero-width characters) are readable in the log. Type-through bugs are invisible without it. + private func logReconciliationMismatch( + session: ActiveSuggestionSession, + rawContext: FocusedInputSnapshot, + reason: String + ) { + func escaped(_ text: Substring) -> String { + text.unicodeScalars.map { scalar in + scalar.isASCII && !CharacterSet.controlCharacters.contains(scalar) + ? String(scalar) + : "\\u{\(String(scalar.value, radix: 16))}" + }.joined() + } + CotabbyLogger.suggestion.debug( + "Reconciliation invalidated the session", + metadata: [ + "stage": .string("reconcile-mismatch"), + "reason": .string(reason), + "base_preceding_tail": .string(escaped(session.baseContext.precedingText.suffix(24))), + "live_preceding_tail": .string(escaped(rawContext.precedingText.suffix(24))), + "base_trailing_head": .string(escaped(session.baseContext.trailingText.prefix(16))), + "live_trailing_head": .string(escaped(rawContext.trailingText.prefix(16))), + "base_preceding_count": .stringConvertible(session.baseContext.precedingText.count), + "live_preceding_count": .stringConvertible(rawContext.precedingText.count), + "full_text": .string(escaped(session.fullText.prefix(24))), + "consumed": .stringConvertible(session.consumedCharacterCount), + "selection": .string("\(rawContext.selection.location)+\(rawContext.selection.length)"), + "awaiting_insert_sync": .stringConvertible(interactionState.isAwaitingPostInsertionSync) + ] + ) + } + /// Applies a `.valid` reconciliation result: completes an exhausted session, or re-renders the /// remaining tail (subject to the stability gate). Extracted from `reconcileActiveSession` so that /// function stays within the project's cyclomatic-complexity budget after the correction branch. @@ -1174,6 +1356,7 @@ extension SuggestionCoordinator { /// Clears the active suggestion and optionally preserves or drops diagnostic breadcrumbs. func clearSuggestion(clearDiagnostics: Bool = false, preservingContinuation: Bool = false) { + hasPrefetchedContinuation = false if !preservingContinuation { cancelPreparedContinuation() } delayedStreamPresentation?.cancel() delayedStreamPresentation = nil diff --git a/Cotabby/App/Coordinators/Suggestion/SuggestionCoordinator.swift b/Cotabby/App/Coordinators/Suggestion/SuggestionCoordinator.swift index e92c42b17..227073e9b 100644 --- a/Cotabby/App/Coordinators/Suggestion/SuggestionCoordinator.swift +++ b/Cotabby/App/Coordinators/Suggestion/SuggestionCoordinator.swift @@ -17,6 +17,11 @@ final class SuggestionCoordinator: ObservableObject { var state: SuggestionDebugState = .idle var overlayState: OverlayState = .hidden(reason: "Overlay idle.") var latestGenerationNumber: UInt64? + /// True while the latest focus snapshot carried host-owned marked text (system inline + /// prediction or IME composition); see `SuggestionCoordinator+HostMarkedText.swift`. + var isHoldingForHostMarkedText = false + /// Work id of a generation re-issued from the word boundary after a seam misspelling, so the + /// retry's own result is judged once and never retried again. @Published var visualContextStatus: VisualContextStatus = .idle @Published var latestVisualContextText: String? @Published var totalTabAcceptedWordCount: Int = 0 @@ -126,6 +131,16 @@ final class SuggestionCoordinator: ObservableObject { /// ready → accepted/rejected) can be joined with a single `jq` filter on `request_id`. /// `nil` between sessions; replaced when `+Prediction` builds the next request. var latestRequestID: String? + /// The text before the caret the request now in flight was built from. A base model's + /// completion is exact text following it, so `GhostSpaceBoundary` reads the model's own word + /// boundary against it (see `SuggestionResult.spacingIsExact`). Kept beside `latestRequestID` + /// because both describe the in-flight request, and every reader is already guarded by the + /// work-id check that makes "in flight" meaningful. + var latestRequestPrecedingText: String? + /// True once the continuation of the active suggestion has been prefetched, so the extra + /// generation happens at most once per suggestion however many characters are typed through it. + /// Cleared whenever the session is torn down or replaced. + var hasPrefetchedContinuation = false /// Set when a full acceptance commits its final chunk; consumed by the next `apply`. Lets the /// coordinator drop a regeneration that only re-proposes the just-accepted tail before the host /// publishes the insert, the Chromium AX-publish race that otherwise loops accept/regenerate/ @@ -142,6 +157,7 @@ final class SuggestionCoordinator: ObservableObject { var suggestionAnchorCache = SuggestionAnchorCache() static let anchorReuseDisabledDefaultsKey = "cotabbyAnchorReuseDisabled" static let speculativePrefetchDisabledDefaultsKey = "cotabbySpeculativePrefetchDisabled" + static let continuationPrefetchDisabledDefaultsKey = "cotabbyContinuationPrefetchDisabled" /// Expected post-acceptance context. A speculative result may predate the live generation /// only when both its writing session and exact text match. A matching draft in a different diff --git a/Cotabby/App/Core/CotabbyAppEnvironment.swift b/Cotabby/App/Core/CotabbyAppEnvironment.swift index c30e83f6f..ee4a16185 100644 --- a/Cotabby/App/Core/CotabbyAppEnvironment.swift +++ b/Cotabby/App/Core/CotabbyAppEnvironment.swift @@ -163,7 +163,11 @@ final class CotabbyAppEnvironment { suggestionInserter.isComposingIMEActiveProvider = { [weak keyboardInputSourceMonitor] in keyboardInputSourceMonitor?.isComposingIMEActive ?? false } - let overlayController = OverlayController(suggestionSettings: suggestionSettings) + let overlayController = OverlayController( + suggestionSettings: suggestionSettings, + baselineCalibrator: HostBaselineCalibrator(), + faceMemoryDefaults: .standard + ) let activationIndicatorController = ActivationIndicatorController() let clipboardContextProvider = ClipboardContextProvider() let clipboardRelevanceFilter = ClipboardRelevanceFilter() @@ -192,7 +196,7 @@ final class CotabbyAppEnvironment { CotabbyLogger.app.info("Foundation model engine unavailable (SDK)") #endif - let suggestionEngine: any SuggestionGenerating = SuggestionEngineRouter( + let routedEngine: any SuggestionGenerating = SuggestionEngineRouter( suggestionSettings: suggestionSettings, foundationModelEngine: foundationModelEngine, llamaEngine: LlamaSuggestionEngine(runtimeManager: runtimeManager), @@ -217,6 +221,11 @@ final class CotabbyAppEnvironment { suggestionSettings?.openAICompatibleModelName.nonEmpty } ) + // Under `-cotabby-debug` with `cotabbyDebugForcedSuggestion` set, every request answers with + // that fixed text so ghost placement can be measured deterministically without a model. + let suggestionEngine: any SuggestionGenerating = DebugForcedSuggestionEngine.isConfigured() + ? DebugForcedSuggestionEngine(wrapping: routedEngine) + : routedEngine // Per-user emoji recents/frequency. Built before the settings coordinator so the // "Clear History" control can reach it, and before the picker which reads and writes it. diff --git a/Cotabby/Models/Focus/FocusModels.swift b/Cotabby/Models/Focus/FocusModels.swift index 6851d57c7..0ac67ebf5 100644 --- a/Cotabby/Models/Focus/FocusModels.swift +++ b/Cotabby/Models/Focus/FocusModels.swift @@ -124,15 +124,79 @@ enum FocusCapability: Equatable { /// back to its default styling. Stored as plain value types (no `NSFont`/`NSColor`) so the snapshot /// stays `Equatable`/`Sendable` and is cheap to carry across async boundaries. nonisolated struct ResolvedFieldStyle: Equatable, Sendable { - /// PostScript font name suitable for `NSFont(name:size:)`. + /// PostScript font name suitable for `NSFont(name:size:)`. Native AppKit hosts report it + /// (`Menlo-Regular`, `Helvetica`, `.AppleSystemUIFont`); web engines usually omit it. let fontName: String? - /// Host-reported point size, used only as the reference for scale-invariant metric sizing. + /// Family name (`AXFontFamily`), kept separately because a host can report the family without + /// a PostScript face, and the family alone still selects the right typeface. + let fontFamily: String? + /// Host-reported point size. This is the ghost font's rendered size whenever it is plausible: + /// matching the host's size exactly is what makes ghost glyphs line up with the host's text. + /// Chromium reports only this field, so a size-only style is a real, useful style. let fontPointSize: CGFloat? /// Foreground text color as a 6-digit hex string (see `SuggestionTextColorCodec`). let colorHex: String? + init(fontName: String?, fontFamily: String? = nil, fontPointSize: CGFloat?, colorHex: String?) { + self.fontName = fontName + self.fontFamily = fontFamily + self.fontPointSize = fontPointSize + self.colorHex = colorHex + } + + /// True when the host exposed nothing usable at all. A size-only style is NOT empty: the point + /// size is the single most important fact for matching the host's rendering. + var isEmpty: Bool { + fontName == nil && fontFamily == nil && fontPointSize == nil && colorHex == nil + } +} + +/// Facts about how the host actually renders the text next to the caret, measured from the host's +/// own Accessibility geometry rather than guessed from font tables. `GhostFontResolver` uses the +/// width sample to pick (or scale) a typeface when the host names no family, and the line pitch +/// lets a wrapped ghost place its second row exactly where the host would place the next line. +/// +/// Every field is optional: a host that exposes no line or range bounds simply contributes nothing +/// and the ghost falls back to font-table metrics. Plain value type so it rides along in the +/// `Equatable`/`Sendable` snapshot. +nonisolated struct HostTextMetrics: Equatable, Sendable { + /// Text immediately before the caret on the caret's line whose rendered width was measured. + let sampleText: String? + /// Rendered width of `sampleText` in points, from the host's own bounds-for-range answer. + let sampleWidth: CGFloat? + /// The host's rendered box for the caret's whole visual line, in global Cocoa coordinates. + /// Its `minX` is the real content left edge (where a wrapped ghost row starts). + let lineRect: CGRect? + /// Vertical distance between consecutive visual lines, when the host exposes line geometry. + let linePitch: CGFloat? + /// True when `lineRect` came from the text-marker API (a Chromium contenteditable, which answers + /// no index-based line query). Its left edge places wrapped rows, but the typographic caret + /// refinement that trusts an index-based line box is not verified against it. + let lineRectIsFromTextMarkers: Bool + /// True when `linePitch` is the height of the caret's one-line paragraph rather than a distance + /// measured between two lines (see `HostTextMetricsProbe.paragraphLinePitch`). Chromium rounds + /// that box out to whole points, 24 for a 23.1pt line at 110% (2026-09-11), so a pitch the host + /// style measured between lines outranks it, and it is never remembered in that one's place. + let linePitchIsFromParagraphBox: Bool + + init( + sampleText: String? = nil, + sampleWidth: CGFloat? = nil, + lineRect: CGRect? = nil, + linePitch: CGFloat? = nil, + lineRectIsFromTextMarkers: Bool = false, + linePitchIsFromParagraphBox: Bool = false + ) { + self.sampleText = sampleText + self.sampleWidth = sampleWidth + self.lineRect = lineRect + self.linePitch = linePitch + self.lineRectIsFromTextMarkers = lineRectIsFromTextMarkers + self.linePitchIsFromParagraphBox = linePitchIsFromParagraphBox + } + var isEmpty: Bool { - fontName == nil && colorHex == nil + sampleText == nil && lineRect == nil && linePitch == nil } } @@ -165,11 +229,30 @@ nonisolated struct ObservedContentEdges: Equatable, Sendable { /// caret is on, so they must not buy that same trust. Defaults to `false` so a future source has /// to opt in deliberately rather than inherit an exemption it did not earn. let isRunMeasured: Bool + /// Vertical distance between consecutive single-line runs, when at least two were seen: the + /// host's line pitch for editors whose line APIs give none (CodeMirror in Obsidian). + var linePitch: CGFloat? + /// Height of a single-line run's box (the rendered line box, which can be shorter than the + /// pitch when the host adds leading between lines). + var lineBoxHeight: CGFloat? + /// Set when the caret sits inside one run whose frame is the union of several wrapped lines; + /// the caret's line inside it is found by laying the paragraph out (see `WrappedRunAnchor`). + var wrappedRun: WrappedRunAnchor? - init(leftX: CGFloat, topY: CGFloat?, isRunMeasured: Bool = false) { + init( + leftX: CGFloat, + topY: CGFloat?, + isRunMeasured: Bool = false, + linePitch: CGFloat? = nil, + lineBoxHeight: CGFloat? = nil, + wrappedRun: WrappedRunAnchor? = nil + ) { self.leftX = leftX self.topY = topY self.isRunMeasured = isRunMeasured + self.linePitch = linePitch + self.lineBoxHeight = lineBoxHeight + self.wrappedRun = wrappedRun } /// A text margin read from one visual line through the host's line-query attributes: a left edge @@ -179,6 +262,25 @@ nonisolated struct ObservedContentEdges: Equatable, Sendable { } } +/// A caret inside a static-text run that spans several wrapped visual lines as one frame. +/// CodeMirror (Obsidian) exposes each paragraph as one `AXStaticText` whose frame is the union of +/// its wrapped lines, with no per-character bounds; proportional placement inside that union is +/// meaningless, but laying the paragraph out in the union's width with the host's font recovers +/// the caret's visual line, and the union's top plus the sibling runs' pitch gives that line's +/// exact position. Measured live: the alternative (mapping the caret against neighbouring runs) +/// put the ghost two lines away and flapped between lines while typing. +nonisolated struct WrappedRunAnchor: Equatable, Sendable { + /// The run's frame in global Cocoa coordinates. + let frame: CGRect + /// The caret's run up to the caret, taken from the live parent value (from where the run's + /// text was anchored in it) rather than from the run's own text, which lags while typing. + let paragraphTextBeforeCaret: String + /// True for a run that is one visual line (a short paragraph): its frame is already the + /// caret's line box, so the pixel read treats it as a single line and nothing lays its text + /// out to find the line. False for a union run whose frame spans wrapped lines. + var spansOneLine: Bool = false +} + /// What one line-margin lookup found, as the per-paragraph cache in `FocusSnapshotResolver` /// remembers it. Produced by `AXTextGeometryResolver.resolveLineContentEdges`. /// @@ -236,6 +338,16 @@ nonisolated struct FocusedInputSnapshot: Equatable { let subrole: String? let caretRect: CGRect let inputFrameRect: CGRect? + /// The focused element's own `AXFrame` in global Cocoa coordinates, read fresh on every poll and + /// never widened. `inputFrameRect` is grown to a parent container or a 500pt minimum for card + /// placement, so it cannot say where the host actually wraps; this can. + let elementFrameRect: CGRect? + /// The host's uncommitted (marked) text range in its own document coordinates, when it has one: + /// macOS inline predictive text shown after the caret, or an IME composition before it. The + /// host owns that span of the field until the user commits or dismisses it, so Cotabby must + /// neither generate against it nor paint a ghost over it (see `HostMarkedTextPolicy`). Marked + /// text after the caret is already excluded from `trailingText`. + let hostMarkedTextRange: NSRange? let caretSource: String let caretQuality: CaretGeometryQuality /// Average character width in points observed from AX child frame measurements. @@ -296,6 +408,12 @@ nonisolated struct FocusedInputSnapshot: Equatable { /// compiling unchanged. let fieldPlaceholder: String? + /// How the host renders text near the caret (measured widths, line box, line pitch), resolved + /// once per field so the ghost can match the host's typeface and wrap geometry. Nil when the + /// host exposes no measurable text geometry. The initializer default keeps existing call sites + /// compiling unchanged. + let hostTextMetrics: HostTextMetrics? + /// Explicit initializer keeps `focusChangeSequence` immutable while preserving the old /// memberwise-call ergonomics for tests that do not care about focus identity. /// @@ -325,7 +443,10 @@ nonisolated struct FocusedInputSnapshot: Equatable { focusedURLString: String? = nil, resolvedFieldStyle: ResolvedFieldStyle? = nil, windowTitle: String? = nil, - fieldPlaceholder: String? = nil + fieldPlaceholder: String? = nil, + hostTextMetrics: HostTextMetrics? = nil, + elementFrameRect: CGRect? = nil, + hostMarkedTextRange: NSRange? = nil ) { self.applicationName = applicationName self.bundleIdentifier = bundleIdentifier @@ -350,6 +471,9 @@ nonisolated struct FocusedInputSnapshot: Equatable { self.resolvedFieldStyle = resolvedFieldStyle self.windowTitle = windowTitle self.fieldPlaceholder = fieldPlaceholder + self.hostTextMetrics = hostTextMetrics + self.elementFrameRect = elementFrameRect + self.hostMarkedTextRange = hostMarkedTextRange } var identity: FocusedInputIdentity { @@ -359,6 +483,11 @@ nonisolated struct FocusedInputSnapshot: Equatable { ) } + /// True while the host shows uncommitted text of its own (see `hostMarkedTextRange`). + var hasHostMarkedText: Bool { + (hostMarkedTextRange?.length ?? 0) > 0 + } + nonisolated var sessionIdentity: FocusedInputSessionIdentity { FocusedInputSessionIdentity( processIdentifier: processIdentifier, bundleIdentifier: bundleIdentifier, diff --git a/Cotabby/Models/Runtime/LlamaRuntimeModels.swift b/Cotabby/Models/Runtime/LlamaRuntimeModels.swift index 86b3e2c5d..6664aab36 100644 --- a/Cotabby/Models/Runtime/LlamaRuntimeModels.swift +++ b/Cotabby/Models/Runtime/LlamaRuntimeModels.swift @@ -168,7 +168,7 @@ struct LlamaRuntimeConfiguration: Equatable, Sendable { "Qwen3.5-0.8B-Base.i1-Q6_K.gguf", "gemma-4-E4B.i1-Q4_K_M.gguf" ], - contextWindowTokens: 2048, + contextWindowTokens: 4096, batchSize: 512, gpuLayerCount: -1 ) @@ -201,6 +201,9 @@ struct LlamaGenerationOptions: Equatable, Sendable { /// degenerate instant stops (e.g. a lone leading period). Lives here so length presets can tune /// the floor without reaching into `DecodeStopPolicy`; the default preserves prior behavior. var sentenceStopMinimumTokens: Int = 2 + /// Minimum whitespace-separated words before the sentence-boundary early stop may fire; the + /// word-count preset's lower bound, so short sentence ends do not cut a suggestion to one word. + var sentenceStopMinimumWords: Int = 0 /// Stop decoding the moment the raw distribution's most-likely next token is end-of-generation, /// even when the stochastic sampler drew something else. The model's top choice being "stop" diff --git a/Cotabby/Models/Settings/CompletionRenderMode.swift b/Cotabby/Models/Settings/CompletionRenderMode.swift index 14d56cec9..a49f34682 100644 --- a/Cotabby/Models/Settings/CompletionRenderMode.swift +++ b/Cotabby/Models/Settings/CompletionRenderMode.swift @@ -34,13 +34,17 @@ nonisolated enum CompletionRenderMode: Equatable, Sendable { /// so the popup tracks the cursor the TextKit layout located. case caretLayoutEstimated - /// The caret sits mid-line: real characters follow it before the next line break. Inline - /// ghost text would draw on top of those trailing characters, so the suggestion is promoted - /// to the card, which anchors to the caret rect (the geometry is trustworthy here) and sits - /// just under the cursor like an inline ghost would. This is also the surface fill-in-middle - /// completions render in, since a FIM result has no inline home. + /// The caret sits mid-line (real characters follow it before the next line break), so an + /// inline ghost could only be painted over the host's own text. The card anchors to the + /// caret rect (the geometry is trustworthy here) and sits just under the cursor like an + /// inline ghost would. This is also the surface fill-in-middle completions render in. case caretMidLine + /// The policy chose inline, but no honest inline layout existed: not even the first word + /// fit on the caret's line and the host exposed no line pitch to place a second row on, or + /// text below the caret kept the ghost to that one row. The card shows the whole suggestion. + case inlineLayoutUnavailable + /// User set their global preference to always use mirror mode. Phase 2 wiring. case userPreference diff --git a/Cotabby/Models/Settings/SuggestionSettingsModel.swift b/Cotabby/Models/Settings/SuggestionSettingsModel.swift index 5726bef31..616443209 100644 --- a/Cotabby/Models/Settings/SuggestionSettingsModel.swift +++ b/Cotabby/Models/Settings/SuggestionSettingsModel.swift @@ -1197,7 +1197,7 @@ final class SuggestionSettingsModel: ObservableObject { } /// Raising the floor past the ceiling (or lowering the ceiling past the floor) would describe an - /// empty range, which `GhostFontMetrics` would resolve by letting the ceiling win — silently + /// empty range, which `GhostFontSizeLimits` would resolve by letting the ceiling win — silently /// ignoring the control the user just moved. Pushing the other value along keeps both controls /// honest and the range non-empty, and it matches how paired min/max controls behave elsewhere. func setGhostFontSizeFloor(_ points: Double) { diff --git a/Cotabby/Models/Suggestion/Request/FocusedInputContext.swift b/Cotabby/Models/Suggestion/Request/FocusedInputContext.swift index e0349941d..a711ada27 100644 --- a/Cotabby/Models/Suggestion/Request/FocusedInputContext.swift +++ b/Cotabby/Models/Suggestion/Request/FocusedInputContext.swift @@ -36,6 +36,14 @@ struct FocusedInputContext: Equatable, Sendable { let isWebContentField: Bool /// The host field's own text font/color, carried through so the overlay can match it. let resolvedFieldStyle: ResolvedFieldStyle? + /// Measured host text geometry (width sample, line box, line pitch), carried through so the + /// overlay can match the host's typeface and wrap exactly where the host would. + let hostTextMetrics: HostTextMetrics? + /// The focused element's unwidened frame (see `FocusedInputSnapshot.elementFrameRect`), the + /// only frame the overlay may use to decide where a ghost row must wrap. + let elementFrameRect: CGRect? + /// The host's uncommitted text range, if any (see `FocusedInputSnapshot.hostMarkedTextRange`). + let hostMarkedTextRange: NSRange? /// Surface metadata captured with the current focus snapshot, carried through so the request factory /// can condition the prompt on what the user is writing in (see `SurfaceContextComposer`). let windowTitle: String? @@ -69,6 +77,9 @@ struct FocusedInputContext: Equatable, Sendable { isSecure = snapshot.isSecure isWebContentField = snapshot.isWebContentField resolvedFieldStyle = snapshot.resolvedFieldStyle + hostTextMetrics = snapshot.hostTextMetrics + elementFrameRect = snapshot.elementFrameRect + hostMarkedTextRange = snapshot.hostMarkedTextRange windowTitle = snapshot.windowTitle fieldPlaceholder = snapshot.fieldPlaceholder focusedURLString = snapshot.focusedURLString @@ -84,6 +95,28 @@ struct FocusedInputContext: Equatable, Sendable { CaretLinePosition.isAtEndOfLine(trailingText: trailingText) } + /// True when real (non-whitespace) characters follow the caret anywhere in the captured + /// trailing window. A multi-row ghost would paint over that content, so the overlay keeps the + /// ghost to one row or promotes it to the card when this is set. + var hasTrailingContent: Bool { + trailingText.contains { !$0.isWhitespace } + } + + /// True for a field the host lays out on one line: a text field or combo box by its + /// Accessibility role (an HTML ``, Chromium's address bar, an `NSTextField`). Its text + /// scrolls sideways instead of wrapping, so a ghost row past its right edge has no line below + /// to go to: measured 2026-09-11 in a Chrome text input and a search input, a long ghost's + /// second row was drawn under the input, over the page. + var isSingleLineField: Bool { + role == "AXTextField" || role == "AXComboBox" + } + + /// True while the host shows uncommitted text of its own: an inline prediction after the caret + /// or an IME composition before it. Generation and ghost rendering pause until it clears. + var hasHostMarkedText: Bool { + (hostMarkedTextRange?.length ?? 0) > 0 + } + /// Stable per-process key for the focused field, intentionally NOT including the input frame /// rect. The polling signature in `FocusTracker` bumps `focusChangeSequence` whenever the /// field's frame changes (e.g., a chat composer growing taller as the user types wraps onto a diff --git a/Cotabby/Models/Suggestion/Request/SuggestionConfiguration.swift b/Cotabby/Models/Suggestion/Request/SuggestionConfiguration.swift index be9e009b5..3e39fffc9 100644 --- a/Cotabby/Models/Suggestion/Request/SuggestionConfiguration.swift +++ b/Cotabby/Models/Suggestion/Request/SuggestionConfiguration.swift @@ -107,6 +107,7 @@ struct SuggestionConfiguration: Equatable, Sendable { /// the app's starting value for a fresh install. let defaultUserName: String? let defaultWordCountPreset: SuggestionWordCountPreset + let focusPollIntervalMilliseconds: Int /// Output ceiling reserved out of the llama context window when sizing the prompt budget: @@ -140,15 +141,17 @@ struct SuggestionConfiguration: Equatable, Sendable { minP: 0.08, repetitionPenalty: 1.025, randomSeed: nil, - maxPrefixWords: 150, - // The llama prefix window matches the Foundation Models one: the extra preceding sentences - // carry the topic and voice that multi-paragraph email/docs continuations need, and the - // token budget below keeps the total prompt bounded by what the model can hold. Latency - // honesty: where KV prefix reuse works (dense models), the larger window is prefilled once - // per field; the hybrid/SWA catalog models reject partial trims and re-prefill per request, - // so there the wider window costs prefill only when the field actually holds more than the - // old 1000-char cap, i.e. long-document sessions, which is exactly where it buys quality. - maxPrefixCharacters: 2500, + // The llama prefix window is bounded by the token budget below (what the model's context + // can hold after the preface), not by a word cap: a 150-word cap left most of a long + // document out of the prompt, and the model's sense of the topic with it. The caps here + // only stop pathological fields (a megabyte of log text) from being windowed each poll. + // Latency honesty: where KV prefix reuse works (dense models), the window is prefilled + // once per field and every keystroke after that decodes only the delta (measured: one + // token); the hybrid/SWA catalog models reject partial trims and re-prefill per request, + // so there the wider window costs prefill only in long-document sessions, which is + // exactly where it buys quality. + maxPrefixWords: 2400, + maxPrefixCharacters: 14000, // Apple's on-device model has a 4096-token shared context. Even with instructions plus // visual/clipboard context, there is room to send ~3x the llama window before crowding // the prompt, and the extra surrounding sentences materially help mid-thought completions. @@ -158,8 +161,11 @@ struct SuggestionConfiguration: Equatable, Sendable { // Derived from the runtime constant so a context-window change can never silently // desynchronize the prompt budget from the KV capacity the model actually has. llamaPromptTokenBudget: SuggestionConfiguration.derivedLlamaPromptTokenBudget, - // Seed the profile settings with lightweight defaults on first launch. - defaultUserName: "Jacob", + // Seed the profile settings with lightweight defaults on first launch. No name until the + // user sets one: the prompt signs with it after a valediction (`SignOffCue`), and the + // endpoint backend can carry that prompt off the Mac, so a real name is an explicit opt-in + // and a placeholder would sign every user's mail with someone else's name. + defaultUserName: nil, defaultWordCountPreset: .twelveToTwenty, focusPollIntervalMilliseconds: 50 ) diff --git a/Cotabby/Models/Suggestion/Request/SuggestionRequest.swift b/Cotabby/Models/Suggestion/Request/SuggestionRequest.swift index ab4ee9c0a..f5bc963cd 100644 --- a/Cotabby/Models/Suggestion/Request/SuggestionRequest.swift +++ b/Cotabby/Models/Suggestion/Request/SuggestionRequest.swift @@ -74,6 +74,9 @@ struct SuggestionRequest: Equatable, Sendable { let surfaceContext: SurfaceContext? /// When enabled, the normalizer keeps multiple lines instead of truncating to the first line. let isMultiLineEnabled: Bool + /// The user's word-count preset, so decoding does not stop at a sentence end before the minimum + /// and the normalizer trims past the maximum (see `SuggestionLengthPolicy`). + let wordRange: SuggestionWordRange? /// Correlation ID stamped onto every log line touching this request — coordinator state /// transitions, router selection, engine generation, LLM I/O capture, insertion. Generated by /// `RequestID.generate()` in `SuggestionRequestFactory`. Defaulted in the init so test fixtures @@ -102,7 +105,8 @@ struct SuggestionRequest: Equatable, Sendable { visualContextSummary: String?, surfaceContext: SurfaceContext? = nil, isMultiLineEnabled: Bool, - requestID: String = "req_unknown" + requestID: String = "req_unknown", + wordRange: SuggestionWordRange? = nil ) { self.context = context self.prefixText = prefixText @@ -126,5 +130,6 @@ struct SuggestionRequest: Equatable, Sendable { self.surfaceContext = surfaceContext self.isMultiLineEnabled = isMultiLineEnabled self.requestID = requestID + self.wordRange = wordRange } } diff --git a/Cotabby/Models/Suggestion/Result/SuggestionResult.swift b/Cotabby/Models/Suggestion/Result/SuggestionResult.swift index a2464b1d8..1b1ef2108 100644 --- a/Cotabby/Models/Suggestion/Result/SuggestionResult.swift +++ b/Cotabby/Models/Suggestion/Result/SuggestionResult.swift @@ -13,6 +13,12 @@ struct SuggestionResult: Equatable, Sendable { /// type, and so engine-specific reasons can ride along without enum churn. The explicit /// initializer default keeps existing call sites compiling unchanged. let suppressionReason: String? + /// True when `text` is exact text following the request's preceding text, leading space + /// included. A base completion model writes its own word boundary, so a completion that starts + /// without a space continues the word before the caret ("1" + "50K" is 150K). Instruct and chat + /// engines drop leading spaces, so their results leave this false and `GhostSpaceBoundary` + /// decides from the characters on either side instead. + let spacingIsExact: Bool // This immutable Sendable value is also constructed by pure candidate-reconciliation rules. // Construction needs no UI actor; engine delivery and presentation remain main-actor owned. @@ -21,12 +27,14 @@ struct SuggestionResult: Equatable, Sendable { rawText: String, text: String, latency: TimeInterval, - suppressionReason: String? = nil + suppressionReason: String? = nil, + spacingIsExact: Bool = false ) { self.generation = generation self.rawText = rawText self.text = text self.latency = latency self.suppressionReason = suppressionReason + self.spacingIsExact = spacingIsExact } } diff --git a/Cotabby/Models/Suggestion/Session/ActiveSuggestionSession.swift b/Cotabby/Models/Suggestion/Session/ActiveSuggestionSession.swift index 0bcf431a6..7acdfcd49 100644 --- a/Cotabby/Models/Suggestion/Session/ActiveSuggestionSession.swift +++ b/Cotabby/Models/Suggestion/Session/ActiveSuggestionSession.swift @@ -50,6 +50,12 @@ struct ActiveSuggestionSession: Equatable, Sendable { fullText.droppingLeadingCharacters(consumedCharacterCount) } + /// The field's text before the caret once the whole suggestion is typed or accepted: the text + /// it was generated for plus all of it, whatever the host has published so far. + var precedingTextOnceTypedThrough: String { + baseContext.precedingText + fullText + } + /// The next visible and acceptable offer. This advances through the buffered prediction even /// in one-word mode, so a smaller ghost does not require a fresh model call after every word. var remainingText: String { diff --git a/Cotabby/Models/Suggestion/Session/SuggestionPresentationModels.swift b/Cotabby/Models/Suggestion/Session/SuggestionPresentationModels.swift index 7527ad550..c1dc188f8 100644 --- a/Cotabby/Models/Suggestion/Session/SuggestionPresentationModels.swift +++ b/Cotabby/Models/Suggestion/Session/SuggestionPresentationModels.swift @@ -25,6 +25,9 @@ struct SuggestionOverlayGeometry: Equatable, Sendable { let caretQuality: CaretGeometryQuality /// Host identity used to resolve presentation that varies by app. let bundleIdentifier: String? + /// The page URL when the host is a browser (`FocusedInputContext.focusedURLString`), so what the + /// overlay remembers about a host's text keeps one site's apart from another's. + let focusedURLString: String? /// True when the caret is at the end of its line: only whitespace, if anything, precedes the /// next line break. When false, real characters follow the caret on this line, so the /// render-mode policy promotes the suggestion to the card: inline ghost text would otherwise @@ -54,6 +57,38 @@ struct SuggestionOverlayGeometry: Equatable, Sendable { /// The host field's own text font/color, so the overlay can render ghost text that matches the /// field instead of always using the system font and a fixed gray. Nil falls back to defaults. let resolvedFieldStyle: ResolvedFieldStyle? + /// Measured host text geometry (see `HostTextMetrics`): width sample for typeface matching, + /// line box for the content left edge, and line pitch for exact multi-row placement. + let hostTextMetrics: HostTextMetrics? + /// True when the field's text is rendered by a web engine. The baseline rule differs between + /// TextKit (baseline at the layout manager's default offset) and web engines (rounded ascent + /// centered in the line box), so the renderer must know which one produced the caret box. + let isWebContentField: Bool + /// True when non-whitespace text follows the caret. A ghost may then occupy only one row: a + /// second row would paint over the host's own following lines. + let hasTrailingContent: Bool + /// True for a field the host keeps on one line (`FocusedInputContext.isSingleLineField`): its + /// text scrolls sideways rather than wrapping, so a ghost takes one row there and reveals the + /// rest as it is accepted. + let isSingleLineField: Bool + /// The focused element's own frame, unwidened (see `FocusedInputSnapshot.elementFrameRect`). + /// Wrapped ghost rows must stay inside it; `inputFrameRect` is grown for card placement and + /// would let a row run past the host's real right edge. + let elementFrameRect: CGRect? + /// The text between the last hard line break and the caret. The pixel typeface match renders + /// its tail in candidate faces; `GhostCaretRefinement` measures its advance for the exact caret. + let lineTextBeforeCaret: String? + /// Set when the caret sits inside a paragraph the host exposes only as one union-framed text + /// run (CodeMirror in Obsidian). AX cannot say which visual line the caret is on or where in it; + /// `PixelCaretLocator` measures both from the host's own pixels before the ghost is placed. + let wrappedRun: WrappedRunAnchor? + /// The caret line's baseline as an offset below the caret box top, read from the same pixels + /// that placed a pixel-measured caret; nil for every other caret. It outranks the baseline + /// policy and the calibrator's own strip for that presentation. + let pixelBaselineOffset: CGFloat? + /// Width of the ink on the caret's line as the pixel caret saw it; the typeface match trims + /// the paragraph tail to what fits it. Nil for every other caret. + let pixelLineInkWidth: CGFloat? /// Where the host actually starts drawing text, when it could be measured. A field's `AXFrame` /// is not its text area — Word publishes the whole page, so its left edge is the paper's edge /// rather than the document's margin. Ghost text that wraps onto another line aligns to this @@ -65,6 +100,7 @@ struct SuggestionOverlayGeometry: Equatable, Sendable { inputFrameRect: CGRect?, caretQuality: CaretGeometryQuality, bundleIdentifier: String? = nil, + focusedURLString: String? = nil, isCaretAtEndOfLine: Bool = true, observedCharWidth: CGFloat?, isRightToLeft: Bool, @@ -72,12 +108,22 @@ struct SuggestionOverlayGeometry: Equatable, Sendable { focusedInputIdentityKey: UInt64 = 0, isCorrection: Bool = false, resolvedFieldStyle: ResolvedFieldStyle? = nil, + hostTextMetrics: HostTextMetrics? = nil, + isWebContentField: Bool = false, + hasTrailingContent: Bool = false, + isSingleLineField: Bool = false, + elementFrameRect: CGRect? = nil, + lineTextBeforeCaret: String? = nil, + wrappedRun: WrappedRunAnchor? = nil, + pixelBaselineOffset: CGFloat? = nil, + pixelLineInkWidth: CGFloat? = nil, observedContentEdges: ObservedContentEdges? = nil ) { self.caretRect = caretRect self.inputFrameRect = inputFrameRect self.caretQuality = caretQuality self.bundleIdentifier = bundleIdentifier + self.focusedURLString = focusedURLString self.isCaretAtEndOfLine = isCaretAtEndOfLine self.observedCharWidth = observedCharWidth self.isRightToLeft = isRightToLeft @@ -85,6 +131,15 @@ struct SuggestionOverlayGeometry: Equatable, Sendable { self.focusedInputIdentityKey = focusedInputIdentityKey self.isCorrection = isCorrection self.resolvedFieldStyle = resolvedFieldStyle + self.hostTextMetrics = hostTextMetrics + self.isWebContentField = isWebContentField + self.hasTrailingContent = hasTrailingContent + self.isSingleLineField = isSingleLineField + self.elementFrameRect = elementFrameRect + self.lineTextBeforeCaret = lineTextBeforeCaret + self.wrappedRun = wrappedRun + self.pixelBaselineOffset = pixelBaselineOffset + self.pixelLineInkWidth = pixelLineInkWidth self.observedContentEdges = observedContentEdges } @@ -96,12 +151,59 @@ struct SuggestionOverlayGeometry: Equatable, Sendable { inputFrameRect: inputFrameRect, caretQuality: caretQuality, bundleIdentifier: bundleIdentifier, + focusedURLString: focusedURLString, isCaretAtEndOfLine: isCaretAtEndOfLine, observedCharWidth: observedCharWidth, isRightToLeft: isRightToLeft, focusChangeSequence: focusChangeSequence, focusedInputIdentityKey: focusedInputIdentityKey, + isCorrection: isCorrection, resolvedFieldStyle: resolvedFieldStyle, + hostTextMetrics: hostTextMetrics, + isWebContentField: isWebContentField, + hasTrailingContent: hasTrailingContent, + isSingleLineField: isSingleLineField, + elementFrameRect: elementFrameRect, + lineTextBeforeCaret: lineTextBeforeCaret, + wrappedRun: wrappedRun, + observedContentEdges: observedContentEdges + ) + } + + /// A copy carrying a pixel-measured caret: the measured box replaces the caret, the quality + /// becomes `.derived` (a real measurement, not an estimate), and the measured line box and + /// pitch ride along as host metrics so wrapped rows land on the host's real next lines. + func withPixelMeasuredCaret( + _ caretRect: CGRect, lineRect: CGRect, linePitch: CGFloat?, baselineOffsetFromTop: CGFloat? = nil, lineInkWidth: CGFloat? = nil + ) -> SuggestionOverlayGeometry { + SuggestionOverlayGeometry( + caretRect: caretRect, + inputFrameRect: inputFrameRect, + caretQuality: .derived, + bundleIdentifier: bundleIdentifier, + focusedURLString: focusedURLString, + isCaretAtEndOfLine: isCaretAtEndOfLine, + observedCharWidth: observedCharWidth, + isRightToLeft: isRightToLeft, + focusChangeSequence: focusChangeSequence, + focusedInputIdentityKey: focusedInputIdentityKey, + isCorrection: isCorrection, + resolvedFieldStyle: resolvedFieldStyle, + hostTextMetrics: HostTextMetrics( + sampleText: hostTextMetrics?.sampleText, + sampleWidth: hostTextMetrics?.sampleWidth, + lineRect: lineRect, + linePitch: linePitch ?? hostTextMetrics?.linePitch, + linePitchIsFromParagraphBox: linePitch == nil && (hostTextMetrics?.linePitchIsFromParagraphBox ?? false) + ), + isWebContentField: isWebContentField, + hasTrailingContent: hasTrailingContent, + isSingleLineField: isSingleLineField, + elementFrameRect: elementFrameRect, + lineTextBeforeCaret: lineTextBeforeCaret, + wrappedRun: wrappedRun, + pixelBaselineOffset: baselineOffsetFromTop, + pixelLineInkWidth: lineInkWidth, observedContentEdges: observedContentEdges ) } diff --git a/Cotabby/Models/Suggestion/SuggestionSubsystemContracts.swift b/Cotabby/Models/Suggestion/SuggestionSubsystemContracts.swift index b982f4da3..c5fce9a4f 100644 --- a/Cotabby/Models/Suggestion/SuggestionSubsystemContracts.swift +++ b/Cotabby/Models/Suggestion/SuggestionSubsystemContracts.swift @@ -253,12 +253,19 @@ protocol SuggestionOverlayControlling: AnyObject { /// safely slide (hidden, mirror mode, RTL, multi-line, or nothing rendered to measure /// against); callers then fall back to a caret-anchored present. func advanceInline(to remainingText: String, insertedText: String) -> Bool + + /// Called when generation starts for `context`, so the controller can do the slow parts of an + /// inline presentation (measuring the host's painted baseline) before the suggestion arrives. + func prepareInlinePresentation(for context: FocusedInputContext) } extension SuggestionOverlayControlling { /// Default: not supported, so conformers that do not render an inline panel (e.g. test doubles) /// transparently fall back to the caret-anchored present path. func advanceInline(to remainingText: String, insertedText: String) -> Bool { false } + + /// Default: nothing to prepare. + func prepareInlinePresentation(for context: FocusedInputContext) {} } @MainActor diff --git a/Cotabby/Services/Focus/Caching/CaretAdvanceSampleStore.swift b/Cotabby/Services/Focus/Caching/CaretAdvanceSampleStore.swift new file mode 100644 index 000000000..f08f05ca4 --- /dev/null +++ b/Cotabby/Services/Focus/Caching/CaretAdvanceSampleStore.swift @@ -0,0 +1,65 @@ +import Foundation +import Logging + +/// Keeps one `CaretAdvanceSampler` for the focused field across focus polls. +/// +/// The sampler is a value type that has to remember the previous poll's caret; the resolver that +/// feeds it is a value type too, so the memory lives here, in a reference the resolver retains. +/// Same lifetime shape as `HostTextMetricsCache`: a new field key starts a fresh sampler, and only +/// the current field's sampler is kept. +@MainActor +final class CaretAdvanceSampleStore { + private var key: String? + private var sampler = CaretAdvanceSampler() + /// The last observation traced for this field, so the debug trace records only polls where the + /// caret's offset, position or text changed: a handful per keystroke, not twenty a second. + private var lastTraced: CaretAdvanceSampler.Observation? + + /// Feeds one poll for the field `key` and returns the sampler's current sample. + func sample(forKey key: String, observation: CaretAdvanceSampler.Observation) -> CaretAdvanceSampler.Sample? { + if key != self.key { + self.key = key + sampler = CaretAdvanceSampler() + lastTraced = nil + } + let sample = sampler.observe(observation) + trace(observation, sample: sample) + return sample + } + + /// Debug-only record of each changed observation and the sample after it. It is the evidence + /// for how a host orders its updates: whether the caret box moves before or after the + /// keystroke's text is published decides how a width sample pairs characters with advances. + private func trace(_ observation: CaretAdvanceSampler.Observation, sample: CaretAdvanceSampler.Sample?) { + guard CotabbyLogger.focus.logLevel <= .debug else { return } + if let lastTraced, lastTraced.documentCaret == observation.documentCaret, + abs(lastTraced.caretX - observation.caretX) < 0.01, lastTraced.precedingText == observation.precedingText { + return + } + lastTraced = observation + // Typed pieces first: Xcode 26.3's type checker gave up on the mixed dictionary literal + // when the optional-chaining and `Double(...)` conversions were inlined into it. + let caretX = Double(observation.caretX) + let lineY = Double(observation.lineY) + let tail = String(observation.precedingText.suffix(8)) + let sampleText: String = sample.map { String($0.text.suffix(24)) } ?? "" + let sampleWidth = Double(sample?.width ?? 0) + let sampleCount: Int = sample?.text.count ?? 0 + let metadata: Logger.Metadata = [ + "stage": .string("caret-advance"), + "doc": .stringConvertible(observation.documentCaret), + "x": .stringConvertible(caretX), + "y": .stringConvertible(lineY), + "tail": .string(tail), + "positioned": .stringConvertible(observation.isPositioned), + "pending": .stringConvertible(sampler.pendingCharacterCount), + "sample": .string(sampleText), + "sample_w": .stringConvertible(sampleWidth), + "sample_n": .stringConvertible(sampleCount) + ] + CotabbyLogger.focus.debug("Caret advance observed", metadata: metadata) + } + + // Value-typed storage only; see `FieldStyleCache` for why the deinit is nonisolated. + nonisolated deinit {} +} diff --git a/Cotabby/Services/Focus/Caching/FieldStyleCache.swift b/Cotabby/Services/Focus/Caching/FieldStyleCache.swift index 2895db1b0..9477d2626 100644 --- a/Cotabby/Services/Focus/Caching/FieldStyleCache.swift +++ b/Cotabby/Services/Focus/Caching/FieldStyleCache.swift @@ -1,35 +1,103 @@ import Foundation /// Caches the resolved field text style per focused element so the `AXAttributedStringForRange` -/// read happens once per field, not on every focus poll. +/// read happens once per style run, not on every focus poll. /// /// Reading per-character font/color is a synchronous cross-process Accessibility call. The focus /// resolver runs many times per second while focus stays on one field, so re-reading every poll -/// would add avoidable main-thread latency on the hot path. Keying on element identity collapses -/// that to one read per field. A nil result (host exposes no style) is cached too, so a plain field -/// is not re-probed on every poll. +/// would add avoidable main-thread latency on the hot path. Keying on element identity alone is not +/// enough, though: rich-text hosts (Notes, Mail, TextEdit, Pages) change font at the caret when the +/// user moves between runs or restyles text, and a style frozen at first focus then renders the +/// ghost in the wrong face. The cache therefore also tracks the *style run* the caret sits in +/// (`AXStyleRangeForIndex`, one extra call only when the caret leaves the known run) and the caret +/// box height (a free signal that the line's font changed on hosts without style ranges). +/// +/// Chromium builds inline text boxes lazily, so its first attributed-string answer after focus is +/// empty and only later polls carry the font; caching that first nil forever left web fields with +/// no size. Empty answers are retried a bounded number of times, spaced over successive polls. /// /// A reference type so it can carry state across the value-typed `FocusSnapshotResolver`'s /// non-mutating `resolveSnapshot`, mirroring `DeepGeometryWalkThrottle`. The resolver is constructed /// once and retained by `FocusTracker`. @MainActor final class FieldStyleCache { - private var key: String? + static let maximumAttempts = 6 + static let retryInterval: TimeInterval = 0.25 + + /// What one cached answer was resolved for. A poll whose caret is still inside `styleRun` (or + /// whose run lookup returns the same run) with an unchanged caret height reuses the answer. + struct Probe: Equatable { + let fieldKey: String + /// Style run containing the character before the caret, in the host's own text + /// coordinates; nil when the host exposes no style ranges. + let styleRun: NSRange? + /// The caret box height rounded to whole points. + let caretHeightBucket: Int + } + + private var probe: Probe? private var style: ResolvedFieldStyle? + private var attempts = 0 + private var lastAttemptAt: Date? - /// Returns the cached style when `key` matches the last resolution, otherwise resolves once and - /// caches the result (including nil). - func style(forKey key: String, resolve: () -> ResolvedFieldStyle?) -> ResolvedFieldStyle? { - if key == self.key { - return style + /// Returns the cached style for the caret's current style run, otherwise resolves and caches. + /// + /// - `caretLocation` is the caret offset in the host's coordinates; a cached style whose + /// `styleRun` contains the character before it is reused without any AX call. + /// - `styleRun` is invoked only when the cached run does not cover the caret (or nothing is + /// cached); it may return nil for hosts without style ranges. + /// - A nil result is retried on later calls (spaced by `retryInterval`) until + /// `maximumAttempts`, after which nil is cached for the probe. + func style( + forKey fieldKey: String, + caretLocation: Int, + caretHeight: CGFloat, + now: Date = Date(), + styleRun: () -> NSRange?, + resolve: () -> ResolvedFieldStyle? + ) -> ResolvedFieldStyle? { + let heightBucket = Int(caretHeight.rounded()) + if let probe, probe.fieldKey == fieldKey, probe.caretHeightBucket == heightBucket, + let cached = style, let run = probe.styleRun, Self.run(run, containsCaret: caretLocation) { + return cached } + let nextProbe = Probe(fieldKey: fieldKey, styleRun: styleRun(), caretHeightBucket: heightBucket) + if nextProbe != probe { + let sameFieldSameRun = probe?.fieldKey == fieldKey + && probe?.caretHeightBucket == heightBucket + && probe?.styleRun != nil + && probe?.styleRun?.location == nextProbe.styleRun?.location + probe = nextProbe + if !sameFieldSameRun { + style = nil + attempts = 0 + lastAttemptAt = nil + } + } + if let style { + return style + } + guard attempts < Self.maximumAttempts else { + return nil + } + if let lastAttemptAt, now.timeIntervalSince(lastAttemptAt) < Self.retryInterval { + return nil + } + attempts += 1 + lastAttemptAt = now let resolved = resolve() - self.key = key style = resolved return resolved } + /// The character before the caret is the one the style was read from, so the caret sits inside + /// the run when that character does. + private static func run(_ run: NSRange, containsCaret caret: Int) -> Bool { + let probedIndex = max(caret - 1, 0) + return probedIndex >= run.location && probedIndex < run.location + max(run.length, 1) + } + // Stored state is plain value types, safe to release anywhere. The nonisolated deinit keeps // deallocation off the back-deployment main-actor executor shim, whose StopLookupScope // double-frees on macOS 26 (see InputSuppressionController). Production's single long-lived diff --git a/Cotabby/Services/Focus/Caching/HostTextMetricsCache.swift b/Cotabby/Services/Focus/Caching/HostTextMetricsCache.swift new file mode 100644 index 000000000..bf0830400 --- /dev/null +++ b/Cotabby/Services/Focus/Caching/HostTextMetricsCache.swift @@ -0,0 +1,143 @@ +import Foundation + +/// Caches `HostTextMetrics` per focused element so the handful of bounds queries behind them run +/// once per field, not on every focus poll. +/// +/// Two retry paths exist because hosts fail in two different ways: +/// - Chromium loads inline text boxes lazily: the first bounds query after focus returns an empty +/// rect and only later queries answer. An empty answer is retried a bounded number of times, +/// spaced out over successive polls, then given up on so a host that never answers costs +/// nothing more. +/// - A field focused while (nearly) empty has no text to measure a width sample from, yet its +/// line geometry may already be known. Caching that answer as final would pin the field to +/// "no sample" for the whole session, and the typeface match would never improve. A missing +/// sample is therefore re-measured when the caret has moved to a new offset where a sample +/// could exist, again a bounded number of times. +/// - A field with a single line so far has no line pitch to measure (there is no other line to +/// compare against). The pitch is re-measured once the caret has travelled far enough that the +/// text may have wrapped, a bounded number of times, so a ghost can wrap exactly once the host +/// has shown where its second line sits. +/// +/// Same lifetime shape as `FieldStyleCache`: a reference type retained by the value-typed resolver. +@MainActor +final class HostTextMetricsCache { + static let maximumAttempts = 6 + static let maximumSampleAttempts = 6 + static let maximumPitchAttempts = 12 + /// Caret travel (UTF-16 units) before a missing pitch is worth another look: a wrapped line is + /// never shorter than this in a field wide enough to wrap at all. + static let pitchRetryCaretDistance = 16 + static let retryInterval: TimeInterval = 0.25 + /// Shortest prefix the probe can measure a width from (see `HostTextMetricsProbe.widthSample`). + static let minimumSampleCaret = 2 + + private var key: String? + private var metrics: HostTextMetrics? + private var attempts = 0 + private var sampleAttempts = 0 + private var pitchAttempts = 0 + private var lastSampleCaret: Int? + private var lastPitchCaret: Int? + private var lastAttemptAt: Date? + + func metrics( + forKey key: String, + caretLocation: Int, + now: Date = Date(), + measure: () -> HostTextMetrics? + ) -> HostTextMetrics? { + if key != self.key { + self.key = key + metrics = nil + attempts = 0 + sampleAttempts = 0 + pitchAttempts = 0 + lastSampleCaret = nil + lastPitchCaret = nil + lastAttemptAt = nil + } + if let metrics { + let retrySample = metrics.sampleText == nil && shouldRetrySample(caretLocation: caretLocation, now: now) + // A paragraph's box stands in for a pitch until two lines give one (see + // `HostTextMetrics.linePitchIsFromParagraphBox`), so it does not end the search. + let retryPitch = (metrics.linePitch == nil || metrics.linePitchIsFromParagraphBox) + && shouldRetryPitch(caretLocation: caretLocation, now: now) + guard retrySample || retryPitch else { + return metrics + } + if retrySample { + sampleAttempts += 1 + lastSampleCaret = caretLocation + } + if retryPitch { + pitchAttempts += 1 + lastPitchCaret = caretLocation + } + lastAttemptAt = now + guard let remeasured = measure() else { + return metrics + } + // Every answer only adds to what is known. WebKit answers no line for a caret at the + // very end of the text, so a re-measure taken there must not drop the line box learned + // at focus time, and a sample-less re-measure must not drop the sample. + // A pitch measured between two lines is not given up for a paragraph's box read later + // (a caret back on a paragraph's first line), which is only its whole-point stand-in. + let keepsMeasuredPitch = metrics.linePitch != nil && !metrics.linePitchIsFromParagraphBox + && remeasured.linePitchIsFromParagraphBox + let keptPitchMetrics = keepsMeasuredPitch || remeasured.linePitch == nil ? metrics : remeasured + let merged = HostTextMetrics( + sampleText: remeasured.sampleText ?? metrics.sampleText, + sampleWidth: remeasured.sampleText != nil ? remeasured.sampleWidth : metrics.sampleWidth, + lineRect: remeasured.lineRect ?? metrics.lineRect, + linePitch: keptPitchMetrics.linePitch, + // The flags travel with whichever line box and pitch were kept. + lineRectIsFromTextMarkers: remeasured.lineRect != nil + ? remeasured.lineRectIsFromTextMarkers : metrics.lineRectIsFromTextMarkers, + linePitchIsFromParagraphBox: keptPitchMetrics.linePitchIsFromParagraphBox + ) + self.metrics = merged + return merged + } + guard attempts < Self.maximumAttempts else { + return nil + } + if let lastAttemptAt, now.timeIntervalSince(lastAttemptAt) < Self.retryInterval { + return nil + } + attempts += 1 + lastAttemptAt = now + lastSampleCaret = caretLocation + lastPitchCaret = caretLocation + let measured = measure() + metrics = measured + return measured + } + + private func shouldRetryPitch(caretLocation: Int, now: Date) -> Bool { + guard pitchAttempts < Self.maximumPitchAttempts, + abs(caretLocation - (lastPitchCaret ?? caretLocation)) >= Self.pitchRetryCaretDistance + else { + return false + } + if let lastAttemptAt, now.timeIntervalSince(lastAttemptAt) < Self.retryInterval { + return false + } + return true + } + + private func shouldRetrySample(caretLocation: Int, now: Date) -> Bool { + guard sampleAttempts < Self.maximumSampleAttempts, + caretLocation >= Self.minimumSampleCaret, + caretLocation != lastSampleCaret + else { + return false + } + if let lastAttemptAt, now.timeIntervalSince(lastAttemptAt) < Self.retryInterval { + return false + } + return true + } + + // Value-typed storage only; see `FieldStyleCache` for why the deinit is nonisolated. + nonisolated deinit {} +} diff --git a/Cotabby/Services/Focus/Chromium/ChromiumAccessibilityEnabler.swift b/Cotabby/Services/Focus/Chromium/ChromiumAccessibilityEnabler.swift index 958de922b..bd059408c 100644 --- a/Cotabby/Services/Focus/Chromium/ChromiumAccessibilityEnabler.swift +++ b/Cotabby/Services/Focus/Chromium/ChromiumAccessibilityEnabler.swift @@ -67,6 +67,21 @@ final class ChromiumAccessibilityEnabler { CotabbyLogger.focus.debug( "CHROME-PRIME enabled web accessibility for \(application.localizedName ?? "?")") case .attributeUnsupported: + // `AXManualAccessibility` is an Electron addition; Chrome itself rejects it. Chrome's + // full accessibility mode (the one that computes inline text boxes, so character bounds + // and font attributes exist) is switched on by the VoiceOver signal instead. Without it + // Chrome answers caret markers but never character bounds or fonts, and the ghost can + // only guess the host's typeface. Measured 2026-09: all four test fields returned empty + // bounds and an empty font dictionary until this flag was set. + if BrowserAppDetector.isChromiumBrowser(bundleIdentifier: application.bundleIdentifier), + !Self.isEnhancedUserInterfaceDisabled { + if AXHelper.setEnhancedUserInterface(true, forApplicationPID: pid) { + primedPIDs.insert(pid) + CotabbyLogger.focus.debug( + "CHROME-PRIME enabled enhanced accessibility for \(application.localizedName ?? "?")") + return + } + } // Codex embeds Chromium in a native shell that rejects AXManualAccessibility. // Its native AXEnhancedUserInterface switch exposes the composed web tree instead. // Keep this fallback specific to Codex: changing it globally can affect window managers. @@ -88,4 +103,13 @@ final class ChromiumAccessibilityEnabler { break } } + + /// Kill switch for the Chromium enhanced-accessibility fallback. Some window managers misplace + /// windows of apps that have `AXEnhancedUserInterface` set; a user who hits that can turn the + /// fallback off and keep the caret-marker geometry (with font matching by size only). + static let enhancedUserInterfaceDisabledDefaultsKey = "cotabbyChromiumEnhancedAccessibilityDisabled" + + private static var isEnhancedUserInterfaceDisabled: Bool { + UserDefaults.standard.bool(forKey: enhancedUserInterfaceDisabledDefaultsKey) + } } diff --git a/Cotabby/Services/Focus/FocusTracker.swift b/Cotabby/Services/Focus/FocusTracker.swift index ffe5ea9d3..3a5f30582 100644 --- a/Cotabby/Services/Focus/FocusTracker.swift +++ b/Cotabby/Services/Focus/FocusTracker.swift @@ -242,6 +242,26 @@ final class FocusTracker { return snapshotChanged || capture.didChangeFocusedInput } + /// A system alert or banner can own the system-wide focused element while the user types into + /// the frontmost app (see `SystemUIFocusShadowPolicy`). Ask the frontmost app for its own focused + /// element in that case so autocomplete does not vanish for the alert's lifetime; when that + /// query fails, the system-wide answer stands as before (and `application` stays nil so the + /// owner is resolved from the element, exactly as for any other system-focused element). + private func resolvingSystemUIShadow( + _ systemFocused: AXUIElement + ) -> (element: AXUIElement, application: NSRunningApplication?) { + guard let frontmost = NSWorkspace.shared.frontmostApplication, + SystemUIFocusShadowPolicy.shouldPreferFrontmostApplication( + owningBundleIdentifier: AXHelper.owningApplication(of: systemFocused)?.bundleIdentifier, + frontmostBundleIdentifier: frontmost.bundleIdentifier + ), + let appFocused = AXHelper.focusedElement(forApplicationPID: frontmost.processIdentifier) + else { + return (systemFocused, nil) + } + return (appFocused, frontmost) + } + /// Captures the current frontmost application's focused element and reduces it into a snapshot. private func captureSnapshot() -> FocusCaptureResult { guard permissionProvider() else { @@ -265,7 +285,9 @@ final class FocusTracker { focusedElement = codex.element preresolvedApplication = codex.application } else if let systemFocused { - focusedElement = systemFocused + let resolved = resolvingSystemUIShadow(systemFocused) + focusedElement = resolved.element + preresolvedApplication = resolved.application // System focus works here, so we are not in the OOPIF fallback mode; drop any stale // hit-test element so it can never shadow a real focus change. chromiumHitTestCache = nil diff --git a/Cotabby/Services/Focus/Resolution/AXTextGeometryResolver.swift b/Cotabby/Services/Focus/Resolution/AXTextGeometryResolver.swift index 90e766349..290d119f9 100644 --- a/Cotabby/Services/Focus/Resolution/AXTextGeometryResolver.swift +++ b/Cotabby/Services/Focus/Resolution/AXTextGeometryResolver.swift @@ -81,67 +81,90 @@ struct AXTextGeometryResolver { textValue: String? = nil, textSelection: NSRange? = nil, staticRunThrottle: StaticTextRunWalkThrottle? = nil, - focusChangeSequence: UInt64 = 0 + focusChangeSequence: UInt64 = 0, + supportsLineQueries: Bool = false ) -> CaretGeometryResult? { let selectionInTextValue = textSelection ?? selection - // Branch 1: Zero-length BoundsForRange at the caret position — ideal case. - // Gated on `supportsBoundsForRange` because the API is a synchronous cross-process - // call into the focused app's AX implementation. In Chrome that's a round-trip into - // the renderer, and the deep-tree walker can touch many leaves per focus poll; calling - // BoundsForRange on nodes that don't advertise support stalled the main thread badly - // enough to freeze typing. The `rectIsNearAnchor` validator stays as a correctness - // guard for supporters that return rects belonging to an unrelated range. + // Branch 1 (previous-character trailing edge). Ask the host for the bounds of the character + // before the caret and take its trailing edge: for left-to-right text that IS the insertion + // point, and its box is the real rendered line. Measured against TextEdit and Chrome, this + // answer coincides with the zero-length caret query on x while the zero-length query is the + // less trustworthy one: TextKit reports the end-of-document caret one line too high, and + // Chrome reports a caret after a trailing newline at the end of the previous line. A previous + // character that is itself a line break describes the previous line, so that case defers to + // the zero-length query below. + // Gated on `supportsBoundsForRange` because the API is a synchronous cross-process call into + // the focused app's AX implementation; the `rectIsNearAnchor` validator stays as a + // correctness guard for supporters that return rects belonging to an unrelated range. + let previousCharacter = Self.character(before: selectionInTextValue.location, in: textValue) if supportsBoundsForRange, + selection.location > 0, + let previousCharacter, !previousCharacter.isNewline, let rect = AXHelper.parameterizedRectValue( for: kAXBoundsForRangeParameterizedAttribute as CFString, - range: NSRange(location: selection.location, length: 0), + range: NSRange(location: selection.location - 1, length: 1), on: element - ), !rect.isEmpty { + ), rect.width > 0, rect.height > 0, AXHelper.rectHasFiniteComponents(rect) { let cocoaRect = AXHelper.validatedCocoaTextRect( fromAccessibilityRect: rect, anchorFrame: cocoaAnchorFrame ) if rectIsNearAnchor(cocoaRect, anchor: cocoaAnchorFrame) { + let isRightToLeft = textValue.map(TextDirectionDetector.isRightToLeft) ?? false return CaretGeometryResult( - rect: normalizedCaretRect(fromZeroLengthRangeRect: cocoaRect), - quality: .exact + rect: Self.caretRect(afterCharacterFrame: cocoaRect, rightToLeft: isRightToLeft), + quality: .exact, + sourceDetail: "previous-character" ) } } - // Branch 1.5: Chromium / WebKit AXTextMarker fallback. - // Apps like Discord/Chrome fail NSRange queries but return a correct bounding box - // when we ask for the caret via their internal AXTextMarkerRange objects. - if let markerRect = AXHelper.textMarkerCaretRect(on: element), !markerRect.isEmpty { - let cocoaRect = AXHelper.validatedCocoaTextRect( - fromAccessibilityRect: markerRect, - anchorFrame: cocoaAnchorFrame - ) - return CaretGeometryResult( - rect: normalizedCaretRect(fromZeroLengthRangeRect: cocoaRect), - quality: .exact - ) - } - - // Branch 2: BoundsForRange on the character before the caret, then shift to its trailing edge. - // Same gate and anchor validation as Branch 1. + // Branch 1.2: zero-length BoundsForRange at the caret. Reached at the start of a field or + // right after a line break. Hosts answer this with a zero-WIDTH rect, so the check is on + // height, not `isEmpty` (which is true for any zero-width rect and used to discard every + // legitimate caret box here). if supportsBoundsForRange, - selection.location > 0, let rect = AXHelper.parameterizedRectValue( for: kAXBoundsForRangeParameterizedAttribute as CFString, - range: NSRange(location: selection.location - 1, length: 1), + range: NSRange(location: selection.location, length: 0), on: element - ), !rect.isEmpty { + ), rect.height > 0, AXHelper.rectHasFiniteComponents(rect) { let cocoaRect = AXHelper.validatedCocoaTextRect( fromAccessibilityRect: rect, anchorFrame: cocoaAnchorFrame ) + if rectIsNearAnchor(cocoaRect, anchor: cocoaAnchorFrame) { + let normalized = normalizedCaretRect(fromZeroLengthRangeRect: cocoaRect) + return zeroLengthCaretResult( + normalized, + context: ZeroLengthCaretContext( + element: element, + caretLocation: selection.location, + supportsLineQueries: supportsLineQueries, + anchorFrame: cocoaAnchorFrame, + isAtTextEndAfterNewline: previousCharacter?.isNewline == true + && selectionInTextValue.location >= ((textValue ?? "") as NSString).length + ) + ) + } + } + + // Branch 1.5: Chromium / WebKit AXTextMarker fallback. + // Apps like Discord/Chrome fail NSRange queries but return a correct bounding box + // when we ask for the caret via their internal AXTextMarkerRange objects. The caret box is + // zero-width, so only its height is checked. + if let markerRect = AXHelper.textMarkerCaretRect(on: element), + markerRect.height > 0, AXHelper.rectHasFiniteComponents(markerRect) { + let cocoaRect = AXHelper.validatedCocoaTextRect( + fromAccessibilityRect: markerRect, + anchorFrame: cocoaAnchorFrame + ) if rectIsNearAnchor(cocoaRect, anchor: cocoaAnchorFrame) { return CaretGeometryResult( - rect: CGRect( - x: cocoaRect.maxX, y: cocoaRect.minY, width: 2, height: cocoaRect.height), - quality: .derived + rect: normalizedCaretRect(fromZeroLengthRangeRect: cocoaRect), + quality: .exact, + sourceDetail: "text-marker" ) } } @@ -419,13 +442,19 @@ struct AXTextGeometryResolver { // Map the caret offset to a run by aligning run texts inside the parent value (see // `caretRunPlacement`). The run frame's Y is a real rendered line position, so a correct // run choice is what makes derived geometry trustworthy vertically. - guard let placement = Self.caretRunPlacement( + guard let placementWithStart = Self.caretRunPlacementWithStart( runTexts: textRuns.map(\.text), parentText: parentText, caretOffset: parentSelection.location ) else { return nil } + let placement = placementWithStart.placement + // The caret's own run up to the caret (see `runTextBeforeCaret`), from the live parent + // value: the run's own text lags while typing. + let runTextBeforeCaret = Self.runTextBeforeCaret( + in: parentText, runStartOffset: placementWithStart.runStartOffset, caretOffset: parentSelection.location + ) // Electron editors may expose one AXStaticText child whose frame is the union of several // soft-wrapped lines. A proportional X inside that union has no relationship to the caret, @@ -433,41 +462,67 @@ struct AXTextGeometryResolver { // if the host withholds those too, demote to field-frame geometry so presentation-time // TextKit repair can lay out the complete prefix. let selectedRun = textRuns[placement.runIndex] - guard selectedRun.allowsProportionalCaretPlacement else { - return resolveWrappedRunCaret( - selectedRun, - parentText: parentText, - parentSelection: parentSelection, - fallbackFrame: fallbackFrame - ) - } + let siblingLines = Self.lineGeometry(fromSingleLineRuns: textRuns) // Derive metrics only from runs that plausibly describe one visual line. A wrapped union // frame would divide one line's width by several lines' characters, poisoning both the - // observed character width and the layout estimator that consumes it. + // observed character width and the layout estimator that consumes it. Runs of a few + // characters (CodeMirror's single-space spacers) carry more padding than glyph and skip + // the width average. let measurableRuns = textRuns.filter(\.allowsProportionalCaretPlacement) var totalChars = 0 var totalWidth: CGFloat = 0 - for run in measurableRuns { + for run in measurableRuns where (run.text as NSString).length >= 4 { totalChars += (run.text as NSString).length totalWidth += run.frame.width } let charWidth: CGFloat? = totalChars > 0 ? totalWidth / CGFloat(totalChars) : nil + guard selectedRun.allowsProportionalCaretPlacement else { + return resolveWrappedRunCaret( + selectedRun, + parentText: parentText, + parentSelection: parentSelection, + paragraphTextBeforeCaret: runTextBeforeCaret, + fallbackFrame: fallbackFrame, + siblingLines: siblingLines, + observedCharWidth: charWidth + ) + } + // Measure content edges from the same single-line frames. These reveal the field's real // padding without letting a multi-line union frame masquerade as calibrated geometry. let cocoaRunFrames = measurableRuns.map { AXHelper.cocoaRect(fromAccessibilityRect: $0.frame) } + let runFrame = AXHelper.cocoaRect(fromAccessibilityRect: selectedRun.frame) let contentEdges: ObservedContentEdges? if let leftX = cocoaRunFrames.map(\.minX).min(), let topY = cocoaRunFrames.map(\.maxY).max() { - contentEdges = ObservedContentEdges(leftX: leftX, topY: topY, isRunMeasured: true) + // The run's frame and the caret's paragraph let the presentation layer read the caret + // from the run's own pixels (`PixelCaretLocator`), exactly as for a wrapped union run. + // The proportional x below is a fraction of the frame by character count, which in a + // proportional face lands several points off (measured 2026-09-10 in Obsidian's + // single-line paragraphs: 3 to 3.5pt on a fifty-character line, the ghost's every word + // that far from the host's); the pixels put the caret at the last glyph's edge. It is + // marked as one line: its frame is already the caret's line box, so only x is read from + // the pixels and nothing lays the text out again to find its line. + contentEdges = ObservedContentEdges( + leftX: leftX, + topY: topY, + isRunMeasured: true, + linePitch: siblingLines.pitch, + lineBoxHeight: siblingLines.boxHeight, + wrappedRun: WrappedRunAnchor( + frame: runFrame, + paragraphTextBeforeCaret: runTextBeforeCaret, + spansOneLine: true + ) + ) } else { contentEdges = nil } - let runFrame = AXHelper.cocoaRect(fromAccessibilityRect: selectedRun.frame) var caretX = runFrame.minX + placement.fraction * runFrame.width // The parent value extends past the matched runs (text published, frames not yet // reflowed): extend the estimate by the measured per-character advance instead of parking @@ -496,7 +551,10 @@ struct AXTextGeometryResolver { _ selectedRun: StaticTextRunWalkThrottle.TextRun, parentText: String, parentSelection: NSRange, - fallbackFrame: CGRect? + paragraphTextBeforeCaret: String, + fallbackFrame: CGRect?, + siblingLines: (pitch: CGFloat?, boxHeight: CGFloat?) = (nil, nil), + observedCharWidth: CGFloat? = nil ) -> CaretGeometryResult? { // Claude's wrapped leaf still exposes the exact previous-character rectangle even though // its zero-length caret query fails. The trailing edge is the real caret insertion point. @@ -529,6 +587,11 @@ struct AXTextGeometryResolver { text: parentText, selection: parentSelection ) + // The union frame and the caret's paragraph let the presentation layer lay the paragraph + // out and find the caret's visual line (see `WrappedRunAnchor`); the rect below is only + // the whole-field fallback for when that layout is rejected. + let unionFrame = AXHelper.cocoaRect(fromAccessibilityRect: selectedRun.frame) + let wrappedRun = WrappedRunAnchor(frame: unionFrame, paragraphTextBeforeCaret: paragraphTextBeforeCaret) return CaretGeometryResult( rect: CGRect( x: min(estimatedX, fallbackFrame.maxX), @@ -537,6 +600,14 @@ struct AXTextGeometryResolver { height: fallbackFrame.height ), quality: .estimated, + observedCharWidth: observedCharWidth, + observedContentEdges: ObservedContentEdges( + leftX: unionFrame.minX, + topY: unionFrame.maxY, + linePitch: siblingLines.pitch, + lineBoxHeight: siblingLines.boxHeight, + wrappedRun: wrappedRun + ), sourceDetail: "wrapped-run", // The child walk already found the best descendant and proved its frame ambiguous. // Repeating a deep BFS would rediscover the same union rect on every poll. @@ -599,9 +670,91 @@ struct AXTextGeometryResolver { } /// Converts the measured character immediately before the selection into Cotabby's normalized - /// caret shape. The trailing edge—not the character origin—is the insertion point. - static func caretRect(afterCharacterFrame frame: CGRect) -> CGRect { - CGRect(x: frame.maxX, y: frame.minY, width: 2, height: frame.height) + /// caret shape. The trailing edge—not the character origin—is the insertion point: the right + /// edge for left-to-right text, the left edge for right-to-left text. + static func caretRect(afterCharacterFrame frame: CGRect, rightToLeft: Bool = false) -> CGRect { + CGRect(x: rightToLeft ? frame.minX : frame.maxX, y: frame.minY, width: 2, height: frame.height) + } + + /// The character immediately before `caretLocation` (a UTF-16 offset into `text`), or nil. + static func character(before caretLocation: Int, in text: String?) -> Character? { + guard let text, caretLocation > 0 else { return nil } + let nsText = text as NSString + guard caretLocation <= nsText.length else { return nil } + let clusterRange = nsText.rangeOfComposedCharacterSequence(at: caretLocation - 1) + return nsText.substring(with: clusterRange).first + } + + /// Finishes a zero-length caret answer. Its x is trustworthy in every measured host, but its + /// line differs: TextKit reports the caret one line too high at every position (measured in + /// TextEdit at line starts, mid-line, and at the end of the document), while Chromium reports a + /// caret that follows a trailing line break at the end of the previous line. + /// + /// TextKit hosts expose `AXLineForIndex`/`AXRangeForLine`, so the caret's real line box is read + /// from them: the line containing the caret, or the (still empty) line below it when the caret + /// sits past that line's trailing break. Hosts without line queries keep the rect as answered, + /// except the trailing-break-at-end case, which is demoted to `.estimated` so the card shows + /// rather than an inline ghost on the wrong line. + /// What the zero-length caret answer needs to be finished (see `zeroLengthCaretResult`). + private struct ZeroLengthCaretContext { + let element: AXUIElement + let caretLocation: Int + let supportsLineQueries: Bool + let anchorFrame: CGRect? + let isAtTextEndAfterNewline: Bool + } + + private func zeroLengthCaretResult(_ rect: CGRect, context: ZeroLengthCaretContext) -> CaretGeometryResult { + if context.supportsLineQueries, + let lineBox = lineBoxForCaret( + element: context.element, + caretLocation: context.caretLocation, + cocoaAnchorFrame: context.anchorFrame + ) { + return CaretGeometryResult( + rect: CGRect(x: rect.minX, y: lineBox.minY, width: rect.width, height: lineBox.height), + quality: .exact, + sourceDetail: "zero-length+line" + ) + } + if context.isAtTextEndAfterNewline { + return CaretGeometryResult(rect: rect, quality: .estimated, sourceDetail: "zero-length-after-break") + } + return CaretGeometryResult(rect: rect, quality: .exact, sourceDetail: "zero-length") + } + + /// The Cocoa box of the visual line the caret is on, from the host's own line queries. When the + /// caret sits after the trailing break of the reported line, the caret is on the next (empty) + /// line, which the host does not enumerate; that box is the reported one moved down by its + /// own height. + private func lineBoxForCaret(element: AXUIElement, caretLocation: Int, cocoaAnchorFrame: CGRect?) -> CGRect? { + guard let lineIndex = AXHelper.parameterizedIntValue( + for: kAXLineForIndexParameterizedAttribute as CFString, + parameter: caretLocation, + on: element + ), lineIndex >= 0, lineIndex < 100_000, + let lineRange = AXHelper.parameterizedRangeValue( + for: kAXRangeForLineParameterizedAttribute as CFString, + parameter: lineIndex, + on: element + ), lineRange.length > 0, + let raw = AXHelper.parameterizedRectValue( + for: kAXBoundsForRangeParameterizedAttribute as CFString, + range: lineRange, + on: element + ), raw.height > 0, AXHelper.rectHasFiniteComponents(raw) else { + return nil + } + let lineBox = AXHelper.validatedCocoaTextRect(fromAccessibilityRect: raw, anchorFrame: cocoaAnchorFrame) + guard rectIsNearAnchor(lineBox, anchor: cocoaAnchorFrame) else { return nil } + let lineText = AXHelper.parameterizedStringValue( + for: kAXStringForRangeParameterizedAttribute as CFString, + range: lineRange, + on: element + ) ?? "" + let caretPastTrailingBreak = caretLocation >= lineRange.location + lineRange.length + && (lineText.last?.isNewline ?? false) + return caretPastTrailingBreak ? lineBox.offsetBy(dx: 0, dy: -lineBox.height) : lineBox } struct CaretRunPlacement: Equatable { @@ -643,6 +796,17 @@ struct AXTextGeometryResolver { parentText: String, caretOffset: Int ) -> CaretRunPlacement? { + caretRunPlacementWithStart(runTexts: runTexts, parentText: parentText, caretOffset: caretOffset)?.placement + } + + /// `caretRunPlacement` plus where the caret's run begins in the parent value, as a UTF-16 + /// offset (valid in the original string, since matching normalizes one unit for one). Nil + /// when the run was not anchored: the legacy cumulative walk matches no text. + static func caretRunPlacementWithStart( + runTexts: [String], + parentText: String, + caretOffset: Int + ) -> (placement: CaretRunPlacement, runStartOffset: Int?)? { guard !runTexts.isEmpty else { return nil } @@ -652,12 +816,30 @@ struct AXTextGeometryResolver { let anchored = anchoredRunRanges(normalizedRuns: normalizedRuns, parent: parent) guard !anchored.isEmpty else { - return legacyCumulativePlacement(runTexts: runTexts, caretOffset: caret) + return (legacyCumulativePlacement(runTexts: runTexts, caretOffset: caret), nil) } let mode: CaretRunMappingMode = anchored.count == runTexts.count ? .aligned : .partiallyAligned - return placementAmongAnchors(anchored, caret: caret, mode: mode, parent: parent) + let result = placementAmongAnchors(anchored, caret: caret, mode: mode, parent: parent) + return (result.placement, result.runStartOffset) + } + + /// The caret's run text up to the caret: the parent value from where the run was anchored. + /// A paragraph cannot be found by line breaks in every host: CodeMirror's value (Obsidian) + /// runs its paragraphs together with nothing between them, so the text after the last line + /// break was every paragraph before the caret, and laying that out in one run's frame put the + /// caret eight to fourteen lines away, off screen (measured 2026-09-10). Falls back to the text + /// after the last line break when the run was not anchored, and never reaches back across one. + static func runTextBeforeCaret(in parentText: String, runStartOffset: Int?, caretOffset: Int) -> String { + let parent = parentText as NSString + let caret = min(max(caretOffset, 0), parent.length) + guard let runStartOffset, runStartOffset >= 0 else { + return paragraphTextBeforeCaret(in: parentText, caretOffset: caret) + } + guard runStartOffset < caret else { return "" } + let runText = parent.substring(with: NSRange(location: runStartOffset, length: caret - runStartOffset)) + return paragraphTextBeforeCaret(in: runText, caretOffset: (runText as NSString).length) } /// Anchors each run's text inside the parent value. Pass one accepts only boundary-clean @@ -670,8 +852,11 @@ struct AXTextGeometryResolver { ) -> [(runIndex: Int, range: NSRange)] { var matchedRanges = [NSRange?](repeating: nil, count: normalizedRuns.count) + // A run that is only whitespace (CodeMirror puts a single-space spacer run at the start of + // every line) matches at any space in the parent, so anchoring it there pushes every run + // after it past its real location; such runs contribute no position and are never anchored. var searchLocation = 0 - for (index, text) in normalizedRuns.enumerated() where !text.isEmpty { + for (index, text) in normalizedRuns.enumerated() where !isWhitespaceOnly(text) { let found = boundaryCleanRange(of: text as NSString, in: parent, from: searchLocation) if found.location != NSNotFound { matchedRanges[index] = found @@ -688,7 +873,7 @@ struct AXTextGeometryResolver { let upperBound = matchedRanges[(index + 1)...] .compactMap { $0 } .first?.location ?? parent.length - guard !text.isEmpty, upperBound > lowerBound else { + guard !isWhitespaceOnly(text), upperBound > lowerBound else { continue } let window = NSRange(location: lowerBound, length: upperBound - lowerBound) @@ -715,14 +900,14 @@ struct AXTextGeometryResolver { caret: Int, mode: CaretRunMappingMode, parent: NSString - ) -> CaretRunPlacement { + ) -> (placement: CaretRunPlacement, runStartOffset: Int) { for (position, entry) in anchored.enumerated() { if caret < entry.range.location { if position > 0 { let previous = anchored[position - 1] let previousEnd = previous.range.location + previous.range.length if caret - previousEnd <= entry.range.location - caret { - return CaretRunPlacement( + let placement = CaretRunPlacement( runIndex: previous.runIndex, fraction: 1, mode: mode, @@ -730,20 +915,21 @@ struct AXTextGeometryResolver { from: previousEnd, to: caret, in: parent ) ) + return (placement, previous.range.location) } } - return CaretRunPlacement(runIndex: entry.runIndex, fraction: 0, mode: mode) + return (CaretRunPlacement(runIndex: entry.runIndex, fraction: 0, mode: mode), entry.range.location) } if caret <= entry.range.location + entry.range.length { let fraction = entry.range.length > 0 ? CGFloat(caret - entry.range.location) / CGFloat(entry.range.length) : 1 - return CaretRunPlacement(runIndex: entry.runIndex, fraction: fraction, mode: mode) + return (CaretRunPlacement(runIndex: entry.runIndex, fraction: fraction, mode: mode), entry.range.location) } } let last = anchored[anchored.count - 1] - return CaretRunPlacement( + let placement = CaretRunPlacement( runIndex: last.runIndex, fraction: 1, mode: mode, @@ -751,12 +937,61 @@ struct AXTextGeometryResolver { from: last.range.location + last.range.length, to: caret, in: parent ) ) + return (placement, last.range.location) } /// The number of characters between a run's trailing edge and the caret when extending the /// caret estimate by that many measured character widths is credible: a short, same-line gap. /// A gap containing a line break renders on another line entirely (the snap is closer to the /// truth there), and a huge gap means a reflow-everything edit no linear extension can model. + private static func isWhitespaceOnly(_ text: String) -> Bool { + text.allSatisfy(\.isWhitespace) + } + + /// The distances between two single-line runs' tops that can be one line's pitch, as multiples + /// of the runs' line box. Only single-line runs are seen here, so two of them can be lines apart + /// with a wrapped paragraph between: in an Obsidian note whose only one-line paragraphs were the + /// second and fourth, the pitch came out 72pt for 20pt boxes on 24pt lines (measured 2026-09-11, + /// 163 presentations), was remembered for the host, and would have stepped a wrapped ghost's next + /// row three lines down. A pitch below the box would overlap the lines. + static let plausibleRunPitchRange: ClosedRange = 0.95...2.2 + + /// The host's line pitch and line box from the single-line runs: the median distance between + /// consecutive distinct run tops that can be a pitch (see `plausibleRunPitchRange`), and the + /// median run height. Nil until two adjacent lines were seen. + static func lineGeometry( + fromSingleLineRuns runs: [StaticTextRunWalkThrottle.TextRun] + ) -> (pitch: CGFloat?, boxHeight: CGFloat?) { + let frames = runs.filter(\.allowsProportionalCaretPlacement).map { AXHelper.cocoaRect(fromAccessibilityRect: $0.frame) } + guard !frames.isEmpty else { return (nil, nil) } + let heights = frames.map(\.height).sorted() + let boxHeight = heights[heights.count / 2] + var tops = Array(Set(frames.map { ($0.maxY * 2).rounded() / 2 })).sorted(by: >) + tops = tops.filter { $0.isFinite } + let plausible = (boxHeight * plausibleRunPitchRange.lowerBound)...(boxHeight * plausibleRunPitchRange.upperBound) + var deltas: [CGFloat] = [] + for (upper, lower) in zip(tops, tops.dropFirst()) { + let delta = upper - lower + if delta >= 6, delta <= 120, plausible.contains(delta) { + deltas.append(delta) + } + } + guard !deltas.isEmpty else { return (nil, boxHeight) } + deltas.sort() + return (deltas[deltas.count / 2], boxHeight) + } + + /// The caret's paragraph (parent text between line breaks) up to the caret, in the live parent + /// value's coordinates. + static func paragraphTextBeforeCaret(in parentText: String, caretOffset: Int) -> String { + let parent = parentText as NSString + let caret = min(max(caretOffset, 0), parent.length) + let before = parent.substring(to: caret) + let start = before.rangeOfCharacter(from: .newlines, options: .backwards) + guard let start, let index = start.upperBound.samePosition(in: before) else { return before } + return String(before[index...]) + } + private static func extrapolableGapCharacters(from runEnd: Int, to caret: Int, in parent: NSString) -> Int { let gap = caret - runEnd guard gap > 0, gap <= maximumExtrapolatedGapCharacters else { diff --git a/Cotabby/Services/Focus/Resolution/FocusSnapshotResolver.swift b/Cotabby/Services/Focus/Resolution/FocusSnapshotResolver.swift index 203c4b70e..63f5c0351 100644 --- a/Cotabby/Services/Focus/Resolution/FocusSnapshotResolver.swift +++ b/Cotabby/Services/Focus/Resolution/FocusSnapshotResolver.swift @@ -29,6 +29,8 @@ struct FocusSnapshotResolver { /// `nonisolated` line-margin key helpers below read it; inheriting the resolver's main-actor /// isolation made that read an error in the Swift 6 language mode. nonisolated static let focusedTextContextWindowUTF16 = FocusedInputSnapshot.textWindowUTF16 + /// The longest value `restoringBlockBreaks` reads to put a web field's paragraph breaks back. + static let blockBreakAlignmentMaximumUTF16 = 16_384 /// Carries deep-walk throttle state across the value-typed resolver's non-mutating polls. private let deepWalkThrottle = DeepGeometryWalkThrottle() @@ -63,6 +65,13 @@ struct FocusSnapshotResolver { /// `deepWalkThrottle`: it carries state across the value-typed resolver's non-mutating polls. private let fieldStyleCache = FieldStyleCache() + /// Caches the measured host text geometry (width sample, line box, line pitch) per field, with + /// bounded retries for hosts that answer empty until their text boxes load. + private let hostTextMetricsCache = HostTextMetricsCache() + /// The host's rendered advance measured from the caret's own movement, for fields whose host + /// answers no width query (Chromium contenteditables, Electron composers); see + /// `CaretAdvanceSampler`. One sampler follows the focused field; a new field starts a new one. + private let caretAdvanceSamples = CaretAdvanceSampleStore() init(geometryResolver: AXTextGeometryResolver? = nil) { self.geometryResolver = geometryResolver ?? AXTextGeometryResolver() } @@ -130,7 +139,7 @@ struct FocusSnapshotResolver { ) } - guard let selection = resolvedCandidate.selection else { + guard let rawSelection = resolvedCandidate.selection else { return FocusSnapshot( applicationName: applicationName, bundleIdentifier: bundleIdentifier, @@ -139,7 +148,7 @@ struct FocusSnapshotResolver { ) } - guard selection.location >= 0, selection.length >= 0 else { + guard rawSelection.location >= 0, rawSelection.length >= 0 else { return FocusSnapshot( applicationName: applicationName, bundleIdentifier: bundleIdentifier, @@ -148,7 +157,23 @@ struct FocusSnapshotResolver { ) } - let value = resolvedCandidate.textValue ?? "" + // Chromium's address bar completes what the user types inline and keeps the completion + // selected after the caret; that selection is the browser's own provisional text, not a + // selection the user made, so it is stripped and the caret stays where the user stopped. + let (value, selection) = Self.strippingChromiumInlineAutocomplete( + value: resolvedCandidate.textValue ?? "", + selection: rawSelection, + role: resolvedCandidate.role, + bundleIdentifier: bundleIdentifier + ) + // While the browser's own completion is on screen it is, for Cotabby's purposes, the host's + // uncommitted text: it occupies the spot a ghost would take and its ink would be read as the + // caret line's end by the pixel caret. Treating it as marked text holds the ghost until the + // browser commits or drops it, exactly as with the system's inline prediction. + let chromiumCompletionRange: NSRange? = + value.utf16.count < (resolvedCandidate.textValue ?? "").utf16.count + ? NSRange(location: selection.location, length: (resolvedCandidate.textValue ?? "").utf16.count - selection.location) + : nil // `NSRange` coming from AX is expressed in UTF-16 code units, which is why the code below // uses `NSString` instead of slicing a native Swift `String` directly. guard selection.location <= value.utf16.count else { @@ -241,22 +266,29 @@ struct FocusSnapshotResolver { for: kAXPlaceholderValueAttribute as CFString, on: resolvedCandidate.element ) let focusedURLString = wantsURL ? AXHelper.webURL(near: resolvedCandidate.element) : nil - // Resolve the host field's own font/color so ghost text can match it. Cached by element - // identity (this is a synchronous AX read and the resolver runs on the focus poll), and - // skipped for secure fields, which are never styled or assisted. - let resolvedFieldStyle: ResolvedFieldStyle? - if resolvedCandidate.isSecure { - resolvedFieldStyle = nil - } else { - let styleKey = "\(application.processIdentifier):\(resolvedCandidate.elementIdentifier)" - resolvedFieldStyle = fieldStyleCache.style(forKey: styleKey) { - AXHelper.resolveFieldStyle( - for: resolvedCandidate.element, - caretLocation: selection.location, - textLength: value.utf16.count - ) - } - } + // Gmail writes its Smart Compose suggestion and a "tab" hint into the compose body right after + // the caret: the host's own prediction, held like the address bar's completion (see + // `HostMarkedTextPolicy.smartComposeSuggestionRange`). + let smartComposeRange = HostMarkedTextPolicy.smartComposeSuggestionRange( + text: value, selection: selection, urlString: focusedURLString + ) + // Resolve the host field's own font/color so ghost text can match it. Cached per style run + // (see `FieldStyleCache`) and skipped for secure fields, which are never styled or assisted. + let resolvedFieldStyle = resolveFieldStyle( + for: resolvedCandidate, + processIdentifier: application.processIdentifier, + selection: selection, + textLength: value.utf16.count, + caretHeight: caretRect.height + ) + let hostTextMetrics = resolveHostTextMetrics( + for: resolvedCandidate, + processIdentifier: application.processIdentifier, + text: value, + selection: selection, + caret: caret, + isBrowser: BrowserAppDetector.isBrowser(bundleIdentifier: bundleIdentifier) + ) // Recognize an xterm.js integrated terminal (VS Code / Cursor / web terminal) from the // focused element's DOM classes. The terminal, code editor, and Copilot chat all live in one // process, so this surface-level signal is the only way to suppress ghost text in the @@ -298,23 +330,19 @@ struct FocusSnapshotResolver { focusedURLString: focusedURLString, resolvedFieldStyle: resolvedFieldStyle, windowTitle: windowTitle, - fieldPlaceholder: fieldPlaceholder + fieldPlaceholder: fieldPlaceholder, + hostTextMetrics: Self.mergingRunLinePitch(hostTextMetrics, edges: observedContentEdges), + elementFrameRect: resolvedCandidate.elementFrameRect, + hostMarkedTextRange: resolvedCandidate.markedTextRange ?? chromiumCompletionRange ?? smartComposeRange ) - if resolvedCandidate.isSecure { - return FocusSnapshot( - applicationName: applicationName, - bundleIdentifier: bundleIdentifier, - capability: .blocked("Secure text input is active."), - context: context - ) - } - - if selection.length > 0 { + if let reason = Self.blockedReason( + for: resolvedCandidate, bundleIdentifier: bundleIdentifier, selection: selection, rawSelection: rawSelection + ) { return FocusSnapshot( applicationName: applicationName, bundleIdentifier: bundleIdentifier, - capability: .blocked("Text is currently selected."), + capability: .blocked(reason), context: context ) } @@ -327,6 +355,186 @@ struct FocusSnapshotResolver { ) } + /// Why a field Cotabby can read is still one it must not complete in, or nil when it may: a + /// secure field, one of Mail's header rows, or a field with text selected. + private static func blockedReason( + for candidate: AXFocusCandidate, + bundleIdentifier: String, + selection: NSRange, + rawSelection: NSRange + ) -> String? { + if candidate.isSecure { + return "Secure text input is active." + } + + // Mail's To/Cc/Bcc/Subject rows: Tab is the writer's way to the next field, not an accept. + // The identifier is one extra AX read, made only for Mail's own text fields. + if MailHeaderFieldDetector.mightBeHeaderField(bundleIdentifier: bundleIdentifier, role: candidate.role), + MailHeaderFieldDetector.isHeaderField( + bundleIdentifier: bundleIdentifier, + role: candidate.role, + accessibilityIdentifier: AXHelper.accessibilityIdentifier(of: candidate.element) + ) { + return MailHeaderFieldDetector.blockedReason + } + + guard selection.length > 0 else { return nil } + if BrowserAppDetector.isChromiumBrowser(bundleIdentifier: bundleIdentifier) { + CotabbyLogger.focus.debug( + "Chromium selection blocks the field", + metadata: [ + "stage": .string("chromium-selection"), + "role": .string(candidate.role), + "selection": .string("\(rawSelection.location),\(rawSelection.length)"), + "value_length": .stringConvertible((candidate.textValue ?? "").utf16.count), + "element": .string(candidate.elementIdentifier) + ] + ) + } + return "Text is currently selected." + } + + /// Reads the host's font/color at the caret through the style cache: one attributed-string read + /// per style run, plus one `AXStyleRangeForIndex` call only when the caret leaves the known run. + private func resolveFieldStyle( + for candidate: AXFocusCandidate, + processIdentifier: pid_t, + selection: NSRange, + textLength: Int, + caretHeight: CGFloat + ) -> ResolvedFieldStyle? { + guard !candidate.isSecure else { + return nil + } + let styleKey = "\(processIdentifier):\(candidate.elementIdentifier)" + let documentCaret = candidate.documentCaretLocation ?? selection.location + let supportsStyleRuns = candidate.supportedParameterizedAttributes.contains( + kAXStyleRangeForIndexParameterizedAttribute as String + ) + return fieldStyleCache.style( + forKey: styleKey, + caretLocation: documentCaret, + caretHeight: caretHeight, + styleRun: { + // Hosts without style ranges (Chromium) return nil and fall back to per-field caching + // plus the caret-height signal. + guard supportsStyleRuns else { return nil } + return AXHelper.parameterizedRangeValue( + for: kAXStyleRangeForIndexParameterizedAttribute as CFString, + parameter: max(documentCaret - 1, 0), + on: candidate.element + ) + }, + resolve: { + AXHelper.resolveFieldStyle( + for: candidate.element, + caretLocation: selection.location, + textLength: textLength + ) + } + ) + } + + /// Measures the host's rendered text geometry for the resolved field, once per field and with + /// bounded retries (see `HostTextMetricsCache`). Skipped for secure fields and for + /// marker-synthesized selections, whose window-relative offsets the NSRange bounds API would + /// misread. A host that answers no width query still gets a width sample, measured from how + /// far its caret moves as the user types (`CaretAdvanceSampler`). + private func resolveHostTextMetrics( + for candidate: AXFocusCandidate, + processIdentifier: pid_t, + text: String, + selection: NSRange, + caret: CaretGeometrySelector.Selected, + isBrowser: Bool = false + ) -> HostTextMetrics? { + guard !candidate.isSecure, !candidate.usesMarkerSelection else { + return nil + } + let caretRect = caret.rect + let caretHeight = caretRect.height + let caretAdvanceSample = observeCaretAdvance( + for: candidate, processIdentifier: processIdentifier, text: text, selection: selection, caret: caret + ) + // The caret box height changes when the line's font changes, and the measured line box + // moves with the element, so a new height or a moved/resized frame re-measures. Without the + // frame in the key, a window dragged after focus kept vending the old line edge. + let frameKey = candidate.elementFrameRect.map { + "\(Int($0.minX.rounded())),\(Int($0.minY.rounded())),\(Int($0.width.rounded())),\(Int($0.height.rounded()))" + } ?? "-" + let metricsKey = "\(processIdentifier):\(candidate.elementIdentifier):\(Int(caretHeight.rounded())):\(frameKey)" + let measured = hostTextMetricsCache.metrics(forKey: metricsKey, caretLocation: selection.location) { + HostTextMetricsProbe.measure( + HostTextMetricsProbe.Input( + element: candidate.element, + caretLocation: candidate.documentCaretLocation ?? selection.location, + text: text, + caretLocationInText: selection.location, + caretHeight: caretHeight, + supportedParameterizedAttributes: candidate.supportedParameterizedAttributes, + anchorFrame: candidate.elementFrameRect ?? candidate.inputFrameRect, + allowsTextMarkerLine: caret.quality == .exact && caret.sourceDetail == "text-marker", + caretRect: caretRect, + isBrowser: isBrowser + ) + ) + } + return Self.mergingCaretAdvanceSample(measured, sample: caretAdvanceSample) + } + + /// Whether this poll's caret is a measured glyph position, the only kind a width sample may be + /// made of: an exact caret, or a derived one from real character bounds. A caret placed inside a + /// text run by its share of the run's characters is not: its movement is the run frame's width + /// spread evenly over the characters, and Obsidian's run frames are wider than their ink. + /// Measured 2026-09-10: once such samples were kept, one of "ely long enough that" read 1% wide + /// and sized the ghost's face at 16.13 for the host's 16, three device pixels long by a line's end. + static func caretMeasuresGlyphs(quality: CaretGeometryQuality, sourceDetail: String?) -> Bool { + switch quality { + case .exact: + return true + case .derived: + return AXTextGeometryResolver.CaretRunMappingMode(rawValue: sourceDetail ?? "") == nil + default: + return false + } + } + + /// Feeds this poll's caret to the field's advance sampler and returns its current sample. + private func observeCaretAdvance( + for candidate: AXFocusCandidate, + processIdentifier: pid_t, + text: String, + selection: NSRange, + caret: CaretGeometrySelector.Selected + ) -> CaretAdvanceSampler.Sample? { + let nsText = text as NSString + let offset = min(max(selection.location, 0), nsText.length) + return caretAdvanceSamples.sample( + forKey: "\(processIdentifier):\(candidate.elementIdentifier)", + observation: CaretAdvanceSampler.Observation( + caretX: caret.rect.minX, + lineY: caret.rect.maxY, + documentCaret: candidate.documentCaretLocation ?? offset, + precedingText: nsText.substring(to: offset), + isPositioned: Self.caretMeasuresGlyphs(quality: caret.quality, sourceDetail: caret.sourceDetail) + ) + ) + } + + /// The probe's own width sample outranks the caret's: it is one query of the host's layout. + /// Only a host that answered none gets the sample measured from its caret. + static func mergingCaretAdvanceSample(_ metrics: HostTextMetrics?, sample: CaretAdvanceSampler.Sample?) -> HostTextMetrics? { + guard metrics?.sampleText == nil, let sample else { return metrics } + return HostTextMetrics( + sampleText: sample.text, + sampleWidth: sample.width, + lineRect: metrics?.lineRect, + linePitch: metrics?.linePitch, + lineRectIsFromTextMarkers: metrics?.lineRectIsFromTextMarkers ?? false, + linePitchIsFromParagraphBox: metrics?.linePitchIsFromParagraphBox ?? false + ) + } + /// Resolves candidate elements lazily and stops as soon as the first fully capable editable /// target is found. /// @@ -369,6 +577,22 @@ struct FocusSnapshotResolver { ) } + // The focused element itself falling short is the one verdict worth a line: it is + // why a neighbour (an ancestor's child, Mail's header row) ends up the target. + if CFEqual(element, focusedReading.element) { + CotabbyLogger.focus.debug( + "Focused element lacks capabilities", + metadata: [ + "stage": .string("focused-element-partial"), + "role": .string(candidate.role), + "missing": .string(evaluation.missingCapabilities.map { "\($0)" }.joined(separator: ",")), + "has_text": .stringConvertible(candidate.textValue != nil), + "has_selection": .stringConvertible(candidate.selection != nil), + "has_caret": .stringConvertible(candidate.caretRect != nil) + ] + ) + } + if bestPartial == nil || evaluation.score > bestPartial!.score { bestPartial = evaluation } @@ -710,6 +934,11 @@ struct FocusSnapshotResolver { // This admits Mail's writable composer without treating every HTML page as editable. isValueSettable: role == "AXWebArea" && explicitEditableFlag == nil && AXHelper.isValueSettable(on: element) + ) || WebContentFieldDetector.isEditableWebArea( + role: role, + isFocusedElement: CFEqual(element, focusedReading.element), + bundleIdentifier: bundleIdentifier, + supportedAttributes: supportedAttributes ) let isKnownReadOnlyRole = AXHelper.isKnownReadOnlyRole(role) let canBeEditableTarget = hasStrongEditabilitySignal && !isKnownReadOnlyRole @@ -733,20 +962,38 @@ struct FocusSnapshotResolver { ) : nil - let nativeTextSelection = nativeSelection.flatMap { + // Uncommitted host text (system inline prediction, IME composition). Read only from fields + // that vend the attribute (NSTextView-backed), one call per poll. + let markedTextRange = + canBeEditableTarget && supportedAttributes.contains(HostMarkedTextPolicy.markedRangeAttribute) + ? AXHelper.rangeValue(for: HostMarkedTextPolicy.markedRangeAttribute as CFString, on: element) + .flatMap { $0.length > 0 ? $0 : nil } + : nil + + let nativeTextSelection = nativeSelection.flatMap { documentSelection in nativeTextWindow( on: element, - selection: $0, + selection: documentSelection, supportedAttributes: supportedAttributes, supportedParameterizedAttributes: supportedParameterizedAttributes - ) + ).map { + Self.withoutHostPrediction($0, documentSelection: documentSelection, markedTextRange: markedTextRange) + } } // Prefer the marker-windowed text when we synthesized one so `selection` (window-relative) // and `textValue` stay consistent; otherwise use a bounded native text window when the host // supports `AXStringForRange`, falling back to the full value for older/native controls. - let textSelection = markerSelection.map { + let textSelection = (markerSelection.map { AXTextSelection(text: $0.text, selection: $0.selection) - } ?? nativeTextSelection + } ?? nativeTextSelection).map { + restoringBlockBreaks( + in: $0, + on: element, + role: role, + supportedAttributes: supportedAttributes, + supportedParameterizedAttributes: supportedParameterizedAttributes + ) + } let selection = textSelection?.selection let selectionForGeometry = nativeSelection ?? markerSelection?.selection let textValue = textSelection?.text @@ -763,6 +1010,7 @@ struct FocusSnapshotResolver { supportedAttributes.contains("AXFrame") ? geometryResolver.resolveInputFrameRect(for: element) : nil + let elementFrameRect = inputFrameRect if let currentFrame = inputFrameRect { var finalWidth = currentFrame.width @@ -812,7 +1060,10 @@ struct FocusSnapshotResolver { staticRunThrottle: CFEqual(element, focusedReading.element) ? staticRunWalkThrottle : nil, - focusChangeSequence: focusChangeSequence + focusChangeSequence: focusChangeSequence, + supportsLineQueries: markerSelection == nil + && supportedParameterizedAttributes.contains(kAXLineForIndexParameterizedAttribute as String) + && supportedParameterizedAttributes.contains(kAXRangeForLineParameterizedAttribute as String) ) } let caretRect = caretResult?.rect @@ -884,12 +1135,37 @@ struct FocusSnapshotResolver { caretSourceDetail: caretResult?.sourceDetail, caretAllowsDeepSearch: caretResult?.allowsDeepSearch ?? true, inputFrameRect: inputFrameRect, + elementFrameRect: elementFrameRect, + markedTextRange: markedTextRange, isSecure: isSecure, vendsDOMAttributes: vendsDOMAttributes, + usesMarkerSelection: markerSelection != nil, + documentCaretLocation: nativeSelection?.location, + supportedParameterizedAttributes: supportedParameterizedAttributes, resolverCandidate: resolverCandidate ) } + /// Drops the host's own inline prediction (marked text after the caret) from a windowed text + /// read, translating the document-coordinate marked range into the window's coordinates. + private static func withoutHostPrediction( + _ window: AXTextSelection, + documentSelection: NSRange, + markedTextRange: NSRange? + ) -> AXTextSelection { + guard let markedTextRange else { return window } + let documentOffset = documentSelection.location - window.selection.location + let windowMarkedRange = NSRange(location: markedTextRange.location - documentOffset, length: markedTextRange.length) + guard windowMarkedRange.location >= 0 else { return window } + let stripped = HostMarkedTextPolicy.strippingPredictionAfterCaret( + text: window.text, + selection: window.selection, + markedRange: windowMarkedRange + ) + guard stripped != window.text else { return window } + return AXTextSelection(text: stripped, selection: window.selection) + } + /// One candidate's already-fetched state that the line-margin lookup needs, bundled so the /// candidate site reads as a single call. private struct LineEdgeLookup { @@ -1175,6 +1451,96 @@ struct FocusSnapshotResolver { ) } + /// The selection re-read in the field's value where the host's range text runs its blocks + /// together (Chromium's contenteditables, see `BlockBreakAlignment`): the paragraph breaks come + /// back into the model's text and the caret line's text, and the text after the caret comes from + /// the value instead of a range query that overshoots the range text's end. Only a window that + /// starts at the field's start can be aligned, and a single-line field has no blocks; anything + /// that does not align keeps the range text. Costs one `AXValue` read per poll in a web text + /// area, bounded by `blockBreakAlignmentMaximumUTF16`. The document caret (`nativeSelection`) + /// stays in the range space, where the host's own offset queries expect it. + private func restoringBlockBreaks( + in selection: AXTextSelection, + on element: AXUIElement, + role: String, + supportedAttributes: Set, + supportedParameterizedAttributes: Set + ) -> AXTextSelection { + let text = selection.text as NSString + guard role != kAXTextFieldRole as String, + AXHelper.readsTextMarkers(parameterizedAttributes: supportedParameterizedAttributes), + selection.selection.location < min(Self.focusedTextContextWindowUTF16, MarkerSelectionSynthesizer.defaultWindow), + NSMaxRange(selection.selection) <= text.length + else { + return selection + } + // A window holding the whole field at the value's own length has no break left out (the + // range text is the value then), so only the caret's side of a break is left to settle and + // the value need not be read: the common case in hosts whose two spaces agree (Obsidian). + let value: String + if let documentLength = AXHelper.intValue(for: kAXNumberOfCharactersAttribute as CFString, on: element), + documentLength == text.length { + value = selection.text + } else { + guard supportedAttributes.contains(kAXValueAttribute as String), + let read = AXHelper.stringValue(for: kAXValueAttribute as CFString, on: element), + (read as NSString).length <= Self.blockBreakAlignmentMaximumUTF16 + else { + return selection + } + value = read + } + guard let split = BlockBreakAlignment.split( + value: value, + rangePrefix: text.substring(to: selection.selection.location), + rangeSelected: text.substring(with: selection.selection), + caretStartsBlock: { + AXHelper.caretStartsTextBlock(on: element, parameterizedAttributes: supportedParameterizedAttributes) + } + ) else { + return selection + } + return AXTextSelection(text: split.text, selection: split.selection) + } + + /// The value and selection with Chromium's inline address-bar completion removed. The omnibox + /// (an `AXTextField` in a Chromium browser) autocompletes a typed prefix and leaves the added + /// text selected through to the end of the value; a selection shaped like that, after at least + /// one typed character, is the browser's suggestion rather than the user's selection (measured + /// live: every keystroke in the address bar arrived as "text is currently selected"). Any other + /// selection, and every other host, passes through untouched. + static func strippingChromiumInlineAutocomplete( + value: String, + selection: NSRange, + role: String, + bundleIdentifier: String + ) -> (String, NSRange) { + let length = (value as NSString).length + guard selection.length > 0, + selection.location > 0, + selection.location + selection.length == length, + role == kAXTextFieldRole as String, + BrowserAppDetector.isChromiumBrowser(bundleIdentifier: bundleIdentifier) + else { + return (value, selection) + } + let typed = (value as NSString).substring(to: selection.location) + return (typed, NSRange(location: selection.location, length: 0)) + } + + /// A host whose line APIs answered nothing (CodeMirror in Obsidian) still shows its line pitch + /// through its sibling text runs; that pitch lets the ghost wrap onto the host's next line. + static func mergingRunLinePitch(_ metrics: HostTextMetrics?, edges: ObservedContentEdges?) -> HostTextMetrics? { + guard metrics?.linePitch == nil, let pitch = edges?.linePitch, pitch > 0 else { return metrics } + return HostTextMetrics( + sampleText: metrics?.sampleText, + sampleWidth: metrics?.sampleWidth, + lineRect: metrics?.lineRect, + linePitch: pitch, + lineRectIsFromTextMarkers: metrics?.lineRectIsFromTextMarkers ?? false + ) + } + /// Detects secure inputs so Cotabby can intentionally refuse to operate in sensitive fields. private func isSecureElement(element: AXUIElement, role: String, subrole: String?) -> Bool { // Read the role description too: a native NSSecureTextField announces its sensitivity there @@ -1224,9 +1590,19 @@ private struct AXFocusCandidate { let caretSourceDetail: String? let caretAllowsDeepSearch: Bool let inputFrameRect: CGRect? + /// The element's own `AXFrame` before any widening (see `FocusedInputSnapshot.elementFrameRect`). + let elementFrameRect: CGRect? + /// The host's uncommitted text range in document coordinates (see `HostMarkedTextPolicy`). + let markedTextRange: NSRange? let isSecure: Bool /// Whether the element advertises DOM-reflection attributes, marking it as web-engine /// content (see `WebContentFieldDetector`). let vendsDOMAttributes: Bool + /// True when the selection was synthesized from text markers, so `selection` is window-relative + /// and the NSRange geometry APIs cannot be used against it. + let usesMarkerSelection: Bool + /// The caret offset in the host's document coordinates when a native selection range exists. + let documentCaretLocation: Int? + let supportedParameterizedAttributes: Set let resolverCandidate: FocusCapabilityCandidate } diff --git a/Cotabby/Services/Focus/Resolution/HostTextMetricsProbe.swift b/Cotabby/Services/Focus/Resolution/HostTextMetricsProbe.swift new file mode 100644 index 000000000..de15bff4c --- /dev/null +++ b/Cotabby/Services/Focus/Resolution/HostTextMetricsProbe.swift @@ -0,0 +1,419 @@ +import AppKit +import ApplicationServices +import Foundation +import Logging + +/// File overview: +/// Measures how the focused host renders text next to the caret, using the host's own bounds +/// answers rather than font tables: the rendered width of the text just before the caret, the box +/// of the caret's visual line, and the distance to the previous line. `GhostFontResolver` uses the +/// width to pick the typeface a web host names only by size, and `GhostTextLayout` uses the line +/// box and pitch to start wrapped rows exactly where the host starts its next line. +/// +/// Native TextKit hosts answer every query (`AXLineForIndex`, `AXRangeForLine`, `AXBoundsForRange`). +/// Chromium answers `AXBoundsForRange` for `