feat(client): motion and self-dismissal for the InfoBar stack (UI/UX v3 P6d) - #95
Merged
Conversation
…(UI/UX v3 P6d) The stack shipped in P6a-P6c had a `Timed` entrance, an `Option<Timed>` exit and an `expires_at` field that nothing ever read - the module carried a file-level `#![allow(dead_code)]` saying so. P6d wires all three and removes the allow. - Motion is per bar, not per stack: `apply_surface_fade` is called inside the builder's loop, so one bar can fade out while the one under it arrives. - Dismissal never deletes. `dismiss_info_bar` starts an exit resuming from what is on screen, `retire_info_bars` drops the bar once it finishes, and `expire_info_bars` applies the info severity's 20 s TTL (D3). Both run from the tick in `lifecycle.rs`, next to the other `retire` calls. - `Esc`, `Enter` and a successful connect all dismiss rather than remove. Slot replacement stays abrupt: the replacing bar is drawn in the same place on the same frame, and cross-fading would smear the two messages together. - A dismissed bar is out of everything but the renderer at once - it takes no key, holds no slot against a repeat, and leaves the AccessKit tree and the tree hash immediately rather than announcing a bar the user just closed. - `top_live` / `top_dismissible` replace `StackLayout::top` so the keyboard path stops re-deriving the stack's order itself. G-idle: `has_active_animation` gains one clause and goes quiet once every bar settles, so a bar counting down its deadline asks for no frames. With animations off every `Timed` is born finished, which is the reduced-motion path: bars appear and leave without a single extra frame. `push_info_bar` and `apply_server_message` now take `&AnimationsConfig`, since the entrance duration is the user's setting. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Coverage reportGenerated by |
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.
Problem
The stack shipped in P6a–P6c already declared a
Timedentrance, anOption<Timed>exit and anexpires_atdeadline — and nothing read any of them.infobar.rscarried a file-level#![allow(dead_code)]saying exactly that. So the three banners were still the only overlays in the product that pop in and out on a frame boundary, and the roadmap's word "auto-dismissing" described none of them.Solution
apply_surface_fadeis called inside the builder's loop, so one bar can fade out while the one under it is still arriving.InfoBar::visibility()is one expression (1 - exit.progress, elseentrance.progress), and the exit resumes from what is on screen viaTimed::resuming_at— a bar dismissed mid-entrance does not jump to opaque first.ClientState::dismiss_info_barstarts the exit,retire_info_barsdrops the bar once it has finished drawing out, andexpire_info_barsapplies the info severity's 20 sINFO_BAR_TTL(D3 — the warning and error severities never expire). Both run from the tick inlifecycle.rs, next to the existingretirecalls.Esc,Enterand a successful connect all dismiss. Slot replacement stays abrupt: the replacing bar is drawn in the same place on the same frame, and cross-fading would only smear the two messages together.infobar::top_live/top_dismissiblereplaceStackLayout::top, so the keyboard path stops re-deriving the stack's order itself.push_info_barandapply_server_messagenow take&AnimationsConfig: the entrance duration is the user's setting, not a constant.Gates
has_active_animationgains one clause and goes quiet once every bar settles. A bar merely counting down its 20 s deadline asks for no frames; covered byonly_a_moving_bar_asks_for_frames.Timedis born finished, so a bar appears and leaves without a single extra frame (motion_off_makes_a_bar_appear_and_leave_without_a_single_extra_frame).Verification
cargo fmt --check— cleancargo clippy --workspace --all-targets -- -D warnings— no warningscargo test --workspace— 28 suites pass (1111 innexterm-client-gpu), 10 new testsCloses P6 (P6a #92, P6b #93, P6c #94, P6d here).
🤖 Generated with Claude Code