fix(macos): keep the Talk overlay visible when a present interrupts its dismissal - #132565
fix(macos): keep the Talk overlay visible when a present interrupts its dismissal#132565HuzaifaChaudary wants to merge 4 commits into
Conversation
…ts dismissal dismiss started a 160ms fade and left isVisible true until the completion. a present arriving inside that window therefore saw the panel as already visible, took the branch that only moves and orders it front, and did not bring the alpha back. the old completion then ran and hid the panel anyway so talk could stay enabled with nothing on screen, which is the sequence in the report, present dismiss present then the old completion visibility is now given up when dismiss starts, so a present during the fade takes the first present path and animates the panel back in itself. each dismissal carries a transition id and its completion only hides the panel if it still owns it. the already visible path restores alpha as well this follows quick chat, which rotates a transitionID and checks it in its dismissal completion, and the decision is a pure function so it can be tested the way VoiceWakeOverlayController.evaluateToken is
|
🦞👀 Pull request received. I will update this pull request when review starts. |
|
Codex review: needs maintainer review before merge. Reviewed August 29, 2026, 3:16 PM ET / 19:16 UTC. ClawSweeper reviewWhat this changesThis PR prevents an old Talk-overlay fade-out completion from hiding a panel that was presented again during the fade. Merge readinessThe patch is a correct, bounded repair for a current macOS Talk-overlay race; it should remain open pending a fresh successful exact-head macOS CI run. Priority: P2 Review scores
Verification
How this fits togetherTalk Mode drives an AppKit overlay panel when the operator enables or disables Talk. The overlay controller converts those requests into panel animations, whose asynchronous completion must respect the latest visibility intent. flowchart LR
A[Talk Mode toggle] --> B[Talk mode controller]
B --> C[Talk overlay controller]
C --> D[Present or dismiss animation]
D --> E[Current transition check]
E --> F[Visible Talk panel]
Before merge
Agent review detailsSecurityNone. PR surfaceOther +209. Total +209 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: Keep the local transition-identity repair and land it once the exact-head macOS suite completes successfully, without introducing a second overlay-state mechanism. Do we have a high-confidence way to reproduce the issue? Yes. Current main retains the pre-fix controller sequence, and the supplied focused regression run and before/after visual evidence exercise present → dismiss → present through the 160 ms completion. Is this the best way to solve the issue? Yes. The lifecycle owner now records the latest visibility intent and rejects stale completion work, matching the established Quick Chat approach rather than compensating in a caller. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 86b9cf728895. LabelsLabel justifications:
EvidenceWhat I checked:
Likely related people:
Rank-up movesOptional improvements that raise the rating; they are not merge blockers.
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (4 earlier review cycles)
|
review asked for proof at the surface the operator actually sees, since the model flag is not that. the panel is readable now, setter still file private, and the test checks alphaValue and isVisible on the real NSPanel after the fade that present interrupted has finished on the base it reports alphaValue 0.0 and isVisible false, so the panel is transparent and ordered out while talk is still enabled. that is the reported symptom measured on the object the animation was driving
|
thanks, the ask was fair. the flag i was asserting is not what an operator sees, so it did not settle the question. pushed on the base, with only so the panel is fully transparent and ordered out while Talk is still enabled. that is the reported symptom measured on the object the dismissal animation was driving, not on the flag. on this branch all four pass. i have not attached a recording. this is a headless assertion of the same two properties a screenshot would be showing, on the rank-up note about inlining
|
review wants proof at the surface an operator sees, and this machine has no display access so screencapture cannot run here. this renders the real overlay view instead and composites it at the panels own alphaValue, drawing nothing at all when the panel is ordered out, which is what reaches the screen it is off unless OPENCLAW_TALK_PROBE_DIR is set, so it does nothing in ci, and a maintainer can run it on both revisions and get the same two images
|
understood, and i have run out of what i can do about it from here, so rather than push a fifth revision at the same gate let me say where it stands and ask a maintainer to decide. what is blocking is not the patch. both re-reviews put patch quality at 🐚 platinum hermit and confirmed the defect on current main, the repair, the caller boundary and the sibling precedent. it is the proof tier, and the remaining ask is a capture from the running app. i cannot produce one. so the honest position: this needs someone with a running OpenClaw install to open Talk, dismiss it and re-enable inside the fade, and record it. i am happy for that to be a maintainer, or i can ask the owner of this machine to set the app up and capture it, which will take a little longer. the question i actually want answered: is a native capture required from outside contributors who do not have the app running, or is it a gate you apply to work that changes UI you can already exercise? if it is required regardless, say so and i will get the recording rather than keep substituting for it. if the evidence already here is enough for a human reviewer, this is ready. not asking anyone to lower a bar. asking which bar applies, so i stop guessing at it. |
screen recording is granted on this machine now, so the panel can be captured where it actually renders rather than composited from the view screencapture is called with -l<windowNumber>, so the frame is the overlay panel and nothing else on the display. all four come out 880x880, the 440pt panel at 2x, against 2704x1756 for a full grab off unless OPENCLAW_TALK_SHOT_DIR is set, same as the render probe
|
screen recording is granted on this machine now, so here is the capture you asked for. real panel, on screen, photographed with
the second base frame is the empty one. Talk is still enabled and its only visible surface is gone.
on scope, since this is a real display. the capture is
it drives |
* 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 #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]> * 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]>
|
The canonical repair landed in #135731 as 1bf5fb68d389, and I verified that commit is on main. Closing this overlapping proposal as superseded, with Huzaifa Iftikhar's contributor credit preserved in the landed commit. The Talk controller now records visibility intent immediately and gives each transition its own token. An old dismissal completion cannot hide a newer presentation. The shared animation factory remains unchanged, and removing the redundant nested task keeps the repair at net +1 production line, versus +35 in the original proposal. The signed synthetic AppKit fixture fails all three rapid-reopen checks when only the token guard is removed; the repaired controller passes all three reopen and all three normal-dismiss checks. Source hashes bind that pre-commit execution to the final committed inputs. The synchronous regression and final-head CI, including Swift tests and the release build, pass. The opt-in AppKit proof is timed native coverage, not a full operator microphone session; details and earlier CI failures are retained in #135731. |
…#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]>








Closes #127645
What Problem This Solves
Fixes an issue where users who dismiss the Talk overlay and immediately bring it back would end up with Talk still enabled but nothing on screen. The overlay is Talk's only operator-visible surface, so once it is hidden this way there is no indication the mode is still running.
It needs only a quick dismiss then present, inside the 160 ms dismissal animation.
Why This Change Was Made
Dismissal starts a fade and used to leave
model.isVisibletrue until the animation completed. A present arriving during that window therefore saw the panel as already visible, took the path that only repositions and orders it front, and never brought the alpha back up. The dismissal completion then ran and hid the panel regardless, so the newer presentation lost.Visibility is now released when the dismissal starts, so a present during the fade takes the first-present path and animates the panel back in on its own. Each dismissal carries a transition id, and its completion hides the panel only if it still owns it.
That is the shape
QuickChatControlleralready uses, which rotates atransitionIDon present and dismiss and checks it in its dismissal completion. The ownership decision is a pure function so it can be tested directly, the wayVoiceWakeOverlayController.evaluateTokenis.User Impact
Dismissing and immediately re-opening Talk keeps the overlay on screen. Talk can no longer end up enabled with its panel hidden, which previously needed a restart of the mode to recover from.
No visual change to either animation on its own.
TalkOverlayViewreadsphase,levelandisPaused, notisVisible, so releasing visibility earlier does not alter what is drawn during the fade.Evidence
The reported sequence as a test,
present -> dismiss -> present, then waiting out the 160 ms fade so the interrupted completion has run.On
651c1ec2before the change:which is the
terminal visible=False (expected True)from the issue. After:Full
swift testforapps/macos, same machine, same run conditions:651c1ec2Same three,
TalkMLXSpeechSynthesizerTests,AppStateIsolationTestsandCLIInstallerTests. They fail on a clean checkout here too and are unrelated to this change, all three environment dependent, around helper process reaping, an app profile and a temp CLI install. The counts differ by exactly the 3 tests and 1 suite added here.swiftformat --lint --config config/swiftformatreports0/2 files require formatting. swiftformat is the pinned 0.62.1; my swiftlint is 0.65.1 against the pinned 0.65.0, so treat the lint result as indicative rather than the pinned check.disclaimer: this contribution was prepared with the assistance of an ai agent. i traced the present and dismiss paths and both call sites myself, confirmed the failing sequence on a clean checkout before changing anything, and ran the suites and formatter above locally.
On the cancelled
macos-swiftjobci-gateis red here becausemacos-swiftwas cancelled, not because it failed an assertion. It ran 14:49 to 15:19 and was stopped at the thirty minute mark.I get the same shape locally. A full
swift testonapps/macosreaches about 1,880 passing tests and then stops making progress with 68 outstanding, printing:None of the outstanding tests are in this area. They are gateway, relay, node lifecycle and update orchestration. The suite holding the largest group of them passes on its own on this branch in well under a second:
What this branch adds, run on its own:
Two of those three suites are the probes, and it is worth being explicit about them since they are the only thing here that could plausibly hold a CI runner. Both return before constructing anything at all unless an environment variable is set, which CI does not set:
No window, no panel, no screenshot on a runner. That is why the filtered run above finishes in a second with all six counted.
I cannot re-run the job myself and I cannot compare against another PR, because this is currently the only open pull request where
macos-swiftis not skipped. So I am not claiming it is repo wide, only that I could not find anything in this branch that accounts for it and that everything this branch touches finishes fast in isolation. Happy to be pointed at whatever I have missed.