system settings
git clone https://git.lucas.co/cce-system-interface.git
fix: gate section_widgets on the load flag, so ids match what was painted
Registration is a side effect of `render_widget` during the view pass, and each
page's search box / backup button is painted only in the loaded branch — but
`section_widgets()` reported its id unconditionally. During the load window the
router therefore resolved a root that was never registered and dropped the event
with `unregistered/stale root ... event dropped`.
Live-confirmed on Packages, whose window is the longest of the three (`pacman -Q`
plus a `checkupdates` db sync): arriving on the page mid-load logged 18 such lines
and every pointer move added exactly one more; once the data landed, three further
moves added zero.
Each `section_widgets()` now mirrors its view's gate. The outer group count stays
the same either way — returning an empty outer Vec would empty `sec_focused` and
kill the ctrl-nav entry point.
Co-Authored-By: Claude Opus 5 <[email protected]>
src/pages/packages.rs | 10 +++++++++-
src/pages/services.rs | 9 ++++++++-
src/pages/storage.rs | 10 +++++++++-
3 files changed, 26 insertions(+), 3 deletions(-)
diff --git a/src/pages/packages.rs b/src/pages/packages.rs
index b36bb4b..478022a 100644
--- a/src/pages/packages.rs
+++ b/src/pages/packages.rs
@@ -716,7 +716,15 @@ impl PackagesState {
impl crate::pages::AppPage for PackagesState {
// Sections: [the one well]
fn section_widgets(&mut self) -> Vec<Vec<cce_ui::widget::WidgetId>> {
- vec![vec![self.search_box.id()]]
+ // Mirrors the view's `!loaded` early return: the search box is only painted
+ // (and so only registered) once `pacman -Q` + `checkupdates` land, which is
+ // the longest load window of any page. The group count stays 1 either way —
+ // an empty outer Vec would kill the ctrl-nav entry point.
+ if self.loaded {
+ vec![vec![self.search_box.id()]]
+ } else {
+ vec![Vec::new()]
+ }
}
fn view(
diff --git a/src/pages/services.rs b/src/pages/services.rs
index fbd4be1..0891ec6 100644
--- a/src/pages/services.rs
+++ b/src/pages/services.rs
@@ -372,7 +372,14 @@ fn service_action(name: &str, action: &str, is_system: bool) {
impl crate::pages::AppPage for ServicesState {
// Sections: [Services]
fn section_widgets(&mut self) -> Vec<Vec<cce_ui::widget::WidgetId>> {
- vec![vec![self.search_box.id()]]
+ // Mirrors the view's `!loaded` branch: the search box is only painted (and
+ // so only registered) once the unit list has landed. The group count stays
+ // 1 either way — an empty outer Vec would kill the ctrl-nav entry point.
+ if self.loaded {
+ vec![vec![self.search_box.id()]]
+ } else {
+ vec![Vec::new()]
+ }
}
fn view(
diff --git a/src/pages/storage.rs b/src/pages/storage.rs
index c5aaa7c..7ca7f43 100644
--- a/src/pages/storage.rs
+++ b/src/pages/storage.rs
@@ -354,7 +354,15 @@ impl crate::pages::AppPage for StorageState {
fn section_widgets(&mut self) -> Vec<Vec<cce_ui::widget::WidgetId>> {
// Only the third section (Full System Backup) has anything focusable;
// the first two are read-only readouts, so ctrl+i there has no target.
- vec![Vec::new(), Vec::new(), vec![self.backup_button.id()]]
+ // Gated on `backup_loaded` to mirror the view: the button is only painted
+ // (and so only registered) in that branch, and an id reported here while
+ // unregistered is a dead root the router drops with a warning.
+ let backup = if self.backup_loaded {
+ vec![self.backup_button.id()]
+ } else {
+ Vec::new()
+ };
+ vec![Vec::new(), Vec::new(), backup]
}
fn view(