git.lucas.co / cce-system-interface
system settings
git clone https://git.lucas.co/cce-system-interface.git

commitd394c48d07b86ece1e573db23078afd4c1dc9a43
parent42a1a84418
authorLucas Galante <[email protected]>
date2026-07-09 18:05
feat: Switcher + Page DISSOLVED (Phase 6u) — sections are the dispatch roots

The page tree's top two layers are gone. The current page was always
app.current_page (the Switcher's active index was derived); page scroll
was already scroll_y/max_scroll_y; what remained load-bearing was Page's
scrollbar child, its out-of-bounds event gate, keyboard scrolling, and
the propagate root. All app-owned now:

- dispatch_page_event replicates Page's routing: the out-of-bounds gate
  (screen-space, scrollbar-drag bypass), then the legacy child order
  reversed — scrollbar first with Page's y-unshift, then the app-held
  section-container clones last-to-first. PointerMove visits all roots;
  other events stop at the first handler.
- The page scrollbar is an app field, placed exactly as Page::layout
  did and updated with LAST frame's content height for the window pass
  (the legacy pass also ran before the frame's content was measured);
  its quads collect into the window assembly's plain slot (before the
  plate wash) where the switcher subtree used to emit them.
- Scroll keys (arrows/PgUp/PgDn/Home/End over the page viewport) and
  the ctrl-nav entry/section-cycling move into the key handler; the
  entry focuses section 0 (network's WiFi highlight ORs that in where
  it used root focus).
- System-page special cases deleted: scroll is the same manual path as
  every other page.

System page: the widget-tree render path SEGFAULTED at launch on the
pre-6u baseline (exit 139 — the one-time raw-pointer section/label tree,
the exact use-after-free class the RFC core rebuild exists to kill), so
this flips it to the immediate-mode view that already existed in the
file but was never wired to the AppPage impl. Its CPU/GPU labels become
sec.text lines (the grid-column widget placement overlapped them), the
governor InfoBoxes advance the section cursor, gpu_strings lands on
state, and its two menus link into the app-held clone sections exactly
like every other page (the state-owned section tree and one-time widget
graveyard are no longer linked).

A/B (audio): WINDOW_PC tuple stream byte-identical; pixels AE=0.
Live-verified: spinbox +/-, right-click context menu, page dropdown
switching, page wheel + fallback (fast path), System gov dropdown
open/select + page scroll (page was previously uninspectable),
notifications bell dropdown, fonts textbox focus. Pre-existing and
unchanged: List/ScrollBox inner wheel is dead (its dissolution step
owns that); the scrollbar track's right half sits in the compositor's
8px edge-resize zone; an instant synthetic click's track-jump is wiped
by the release-frame sync (humans get a rebuild while dragging, same
as legacy). Not headlessly drivable: keyboard scroll/search/focus-nav,
held drags (slider, scrollbar thumb).

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016MjP3pGQEDLkJbV5WEmYBe

 src/input_handler.rs       | 221 +++++++++++++++++++++++++++++++--------------
 src/main.rs                |  51 ++---------
 src/pages/accounts.rs      |   3 -
 src/pages/audio.rs         |   3 -
 src/pages/fonts.rs         |   4 -
 src/pages/mod.rs           |   3 +-
 src/pages/network.rs       |  10 +-
 src/pages/notifications.rs |   4 -
 src/pages/packages.rs      |   4 -
 src/pages/processes.rs     |   3 -
 src/pages/storage.rs       |   8 +-
 src/pages/system_info.rs   | 163 +++++++++------------------------
 src/renderer.rs            |  63 ++++++-------
 13 files changed, 237 insertions(+), 303 deletions(-)

diff --git a/src/input_handler.rs b/src/input_handler.rs
index f6cf58b..7421d8e 100644
--- a/src/input_handler.rs
+++ b/src/input_handler.rs
@@ -36,9 +36,6 @@ impl SystemInterface {
         if self.page_dropdown.cursor_moved(lx_no_scroll, ly_no_scroll, &mut self.ui_context) {
             changed = true;
         }
-        if self.switcher.cursor_moved(lx_no_scroll, ly_no_scroll, &mut self.ui_context) {
-            changed = true;
-        }
 
         // Drag updates are high-priority overrides
         let mut drag_handled = false;
@@ -53,10 +50,8 @@ impl SystemInterface {
 
         if !drag_handled {
             let event = cce_ui::widget::Event::PointerMove { x: lx, y: ly, local_x: lx, local_y: ly };
-            if let Some(root) = self.get_page_root_widget() {
-                if self.ui_context.propagate_event(&event, root) {
-                    changed = true;
-                }
+            if self.dispatch_page_event(&event) {
+                changed = true;
             }
         }
 
@@ -114,7 +109,6 @@ impl SystemInterface {
             }
         }
 
-        let mut handled = false;
         if self.page_dropdown.mouse_input(button, state, lx_no_scroll, ly_no_scroll, &mut self.ui_context) {
             if self.page_dropdown.take_change() {
                 let idx = self.page_dropdown.selected;
@@ -128,12 +122,6 @@ impl SystemInterface {
                 }
             }
             self.needs_rebuild = true;
-            handled = true;
-        } else if self.switcher.mouse_input(button, state, lx_no_scroll, ly_no_scroll, &mut self.ui_context) {
-            self.needs_rebuild = true;
-            handled = true;
-        }
-        if handled {
             return true;
         }
 
@@ -170,9 +158,7 @@ impl SystemInterface {
         let lx = self.cursor_x / s;
         let ly = self.cursor_y / s + self.scroll_y;
         let event = cce_ui::widget::Event::MouseButton { button, state, x: lx, y: ly, local_x: lx, local_y: ly };
-        if let Some(root) = self.get_page_root_widget() {
-            self.ui_context.propagate_event(&event, root);
-        }
+        self.dispatch_page_event(&event);
 
         if state == cce_ui::widget::ElementState::Pressed {
             self.app.get_current_page_mut().handle_pointer_down(lx, ly, &mut self.ui_context);
@@ -208,33 +194,10 @@ impl SystemInterface {
 
 
             let event = cce_ui::widget::Event::MouseWheel { delta: delta.clone(), x: lx, y: ly, local_x: lx, local_y: ly };
-            let mut handled = false;
-            if let Some(root) = self.get_page_root_widget() {
-                if self.app.current_page == Page::System {
-                    if self.ui_context.propagate_event(&event, root) {
-                        handled = true;
-                    }
-                } else {
-                    unsafe {
-                        for child in (*root).children(&self.ui_context).into_iter().rev() {
-                            let (cx, cy, _, _) = (*child).rect();
-                            let mut local_adjusted = event.clone();
-                            match &mut local_adjusted {
-                                cce_ui::widget::Event::MouseWheel { local_x, local_y, .. } => {
-                                    *local_x -= cx;
-                                    *local_y -= cy;
-                                }
-                                _ => {}
-                            }
-                            let adjusted_event = (*root).transform_event_for_child(child, local_adjusted, &self.ui_context);
-                            if self.ui_context.propagate_event(&adjusted_event, child) {
-                                handled = true;
-                                break;
-                            }
-                        }
-                    }
-                }
-            }
+            // Page dissolved (6u): one dispatch path for every page — scrollbar, then
+            // sections (inner ScrollBoxes take the wheel first), then the manual page
+            // scroll below as the fallback, exactly as the non-System pages worked.
+            let handled = self.dispatch_page_event(&event);
 
             let mut actions = Vec::new();
             self.propagate_widget_changes(&mut actions);
@@ -281,9 +244,74 @@ impl SystemInterface {
         false
     }
 
-    pub(crate) fn get_page_root_widget(&mut self) -> Option<*mut (dyn cce_ui::widget::Element + 'static)> {
-        let page_idx = Page::ALL.iter().position(|&p| p == self.app.current_page).unwrap_or(0);
-        Some(self.pages[page_idx].as_ptr_mut())
+    /// The current page's event-dispatch roots (Phase 6u — the Page widget is dissolved):
+    /// the app-held section-container clones every page links its widgets under.
+    pub(crate) fn page_dispatch_roots(&mut self) -> Vec<*mut (dyn cce_ui::widget::Element + 'static)> {
+        self.page_sec_containers.iter_mut().map(|s| s.as_ptr_mut()).collect()
+    }
+
+    /// Replicates the dissolved Page's event routing: the out-of-bounds gate (events whose
+    /// screen position is outside the page viewport never reach page widgets, unless the
+    /// scrollbar is mid-drag), then the legacy child order reversed — the scrollbar first
+    /// (with Page's y-unshift, its coords are screen-space while the event carries the
+    /// scroll offset), then the sections last-to-first. PointerMove visits everything
+    /// (hover bookkeeping); other events stop at the first handler.
+    pub(crate) fn dispatch_page_event(&mut self, event: &cce_ui::widget::Event) -> bool {
+        use cce_ui::widget::Event;
+        let is_pointer_event = matches!(
+            event,
+            Event::PointerMove { .. } | Event::MouseButton { .. } | Event::MouseWheel { .. }
+        );
+        if is_pointer_event && !self.page_scroll_bar.dragging {
+            if let Event::PointerMove { x, y, .. }
+            | Event::MouseButton { x, y, .. }
+            | Event::MouseWheel { x, y, .. } = event
+            {
+                let rx = self.sidebar_width;
+                let ry = self.header_height;
+                let rw = self.width as f32 - self.sidebar_width;
+                let mut rh = self.height as f32 - self.header_height - self.status_height;
+                if self.search_open {
+                    rh -= 42.0;
+                }
+                let screen_y = *y - self.scroll_y;
+                if *x < rx || *x > rx + rw || screen_y < ry || screen_y > ry + rh {
+                    return false;
+                }
+            }
+        }
+
+        let is_pointer_move = matches!(event, Event::PointerMove { .. });
+        let mut handled = false;
+
+        if self.page_scroll_bar.content_h > self.page_scroll_bar.viewport_h {
+            let mut sb_event = event.clone();
+            if let Event::PointerMove { y, local_y, .. }
+            | Event::MouseButton { y, local_y, .. }
+            | Event::MouseWheel { y, local_y, .. } = &mut sb_event
+            {
+                *y -= self.scroll_y;
+                *local_y -= self.scroll_y;
+            }
+            let sb_ptr = self.page_scroll_bar.as_ptr_mut();
+            if self.ui_context.propagate_event(&sb_event, sb_ptr) {
+                if !is_pointer_move {
+                    return true;
+                }
+                handled = true;
+            }
+        }
+
+        let roots = self.page_dispatch_roots();
+        for root in roots.into_iter().rev() {
+            if self.ui_context.propagate_event(event, root) {
+                if !is_pointer_move {
+                    return true;
+                }
+                handled = true;
+            }
+        }
+        handled
     }
 
     pub(crate) fn handle_key_input_internal(&mut self, event: &cce_ui::widget::KeyEvent) -> bool {
@@ -346,15 +374,46 @@ impl SystemInterface {
                         self.needs_rebuild = true;
                         return true;
                     }
+                    // Sections are parentless with the Page dissolved (6u), so the
+                    // parent-pointer walk can't cycle BETWEEN them — do it app-side.
+                    if let cce_ui::widget::Key::Character(c) = &event.logical_key {
+                        let forward = c == "j" || c == "J";
+                        let backward = c == "k" || c == "K";
+                        if forward || backward {
+                            let mut roots = self.page_dispatch_roots();
+                            let focused_idx = roots.iter().position(|&r| unsafe {
+                                cce_ui::widget::focus::is_focused(&*r)
+                            });
+                            if let (Some(idx), true) = (focused_idx, !roots.is_empty()) {
+                                let next = if forward {
+                                    (idx + 1) % roots.len()
+                                } else if idx == 0 {
+                                    roots.len() - 1
+                                } else {
+                                    idx - 1
+                                };
+                                unsafe {
+                                    let sec = &mut *roots[next];
+                                    cce_ui::widget::focus::set_focused(sec);
+                                    sec.focus();
+                                }
+                                self.needs_rebuild = true;
+                                return true;
+                            }
+                        }
+                    }
                 } else {
-                    if let Some(root_ptr) = self.get_page_root_widget() {
+                    // Entry point: the dissolved page root used to take focus here; focus
+                    // the first section instead.
+                    let roots = self.page_dispatch_roots();
+                    if let Some(&first) = roots.first() {
                         unsafe {
-                            let root_ref = &mut *root_ptr;
-                            cce_ui::widget::focus::set_focused(root_ref);
-                            root_ref.focus();
-                            self.needs_rebuild = true;
-                            return true;
+                            let sec = &mut *first;
+                            cce_ui::widget::focus::set_focused(sec);
+                            sec.focus();
                         }
+                        self.needs_rebuild = true;
+                        return true;
                     }
                 }
             }
@@ -362,25 +421,47 @@ impl SystemInterface {
 
         let event_wrapper = cce_ui::widget::Event::KeyInput(event.clone());
         let mut key_handled = false;
-        if let Some(root) = self.get_page_root_widget() {
-            let page_idx = Page::ALL.iter().position(|&p| p == self.app.current_page).unwrap_or(0);
-            let old_page_scroll = self.pages[page_idx].scroll_y;
-
-            if self.ui_context.propagate_event(&event_wrapper, root) {
-                let mut actions = Vec::new();
-                self.propagate_widget_changes(&mut actions);
-                for a in actions {
-                    self.handle_action(&a);
-                }
-                self.needs_rebuild = true;
-                key_handled = true;
+        if self.dispatch_page_event(&event_wrapper) {
+            let mut actions = Vec::new();
+            self.propagate_widget_changes(&mut actions);
+            for a in actions {
+                self.handle_action(&a);
             }
+            self.needs_rebuild = true;
+            key_handled = true;
+        }
 
-            let new_page_scroll = self.pages[page_idx].scroll_y;
-            if (new_page_scroll - old_page_scroll).abs() > 0.01 {
-                self.scroll_y = new_page_scroll;
-                self.needs_rebuild = true;
-                key_handled = true;
+        // The dissolved Page's keyboard scrolling: when nothing in the page tree took the
+        // key and the cursor is over the page viewport, scroll keys move the page.
+        if !key_handled && event.state == cce_ui::widget::ElementState::Pressed && self.max_scroll_y > 0.0 {
+            let over_page = {
+                let ry = self.header_height;
+                let mut rh = self.height as f32 - self.header_height - self.status_height;
+                if self.search_open {
+                    rh -= 42.0;
+                }
+                self.cursor_x >= self.sidebar_width
+                    && self.cursor_y >= ry
+                    && self.cursor_y <= ry + rh
+            };
+            if over_page {
+                use cce_ui::widget::{Key, NamedKey};
+                let viewport_h = self.height as f32 - self.header_height - self.status_height
+                    - if self.search_open { 42.0 } else { 0.0 };
+                let old_scroll = self.scroll_y;
+                match &event.logical_key {
+                    Key::Named(NamedKey::ArrowDown) => self.scroll_y = (self.scroll_y + 24.0).min(self.max_scroll_y),
+                    Key::Named(NamedKey::ArrowUp) => self.scroll_y = (self.scroll_y - 24.0).max(0.0),
+                    Key::Named(NamedKey::PageDown) => self.scroll_y = (self.scroll_y + viewport_h).min(self.max_scroll_y),
+                    Key::Named(NamedKey::PageUp) => self.scroll_y = (self.scroll_y - viewport_h).max(0.0),
+                    Key::Named(NamedKey::Home) => self.scroll_y = 0.0,
+                    Key::Named(NamedKey::End) => self.scroll_y = self.max_scroll_y,
+                    _ => {}
+                }
+                if (self.scroll_y - old_scroll).abs() > 0.01 {
+                    self.needs_rebuild = true;
+                    key_handled = true;
+                }
             }
         }
 
diff --git a/src/main.rs b/src/main.rs
index 993f320..2b00342 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,4 @@
-use cce_ui::widget::{hover_animation, Element};
+use cce_ui::widget::hover_animation;
 use glyphon::{Buffer, FontSystem};
 
 use cce_settings::app::{AppAction, AppState};
@@ -80,8 +80,12 @@ struct SystemInterface {
     last_scroll_y: f32,
     page_sec_containers: Vec<cce_ui::widget::SectionContainer>,
     page_dropdown: cce_ui::widget::Adapted<cce_ui::widget::input::Dropdown>,
-    switcher: cce_ui::widget::Adapted<cce_ui::widget::Switcher>,
-    pages: Vec<cce_ui::widget::Page>,
+    // Switcher + Page DISSOLVED (Phase 6u): the current page is app.current_page, page
+    // scroll is scroll_y/max_scroll_y, and the page scrollbar is this app-owned widget
+    // (rendered into the window assembly, evented directly). content_h feeds it — the
+    // window pass reads last frame's value, exactly as the legacy Page did.
+    page_scroll_bar: cce_ui::widget::ScrollBar,
+    content_h: f32,
     // Root Backplate + StatusBar DISSOLVED (Phase 6s): the window plate and the status
     // bar are emitted as tuples in rebuild_layout; this is the bar's text.
     status_text: String,
@@ -123,23 +127,6 @@ impl cce_ui::engine::Application for SystemInterface {
             .with_auto_width(true);
         let sidebar_width = 0.0f32;
 
-        let switcher = cce_ui::widget::Switcher::new(sidebar_width, 0.0, 820.0 - sidebar_width, 680.0);
-        let mut pages = Vec::new();
-        for page in Page::ALL.iter() {
-            let mut page_widget = cce_ui::widget::Page::new(sidebar_width, 0.0, 820.0 - sidebar_width, 680.0)
-                .with_label(page.label());
-            if *page == Page::System {
-                page_widget.layout = Box::new(cce_ui::widget::AdaptiveGridLayout {
-                    min_col_width: 320.0,
-                    gap: 20.0,
-                    padding_x: 10.0,
-                    padding_y: 10.0,
-                    grid: None,
-                });
-            }
-            pages.push(page_widget);
-        }
-
         let mut app_state = app;
         app_state.current_page = Page::ALL[initial_page_idx];
 
@@ -186,8 +173,8 @@ impl cce_ui::engine::Application for SystemInterface {
             last_scroll_y: 0.0,
             page_sec_containers: Vec::new(),
             page_dropdown,
-            switcher,
-            pages,
+            page_scroll_bar: cce_ui::widget::ScrollBar::new(),
+            content_h: 0.0,
             status_text: String::new(),
             sans_serif_family: sans_family,
             serif_family,
@@ -204,12 +191,6 @@ impl cce_ui::engine::Application for SystemInterface {
         };
         this.app.system_info.sender = Some(this.sender.clone());
 
-        for page in &mut this.pages {
-            this.switcher.add_child(page.as_ptr(), &mut this.ui_context);
-        }
-
-        // Root Backplate dissolved: switcher/dropdown register parentless via
-        // render_widget in rebuild_layout.
         this.update_status_text();
 
         this.rebuild_layout(820.0, 680.0);
@@ -359,20 +340,6 @@ impl cce_ui::engine::Application for SystemInterface {
 
 impl SystemInterface {
 
-fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f32, f32, f32, f32)>, ctx: &cce_ui::context::UiContext) {
-    if let Some(rect) = w.popover_rect() {
-        popovers.push(rect);
-    }
-    for child_ptr in w.children(ctx) {
-        unsafe {
-            if let Some(child) = child_ptr.as_ref() {
-                Self::collect_popover_rects(child, popovers, ctx);
-            }
-        }
-    }
-}
-
-
     fn tick_internal(&mut self, dt: f32) -> bool {
         let mut needs_redraw = false;
         if hover_animation::tick(dt) {
diff --git a/src/pages/accounts.rs b/src/pages/accounts.rs
index c6fab77..f9f2643 100644
--- a/src/pages/accounts.rs
+++ b/src/pages/accounts.rs
@@ -863,12 +863,9 @@ impl crate::pages::AppPage for AccountsState {
 
     fn link_children(
         &mut self,
-        page_root: &mut dyn cce_ui::widget::Element,
         sec_containers: &mut [cce_ui::widget::SectionContainer],
         ctx: &mut cce_ui::context::UiContext,
     ) {
-        cce_ui::widget::link_parent_child(page_root, &mut sec_containers[0], ctx);
-        cce_ui::widget::link_parent_child(page_root, &mut sec_containers[1], ctx);
 
         if self.editing_oauth_creds {
             cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.oauth_client_id_box, ctx);
diff --git a/src/pages/audio.rs b/src/pages/audio.rs
index cc2e53b..4ff7733 100644
--- a/src/pages/audio.rs
+++ b/src/pages/audio.rs
@@ -451,12 +451,9 @@ impl crate::pages::AppPage for AudioState {
 
     fn link_children(
         &mut self,
-        page_root: &mut dyn cce_ui::widget::Element,
         sec_containers: &mut [cce_ui::widget::SectionContainer],
         ctx: &mut cce_ui::context::UiContext,
     ) {
-        cce_ui::widget::link_parent_child(page_root, &mut sec_containers[0], ctx);
-        cce_ui::widget::link_parent_child(page_root, &mut sec_containers[1], ctx);
 
         for (i, sink) in self.sinks.iter().enumerate() {
             if sink.active {
diff --git a/src/pages/fonts.rs b/src/pages/fonts.rs
index 69c8749..87a692f 100644
--- a/src/pages/fonts.rs
+++ b/src/pages/fonts.rs
@@ -138,13 +138,9 @@ impl AppPage for FontsState {
 
     fn link_children(
         &mut self,
-        page_root: &mut dyn cce_ui::widget::Element,
         sec_containers: &mut [cce_ui::widget::SectionContainer],
         ctx: &mut cce_ui::context::UiContext,
     ) {
-        for sec in sec_containers.iter_mut() {
-            cce_ui::widget::link_parent_child(page_root, sec, ctx);
-        }
         
         cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.sans_box, ctx);
         cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.serif_box, ctx);
diff --git a/src/pages/mod.rs b/src/pages/mod.rs
index c95cec3..a6c9bc7 100644
--- a/src/pages/mod.rs
+++ b/src/pages/mod.rs
@@ -63,9 +63,10 @@ pub trait AppPage {
 
     fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer>;
 
+    /// Wire the page's widgets under the section containers (Phase 6u: the Page widget is
+    /// dissolved — the app-held section clones are the top-level dispatch/focus roots).
     fn link_children(
         &mut self,
-        page_root: &mut dyn cce_ui::widget::Element,
         sec_containers: &mut [cce_ui::widget::SectionContainer],
         ctx: &mut cce_ui::context::UiContext,
     );
diff --git a/src/pages/network.rs b/src/pages/network.rs
index d7a6ba8..fabe943 100644
--- a/src/pages/network.rs
+++ b/src/pages/network.rs
@@ -549,12 +549,9 @@ impl crate::pages::AppPage for NetworkState {
 
     fn link_children(
         &mut self,
-        page_root: &mut dyn cce_ui::widget::Element,
         sec_containers: &mut [cce_ui::widget::SectionContainer],
         ctx: &mut cce_ui::context::UiContext,
     ) {
-        cce_ui::widget::link_parent_child(page_root, &mut sec_containers[0], ctx);
-        cce_ui::widget::link_parent_child(page_root, &mut sec_containers[1], ctx);
 
         cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.wifi_toggle, ctx);
         cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.wifi_list_box.scroll_box, ctx);
@@ -568,11 +565,14 @@ impl crate::pages::AppPage for NetworkState {
         cw: f32,
         ch: f32,
         root_focused: bool,
-        _sec_focused: &[bool],
+        sec_focused: &[bool],
         layout: &mut dyn LayoutStrategy,
         ctx: &mut cce_ui::context::UiContext,
     ) -> crate::app::PageContent {
-        view(self, cx, cy, cw, ch, root_focused, layout, ctx)
+        // Page root dissolved (6u): the ctrl-nav entry focuses section 0 now, which used to
+        // be expressed as root focus here.
+        let focused = root_focused || sec_focused.first().copied().unwrap_or(false);
+        view(self, cx, cy, cw, ch, focused, layout, ctx)
     }
 
     fn propagate_widget_changes(&mut self, actions: &mut Vec<crate::app::AppAction>) {
diff --git a/src/pages/notifications.rs b/src/pages/notifications.rs
index 2d9c09f..8697698 100644
--- a/src/pages/notifications.rs
+++ b/src/pages/notifications.rs
@@ -209,13 +209,9 @@ impl AppPage for NotificationsState {
 
     fn link_children(
         &mut self,
-        page_root: &mut dyn cce_ui::widget::Element,
         sec_containers: &mut [cce_ui::widget::SectionContainer],
         ctx: &mut cce_ui::context::UiContext,
     ) {
-        for sec in sec_containers.iter_mut() {
-            cce_ui::widget::link_parent_child(page_root, sec, ctx);
-        }
         cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.enable_toggle, ctx);
         cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.bell_menu, ctx);
         cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.duration_spinbox, ctx);
diff --git a/src/pages/packages.rs b/src/pages/packages.rs
index cd5899b..083e135 100644
--- a/src/pages/packages.rs
+++ b/src/pages/packages.rs
@@ -750,13 +750,9 @@ impl crate::pages::AppPage for PackagesState {
 
     fn link_children(
         &mut self,
-        page_root: &mut dyn cce_ui::widget::Element,
         sec_containers: &mut [cce_ui::widget::SectionContainer],
         ctx: &mut cce_ui::context::UiContext,
     ) {
-        cce_ui::widget::link_parent_child(page_root, &mut sec_containers[0], ctx);
-        cce_ui::widget::link_parent_child(page_root, &mut sec_containers[1], ctx);
-        cce_ui::widget::link_parent_child(page_root, &mut sec_containers[2], ctx);
 
         cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.search_box, ctx);
         cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.installed_list_box.scroll_box, ctx);
diff --git a/src/pages/processes.rs b/src/pages/processes.rs
index ad8a97b..7a6477c 100644
--- a/src/pages/processes.rs
+++ b/src/pages/processes.rs
@@ -514,12 +514,9 @@ impl crate::pages::AppPage for ProcessesState {
 
     fn link_children(
         &mut self,
-        page_root: &mut dyn cce_ui::widget::Element,
         sec_containers: &mut [cce_ui::widget::SectionContainer],
         ctx: &mut cce_ui::context::UiContext,
     ) {
-        cce_ui::widget::link_parent_child(page_root, &mut sec_containers[0], ctx);
-        cce_ui::widget::link_parent_child(page_root, &mut sec_containers[1], ctx);
 
         cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.cpu_list_box.scroll_box, ctx);
         cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.services_search_box, ctx);
diff --git a/src/pages/storage.rs b/src/pages/storage.rs
index a426722..96a8034 100644
--- a/src/pages/storage.rs
+++ b/src/pages/storage.rs
@@ -345,13 +345,9 @@ impl crate::pages::AppPage for StorageState {
 
     fn link_children(
         &mut self,
-        page_root: &mut dyn cce_ui::widget::Element,
-        sec_containers: &mut [cce_ui::widget::SectionContainer],
-        ctx: &mut cce_ui::context::UiContext,
+        _sec_containers: &mut [cce_ui::widget::SectionContainer],
+        _ctx: &mut cce_ui::context::UiContext,
     ) {
-        cce_ui::widget::link_parent_child(page_root, &mut sec_containers[0], ctx);
-        cce_ui::widget::link_parent_child(page_root, &mut sec_containers[1], ctx);
-        cce_ui::widget::link_parent_child(page_root, &mut sec_containers[2], ctx);
     }
 
     fn view(
diff --git a/src/pages/system_info.rs b/src/pages/system_info.rs
index 6f13720..98af427 100644
--- a/src/pages/system_info.rs
+++ b/src/pages/system_info.rs
@@ -34,6 +34,7 @@ pub struct SystemState {
     pub cpu_usage: f32,
     pub cpu_cores: u32,
     pub gpus: Vec<String>,
+    pub gpu_strings: Vec<String>,
     pub cpu_label: cce_ui::widget::Adapted<cce_ui::widget::Label>,
     pub cpu_usage_label: cce_ui::widget::Adapted<cce_ui::widget::Label>,
     pub cpu_temp_label: cce_ui::widget::Adapted<cce_ui::widget::Label>,
@@ -102,6 +103,7 @@ impl Default for SystemState {
             cpu_usage: 0.0,
             cpu_cores: 0,
             gpus: Vec::new(),
+            gpu_strings: Vec::new(),
             cpu_label: Label::new("CPU Info"),
             cpu_usage_label: Label::new("CPU Usage"),
             cpu_temp_label: Label::new("CPU Temp"),
@@ -600,14 +602,12 @@ pub fn view(state: &mut SystemState, cx: f32, cy: f32, cw: f32, ch: f32, _root_f
         if !state.loaded {
             sec.text("Loading CPU model and utilization...", 12.0, 0.0, 12.0, TEXT_FG);
         } else {
-            // CPU Info Label
-            sec.widget(&mut state.cpu_label, 12.0, sec.cw - 24.0, 26.0, ctx);
-
-            // CPU Usage Label
-            sec.widget(&mut state.cpu_usage_label, 12.0, sec.cw - 24.0, 26.0, ctx);
-
-            // CPU Temp Label
-            sec.widget(&mut state.cpu_temp_label, 12.0, sec.cw - 24.0, 26.0, ctx);
+            // Same strings the old Label widgets carried, stacked vertically (the
+            // grid-column widget placement overlapped them at narrow widths).
+            sec.text(&format!("CPU  {}  ({} cores)", state.cpu_model, state.cpu_cores), 12.0, 0.0, 12.0, TEXT_FG);
+            sec.text(&format!("Usage  {:.0}%", state.cpu_usage), 12.0, 0.0, 12.0, TEXT_FG);
+            let cpu_temp_text = read_cpu_temp().map(|t| format!("Temp  {:.0}°C", t)).unwrap_or_else(|| "Temp  N/A".to_string());
+            sec.text(&cpu_temp_text, 12.0, 0.0, 12.0, TEXT_FG);
         }
     });
 
@@ -616,8 +616,8 @@ pub fn view(state: &mut SystemState, cx: f32, cy: f32, cw: f32, ch: f32, _root_f
         if !state.loaded {
             sec_gpu.text("Loading GPU models...", 12.0, 0.0, 12.0, TEXT_FG);
         } else {
-            for gpu_lbl in state.gpu_labels.iter_mut() {
-                sec_gpu.widget(gpu_lbl, 12.0, sec_gpu.cw - 24.0, 26.0, ctx);
+            for gpu_text in state.gpu_strings.iter() {
+                sec_gpu.text(gpu_text, 12.0, 0.0, 12.0, TEXT_FG);
             }
         }
     });
@@ -652,6 +652,11 @@ pub fn view(state: &mut SystemState, cx: f32, cy: f32, cw: f32, ch: f32, _root_f
             let info_h = 80.0;
             let info_y = sec_gov.ay();
             render_widget(sec_gov.pc, &mut info_box, rx + 12.0, info_y, sec_gov.cw - 24.0, info_h, ctx);
+            // Advance the section cursor past the hand-placed box.
+            sec_gov.content_y = sec_gov.content_y.max(info_y + info_h);
+            for h in &mut sec_gov.grid.col_heights {
+                *h = h.max(sec_gov.content_y);
+            }
         }
     });
 
@@ -685,6 +690,11 @@ pub fn view(state: &mut SystemState, cx: f32, cy: f32, cw: f32, ch: f32, _root_f
             let info_h = 80.0;
             let info_y = sec_gpow.ay();
             render_widget(sec_gpow.pc, &mut info_box, rx + 12.0, info_y, sec_gpow.cw - 24.0, info_h, ctx);
+            // Advance the section cursor past the hand-placed box.
+            sec_gpow.content_y = sec_gpow.content_y.max(info_y + info_h);
+            for h in &mut sec_gpow.grid.col_heights {
+                *h = h.max(sec_gpow.content_y);
+            }
         }
     });
 
@@ -745,6 +755,7 @@ pub fn update(state: &mut SystemState, msg: SystemMessage, ctx: &mut cce_ui::con
             state.cpu_usage = new.cpu_usage;
             state.cpu_cores = new.cpu_cores;
             state.gpus = new.gpus;
+            state.gpu_strings = new.gpu_strings.clone();
 
             state.battery = new.battery;
             state.on_ac = new.on_ac;
@@ -904,124 +915,34 @@ impl crate::pages::AppPage for SystemState {
 
     fn link_children(
         &mut self,
-        page_root: &mut dyn cce_ui::widget::Element,
-        _sec_containers: &mut [cce_ui::widget::SectionContainer],
+        sec_containers: &mut [cce_ui::widget::SectionContainer],
         ctx: &mut cce_ui::context::UiContext,
     ) {
-        if !self.initialized {
-            self.initialized = true;
-
-            // Clear page root children to prevent duplicates
-            page_root.clear_children(ctx);
-
-            // Bind click callbacks to actions
-            if let Some(ref tx) = self.sender {
-                let tx1 = tx.clone();
-                self.suspend_btn = self.suspend_btn.clone().on_click(move || {
-                    let _ = tx1.send(AppAction::SystemInfo(SystemMessage::Suspend));
-                });
-
-                let tx2 = tx.clone();
-                self.hibernate_btn = self.hibernate_btn.clone().on_click(move || {
-                    let _ = tx2.send(AppAction::SystemInfo(SystemMessage::Hibernate));
-                });
-
-                let tx3 = tx.clone();
-                self.reboot_btn = self.reboot_btn.clone().on_click(move || {
-                    let _ = tx3.send(AppAction::SystemInfo(SystemMessage::Reboot));
-                });
-
-                let tx4 = tx.clone();
-                self.poweroff_btn = self.poweroff_btn.clone().on_click(move || {
-                    let _ = tx4.send(AppAction::SystemInfo(SystemMessage::PowerOff));
-                });
-
-                let tx5 = tx.clone();
-                self.force_shutdown_btn = self.force_shutdown_btn.clone().on_click(move || {
-                    let _ = tx5.send(AppAction::SystemInfo(SystemMessage::ForceShutdown));
-                });
-            }
-
-            // Set up initial rect sizes for info boxes
-            self.cpu_info_box.set_rect(0.0, 0.0, 0.0, 80.0);
-            self.gpu_info_box.set_rect(0.0, 0.0, 0.0, 80.0);
-
-            // Build layout tree:
-            // ── 1. System Section ──
-            self.sec_system.add_child(self.hostname_label.as_ptr_mut(), ctx);
-            self.sec_system.add_child(self.uptime_label.as_ptr_mut(), ctx);
-
-            // ── 2. Actions Section ──
-            self.actions_row.add_child(self.suspend_btn.as_ptr_mut(), ctx);
-            self.actions_row.add_child(self.hibernate_btn.as_ptr_mut(), ctx);
-            self.actions_row.add_child(self.reboot_btn.as_ptr_mut(), ctx);
-            self.actions_row.add_child(self.poweroff_btn.as_ptr_mut(), ctx);
-
-            self.sec_actions.add_child(self.actions_row.as_ptr_mut(), ctx);
-            self.sec_actions.add_child(self.force_shutdown_btn.as_ptr_mut(), ctx);
-
-            // ── 3. CPU Section ──
-            self.sec_cpu.add_child(self.cpu_label.as_ptr_mut(), ctx);
-            self.sec_cpu.add_child(self.cpu_usage_label.as_ptr_mut(), ctx);
-            self.sec_cpu.add_child(self.cpu_temp_label.as_ptr_mut(), ctx);
-
-            // ── 4. GPU Section ──
-            for gpu_lbl in &mut self.gpu_labels {
-                self.sec_gpu.add_child(gpu_lbl.as_ptr_mut(), ctx);
-            }
-
-            // ── 5. CPU Governor Section ──
-            self.sec_cpu_gov.add_child(self.cpu_gov_menu.as_ptr_mut(), ctx);
-            self.sec_cpu_gov.add_child(self.cpu_info_box.as_ptr_mut(), ctx);
-
-            // ── 6. GPU Power Section ──
-            self.sec_gpu_gov.add_child(self.gpu_gov_menu.as_ptr_mut(), ctx);
-            self.sec_gpu_gov.add_child(self.gpu_info_box.as_ptr_mut(), ctx);
-
-            // ── 7. Battery Section ──
-            self.sec_battery.add_child(self.battery_label_pct.as_ptr_mut(), ctx);
-            self.sec_battery.add_child(self.battery_label_state.as_ptr_mut(), ctx);
-            self.sec_battery.add_child(self.battery_label_time.as_ptr_mut(), ctx);
-            self.sec_battery.add_child(self.battery_label_details.as_ptr_mut(), ctx);
-            self.sec_battery.add_child(self.battery_label_ac.as_ptr_mut(), ctx);
-
-            // Mount SectionContainers onto the Page
-            page_root.add_child(self.sec_system.as_ptr_mut(), ctx);
-            page_root.add_child(self.sec_actions.as_ptr_mut(), ctx);
-            page_root.add_child(self.sec_cpu.as_ptr_mut(), ctx);
-            page_root.add_child(self.sec_gpu.as_ptr_mut(), ctx);
-            page_root.add_child(self.sec_cpu_gov.as_ptr_mut(), ctx);
-            page_root.add_child(self.sec_gpu_gov.as_ptr_mut(), ctx);
-            page_root.add_child(self.sec_battery.as_ptr_mut(), ctx);
-        }
-
-        // Link parent-child for focus context
-        cce_ui::widget::link_parent_child(page_root, &mut self.sec_system, ctx);
-        cce_ui::widget::link_parent_child(page_root, &mut self.sec_actions, ctx);
-        cce_ui::widget::link_parent_child(page_root, &mut self.sec_cpu, ctx);
-        cce_ui::widget::link_parent_child(page_root, &mut self.sec_gpu, ctx);
-        cce_ui::widget::link_parent_child(page_root, &mut self.sec_cpu_gov, ctx);
-        cce_ui::widget::link_parent_child(page_root, &mut self.sec_gpu_gov, ctx);
-        cce_ui::widget::link_parent_child(page_root, &mut self.sec_battery, ctx);
-
-        cce_ui::widget::link_parent_child(&mut self.sec_cpu_gov, &mut self.cpu_gov_menu, ctx);
-        cce_ui::widget::link_parent_child(&mut self.sec_cpu_gov, &mut self.cpu_info_box, ctx);
-        cce_ui::widget::link_parent_child(&mut self.sec_gpu_gov, &mut self.gpu_gov_menu, ctx);
-        cce_ui::widget::link_parent_child(&mut self.sec_gpu_gov, &mut self.gpu_info_box, ctx);
+        // Phase 6u: System renders through the immediate view like every other page;
+        // only its two menus need event dispatch/focus, linked into the app-held clone
+        // sections exactly as the other pages do (the old one-time widget tree — labels,
+        // buttons, state-owned sections — is dead; the view emits text/buttons directly).
+        cce_ui::widget::link_parent_child(&mut sec_containers[4], &mut self.cpu_gov_menu, ctx);
+        cce_ui::widget::link_parent_child(&mut sec_containers[5], &mut self.gpu_gov_menu, ctx);
     }
 
     fn view(
         &mut self,
-        _cx: f32,
-        _cy: f32,
-        _cw: f32,
-        _ch: f32,
-        _root_focused: bool,
-        _sec_focused: &[bool],
-        _layout: &mut dyn LayoutStrategy,
-        _ctx: &mut cce_ui::context::UiContext,
+        cx: f32,
+        cy: f32,
+        cw: f32,
+        ch: f32,
+        root_focused: bool,
+        sec_focused: &[bool],
+        layout: &mut dyn LayoutStrategy,
+        ctx: &mut cce_ui::context::UiContext,
     ) -> crate::app::PageContent {
-        crate::app::PageContent::new()
+        // Phase 6u: System used to render through the WIDGET TREE (the only page that
+        // did) — its content reached the frame via the root aggregate walking
+        // Switcher → Page(AdaptiveGridLayout) → sections → widgets. With that chain
+        // dissolved, the page renders through the same immediate-mode view as every
+        // other page (this free `view` predates the flip; it was never wired up).
+        view(self, cx, cy, cw, ch, root_focused, sec_focused, layout, ctx)
     }
 
     fn propagate_widget_changes(&mut self, actions: &mut Vec<crate::app::AppAction>) {
diff --git a/src/renderer.rs b/src/renderer.rs
index 56a34c5..1def5fb 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -85,18 +85,8 @@ impl SystemInterface {
 
     pub(crate) fn rebuild_layout(&mut self, sw: f32, sh: f32) {
         self.ui_context.clear_hierarchy();
-        self.switcher.clear_children(&mut self.ui_context);
-        for page in &mut self.pages {
-            self.switcher.add_child(page.as_ptr(), &mut self.ui_context);
-        }
-
-        let page_idx = Page::ALL.iter().position(|&p| p == self.app.current_page).unwrap_or(0);
-
-        // ── Rebuild Element Focus Hierarchy ──
-        for page in &mut self.pages {
-            page.clear_children(&mut self.ui_context);
-        }
-        let page_root = &mut self.pages[page_idx];
+        // ── Rebuild Element Focus Hierarchy (Switcher + Page dissolved, Phase 6u:
+        // sections are the top-level dispatch/focus roots) ──
         for c in &mut self.page_sec_containers {
             c.clear_children(&mut self.ui_context);
             c.set_parent(None, &mut self.ui_context);
@@ -113,7 +103,7 @@ impl SystemInterface {
         let active_page = self.app.get_current_page_mut();
         self.page_sec_containers = active_page.get_section_containers();
 
-        active_page.link_children(page_root, &mut self.page_sec_containers, &mut self.ui_context);
+        active_page.link_children(&mut self.page_sec_containers, &mut self.ui_context);
 
         self.sidebar_width = 0.0;
         self.header_height = 0.0; // No CSD Titlebar
@@ -146,7 +136,6 @@ impl SystemInterface {
 
         let page_idx = Page::ALL.iter().position(|&p| p == self.app.current_page).unwrap_or(0);
         self.page_dropdown.selected = page_idx;
-        self.switcher.set_active_index(Some(page_idx));
 
         // Root Backplate DISSOLVED (Phase 6s): top-level widgets stay parentless
         // (render_widget registers them); the window plate, the root aggregate's
@@ -173,7 +162,19 @@ impl SystemInterface {
         } else {
             logical_sh - self.header_height - self.status_height
         };
-        cce_ui::layout::render_widget(&mut dummy_pc, &mut self.switcher, self.sidebar_width, self.header_height, logical_sw - self.sidebar_width, switcher_h, &mut self.ui_context);
+        // The page scrollbar (the only geometry the dissolved Page subtree ever emitted):
+        // placed exactly as Page::layout did, updated with LAST frame's content height —
+        // the legacy window pass also ran before this frame's content was measured.
+        self.page_scroll_bar.set_rect(
+            self.sidebar_width + (logical_sw - self.sidebar_width) - 6.0 - 2.0,
+            self.header_height + 4.0,
+            6.0,
+            switcher_h - 8.0,
+        );
+        if self.page_scroll_bar.dragging {
+            self.scroll_y = self.page_scroll_bar.scroll_y;
+        }
+        self.page_scroll_bar.update(self.scroll_y, self.content_h, switcher_h);
 
         // Assemble the window exactly as the legacy `render_widget(root Backplate)`
         // aggregate did: every child plain quad (clipped to the window, with the root's
@@ -190,7 +191,7 @@ impl SystemInterface {
             let mut rounded: Vec<RectTuple> = Vec::new();
             let mut wtexts: Vec<TextTuple> = Vec::new();
 
-            collect_window_child(&self.switcher, &self.ui_context, logical_sw, logical_sh, plate_radius, &mut plain, &mut rounded, &mut wtexts);
+            collect_window_child(&self.page_scroll_bar, &self.ui_context, logical_sw, logical_sh, plate_radius, &mut plain, &mut rounded, &mut wtexts);
 
             // The dissolved status bar's slot in the child order.
             let sb_y = logical_sh - self.status_height;
@@ -227,13 +228,6 @@ impl SystemInterface {
             window_pc.texts.extend(wtexts);
         }
 
-        let page_idx = Page::ALL.iter().position(|&p| p == self.app.current_page).unwrap_or(0);
-        let active_page_widget = &self.pages[page_idx];
-        if self.app.current_page == Page::System {
-            self.scroll_y = active_page_widget.scroll_y;
-            self.max_scroll_y = (active_page_widget.content_h - active_page_widget.base.base.h).max(0.0);
-        }
-
         let mut search_pc = PageContent::new();
         if self.search_open {
             search_pc.rects.push((
@@ -298,7 +292,7 @@ impl SystemInterface {
 
 
 
-        if self.search_open && !self.search_query.is_empty() && !self.pages[page_idx].scroll_bar.dragging {
+        if self.search_open && !self.search_query.is_empty() && !self.page_scroll_bar.dragging {
             let query_lower = self.search_query.to_lowercase();
             let mut first_match_y = None;
             for (t, _, _, y, _, _, _) in &pc.texts {
@@ -324,9 +318,6 @@ impl SystemInterface {
             }
         }
 
-        let mut popovers = Vec::new();
-        Self::collect_popover_rects(&self.pages[page_idx], &mut popovers, &self.ui_context);
-
         let mut max_y = 0.0f32;
         for (_, _, y, _, h, _, _) in &pc.rects {
             max_y = max_y.max(y + h);
@@ -348,15 +339,13 @@ impl SystemInterface {
             }
         }
 
-        let page_root = &mut self.pages[page_idx];
-        if page_root.scroll_bar.dragging {
-            self.scroll_y = page_root.scroll_bar.scroll_y;
+        if self.page_scroll_bar.dragging {
+            self.scroll_y = self.page_scroll_bar.scroll_y;
         } else {
-            page_root.scroll_y = self.scroll_y;
-            page_root.scroll_bar.scroll_y = self.scroll_y;
+            self.page_scroll_bar.scroll_y = self.scroll_y;
         }
-        page_root.content_h = max_y;
-        page_root.scroll_bar.update(self.scroll_y, max_y, lch);
+        self.content_h = max_y;
+        self.page_scroll_bar.update(self.scroll_y, max_y, lch);
 
         let scroll_offset_y = self.scroll_y;
 
@@ -675,12 +664,12 @@ impl SystemInterface {
         let cw = (cw - 2.0 * margin).max(1.0);
         let ch = (ch - 2.0 * margin).max(1.0);
         let mut layout = AdaptiveGrid::new(260.0, 20.0);
-        let page_idx = Page::ALL.iter().position(|&p| p == self.app.current_page).unwrap_or(0);
-        let root_focused = cce_ui::widget::focus::is_focused(&self.pages[page_idx]);
+        // Page root dissolved (6u): the ctrl-nav entry focuses section 0, so root focus is
+        // permanently false; views that highlighted on it OR in their first section's bool.
         let sec_focused: Vec<bool> = self.page_sec_containers.iter()
             .map(|c| cce_ui::widget::focus::is_focused(c))
             .collect();
-        self.app.get_current_page_mut().view(cx, cy, cw, ch, root_focused, &sec_focused, &mut layout, &mut self.ui_context)
+        self.app.get_current_page_mut().view(cx, cy, cw, ch, false, &sec_focused, &mut layout, &mut self.ui_context)
     }
 
 }