Extract coordinator state mechanisms#800
Merged
Merged
Conversation
FuJacob
force-pushed
the
codex/settings-domains
branch
from
July 18, 2026 03:06
7a0bad7 to
9c15593
Compare
FuJacob
force-pushed
the
codex/coordinator-mechanisms
branch
from
July 18, 2026 03:10
d68eeaa to
7b56688
Compare
FuJacob
marked this pull request as ready for review
July 18, 2026 03:13
| /// latest-wins, at most one drain is scheduled per runloop turn, and visible text may only grow | ||
| /// monotonically. It does not schedule work or render UI; the coordinator remains responsible for | ||
| /// those side effects. | ||
| struct SuggestionStreamingState { |
Contributor
There was a problem hiding this comment.
The doc comment opens with "Main-actor bookkeeping" but the struct itself carries no @MainActor isolation. The coordinator's own main-actor isolation provides the protection in practice, but an explicit annotation would make this a compile-time guarantee rather than an implicit contract — and would prevent a future caller from accidentally constructing and mutating a SuggestionStreamingState off the main actor. PostExhaustionAcceptanceState has only primitive fields so the omission there is less material, but SuggestionStreamingState stores a SuggestionResult value.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Extract the coordinator's two clearest independent state mechanisms: streamed-partial coalescing/monotonic rendering and the short post-exhaustion Tab-ownership window. The coordinator still owns generation, timers, input interception, Accessibility reads, session mutation, logging, and presentation; the new values own only deterministic state transitions.
This is the final PR in the repository-organization stack and is based on #799.
Validation
swiftlint lint --strict— 0 violations.xcodegen generate— succeeded and the generated project is committed.xcodebuild -project Cotabby.xcodeproj -scheme Cotabby -destination 'platform=macOS' build-for-testing -derivedDataPath build/DerivedData— TEST BUILD SUCCEEDED.ScreenshotContextGeneratorTestsblocked while enumerating the developer debug-screenshot directory on Desktop. That filesystem/debug-artifact path is unchanged by this PR.Linked issues
None.
Risk / rollout notes
No coordinator side effects moved into the extracted values. Existing coordinator acceptance tests still cover input interception and insertion, while focused tests now cover backstop invalidation, bounded queued acceptance, latest-wins stream coalescing, scheduled-drain preservation, teardown, and monotonic rendering.
Greptile Summary
This PR extracts two independent coordinator state mechanisms — streamed-partial coalescing/monotonic rendering (
SuggestionStreamingState) and the bounded post-exhaustion Tab-ownership window (PostExhaustionAcceptanceState) — into dedicated value types with pure state transitions, leaving all side effects (timers, input interception, overlay presentation) in the coordinator.SuggestionStreamingStateencapsulates three invariants: latest-wins coalescing, at-most-one scheduled drain per runloop turn, and monotonic text rendering.beginGenerationandclearSessionintentionally share the same implementation to preserve an already-dispatched-but-uncancellable drain callback.PostExhaustionAcceptanceStateuses a monotonic generation token to ensure stale backstop timers always fire as no-ops, withconsumeQueuedAccept()atomically reading and clearing the window to avoid TOCTOU between the queued-flag check and the clear.+Acceptance.swiftand+Prediction.swiftare mechanical substitutions with no observable behavioral change, and focused unit tests cover every state-transition rule for both new types.Confidence Score: 4/5
Clean, behavior-preserving refactoring with no side effects moved out of the coordinator; safe to merge.
The refactoring is correct end-to-end: generation token, coalescing, drain scheduling, and atomic consume all match the replaced code. The only observation is that
SuggestionStreamingStatedocuments itself as main-actor bookkeeping but carries no@MainActorannotation, leaving the concurrency contract implicit rather than enforced at compile time.SuggestionStreamingState.swift— would benefit from an explicit@MainActorannotation to match its own documentation.Important Files Changed
beginGenerationandclearSessionintentionally share identical implementations to preserve a scheduled-but-uncancellable drain callback; the design is sound and documented. Missing@MainActorannotation despite the doc comment saying "Main-actor bookkeeping".arm()andclear().needsReleaseusing `isArmedqueueAcceptIfArmed()return value correctly gates the log call;consumeQueuedAccept()atomically clears and returns the queued flag, matching the old two-step pattern exactly.SuggestionStreamingState.beginGeneration()preservesisDrainScheduled(matching the old pattern),enqueuereturn value controls drain scheduling, anddrain()atomically clears the flag and takes the partial — all equivalent to the replaced code.arm()/queueAcceptIfArmed()calls which preserve the same armed+queued state for the flush test.Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant Engine participant Coordinator as SuggestionCoordinator participant SSS as SuggestionStreamingState participant PEAS as PostExhaustionAcceptanceState participant InputMonitor Note over Coordinator,SSS: New generation starts Coordinator->>SSS: beginGeneration() Note right of SSS: clears renderedText, pendingPartial<br/>preserves isDrainScheduled loop Token-rate partial callbacks Engine->>Coordinator: onPartial(result, workID) Coordinator->>SSS: enqueue(result, workID) alt drain not yet scheduled SSS-->>Coordinator: true (schedule drain) Coordinator->>Coordinator: DispatchQueue.main.async drainStreamedPartial else drain already scheduled SSS-->>Coordinator: false (coalesce, skip) end end Note over Coordinator,SSS: Runloop drain fires Coordinator->>SSS: drain() SSS-->>Coordinator: PendingPartial? (clears flag atomically) Coordinator->>SSS: canRender(partial.text) SSS-->>Coordinator: Bool (monotonic check via StreamedGhostTextPolicy) Coordinator->>SSS: recordRendered(partial.text) Note over Coordinator,PEAS: Final-chunk accept — exhausted session Coordinator->>PEAS: arm() PEAS-->>Coordinator: generation (UInt64) Coordinator->>InputMonitor: setAcceptInterceptionActive(true) Coordinator->>Coordinator: asyncAfter(postExhaustionWindow) alt Fast-follow Tab arrives during regen window InputMonitor->>Coordinator: shouldConsumeAcceptKey? Coordinator->>PEAS: isArmed (read) PEAS-->>Coordinator: true Coordinator->>PEAS: queueAcceptIfArmed() end Note over Coordinator,PEAS: Regen completes — new suggestion visible Coordinator->>PEAS: consumeQueuedAccept() PEAS-->>Coordinator: shouldAccept Bool (clears window atomically) Note over Coordinator,PEAS: Backstop timer fires (if not superseded) Coordinator->>PEAS: ownsBackstop(generation) alt generation matches PEAS-->>Coordinator: true → releasePostExhaustionAcceptanceWindow else stale timer PEAS-->>Coordinator: false → no-op end%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant Engine participant Coordinator as SuggestionCoordinator participant SSS as SuggestionStreamingState participant PEAS as PostExhaustionAcceptanceState participant InputMonitor Note over Coordinator,SSS: New generation starts Coordinator->>SSS: beginGeneration() Note right of SSS: clears renderedText, pendingPartial<br/>preserves isDrainScheduled loop Token-rate partial callbacks Engine->>Coordinator: onPartial(result, workID) Coordinator->>SSS: enqueue(result, workID) alt drain not yet scheduled SSS-->>Coordinator: true (schedule drain) Coordinator->>Coordinator: DispatchQueue.main.async drainStreamedPartial else drain already scheduled SSS-->>Coordinator: false (coalesce, skip) end end Note over Coordinator,SSS: Runloop drain fires Coordinator->>SSS: drain() SSS-->>Coordinator: PendingPartial? (clears flag atomically) Coordinator->>SSS: canRender(partial.text) SSS-->>Coordinator: Bool (monotonic check via StreamedGhostTextPolicy) Coordinator->>SSS: recordRendered(partial.text) Note over Coordinator,PEAS: Final-chunk accept — exhausted session Coordinator->>PEAS: arm() PEAS-->>Coordinator: generation (UInt64) Coordinator->>InputMonitor: setAcceptInterceptionActive(true) Coordinator->>Coordinator: asyncAfter(postExhaustionWindow) alt Fast-follow Tab arrives during regen window InputMonitor->>Coordinator: shouldConsumeAcceptKey? Coordinator->>PEAS: isArmed (read) PEAS-->>Coordinator: true Coordinator->>PEAS: queueAcceptIfArmed() end Note over Coordinator,PEAS: Regen completes — new suggestion visible Coordinator->>PEAS: consumeQueuedAccept() PEAS-->>Coordinator: shouldAccept Bool (clears window atomically) Note over Coordinator,PEAS: Backstop timer fires (if not superseded) Coordinator->>PEAS: ownsBackstop(generation) alt generation matches PEAS-->>Coordinator: true → releasePostExhaustionAcceptanceWindow else stale timer PEAS-->>Coordinator: false → no-op endReviews (1): Last reviewed commit: "refactor: extract coordinator state mech..." | Re-trigger Greptile