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

commit3bed8a4f4072f1e023bdcc67b6762b7f130225fd
parent34f1236429
authorLucas Galante <[email protected]>
date2026-07-10 09:01
feat: SectionContainer DISSOLVED (Phase 6w) — settings is embedded-base-FREE

The per-rebuild SectionContainer clones were pure event/focus plumbing (never
painted, zero rects): propagate roots whose container children were the pages'
widgets, plus the ctrl-nav focus targets. Both reduce to app state:

- AppPage::section_widgets() replaces get_section_containers + link_children +
  clear_children: one widget-pointer group per section (old count/order/link
  order). The widgets dispatch directly as propagate roots, flattened in the
  legacy order (sections last-to-first, reverse link order within).
- Section-level keyboard focus is SystemInterface::focused_section (index),
  single-slot with the global widget focus like the shared FOCUSED_WIDGET was:
  entry focuses section 0; ctrl+j/k cycle sections; ctrl+i descends to the
  section's first widget (legacy went via the header/container intermediates);
  ctrl+u ascends from a widget back to its section; a widget click that takes
  global focus drops the section highlight; page switch resets it.
- This also removes a latent use-after-free: the focused section clone was
  dropped and reallocated every rebuild while focus::FOCUSED_WIDGET kept the
  raw pointer (surviving only by same-size allocation reuse).

A/B render dumps: all nine pages byte-identical modulo live data (sections
never painted). Live-verified: notifications spinbox/menu open+select with
in-frame occlusion, audio spinbox round trip through pactl+watcher, processes
filter-box click-to-focus, services list wheel (6v regions intact).
Ctrl-nav not headlessly drivable — user spot-check pending.

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/input_handler.rs       | 125 ++++++++++++++++++++++++++++++++-------------
 src/main.rs                |   7 ++-
 src/pages/accounts.rs      |  70 +++++++------------------
 src/pages/audio.rs         |  72 ++++++--------------------
 src/pages/fonts.rs         |  68 +++---------------------
 src/pages/mod.rs           |  17 +++---
 src/pages/network.rs       |  40 ++-------------
 src/pages/notifications.rs |  39 +++-----------
 src/pages/packages.rs      |  47 ++---------------
 src/pages/processes.rs     |  37 ++------------
 src/pages/storage.rs       |  42 ++-------------
 src/pages/system_info.rs   |  38 ++++----------
 src/renderer.rs            |  28 +++-------
 13 files changed, 182 insertions(+), 448 deletions(-)

diff --git a/src/input_handler.rs b/src/input_handler.rs
index 9ec603b..83dc7a0 100644
--- a/src/input_handler.rs
+++ b/src/input_handler.rs
@@ -114,6 +114,7 @@ impl SystemInterface {
                 let idx = self.page_dropdown.selected;
                 if idx < Page::ALL.len() {
                     cce_ui::widget::focus::clear_focus();
+                    self.focused_section = None;
                     let new_page = Page::ALL[idx];
                     self.app.current_page = new_page;
                     self.current_page_shared.store(idx as u8, std::sync::atomic::Ordering::SeqCst);
@@ -166,6 +167,12 @@ impl SystemInterface {
 
         self.propagate_widget_changes(&mut actions);
 
+        // Single-slot focus (Phase 6w): if a widget click took the global focus, the
+        // section-level highlight yields — exactly as when both lived in FOCUSED_WIDGET.
+        if state == cce_ui::widget::ElementState::Pressed && cce_ui::widget::focus::has_focus() {
+            self.focused_section = None;
+        }
+
         for a in &actions {
             self.handle_action(a);
         }
@@ -248,10 +255,18 @@ impl SystemInterface {
         false
     }
 
-    /// 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.
+    /// The current page's event-dispatch roots (Phase 6w — SectionContainer dissolved):
+    /// the pages' widgets themselves, flattened in the legacy propagate order (sections
+    /// last-to-first, and within a section the container children were visited in
+    /// reverse link order).
     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()
+        self.app
+            .get_current_page_mut()
+            .section_widgets()
+            .into_iter()
+            .rev()
+            .flat_map(|group| group.into_iter().rev())
+            .collect()
     }
 
     /// Replicates the dissolved Page's event routing: the out-of-bounds gate (events whose
@@ -322,7 +337,7 @@ impl SystemInterface {
         }
 
         let roots = self.page_dispatch_roots();
-        for root in roots.into_iter().rev() {
+        for root in roots {
             if self.ui_context.propagate_event(event, root) {
                 if !is_pointer_move {
                     return true;
@@ -383,54 +398,92 @@ impl SystemInterface {
         }
 
         if event.state == cce_ui::widget::ElementState::Pressed && !event.repeat {
-            let is_nav_key = match (&event.logical_key, event.ctrl) {
-                (cce_ui::widget::Key::Character(c), true) if c == "j" || c == "J" || c == "k" || c == "K" || c == "u" || c == "U" || c == "i" || c == "I" => true,
-                _ => false,
+            let (forward, backward, ascend, descend) = match (&event.logical_key, event.ctrl) {
+                (cce_ui::widget::Key::Character(c), true) => (
+                    c == "j" || c == "J",
+                    c == "k" || c == "K",
+                    c == "u" || c == "U",
+                    c == "i" || c == "I",
+                ),
+                _ => (false, false, false, false),
             };
-            if is_nav_key {
+            if forward || backward || ascend || descend {
+                // SectionContainer dissolved (Phase 6w): section-level focus is the
+                // app-side index, widget-level focus stays in the global focus module,
+                // and the two are single-slot (as when sections and widgets shared the
+                // one FOCUSED_WIDGET). Nav within a section walks the page's widget
+                // group where the container's child list used to be walked.
                 if cce_ui::widget::focus::has_focus() {
+                    // Widget-internal nav first (ctrl+i descend into a widget's own
+                    // children still works through the pointer walk).
                     if cce_ui::widget::focus::navigate_focus(&event.logical_key, event.ctrl) {
                         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";
+                    let groups = self.app.get_current_page_mut().section_widgets();
+                    let focused_pos = groups.iter().enumerate().find_map(|(si, g)| {
+                        g.iter()
+                            .position(|&w| unsafe { cce_ui::widget::focus::is_focused(&*w) })
+                            .map(|wi| (si, wi))
+                    });
+                    if let Some((si, wi)) = focused_pos {
+                        if forward || backward {
+                            let group = &groups[si];
+                            let next = if forward {
+                                (wi + 1) % group.len()
+                            } else if wi == 0 {
+                                group.len() - 1
+                            } else {
+                                wi - 1
+                            };
+                            let next_ptr = group[next];
+                            unsafe {
+                                let w = &mut *next_ptr;
+                                cce_ui::widget::focus::set_focused(w);
+                                w.focus();
+                            }
+                            self.needs_rebuild = true;
+                            return true;
+                        }
+                        if ascend {
+                            cce_ui::widget::focus::clear_focus();
+                            self.focused_section = Some(si);
+                            self.needs_rebuild = true;
+                            return true;
+                        }
+                    }
+                } else if let Some(idx) = self.focused_section {
+                    let groups = self.app.get_current_page_mut().section_widgets();
+                    if !groups.is_empty() {
+                        let idx = idx.min(groups.len() - 1);
                         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)
+                            self.focused_section = Some(if forward {
+                                (idx + 1) % groups.len()
+                            } else if idx == 0 {
+                                groups.len() - 1
+                            } else {
+                                idx - 1
                             });
-                            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
-                                };
+                            self.needs_rebuild = true;
+                            return true;
+                        }
+                        if descend {
+                            if let Some(&first) = groups[idx].first() {
                                 unsafe {
-                                    let sec = &mut *roots[next];
-                                    cce_ui::widget::focus::set_focused(sec);
-                                    sec.focus();
+                                    let w = &mut *first;
+                                    cce_ui::widget::focus::set_focused(w);
+                                    w.focus();
                                 }
+                                self.focused_section = None;
                                 self.needs_rebuild = true;
                                 return true;
                             }
                         }
                     }
                 } else {
-                    // 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 sec = &mut *first;
-                            cce_ui::widget::focus::set_focused(sec);
-                            sec.focus();
-                        }
+                    // Entry point: focus the first section.
+                    if !self.app.get_current_page_mut().section_widgets().is_empty() {
+                        self.focused_section = Some(0);
                         self.needs_rebuild = true;
                         return true;
                     }
diff --git a/src/main.rs b/src/main.rs
index 2b00342..cc1bc3e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -78,7 +78,10 @@ struct SystemInterface {
     scrollable_text_items_start_idx: usize,
     scrollable_buttons_start_idx: usize,
     last_scroll_y: f32,
-    page_sec_containers: Vec<cce_ui::widget::SectionContainer>,
+    // SectionContainer DISSOLVED (Phase 6w): section-level keyboard focus is this index
+    // (single-slot with the global widget focus — descending clears it); the per-section
+    // widget groups come from AppPage::section_widgets each time they're needed.
+    focused_section: Option<usize>,
     page_dropdown: cce_ui::widget::Adapted<cce_ui::widget::input::Dropdown>,
     // 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
@@ -171,7 +174,7 @@ impl cce_ui::engine::Application for SystemInterface {
             scrollable_text_items_start_idx: 0,
             scrollable_buttons_start_idx: 0,
             last_scroll_y: 0.0,
-            page_sec_containers: Vec::new(),
+            focused_section: None,
             page_dropdown,
             page_scroll_bar: cce_ui::widget::ScrollBar::new(),
             content_h: 0.0,
diff --git a/src/pages/accounts.rs b/src/pages/accounts.rs
index f9f2643..acf59aa 100644
--- a/src/pages/accounts.rs
+++ b/src/pages/accounts.rs
@@ -823,59 +823,25 @@ pub fn update(state: &mut AccountsState, msg: AccountsMessage) {
 }
 
 impl crate::pages::AppPage for AccountsState {
-    fn clear_children(&mut self, ctx: &mut cce_ui::context::UiContext) {
-        self.email_box.clear_children(ctx);
-        self.email_box.set_parent(None, ctx);
-        self.password_box.clear_children(ctx);
-        self.password_box.set_parent(None, ctx);
-        self.imap_box.clear_children(ctx);
-        self.imap_box.set_parent(None, ctx);
-        self.smtp_box.clear_children(ctx);
-        self.smtp_box.set_parent(None, ctx);
-        self.oauth_client_id_box.clear_children(ctx);
-        self.oauth_client_id_box.set_parent(None, ctx);
-        self.oauth_client_secret_box.clear_children(ctx);
-        self.oauth_client_secret_box.set_parent(None, ctx);
-    }
-
-    fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
-        vec![
-            cce_ui::widget::SectionContainer::new("Accounts")
-                .with_draw_children(false)
-                .with_layout(cce_ui::widget::AdaptiveGridLayout {
-                    min_col_width: 140.0,
-                    gap: 8.0,
-                    padding_x: 0.0,
-                    padding_y: 0.0,
-                    grid: None,
-                }),
-            cce_ui::widget::SectionContainer::new("Modify Accounts")
-                .with_draw_children(false)
-                .with_layout(cce_ui::widget::AdaptiveGridLayout {
-                    min_col_width: 140.0,
-                    gap: 8.0,
-                    padding_x: 0.0,
-                    padding_y: 0.0,
-                    grid: None,
-                }),
-        ]
-    }
-
-    fn link_children(
-        &mut self,
-        sec_containers: &mut [cce_ui::widget::SectionContainer],
-        ctx: &mut cce_ui::context::UiContext,
-    ) {
-
-        if self.editing_oauth_creds {
-            cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.oauth_client_id_box, ctx);
-            cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.oauth_client_secret_box, ctx);
+    // Sections: [Accounts, Modify Accounts] — the modify group depends on the mode.
+    fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn cce_ui::widget::Element + 'static)>> {
+        use cce_ui::widget::Element;
+        let modify: Vec<*mut (dyn Element + 'static)> = if self.editing_oauth_creds {
+            vec![
+                self.oauth_client_id_box.as_ptr_mut(),
+                self.oauth_client_secret_box.as_ptr_mut(),
+            ]
         } else if self.adding_new {
-            cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.email_box, ctx);
-            cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.password_box, ctx);
-            cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.imap_box, ctx);
-            cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.smtp_box, ctx);
-        }
+            vec![
+                self.email_box.as_ptr_mut(),
+                self.password_box.as_ptr_mut(),
+                self.imap_box.as_ptr_mut(),
+                self.smtp_box.as_ptr_mut(),
+            ]
+        } else {
+            Vec::new()
+        };
+        vec![Vec::new(), modify]
     }
 
     fn view(
diff --git a/src/pages/audio.rs b/src/pages/audio.rs
index 4ff7733..4c7fef6 100644
--- a/src/pages/audio.rs
+++ b/src/pages/audio.rs
@@ -407,75 +407,33 @@ pub fn update(state: &mut AudioState, msg: AudioMessage) {
 }
 
 impl crate::pages::AppPage for AudioState {
-    fn clear_children(&mut self, ctx: &mut cce_ui::context::UiContext) {
-        for sb in &mut self.sink_spinboxes {
-            sb.clear_children(ctx);
-            sb.set_parent(None, ctx);
-        }
-        for sb in &mut self.source_spinboxes {
-            sb.clear_children(ctx);
-            sb.set_parent(None, ctx);
-        }
-        for slider in &mut self.sink_sliders {
-            slider.clear_children(ctx);
-            slider.set_parent(None, ctx);
-        }
-        for slider in &mut self.source_sliders {
-            slider.clear_children(ctx);
-            slider.set_parent(None, ctx);
-        }
-    }
-
-    fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
-        vec![
-            cce_ui::widget::SectionContainer::new("Output")
-                .with_layout(cce_ui::widget::AdaptiveGridLayout {
-                    min_col_width: 140.0,
-                    gap: 8.0,
-                    padding_x: 0.0,
-                    padding_y: 0.0,
-                    grid: None,
-                })
-                .with_draw_children(false),
-            cce_ui::widget::SectionContainer::new("Input")
-                .with_layout(cce_ui::widget::AdaptiveGridLayout {
-                    min_col_width: 140.0,
-                    gap: 8.0,
-                    padding_x: 0.0,
-                    padding_y: 0.0,
-                    grid: None,
-                })
-                .with_draw_children(false),
-        ]
-    }
-
-    fn link_children(
-        &mut self,
-        sec_containers: &mut [cce_ui::widget::SectionContainer],
-        ctx: &mut cce_ui::context::UiContext,
-    ) {
-
+    // Sections: [Output, Input] — only active devices' controls, spinbox before slider
+    // per device (the old link order).
+    fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn cce_ui::widget::Element + 'static)>> {
+        use cce_ui::widget::Element;
+        let mut output: Vec<*mut (dyn Element + 'static)> = Vec::new();
         for (i, sink) in self.sinks.iter().enumerate() {
             if sink.active {
-                if i < self.sink_spinboxes.len() {
-                    cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut *self.sink_spinboxes[i], ctx);
+                if let Some(sb) = self.sink_spinboxes.get_mut(i) {
+                    output.push(sb.as_ptr_mut());
                 }
-                if i < self.sink_sliders.len() {
-                    cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut *self.sink_sliders[i], ctx);
+                if let Some(sl) = self.sink_sliders.get_mut(i) {
+                    output.push(sl.as_ptr_mut());
                 }
             }
         }
-
+        let mut input: Vec<*mut (dyn Element + 'static)> = Vec::new();
         for (i, src) in self.sources.iter().enumerate() {
             if src.active {
-                if i < self.source_spinboxes.len() {
-                    cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut *self.source_spinboxes[i], ctx);
+                if let Some(sb) = self.source_spinboxes.get_mut(i) {
+                    input.push(sb.as_ptr_mut());
                 }
-                if i < self.source_sliders.len() {
-                    cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut *self.source_sliders[i], ctx);
+                if let Some(sl) = self.source_sliders.get_mut(i) {
+                    input.push(sl.as_ptr_mut());
                 }
             }
         }
+        vec![output, input]
     }
 
     fn view(
diff --git a/src/pages/fonts.rs b/src/pages/fonts.rs
index 87a692f..de53a04 100644
--- a/src/pages/fonts.rs
+++ b/src/pages/fonts.rs
@@ -97,70 +97,18 @@ impl Default for FontsState {
 }
 
 impl AppPage for FontsState {
-    fn clear_children(&mut self, ctx: &mut cce_ui::context::UiContext) {
-        self.sans_box.clear_children(ctx);
-        self.sans_box.set_parent(None, ctx);
-        self.serif_box.clear_children(ctx);
-        self.serif_box.set_parent(None, ctx);
-        self.mono_box.clear_children(ctx);
-        self.mono_box.set_parent(None, ctx);
-        self.borders_box.clear_children(ctx);
-        self.borders_box.set_parent(None, ctx);
-        self.status_box.clear_children(ctx);
-        self.status_box.set_parent(None, ctx);
-        self.fuzzel_box.clear_children(ctx);
-        self.fuzzel_box.set_parent(None, ctx);
-        self.terminal_box.clear_children(ctx);
-        self.terminal_box.set_parent(None, ctx);
-        self.borders_menu.clear_children(ctx);
-        self.borders_menu.set_parent(None, ctx);
-        self.status_menu.clear_children(ctx);
-        self.status_menu.set_parent(None, ctx);
-        self.fuzzel_menu.clear_children(ctx);
-        self.fuzzel_menu.set_parent(None, ctx);
-        self.terminal_menu.clear_children(ctx);
-        self.terminal_menu.set_parent(None, ctx);
-        self.fuzzel_size_box.clear_children(ctx);
-        self.fuzzel_size_box.set_parent(None, ctx);
-        self.terminal_size_box.clear_children(ctx);
-        self.terminal_size_box.set_parent(None, ctx);
-    }
-
-    fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
+    // Sections: [Preferred Fonts, Borders, Status Interface, Fuzzel, Terminal]
+    fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn cce_ui::widget::Element + 'static)>> {
+        use cce_ui::widget::Element;
         vec![
-            cce_ui::widget::SectionContainer::new("Preferred Fonts").with_draw_children(false),
-            cce_ui::widget::SectionContainer::new("Borders").with_draw_children(false),
-            cce_ui::widget::SectionContainer::new("Status Interface").with_draw_children(false),
-            cce_ui::widget::SectionContainer::new("Fuzzel").with_draw_children(false),
-            cce_ui::widget::SectionContainer::new("Terminal").with_draw_children(false),
+            vec![self.sans_box.as_ptr_mut(), self.serif_box.as_ptr_mut(), self.mono_box.as_ptr_mut()],
+            vec![self.borders_menu.as_ptr_mut(), self.borders_box.as_ptr_mut()],
+            vec![self.status_menu.as_ptr_mut(), self.status_box.as_ptr_mut()],
+            vec![self.fuzzel_menu.as_ptr_mut(), self.fuzzel_box.as_ptr_mut(), self.fuzzel_size_box.as_ptr_mut()],
+            vec![self.terminal_menu.as_ptr_mut(), self.terminal_box.as_ptr_mut(), self.terminal_size_box.as_ptr_mut()],
         ]
     }
 
-    fn link_children(
-        &mut self,
-        sec_containers: &mut [cce_ui::widget::SectionContainer],
-        ctx: &mut cce_ui::context::UiContext,
-    ) {
-        
-        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);
-        cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.mono_box, ctx);
-
-        cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.borders_menu, ctx);
-        cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.borders_box, ctx);
-
-        cce_ui::widget::link_parent_child(&mut sec_containers[2], &mut self.status_menu, ctx);
-        cce_ui::widget::link_parent_child(&mut sec_containers[2], &mut self.status_box, ctx);
-
-        cce_ui::widget::link_parent_child(&mut sec_containers[3], &mut self.fuzzel_menu, ctx);
-        cce_ui::widget::link_parent_child(&mut sec_containers[3], &mut self.fuzzel_box, ctx);
-        cce_ui::widget::link_parent_child(&mut sec_containers[3], &mut self.fuzzel_size_box, ctx);
-
-        cce_ui::widget::link_parent_child(&mut sec_containers[4], &mut self.terminal_menu, ctx);
-        cce_ui::widget::link_parent_child(&mut sec_containers[4], &mut self.terminal_box, ctx);
-        cce_ui::widget::link_parent_child(&mut sec_containers[4], &mut self.terminal_size_box, ctx);
-    }
-
     fn view(
         &mut self,
         cx: f32,
diff --git a/src/pages/mod.rs b/src/pages/mod.rs
index 684c14f..31f8ef1 100644
--- a/src/pages/mod.rs
+++ b/src/pages/mod.rs
@@ -59,17 +59,12 @@ impl Page {
 }
 
 pub trait AppPage {
-    fn clear_children(&mut self, ctx: &mut cce_ui::context::UiContext);
-
-    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,
-        sec_containers: &mut [cce_ui::widget::SectionContainer],
-        ctx: &mut cce_ui::context::UiContext,
-    );
+    /// Per-section event/nav widget groups (Phase 6w: SectionContainer dissolved). One
+    /// inner Vec per section — same count and order as the old section containers (the
+    /// outer length drives the `sec_focused` flags) — holding the widgets that used to
+    /// hang under that section's container, in the old link order. The widgets dispatch
+    /// directly as propagate roots and the groups drive the app-side ctrl-nav.
+    fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn cce_ui::widget::Element + 'static)>>;
 
     fn view(
         &mut self,
diff --git a/src/pages/network.rs b/src/pages/network.rs
index 2a6dbca..164f215 100644
--- a/src/pages/network.rs
+++ b/src/pages/network.rs
@@ -525,46 +525,14 @@ impl NetworkState {
 }
 
 impl crate::pages::AppPage for NetworkState {
-    fn clear_children(&mut self, ctx: &mut cce_ui::context::UiContext) {
-        self.wifi_toggle.clear_children(ctx);
-        self.wifi_toggle.set_parent(None, ctx);
-        self.bt_toggle.clear_children(ctx);
-        self.bt_toggle.set_parent(None, ctx);
-    }
-
-    fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
+    // Sections: [WiFi, Bluetooth]
+    fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn Element + 'static)>> {
         vec![
-            cce_ui::widget::SectionContainer::new("WiFi")
-                .with_draw_children(false)
-                .with_layout(cce_ui::widget::AdaptiveGridLayout {
-                    min_col_width: 140.0,
-                    gap: 8.0,
-                    padding_x: 0.0,
-                    padding_y: 0.0,
-                    grid: None,
-                }),
-            cce_ui::widget::SectionContainer::new("Bluetooth")
-                .with_draw_children(false)
-                .with_layout(cce_ui::widget::AdaptiveGridLayout {
-                    min_col_width: 140.0,
-                    gap: 8.0,
-                    padding_x: 0.0,
-                    padding_y: 0.0,
-                    grid: None,
-                }),
+            vec![self.wifi_toggle.as_ptr_mut()],
+            vec![self.bt_toggle.as_ptr_mut()],
         ]
     }
 
-    fn link_children(
-        &mut self,
-        sec_containers: &mut [cce_ui::widget::SectionContainer],
-        ctx: &mut cce_ui::context::UiContext,
-    ) {
-
-        cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.wifi_toggle, ctx);
-        cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.bt_toggle, ctx);
-    }
-
     fn view(
         &mut self,
         cx: f32,
diff --git a/src/pages/notifications.rs b/src/pages/notifications.rs
index 8697698..3d83dcd 100644
--- a/src/pages/notifications.rs
+++ b/src/pages/notifications.rs
@@ -184,37 +184,14 @@ fn get_config_path() -> String {
 }
 
 impl AppPage for NotificationsState {
-    fn clear_children(&mut self, ctx: &mut cce_ui::context::UiContext) {
-        self.enable_toggle.clear_children(ctx);
-        self.enable_toggle.set_parent(None, ctx);
-        self.bell_menu.clear_children(ctx);
-        self.bell_menu.set_parent(None, ctx);
-        self.duration_spinbox.clear_children(ctx);
-        self.duration_spinbox.set_parent(None, ctx);
-    }
-
-    fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
-        vec![
-            cce_ui::widget::SectionContainer::new("Notifications Settings")
-                .with_draw_children(false)
-                .with_layout(cce_ui::widget::AdaptiveGridLayout {
-                    min_col_width: 140.0,
-                    gap: 8.0,
-                    padding_x: 0.0,
-                    padding_y: 0.0,
-                    grid: None,
-                }),
-        ]
-    }
-
-    fn link_children(
-        &mut self,
-        sec_containers: &mut [cce_ui::widget::SectionContainer],
-        ctx: &mut cce_ui::context::UiContext,
-    ) {
-        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);
+    // Sections: [Notifications Settings]
+    fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn cce_ui::widget::Element + 'static)>> {
+        use cce_ui::widget::Element;
+        vec![vec![
+            self.enable_toggle.as_ptr_mut(),
+            self.bell_menu.as_ptr_mut(),
+            self.duration_spinbox.as_ptr_mut(),
+        ]]
     }
 
     fn view(
diff --git a/src/pages/packages.rs b/src/pages/packages.rs
index 9761917..13c6daa 100644
--- a/src/pages/packages.rs
+++ b/src/pages/packages.rs
@@ -718,52 +718,15 @@ impl PackagesState {
 }
 
 impl crate::pages::AppPage for PackagesState {
-    fn clear_children(&mut self, ctx: &mut cce_ui::context::UiContext) {
-        self.search_box.clear_children(ctx);
-        self.search_box.set_parent(None, ctx);
-    }
-
-    fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
+    // Sections: [Packages, Package Info, System Update]
+    fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn Element + 'static)>> {
         vec![
-            cce_ui::widget::SectionContainer::new("Packages")
-                .with_draw_children(false)
-                .with_layout(cce_ui::widget::AdaptiveGridLayout {
-                    min_col_width: 140.0,
-                    gap: 8.0,
-                    padding_x: 0.0,
-                    padding_y: 0.0,
-                    grid: None,
-                }),
-            cce_ui::widget::SectionContainer::new("Package Info")
-                .with_draw_children(false)
-                .with_layout(cce_ui::widget::AdaptiveGridLayout {
-                    min_col_width: 140.0,
-                    gap: 8.0,
-                    padding_x: 0.0,
-                    padding_y: 0.0,
-                    grid: None,
-                }),
-            cce_ui::widget::SectionContainer::new("System Update")
-                .with_draw_children(false)
-                .with_layout(cce_ui::widget::AdaptiveGridLayout {
-                    min_col_width: 140.0,
-                    gap: 8.0,
-                    padding_x: 0.0,
-                    padding_y: 0.0,
-                    grid: None,
-                }),
+            vec![self.search_box.as_ptr_mut()],
+            Vec::new(),
+            Vec::new(),
         ]
     }
 
-    fn link_children(
-        &mut self,
-        sec_containers: &mut [cce_ui::widget::SectionContainer],
-        ctx: &mut cce_ui::context::UiContext,
-    ) {
-
-        cce_ui::widget::link_parent_child(&mut sec_containers[0], &mut self.search_box, ctx);
-    }
-
     fn view(
         &mut self,
         cx: f32,
diff --git a/src/pages/processes.rs b/src/pages/processes.rs
index 92f5456..352445f 100644
--- a/src/pages/processes.rs
+++ b/src/pages/processes.rs
@@ -481,43 +481,14 @@ fn service_action(name: &str, action: &str, is_system: bool) {
 }
 
 impl crate::pages::AppPage for ProcessesState {
-    fn clear_children(&mut self, ctx: &mut cce_ui::context::UiContext) {
-        self.services_search_box.clear_children(ctx);
-        self.services_search_box.set_parent(None, ctx);
-    }
-
-    fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
+    // Sections: [Processes, Services]
+    fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn Element + 'static)>> {
         vec![
-            cce_ui::widget::SectionContainer::new("Processes")
-                .with_draw_children(false)
-                .with_layout(cce_ui::widget::AdaptiveGridLayout {
-                    min_col_width: 140.0,
-                    gap: 8.0,
-                    padding_x: 0.0,
-                    padding_y: 0.0,
-                    grid: None,
-                }),
-            cce_ui::widget::SectionContainer::new("Services")
-                .with_draw_children(false)
-                .with_layout(cce_ui::widget::AdaptiveGridLayout {
-                    min_col_width: 140.0,
-                    gap: 8.0,
-                    padding_x: 0.0,
-                    padding_y: 0.0,
-                    grid: None,
-                }),
+            Vec::new(),
+            vec![self.services_search_box.as_ptr_mut()],
         ]
     }
 
-    fn link_children(
-        &mut self,
-        sec_containers: &mut [cce_ui::widget::SectionContainer],
-        ctx: &mut cce_ui::context::UiContext,
-    ) {
-
-        cce_ui::widget::link_parent_child(&mut sec_containers[1], &mut self.services_search_box, ctx);
-    }
-
     fn view(
         &mut self,
         cx: f32,
diff --git a/src/pages/storage.rs b/src/pages/storage.rs
index 96a8034..ef81d1d 100644
--- a/src/pages/storage.rs
+++ b/src/pages/storage.rs
@@ -309,45 +309,9 @@ pub fn update(state: &mut StorageState, msg: StorageMessage) {
 }
 
 impl crate::pages::AppPage for StorageState {
-    fn clear_children(&mut self, _ctx: &mut cce_ui::context::UiContext) {}
-
-    fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
-        vec![
-            cce_ui::widget::SectionContainer::new("Local Storage")
-                .with_draw_children(false)
-                .with_layout(cce_ui::widget::AdaptiveGridLayout {
-                    min_col_width: 140.0,
-                    gap: 8.0,
-                    padding_x: 0.0,
-                    padding_y: 0.0,
-                    grid: None,
-                }),
-            cce_ui::widget::SectionContainer::new("Memory")
-                .with_draw_children(false)
-                .with_layout(cce_ui::widget::AdaptiveGridLayout {
-                    min_col_width: 140.0,
-                    gap: 8.0,
-                    padding_x: 0.0,
-                    padding_y: 0.0,
-                    grid: None,
-                }),
-            cce_ui::widget::SectionContainer::new("Full System Backup")
-                .with_draw_children(false)
-                .with_layout(cce_ui::widget::AdaptiveGridLayout {
-                    min_col_width: 140.0,
-                    gap: 8.0,
-                    padding_x: 0.0,
-                    padding_y: 0.0,
-                    grid: None,
-                }),
-        ]
-    }
-
-    fn link_children(
-        &mut self,
-        _sec_containers: &mut [cce_ui::widget::SectionContainer],
-        _ctx: &mut cce_ui::context::UiContext,
-    ) {
+    // Sections: [Local Storage, Memory, Full System Backup] — no evented widgets.
+    fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn cce_ui::widget::Element + 'static)>> {
+        vec![Vec::new(), Vec::new(), Vec::new()]
     }
 
     fn view(
diff --git a/src/pages/system_info.rs b/src/pages/system_info.rs
index 98af427..75102f8 100644
--- a/src/pages/system_info.rs
+++ b/src/pages/system_info.rs
@@ -894,38 +894,20 @@ pub fn update(state: &mut SystemState, msg: SystemMessage, ctx: &mut cce_ui::con
 
 
 impl crate::pages::AppPage for SystemState {
-    fn clear_children(&mut self, ctx: &mut cce_ui::context::UiContext) {
-        self.cpu_gov_menu.clear_children(ctx);
-        self.cpu_gov_menu.set_parent(None, ctx);
-        self.gpu_gov_menu.clear_children(ctx);
-        self.gpu_gov_menu.set_parent(None, ctx);
-    }
-
-    fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
+    // Sections: [System, System Actions, CPU, GPU, CPU Governor, GPU Power, Battery]
+    fn section_widgets(&mut self) -> Vec<Vec<*mut (dyn cce_ui::widget::Element + 'static)>> {
+        use cce_ui::widget::Element;
         vec![
-            cce_ui::widget::SectionContainer::new("System").with_layout(cce_ui::widget::AdaptiveGridLayout { min_col_width: 140.0, gap: 8.0, padding_x: 0.0, padding_y: 0.0, grid: None }),
-            cce_ui::widget::SectionContainer::new("System Actions").with_layout(cce_ui::widget::AdaptiveGridLayout { min_col_width: 140.0, gap: 8.0, padding_x: 0.0, padding_y: 0.0, grid: None }),
-            cce_ui::widget::SectionContainer::new("CPU").with_layout(cce_ui::widget::AdaptiveGridLayout { min_col_width: 140.0, gap: 8.0, padding_x: 0.0, padding_y: 0.0, grid: None }),
-            cce_ui::widget::SectionContainer::new("GPU").with_layout(cce_ui::widget::AdaptiveGridLayout { min_col_width: 140.0, gap: 8.0, padding_x: 0.0, padding_y: 0.0, grid: None }),
-            cce_ui::widget::SectionContainer::new("CPU Governor").with_layout(cce_ui::widget::AdaptiveGridLayout { min_col_width: 140.0, gap: 8.0, padding_x: 0.0, padding_y: 0.0, grid: None }),
-            cce_ui::widget::SectionContainer::new("GPU Power").with_layout(cce_ui::widget::AdaptiveGridLayout { min_col_width: 140.0, gap: 8.0, padding_x: 0.0, padding_y: 0.0, grid: None }),
-            cce_ui::widget::SectionContainer::new("Battery").with_layout(cce_ui::widget::AdaptiveGridLayout { min_col_width: 140.0, gap: 8.0, padding_x: 0.0, padding_y: 0.0, grid: None }),
+            Vec::new(),
+            Vec::new(),
+            Vec::new(),
+            Vec::new(),
+            vec![self.cpu_gov_menu.as_ptr_mut()],
+            vec![self.gpu_gov_menu.as_ptr_mut()],
+            Vec::new(),
         ]
     }
 
-    fn link_children(
-        &mut self,
-        sec_containers: &mut [cce_ui::widget::SectionContainer],
-        ctx: &mut cce_ui::context::UiContext,
-    ) {
-        // 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,
diff --git a/src/renderer.rs b/src/renderer.rs
index fbd8d28..c93dc06 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -84,26 +84,10 @@ fn collect_window_child(
 impl SystemInterface {
 
     pub(crate) fn rebuild_layout(&mut self, sw: f32, sh: f32) {
+        // SectionContainer dissolved (Phase 6w): no per-rebuild section clones to
+        // relink — the page's widgets dispatch directly (registration happens in
+        // render_widget during the view pass below).
         self.ui_context.clear_hierarchy();
-        // ── 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);
-        }
-
-        // Clear all widgets' hierarchy links
-        self.search_box.clear_children(&mut self.ui_context);
-        self.search_box.set_parent(None, &mut self.ui_context);
-
-        for p in Page::ALL {
-            self.app.get_page_mut(p).clear_children(&mut self.ui_context);
-        }
-
-        let active_page = self.app.get_current_page_mut();
-        self.page_sec_containers = active_page.get_section_containers();
-
-        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
@@ -664,8 +648,10 @@ impl SystemInterface {
         let mut layout = AdaptiveGrid::new(260.0, 20.0);
         // 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))
+        // Section focus is the app-side index now (Phase 6w).
+        let n_sections = self.app.get_current_page_mut().section_widgets().len();
+        let sec_focused: Vec<bool> = (0..n_sections)
+            .map(|i| self.focused_section == Some(i))
             .collect();
         self.app.get_current_page_mut().view(cx, cy, cw, ch, false, &sec_focused, &mut layout, &mut self.ui_context)
     }