Repository navigation
fix(macos): preserve the Talk overlay through quick toggles - #135731
Conversation
Record desired visibility at the overlay owner and fence animation completion with the current presentation token. Remove the redundant nested task. Rewrites #132565; fixes #127645. Actual signed AppKit before/after proof and Swift Testing regression cover re-presentation and ordinary dismissal. Co-authored-by: Huzaifa Iftikhar <[email protected]>
|
🦞👀 Pull request received. I will update this pull request when review starts. |
|
Codex review: needs maintainer review before merge. Reviewed September 2, 2026, 2:28 AM ET / 06:28 UTC. ClawSweeper reviewWhat this changesThis PR prevents an older Talk-overlay dismissal animation from hiding a newly presented panel and adds synchronous plus opt-in AppKit regression coverage. Regression provenancePossible regression — probable (reproduction; reviewed change). No predecessor PR is attributed. Merge readinessKeep open for the explicit macOS maintainer decision: current main still contains the stale completion path, while this member-authored, maintainer-labeled PR supplies the owner-side repair with no blocking code finding. Priority: P2 Review scores
Verification
How this fits togetherThe macOS Talk controller presents and dismisses a floating AppKit panel when Talk mode changes. Its animation completion determines whether a quick re-enable leaves the operator-visible panel on screen or orders it out. flowchart LR
A[Talk mode change] --> B[Talk overlay controller]
B --> C[Presentation or dismissal token]
C --> D[AppKit panel animation]
D --> E{Token still current?}
E -->|yes| F[Hide panel]
E -->|no| G[Keep newer panel visible]
Decision needed
Why: The Before merge
Agent review detailsSecurityNone. PR surfaceOther +141. Total +141 across 4 files. View PR surface stats
Review metrics
Root-cause clusterRelationship: Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge-risk optionsMaintainer options:
Technical reviewBest possible solution: Adopt the Talk-controller transition fence and retain its AppKit fixture; leave Voice Wake unchanged because its session-token lifecycle has different side effects. Do we have a high-confidence way to reproduce the issue? Yes—current source exposes the off/on ordering, and the submitted AppKit event-loop fixture records the stale-completion scenario at three timing offsets. This read-only Linux review did not rerun the macOS fixture. Is this the best way to solve the issue? Yes. The controller owns both transitions, so invalidating an obsolete dismissal completion there is narrower than altering the shared animation factory; Quick Chat uses the same ownership principle, while Voice Wake has distinct session cleanup. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 728c9c579fe8. LabelsLabel changes:
Label justifications:
EvidenceWhat I checked:
Likely related people:
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (8 earlier review cycles)
|
Keep immediate visibility intent in the native test suite and retain separate signed AppKit evidence for animation completion. Avoid an unbounded animation wait in the CLI test runner.
|
Merged via squash.
|
…#135731) * fix(macos): preserve Talk panel after interrupted dismissal Record desired visibility at the overlay owner and fence animation completion with the current presentation token. Remove the redundant nested task. Rewrites openclaw#132565; fixes openclaw#127645. Actual signed AppKit before/after proof and Swift Testing regression cover re-presentation and ordinary dismissal. Co-authored-by: Huzaifa Iftikhar <[email protected]> * test(macos): await Talk overlay animation completion * test(macos): check Talk visibility intent synchronously Keep immediate visibility intent in the native test suite and retain separate signed AppKit evidence for animation completion. Avoid an unbounded animation wait in the CLI test runner. * test(macos): retain native Talk overlay race proof --------- Co-authored-by: Huzaifa Iftikhar <[email protected]>
Related: #132565 (macOS Talk overlay dismissal race). Closes #127645 (Talk panel disappears after rapid toggling).
What Problem This Solves
The macOS Talk panel can disappear when Talk is re-enabled during its previous fade-out: the old dismissal completion hides the newly presented panel.
Why This Change Was Made
The overlay controller now records desired visibility immediately and gives each presentation or dismissal a new token. A dismissal completion hides the panel only while it still owns that token. The existing animation factory remains unchanged, and the redundant nested task is removed. No new helper, production test hook, dependency, configuration, protocol, or storage surface is added.
This is a maintainer rewrite of Huzaifa Iftikhar's (@HuzaifaChaudary) work in #132565 and the selected canonical replacement for that overlapping proposal. The original will be closed only after this repair is verified on main. The controller repair is production +10/-9 (net +1), compared with the original PR's +35 production lines. A 27-line unit regression is supplemented by a 73-line native fixture and 40-line opt-in runner.
User Impact
Quick off/on transitions leave the Talk panel visible. Ordinary dismissal still hides it.
Evidence
A Developer ID-signed isolated AppKit application ran the actual controller and panel factory with an inert synthetic view. Present → dismiss → present failed on the baseline at 0, 40, and 120 ms, then passed at every timing with this repair. Normal dismissal also passed. These actual animation checks remain separate from the checked-in synchronous regression.
The final Swift Testing regression checks immediate desired visibility through present → dismiss → present → dismiss. Against the original controller it fails both immediate-dismiss assertions; against the repaired controller it passes. The local proof uses the native SwiftPM backend, Swift 6 language mode, and the actual overlay and waveform views, with inert application-state/control dependencies. It ran on Swift 6.4/Testing 2077; a local Swift 6.3.3 build could not import the missing Testing module, so no local Swift 6.3 runtime proof is claimed. Scoped SwiftFormat 0.63.0, SwiftLint 0.65.1, and whitespace checks pass.
CI history matters here: the first full run failed the original 400 ms test on post-animation alpha/visibility assertions. The next completion-wait version reached the job's 20-minute timeout; TalkOverlayTests and three unrelated suites remained unfinished. The exact hang cause was not reproduced locally, including with the native backend and actual views. The final test does not await an AppKit animation completion or claim to prove final rendered state; the separate signed AppKit proof covers that behavior. CI run 33582520467 passed on the controller-and-unit-test head
27ae3f1a016d73b8882cdc6df0768e1aff6ddf4b, including macOS Swift tests, the release build, all three macOS Node jobs, and the final CI gate.This is not a full operator OpenClaw session or microphone test. Both complete before/after captures were inspected and contain only synthetic data. Both attached images are publicly accessible: downloaded PNG bytes match the inspected originals exactly. No alternate image host was used.
Adjacent Notify and Voice Wake overlay completions need separate reproduction; this PR does not claim to repair them.
Rerunnable native regression
The late review correctly identified that the synchronous unit test does not execute the stale completion. The opt-in fixture in
apps/macos/Tests/Fixtures/TalkOverlay/now compiles the actual controller and factory into a separately signed synthetic AppKit app and runs the normal application event loop. It verifies its Developer ID Application signature before launch, reports source and binary hashes, and removes its temporary app. It does not load operator app state, a Gateway, or microphone services.bash apps/macos/Tests/Fixtures/TalkOverlay/run.sh '<Developer ID Application identity or certificate SHA>'Removing only the dismissal-token guard makes the same fixture fail the real panel-visibility assertion at all three reopen offsets (0, 40, and 120 ms), while ordinary dismissal still passes. The current controller passes all three reopen and all three ordinary-dismissal checks, including visible alpha for each reopened panel. This is timed end-to-end proof using 240/450/250 ms observation intervals, not deterministic completion control or a formal callback acknowledgment. The fixture is opt-in and separate from automatic Swift Testing. CI run 33593368734 passed on final head
58b669e60f91ada1955aa3a328c68a3b3605febf, including macOS Swift tests and the release build. CI does not automatically execute the opt-in fixture.For the review's rank-up request, the real event-loop fixture supplies durable coverage of the asynchronous outcome. Deterministic unit completion control is intentionally not added: tracing disproved the proposed animation-disable/task-barrier ordering, and exposing a new production hook solely for this test would weaken the implementation. No workaround, swizzle, timeout increase, or production change is included.
The retained signed run occurred before committing
58b669e60f91ada1955aa3a328c68a3b3605febf; all four input hashes were rechecked after the commit and match its files exactly. It is not a claim that the process was launched after that commit. The runtime log below omits only the temporary app path and signing-status line:The parent runner exited 0. The preceding negative control, with only the dismissal-token guard removed, exited 1 and reported
RESULT failures=3: all reopened panels were hidden, while all ordinary dismissals passed. That control's controller hash was8b469e80fe828f59bb7e4047e01002ec754e2541d1e771470f09dabb9e956728and its signed binary hash was4aec244aaa9bda7b9c35792b74a7cff1ba875120a5412122291d69a2dc0cca89; the other three source hashes were unchanged.Before/after pictures
Retained captures of the actual Talk overlay controller and unchanged animation factory in an isolated AppKit fixture with a synthetic view. After present → dismiss → present, the original owner hides the reopened panel; the repaired owner leaves it visible.
These pictures cover the owner and animation behavior. They do not establish full-app, microphone, current PR-head, or merged-main execution; immutable identities of the captured binaries were not retained.