feat(ui): replace the sync modal with a corner toast - #221
Merged
Merged
Conversation
romaintb
force-pushed
the
feat/sync-toast
branch
from
September 4, 2026 15:59
24edbc7 to
5991bf5
Compare
Every successful sync fired a centered Info dialog, and every failure a centered Error one. That was tolerable while sync blocked the UI, but sync now runs in the background and, since the auto-sync interval was wired up, fires on a timer. The modal had started interrupting the user every five minutes on its own. Sync outcomes now land in a small box in the bottom-right of the task list: success in theme green, gone after three seconds; failure in theme red, given fifteen because it carries something the user has to actually read. The in-flight indicator moves to the same corner instead of covering the middle of the screen. Task operation errors are untouched and still get a modal, since those are user-initiated. Nothing dismisses a toast by hand. Clearing it on any keypress meant a failure notice died to the next `j`, and Esc is already the quit key, so the TTL is the only thing that takes a toast down. The event loop only repaints on input or background work, so an expired toast would otherwise sit on screen until the next keystroke. `toast_expired()` asks for a repaint on tick once the TTL has passed, and that single repaint is what sweeps the toast away. The box is sized from the line's display width rather than its character count, because the status emoji occupy two columns and this is the same measure ratatui centers by. Getting that wrong put the leftover column entirely on one side. `info_message`, `error_message` and `clear_messages` are gone from `AppState`: they existed only to feed these dialogs, and `clear_messages` had no callers at all.
romaintb
force-pushed
the
feat/sync-toast
branch
from
September 4, 2026 16:04
5991bf5 to
9209db8
Compare
is_busy, active_task_count and task_count had zero callers; the lib target keeps the dead_code lint quiet about pub items.
The corner toast lived in app_component.rs as a private struct plus four methods, with its layout helper made pub only so a test could reach it. It now sits in src/ui/components/toast.rs next to badge.rs, and its test in the mirroring tests/ui/components/toast.rs. Toast::success, Toast::error and Toast::spinner replace the ad-hoc constructors, and `expires` became Option<Instant>: a spinner is a toast with no deadline, so expired() no longer has to rely on nobody asking. app_component.rs keeps only what the app owns, the Option<Toast> field and sweep_toast(), which is an event-loop concern rather than a widget one. current_toast() and render_toast() are gone, the render site is a plain if/else.
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.
Every successful sync fired a centered Info dialog, and every failure a centered Error one. That was tolerable while sync blocked the UI, but sync now runs in the background and, since the auto-sync interval was wired up, fires on a timer. The modal had started interrupting the user every five minutes on its own.
Sync outcomes now land in a small box in the bottom-right of the task list: success in theme green, gone after three seconds; failure in theme red, staying until any keypress dismisses it. The in-flight indicator moves to the same corner instead of covering the middle of the screen. Task operation errors are untouched and still get a modal, since those are user-initiated.
The event loop only repaints on input or background work, so a toast with an expiry would otherwise sit on screen until the next keystroke.
toast_expiring()forces a repaint on tick, but only while something is counting down, so a failure toast cannot pin the TUI at tick rate.Changes
Checklist
cargo fmtpassescargo clippy -- -D warningspassescargo testpassesRelated issues
Taken from #209