Hand the caret back after errors, and make the panel yield the stage - #14
Merged
Merged
Conversation
Four fixes to how the panel treats focus and the user's work in progress, all in the update loop: * A failed run now returns the caret to the composer, the same way StreamEvent::Done always has. UiEvent::Failed, ContextFailed and DictationFailed each ended a run with focus left nowhere — the pattern was consistently "the success path refocuses, the failure path forgot" (DictationEnded focuses; DictationFailed did not). * The toggle hotkey pressed while the panel is visible but another app holds the keyboard now summons aibo — activate, focus window, focus composer — instead of hiding a panel the user was trying to get back to and destroying the session it displayed. A new panel_focused flag tracks the window server's Focused/Unfocused answers, optimistically set on show so a quick double-press still means open-then-dismiss. * Opening settings hides the panel. The panel floats AlwaysOnTop and the settings window does not, so with both on screen settings could only ever open behind aibo, unreachable. Nothing is lost: hide_panel never cancels a run and the hotkey resumes the conversation. * The capture hotkey pressed while the panel is open joins the conversation in progress, exactly like the attach menu's screenshot row: composer text, transcript and attachments survive and the crop lands beside them. Discarding the session there was an implicit destructive gesture. Crop-then-ask still starts fresh from a closed panel, and a cancelled crop now brings the conversation back instead of leaving it destroyed and hidden.
There was a problem hiding this comment.
Pull request overview
This PR adjusts focus and session-handling behaviors in the UI panel to better preserve in-progress work and ensure the user can immediately continue typing after interruptions or failures.
Changes:
- Track whether the panel window is actually focused (
panel_focused) to make the toggle hotkey “summon” the panel (rather than dismiss) when another app currently has keyboard focus. - Return input focus to the composer after failure-ending events (
UiEvent::Failed,ContextFailed,DictationFailed) to avoid “dead” keystrokes after errors. - Hide the always-on-top panel when opening settings so the settings window is reachable; adds a regression test.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+1434
to
+1438
| let step_aside = if state.panel_visible { | ||
| state.hide_panel() | ||
| } else { | ||
| Task::none() | ||
| }; |
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.
Four small behavioral fixes to how the panel treats focus and work in progress (all in
aibo-ui/src/app.rs):Caret returns after errors
UiEvent::Failed,ContextFailed, andDictationFailedeach ended a run with focus left nowhere, so the next keystroke went into the void until the input was clicked. The success twins already refocused (StreamEvent::Done,DictationEnded); the failure paths now do the same. The blocking no-provider error still routes focus to settings.Hotkey summons instead of dismissing
Pressing the panel hotkey while the panel was visible but another app held the keyboard hid the panel and discarded its session — the opposite of "get me back to aibo". A new
panel_focusedflag (tracked fromFocused/Unfocusedwindow events, set optimistically on show) lets the toggle tell the two intents apart. Dismissal stays one press away once the panel has focus.Settings can't open behind the panel
The panel floats
AlwaysOnTop; the settings window doesn't, so settings always landed behind aibo and couldn't be raised. Opening settings now hides the panel (non-destructive — the hotkey resumes the same conversation). Regression test included.⌥⇧Space appends to the open conversation
The capture hotkey discarded the session even when the panel was open with typed text. It now takes the attach-menu path (
region_capture_keeps_session): panel hides for clean pixels, capture re-presents it with the crop attached beside the composer text. Crop-then-ask still starts fresh from a closed panel, and a cancelled crop brings the conversation back.Verified with CI's gates:
cargo fmt --check,cargo clippy --workspace --all-features --all-targets --locked -- -D warnings -A unsafe-code,cargo test(298 passed).