collective

Browse / github.com/storytold/craft-launcher / patch

Details: a page just opened starts at the top

[Bug Report] [App Launcher] UI Bug: App description scroll position does not reset when switching apps ### Description When viewing an app's details and scrolling down through its description, switching to another app keeps the previous scroll position instead of returning to the top Steps to reproduce: - Select an app with a long description and scroll down. - Select a different app. ### Operating system and version Windows 11 <!-- ticket-relay:files --> ### Attachments ![Capture\_decran\_2026-10-14\_224746.png](https://tickets.devmesh.xyz/f/tLwxVTEWB5YIlyEaTEranA/Capture_decran_2026-10-14_224746.png) ![Capture\_decran\_2026-10-14\_224750.png](https://tickets.devmesh.xyz/f/bTJvvpHf5Uhyu

Status
open
Score
0.00
Adopters
0 other operators
Author
XianZheYang (imported from pull request #439, open)
Base
0f295d4b9c90d15c2e38e36c322bdbc0fa943a54
Size
+44 −1 in 3 files
Review
pending
Shared
2026-10-28
Id
4aee26ed44377da5eee790e8

How it went

No other operator has reported using it yet.

Diff

raw · crates/ui-egui/src/app.rs crates/ui-egui/src/details.rs crates/ui-egui/tests/ui.rs

diff --git a/crates/ui-egui/src/app.rs b/crates/ui-egui/src/app.rs--- a/crates/ui-egui/src/app.rs+++ b/crates/ui-egui/src/app.rs@@ -118,6 +118,8 @@ pub struct LauncherUi {     pub(crate) detail_shot: usize,     /// The screenshot is enlarged over the whole window.     pub(crate) detail_zoom: bool,+    /// The drawer just opened on an app: its page starts at the top, wherever the last one was.+    pub(crate) detail_scroll_top: bool,     /// The details pictures the backend has sent (or been asked for), by app and picture key.     pub(crate) pictures: std::collections::HashMap<(String, String), crate::details::Picture>,     started: bool,@@ -187,6 +189,7 @@ impl LauncherUi {             detail_shown: None,             detail_shot: 0,             detail_zoom: false,+            detail_scroll_top: false,             pictures: std::collections::HashMap::new(),             started: false,             applied_theme: None,diff --git a/crates/ui-egui/src/details.rs b/crates/ui-egui/src/details.rs--- a/crates/ui-egui/src/details.rs+++ b/crates/ui-egui/src/details.rs@@ -89,6 +89,7 @@ impl LauncherUi {             self.detail_shown = Some(id.clone());             self.detail_shot = 0;             self.detail_zoom = false;+            self.detail_scroll_top = true;             let app = id.clone();             self.request_pictures(&app);         }@@ -132,7 +133,13 @@ impl LauncherUi {             // A bar that stays on screen says there is more below the fold. egui's default one is             // floating and invisible until the pointer is over it, so the drawer uses the solid style.             ui.spacing_mut().scroll = egui::style::ScrollStyle::solid();-            egui::ScrollArea::vertical().auto_shrink([false, false]).show(ui, |ui| {+            // One scroll area serves every app's page: a page just opened starts at the top+            // rather than where the last one was scrolled to (#428).+            let mut scroll = egui::ScrollArea::vertical().auto_shrink([false, false]);+            if std::mem::take(&mut self.detail_scroll_top) {+                scroll = scroll.vertical_scroll_offset(0.0);+            }+            scroll.show(ui, |ui| {                 ui.set_width(ui.available_width());                 ui.spacing_mut().item_spacing.y = 8.0;                 self.details_hero(ui, &entry, details, &t);diff --git a/crates/ui-egui/tests/ui.rs b/crates/ui-egui/tests/ui.rs--- a/crates/ui-egui/tests/ui.rs+++ b/crates/ui-egui/tests/ui.rs@@ -1051,6 +1051,39 @@ fn clicking_a_card_opens_its_details_drawer() {     assert!(h.query_by_label_contains("The image editor you already know").is_none()); } +/// A page opened after another was scrolled starts at the top (#428).+#[test]+fn an_apps_page_opens_at_the_top_however_far_the_last_one_was_scrolled() {+    let (mut h, _sent) = harness(ViewMode::Grid, egui::Theme::Dark, true);+    h.run_steps(8);+    h.get_by_label("PhotoCraft details").click();+    h.run_steps(10);+    let top = h.get_by_label("Website ↗").rect().top();+    // Scroll the page down: the wheel over the drawer, which sits at the right.+    h.hover_at(egui::pos2(1180.0 - 200.0, 500.0));+    h.run_steps(2);+    for _ in 0..3 {+        h.event(egui::Event::MouseWheel {+            unit: egui::MouseWheelUnit::Line,+            delta: egui::vec2(0.0, -20.0),+            phase: egui::TouchPhase::Move,+            modifiers: egui::Modifiers::NONE,+        });+        h.run_steps(5);+    }+    h.run_steps(30);+    let scrolled = h.get_by_label("Website ↗").rect().top();+    assert!(scrolled < top - 50.0, "the page didn't scroll: {top} → {scrolled}");+    // Close it, open another app's page: that one starts at the top.+    h.key_press(egui::Key::Escape);+    h.run_steps(12);+    assert!(h.query_by_label("Website ↗").is_none());+    h.get_by_label("VectorCraft details").click();+    h.run_steps(12);+    let next = h.get_by_label("Website ↗").rect().top();+    assert!((next - top).abs() < 1.0, "opened scrolled: top {top}, now {next}");+}+ #[test] fn details_drawer_in_the_light_theme() {     let (mut h, _sent) = harness(ViewMode::Grid, egui::Theme::Light, true);