git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commit662c5b2e345014b8fcc28438d0f61862742137b3
parent8e83207876
authorLucas Galante <[email protected]>
date2026-07-12 01:19
refactor(widget)!: DELETE the as_*_controller capability system (Phase 6aw)

Option 2 executed: capability discovery dies with its callers retyped.
Deleted in one motion, since nothing outside them remains:
- Element's 12 as_*_controller/as_page_selector discovery hooks (the
  dynamic "are you an X?" queries over dyn Element),
- Adapted's 12 forwarding impls,
- the 14 Input capability hooks (menu_controller/page_selector/...)
  and their per-widget impls in Breadcrumb, Spreadsheet, Node,
  Paginator, Graph, ContentBg, MenuBar, ParametersBg — they existed
  only to feed the Adapted forwards.

serialize_widgets' menu-state dump (the one cce-ui production caller)
now downcasts to the two MenuController implementors a roster can hold
(MenuBar / Paginator) via as_any — which exposes the INNER widget of an
adapter, so the controller traits are reached directly on it. In-file
tests rewritten to call the controller traits through the concrete
adapters by deref. Element: 104 -> 92 methods.

168 tests pass. A/B on the live compositor: designer static diff = the
known 2px status sliver; TI sub-threshold; designer HTTP menu_click
(the retyped dynamic-index path) verified live — View > Circular Pane
toggles the layout.

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

 src/widget/container/breadcrumb.rs    |  17 ++---
 src/widget/container/content_bg.rs    |   6 --
 src/widget/container/menu.rs          |  32 ++--------
 src/widget/container/paginator.rs     |  28 ++------
 src/widget/container/parameters_bg.rs |  26 ++------
 src/widget/container/spreadsheet.rs   |  11 +---
 src/widget/display/graph.rs           |  12 +---
 src/widget/display/node.rs            |  22 +------
 src/widget/display/serialize.rs       |  10 ++-
 src/widget/mod.rs                     |  14 +---
 src/widget/model.rs                   | 116 ++++------------------------------
 11 files changed, 48 insertions(+), 246 deletions(-)

diff --git a/src/widget/container/breadcrumb.rs b/src/widget/container/breadcrumb.rs
index 38dd772..372c133 100644
--- a/src/widget/container/breadcrumb.rs
+++ b/src/widget/container/breadcrumb.rs
@@ -1,6 +1,6 @@
-//! Narrow-trait `Breadcrumb` (Phase 5k) — the first controller widget across: it re-exposes its
-//! [`PathController`] impl through the `Input` capability hooks, so the legacy
-//! `Element::as_path_controller` downcasts (cce-designer's `path_mut`) keep working. Segment
+//! Narrow-trait `Breadcrumb` (Phase 5k) — the first controller widget across: its
+//! [`PathController`] impl is reached through the concrete `Adapted<Breadcrumb>` by deref
+//! (cce-designer's `path_mut` downcasts the roster entry; Phase 6aw). Segment
 //! geometry (hit zones, hover overlay, per-segment text) is derived from the paint rect in one
 //! place; the right-press records the clicked segment *before* opening the shared context menu
 //! via [`EventCtx::open_context_menu`], so the menu header shows that segment's path.
@@ -184,12 +184,6 @@ impl Input for Breadcrumb {
         }
     }
 
-    fn path_controller(&self) -> Option<&dyn PathController> {
-        Some(self)
-    }
-    fn path_controller_mut(&mut self) -> Option<&mut dyn PathController> {
-        Some(self)
-    }
 
     fn copy_path(&self) {
         let idx = self.right_clicked_seg.unwrap_or(self.path.len());
@@ -270,10 +264,7 @@ mod tests {
     #[test]
     fn path_controller_reachable_through_element() {
         let mut breadcrumb = Breadcrumb::new();
-        let elem: &mut dyn Element = &mut breadcrumb;
-        elem.as_path_controller_mut()
-            .expect("Breadcrumb exposes PathController through the adapter")
-            .set_path(&["a".to_string()]);
+        PathController::set_path(&mut *breadcrumb, &["a".to_string()]);
         assert_eq!(breadcrumb.path, vec!["a".to_string()]);
     }
 }
diff --git a/src/widget/container/content_bg.rs b/src/widget/container/content_bg.rs
index 49949d4..1f61832 100644
--- a/src/widget/container/content_bg.rs
+++ b/src/widget/container/content_bg.rs
@@ -33,12 +33,6 @@ impl Input for ContentBg {
         false
     }
 
-    fn graph_controller(&self) -> Option<&dyn GraphController> {
-        Some(self)
-    }
-    fn graph_controller_mut(&mut self) -> Option<&mut dyn GraphController> {
-        Some(self)
-    }
 }
 
 impl Paint for ContentBg {
diff --git a/src/widget/container/menu.rs b/src/widget/container/menu.rs
index d0d9043..110a71e 100644
--- a/src/widget/container/menu.rs
+++ b/src/widget/container/menu.rs
@@ -947,18 +947,6 @@ impl Input for MenuBar {
         }
     }
 
-    fn menu_controller(&self) -> Option<&dyn MenuController> {
-        Some(self)
-    }
-    fn menu_controller_mut(&mut self) -> Option<&mut dyn MenuController> {
-        Some(self)
-    }
-    fn page_selector(&self) -> Option<&dyn PageSelector> {
-        Some(self)
-    }
-    fn page_selector_mut(&mut self) -> Option<&mut dyn PageSelector> {
-        Some(self)
-    }
 }
 
 impl MenuController for MenuBar {
@@ -1121,23 +1109,18 @@ mod tests {
         assert!(bw > 0.0, "strip laid out");
         assert!(mb.mouse_input(MouseButton::Left, ElementState::Pressed, bx + bw / 2.0, by + bh / 2.0, &mut ctx));
         assert!(mb.mouse_input(MouseButton::Left, ElementState::Released, bx + bw / 2.0, by + bh / 2.0, &mut ctx));
-        let elem: &dyn Element = &mb;
-        assert!(elem.as_menu_controller().unwrap().is_menu_open(), "dropdown open");
+        assert!(MenuController::is_menu_open(&*mb), "dropdown open");
         assert!(Element::focused(&mb, &ctx), "bar holds focus while open");
         let (dx, dy, _, _) = Element::popover_rect(&mb).expect("dropdown popover");
 
         // Click the second item ("Save"): menu_click reports (0, 1) and everything closes.
         assert!(mb.mouse_input(MouseButton::Left, ElementState::Pressed, dx + 10.0, dy + DROPDOWN_ITEM_H * 1.5, &mut ctx));
-        {
-            let elem: &mut dyn Element = &mut mb;
-            assert_eq!(elem.as_menu_controller_mut().unwrap().menu_click(), Some((0, 1)));
-            assert!(!elem.as_menu_controller().unwrap().is_menu_open());
-        }
+        assert_eq!(MenuController::menu_click(&mut *mb), Some((0, 1)));
+        assert!(!MenuController::is_menu_open(&*mb));
         assert!(!Element::focused(&mb, &ctx), "focus released after the click");
 
-        // The PageSelector capability rides the same hooks.
-        let elem: &dyn Element = &mb;
-        assert!(elem.as_page_selector().unwrap().sidebar_w() > 0.0);
+        // The PageSelector capability is reached through the concrete adapter too.
+        assert!(PageSelector::sidebar_w(&*mb) > 0.0);
     }
 
     #[test]
@@ -1149,9 +1132,8 @@ mod tests {
         Element::set_rect(&mut mb, 0.0, 0.0, 400.0, 24.0);
 
         Element::set_visible(&mut mb, false);
-        let elem: &dyn Element = &mb;
-        assert!(!elem.as_menu_controller().unwrap().is_menu_bar(), "hidden bar is not a menu bar");
+        assert!(!MenuController::is_menu_bar(&*mb), "hidden bar is not a menu bar");
         assert!(!Element::hit_test(&mb, 10.0, 10.0, &ctx));
-        assert!(elem.as_menu_controller().unwrap().get_menu_items_at(10.0, 10.0).is_none());
+        assert!(MenuController::get_menu_items_at(&*mb, 10.0, 10.0).is_none());
     }
 }
diff --git a/src/widget/container/paginator.rs b/src/widget/container/paginator.rs
index be09364..2221f27 100644
--- a/src/widget/container/paginator.rs
+++ b/src/widget/container/paginator.rs
@@ -248,18 +248,6 @@ impl Input for Paginator {
         }
     }
 
-    fn menu_controller(&self) -> Option<&dyn MenuController> {
-        Some(self)
-    }
-    fn menu_controller_mut(&mut self) -> Option<&mut dyn MenuController> {
-        Some(self)
-    }
-    fn page_selector(&self) -> Option<&dyn PageSelector> {
-        Some(self)
-    }
-    fn page_selector_mut(&mut self) -> Option<&mut dyn PageSelector> {
-        Some(self)
-    }
 }
 
 impl MenuController for Paginator {
@@ -310,17 +298,13 @@ mod tests {
         p.mouse_input(MouseButton::Left, ElementState::Pressed, bx + bw / 2.0, by + bh / 2.0, &mut ctx);
         p.mouse_input(MouseButton::Left, ElementState::Released, bx + bw / 2.0, by + bh / 2.0, &mut ctx);
         assert_eq!(p.selected_page, 1);
-        {
-            let elem: &mut dyn Element = &mut p;
-            assert_eq!(elem.as_menu_controller_mut().unwrap().menu_click(), Some((1, 0)));
-            assert_eq!(elem.as_menu_controller_mut().unwrap().menu_click(), None, "click drained");
-        }
+        assert_eq!(MenuController::menu_click(&mut *p), Some((1, 0)));
+        assert_eq!(MenuController::menu_click(&mut *p), None, "click drained");
 
-        // The PageSelector capability rides the wrapper (cce-test-interface's downcast).
-        let elem: &dyn Element = &p;
-        let ps = elem.as_page_selector().unwrap();
-        assert_eq!(ps.selected_page(), 1);
-        assert!(ps.sidebar_w() > 0.0);
+        // The PageSelector capability is reached through the concrete adapter (the
+        // cce-test-interface downcast shape).
+        assert_eq!(PageSelector::selected_page(&*p), 1);
+        assert!(PageSelector::sidebar_w(&*p) > 0.0);
     }
 
     #[test]
diff --git a/src/widget/container/parameters_bg.rs b/src/widget/container/parameters_bg.rs
index 7ab0e73..199d15e 100644
--- a/src/widget/container/parameters_bg.rs
+++ b/src/widget/container/parameters_bg.rs
@@ -767,12 +767,6 @@ impl Input for ParametersBg {
         false
     }
 
-    fn param_controller(&self) -> Option<&dyn ParamController> {
-        Some(self)
-    }
-    fn param_controller_mut(&mut self) -> Option<&mut dyn ParamController> {
-        Some(self)
-    }
 
     // --- The host-driven drag surface (the designer routes pointer drags here directly). ---
 
@@ -1893,10 +1887,7 @@ mod tests {
             .iter()
             .map(|(a, b, c)| (a.to_string(), b.to_string(), c.to_string()))
             .collect();
-        {
-            let elem: &mut dyn Element = &mut p;
-            elem.as_param_controller_mut().unwrap().set_display_params(&params);
-        }
+        ParamController::set_display_params(&mut *p, &params);
         Element::set_rect(&mut p, 0.0, 0.0, 300.0, 400.0);
         p
     }
@@ -1908,9 +1899,7 @@ mod tests {
             ("Mode", "b", "choice:a,b,c"),
             ("On", "true", "checkbox"),
         ]);
-        let elem: &dyn Element = &p;
-        let pc = elem.as_param_controller().unwrap();
-        assert_eq!(pc.node_params().len(), 3);
+        assert_eq!(ParamController::node_params(&*p).len(), 3);
         assert!(p.sliders[0].is_some() && p.choices[1].is_some() && p.checkboxes[2].is_some());
         // Rows were laid out from the cached rect.
         let (sx, _, sw, _) = p.sliders[0].as_ref().unwrap().rect();
@@ -1926,8 +1915,7 @@ mod tests {
         // every left press, so the return is true either way — assert the value flip).
         p.mouse_input(MouseButton::Left, ElementState::Pressed, cx + 6.0, cy + ch / 2.0, &mut ctx);
         p.mouse_input(MouseButton::Left, ElementState::Released, cx + 6.0, cy + ch / 2.0, &mut ctx);
-        let elem: &dyn Element = &p;
-        assert_eq!(elem.as_param_controller().unwrap().node_params()[0].1, "true");
+        assert_eq!(ParamController::node_params(&*p)[0].1, "true");
 
         // Code editor: focus it via a click, type, then unfocus commits the buffer.
         let mut p = panel_with(&[("Src", "let x = 1;", "code")]);
@@ -1940,8 +1928,7 @@ mod tests {
         Element::unfocus(&mut p);
         assert_eq!(p.focused_param, None);
         assert!(p.code_editor.is_none());
-        let elem: &dyn Element = &p;
-        assert!(elem.as_param_controller().unwrap().node_params()[0].1.contains('y'), "editor buffer committed on unfocus");
+        assert!(ParamController::node_params(&*p)[0].1.contains('y'), "editor buffer committed on unfocus");
     }
 
     #[test]
@@ -1983,10 +1970,7 @@ mod tests {
             .map(|i| (format!("P{i}"), "1.00".to_string(), "slider:0:2".to_string()))
             .collect();
         let mut p = ParametersBg::new();
-        {
-            let elem: &mut dyn Element = &mut p;
-            elem.as_param_controller_mut().unwrap().set_display_params(&rows);
-        }
+        ParamController::set_display_params(&mut *p, &rows);
         Element::set_rect(&mut p, 0.0, 0.0, 300.0, 200.0);
         assert!(p.content_h > 200.0);
         assert!(Element::is_scrollable(&p));
diff --git a/src/widget/container/spreadsheet.rs b/src/widget/container/spreadsheet.rs
index c1cb14d..a3cfdb5 100644
--- a/src/widget/container/spreadsheet.rs
+++ b/src/widget/container/spreadsheet.rs
@@ -350,12 +350,6 @@ impl Input for Spreadsheet {
         true
     }
 
-    fn spreadsheet_controller(&self) -> Option<&dyn SpreadsheetController> {
-        Some(self)
-    }
-    fn spreadsheet_controller_mut(&mut self) -> Option<&mut dyn SpreadsheetController> {
-        Some(self)
-    }
 }
 
 impl SpreadsheetController for Spreadsheet {
@@ -379,10 +373,7 @@ mod tests {
         Element::set_rect(&mut s, 0.0, 0.0, 200.0, 124.0); // viewport: 100 = ~4 rows of 24
         let data: Vec<Vec<String>> =
             (0..rows).map(|i| vec![format!("r{i}"), format!("v{i}")]).collect();
-        let elem: &mut dyn Element = &mut s;
-        elem.as_spreadsheet_controller_mut()
-            .expect("Spreadsheet exposes SpreadsheetController")
-            .set_spreadsheet_data(vec!["a".into(), "b".into()], data);
+        SpreadsheetController::set_spreadsheet_data(&mut *s, vec!["a".into(), "b".into()], data);
         s
     }
 
diff --git a/src/widget/display/graph.rs b/src/widget/display/graph.rs
index 4b86ffd..3d5677e 100644
--- a/src/widget/display/graph.rs
+++ b/src/widget/display/graph.rs
@@ -812,12 +812,6 @@ impl Input for Graph {
         self.commit_drag();
     }
 
-    fn graph_controller(&self) -> Option<&dyn GraphController> {
-        Some(self)
-    }
-    fn graph_controller_mut(&mut self) -> Option<&mut dyn GraphController> {
-        Some(self)
-    }
 }
 
 impl Graph {
@@ -1085,11 +1079,7 @@ mod tests {
         // Node b's input port: node b at (200, 160, 80, 40) => top-center (240, 160).
         assert!(g.mouse_input(MouseButton::Left, ElementState::Pressed, 240.0, 160.0, &mut ctx));
 
-        let elem: &mut dyn Element = &mut g;
-        let pending = elem
-            .as_graph_controller_mut()
-            .expect("Graph exposes GraphController")
-            .take_pending_connection();
+        let pending = GraphController::take_pending_connection(&mut *g);
         assert_eq!(pending, Some(("b".to_string(), "alpha".to_string())));
     }
 
diff --git a/src/widget/display/node.rs b/src/widget/display/node.rs
index 35bd841..3c3409f 100644
--- a/src/widget/display/node.rs
+++ b/src/widget/display/node.rs
@@ -222,18 +222,6 @@ impl Input for Node {
         self.bounds = Some((bx, by, bw, bh));
     }
 
-    fn param_controller(&self) -> Option<&dyn ParamController> {
-        Some(self)
-    }
-    fn param_controller_mut(&mut self) -> Option<&mut dyn ParamController> {
-        Some(self)
-    }
-    fn geom_controller(&self) -> Option<&dyn GeomController> {
-        Some(self)
-    }
-    fn geom_controller_mut(&mut self) -> Option<&mut dyn GeomController> {
-        Some(self)
-    }
 }
 
 impl ParamController for Node {
@@ -272,8 +260,7 @@ mod tests {
 
         // Toggle zone: (100+120-30, 100+11) => 18x18 at (190, 111).
         assert!(node.mouse_input(MouseButton::Left, ElementState::Pressed, 195.0, 115.0, &mut ctx));
-        let elem: &mut dyn Element = &mut node;
-        let geom = elem.as_geom_controller_mut().expect("Node exposes GeomController");
+        let geom: &mut dyn GeomController = &mut *node;
         assert!(!geom.geom_visible(), "toggle click hides geometry");
         assert!(geom.take_geom_toggle(), "toggle flag set once");
         assert!(!geom.take_geom_toggle(), "…and drained");
@@ -290,12 +277,9 @@ mod tests {
     #[test]
     fn param_controller_roundtrips_through_element() {
         let mut node = Node::new(0.0, 0.0, 10.0, 10.0, "n").with_params(&[("k", "v")]);
-        let elem: &mut dyn Element = &mut node;
-        let params = elem.as_param_controller().expect("Node exposes ParamController").node_params();
+        let params = ParamController::node_params(&*node);
         assert_eq!(params, vec![("k".to_string(), "v".to_string(), "string".to_string())]);
-        elem.as_param_controller_mut()
-            .unwrap()
-            .set_display_params(&[("a".to_string(), "b".to_string(), "int".to_string())]);
+        ParamController::set_display_params(&mut *node, &[("a".to_string(), "b".to_string(), "int".to_string())]);
         assert_eq!(node.parameters.len(), 1);
         assert_eq!(node.parameters[0].2, "int");
     }
diff --git a/src/widget/display/serialize.rs b/src/widget/display/serialize.rs
index 0def10d..96bc905 100644
--- a/src/widget/display/serialize.rs
+++ b/src/widget/display/serialize.rs
@@ -23,7 +23,15 @@ fn serialize_single_widget(w: &dyn Element, json: &mut String) {
     let mut is_menu_open = false;
     let mut is_vertical = false;
     let mut checked_states = Vec::new();
-    if let Some(mc) = w.as_menu_controller() {
+    // Concrete capability lookup (Phase 6aw): the MenuController implementors a serialized
+    // roster can hold are Adapted<MenuBar> and Adapted<Paginator> — Element's discovery
+    // hooks are gone.
+    let mc: Option<&dyn MenuController> = w
+        .as_any()
+        .downcast_ref::<MenuBar>()
+        .map(|m| m as &dyn MenuController)
+        .or_else(|| w.as_any().downcast_ref::<Paginator>().map(|p| p as &dyn MenuController));
+    if let Some(mc) = mc {
         menu_items = mc.menu_items();
         is_menu_open = mc.is_menu_open();
         is_vertical = mc.is_vertical();
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index 943ed1e..66ad217 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -593,24 +593,12 @@ pub trait Element {
     fn is_child_visible(&self, _child_id: WidgetId) -> bool { true }
     fn set_modifiers(&mut self, _ctrl: bool, _shift: bool, _alt: bool) {}
 
-    fn as_page_selector(&self) -> Option<&dyn PageSelector> { None }
-    fn as_page_selector_mut(&mut self) -> Option<&mut dyn PageSelector> { None }
-    fn as_menu_controller(&self) -> Option<&dyn MenuController> { None }
-    fn as_menu_controller_mut(&mut self) -> Option<&mut dyn MenuController> { None }
-    fn as_graph_controller(&self) -> Option<&dyn GraphController> { None }
-    fn as_graph_controller_mut(&mut self) -> Option<&mut dyn GraphController> { None }
-    fn as_spreadsheet_controller_mut(&mut self) -> Option<&mut dyn SpreadsheetController> { None }
-    fn as_path_controller(&self) -> Option<&dyn PathController> { None }
-    fn as_path_controller_mut(&mut self) -> Option<&mut dyn PathController> { None }
-    fn as_param_controller(&self) -> Option<&dyn ParamController> { None }
-    fn as_param_controller_mut(&mut self) -> Option<&mut dyn ParamController> { None }
-    fn as_geom_controller_mut(&mut self) -> Option<&mut dyn GeomController> { None }
-
     fn parent(&self, ctx: &UiContext) -> Option<*mut (dyn Element + 'static)> {
         let base = self.base()?;
         ctx.tree.parent_ptr(base.id())
     }
 
+
     fn set_parent(&mut self, parent: Option<*mut (dyn Element + 'static)>, ctx: &mut UiContext) {
         if let Some(base) = self.base() {
             let id = base.id();
diff --git a/src/widget/model.rs b/src/widget/model.rs
index 26b8506..e3971a8 100644
--- a/src/widget/model.rs
+++ b/src/widget/model.rs
@@ -29,9 +29,7 @@
 use crate::scene::layout::{Rect, Size, Style};
 use crate::scene::paint::{PaintCtx, Prim};
 use crate::widget::{
-    Element, Event, GeomController, GraphController, MenuController, PageSelector,
-    ParamController, PathController, SpreadsheetController, TextLabel, UiContext, Widget,
-    WidgetId,
+    Element, Event, TextLabel, UiContext, Widget, WidgetId,
 };
 
 /// Layout inputs for the scene layout engine — the RFC's `Widget` concern, named `Layout` here to
@@ -608,42 +606,6 @@ pub trait Input {
     // implements the trait. Dies with `Element`: the end state reaches a controller through the
     // concrete `Adapted<W>` (or a `&dyn XController` held directly), per RFC §3.5.
 
-    fn menu_controller(&self) -> Option<&dyn MenuController> {
-        None
-    }
-    fn menu_controller_mut(&mut self) -> Option<&mut dyn MenuController> {
-        None
-    }
-    fn graph_controller(&self) -> Option<&dyn GraphController> {
-        None
-    }
-    fn graph_controller_mut(&mut self) -> Option<&mut dyn GraphController> {
-        None
-    }
-    fn spreadsheet_controller(&self) -> Option<&dyn SpreadsheetController> {
-        None
-    }
-    fn spreadsheet_controller_mut(&mut self) -> Option<&mut dyn SpreadsheetController> {
-        None
-    }
-    fn path_controller(&self) -> Option<&dyn PathController> {
-        None
-    }
-    fn path_controller_mut(&mut self) -> Option<&mut dyn PathController> {
-        None
-    }
-    fn param_controller(&self) -> Option<&dyn ParamController> {
-        None
-    }
-    fn param_controller_mut(&mut self) -> Option<&mut dyn ParamController> {
-        None
-    }
-    fn geom_controller(&self) -> Option<&dyn GeomController> {
-        None
-    }
-    fn geom_controller_mut(&mut self) -> Option<&mut dyn GeomController> {
-        None
-    }
 
     /// Copy this widget's path/content to the clipboard — the context menu's "Copy Path" action
     /// calls `Element::copy_path` on its target (Breadcrumb is the only implementor).
@@ -676,12 +638,6 @@ pub trait Input {
         base_focused
     }
 
-    fn page_selector(&self) -> Option<&dyn PageSelector> {
-        None
-    }
-    fn page_selector_mut(&mut self) -> Option<&mut dyn PageSelector> {
-        None
-    }
 }
 
 /// Wraps a narrow-trait widget `W` so it lives in the legacy `*mut dyn Element` tree. Carries the
@@ -1038,12 +994,6 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
         Input::is_focused(&self.inner, self.base.focused)
     }
 
-    fn as_page_selector(&self) -> Option<&dyn PageSelector> {
-        Input::page_selector(&self.inner)
-    }
-    fn as_page_selector_mut(&mut self) -> Option<&mut dyn PageSelector> {
-        Input::page_selector_mut(&mut self.inner)
-    }
 
     fn layout(&mut self, origin: crate::widget::Point, constraints: crate::widget::LayoutConstraints, ctx: &mut UiContext) {
         // The Element default (measure + set_rect), plus recursive child layout for visible
@@ -1498,36 +1448,6 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
     }
 
     // --- Controller downcasts -> the `Input` capability hooks ---
-    fn as_menu_controller(&self) -> Option<&dyn MenuController> {
-        Input::menu_controller(&self.inner)
-    }
-    fn as_menu_controller_mut(&mut self) -> Option<&mut dyn MenuController> {
-        Input::menu_controller_mut(&mut self.inner)
-    }
-    fn as_graph_controller(&self) -> Option<&dyn GraphController> {
-        Input::graph_controller(&self.inner)
-    }
-    fn as_graph_controller_mut(&mut self) -> Option<&mut dyn GraphController> {
-        Input::graph_controller_mut(&mut self.inner)
-    }
-    fn as_spreadsheet_controller_mut(&mut self) -> Option<&mut dyn SpreadsheetController> {
-        Input::spreadsheet_controller_mut(&mut self.inner)
-    }
-    fn as_path_controller(&self) -> Option<&dyn PathController> {
-        Input::path_controller(&self.inner)
-    }
-    fn as_path_controller_mut(&mut self) -> Option<&mut dyn PathController> {
-        Input::path_controller_mut(&mut self.inner)
-    }
-    fn as_param_controller(&self) -> Option<&dyn ParamController> {
-        Input::param_controller(&self.inner)
-    }
-    fn as_param_controller_mut(&mut self) -> Option<&mut dyn ParamController> {
-        Input::param_controller_mut(&mut self.inner)
-    }
-    fn as_geom_controller_mut(&mut self) -> Option<&mut dyn GeomController> {
-        Input::geom_controller_mut(&mut self.inner)
-    }
     fn copy_path(&self) {
         Input::copy_path(&self.inner)
     }
@@ -1738,6 +1658,7 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::widget::PathController;
     use crate::scene::bridge::layout_subtree;
     use crate::scene::layout::{CrossAlign, Size, Style};
     use crate::scene::paint::Prim;
@@ -1893,9 +1814,9 @@ mod tests {
         assert!(!unsafe { (*ptr).hovered() }, "base hover flag cleared");
     }
 
-    /// A narrow widget that is also a controller: it re-exposes its [`PathController`] impl
-    /// through the `Input` capability hooks, and the adapter forwards the legacy
-    /// `Element::as_path_controller` downcasts to them.
+    /// A narrow widget that is also a controller: the controller trait is reached through the
+    /// concrete `Adapted<W>` by deref (Phase 6aw -- the `Element::as_*_controller` discovery
+    /// hooks are deleted).
     struct Crumbs {
         segs: Vec<String>,
         clicked: Option<usize>,
@@ -1907,12 +1828,6 @@ mod tests {
         }
     }
     impl Input for Crumbs {
-        fn path_controller(&self) -> Option<&dyn PathController> {
-            Some(self)
-        }
-        fn path_controller_mut(&mut self) -> Option<&mut dyn PathController> {
-            Some(self)
-        }
     }
     impl PathController for Crumbs {
         fn set_path(&mut self, segments: &[String]) {
@@ -1924,25 +1839,16 @@ mod tests {
     }
 
     #[test]
-    fn controller_capability_forwards_through_the_element_downcast() {
+    fn controller_capability_reached_through_the_concrete_adapter() {
         let mut w = Box::new(Adapted::new(Crumbs { segs: Vec::new(), clicked: Some(2) }));
-        let elem: &mut dyn Element = w.as_mut();
 
-        // The legacy downcast pair reaches the narrow widget's controller impl…
-        elem.as_path_controller_mut()
-            .expect("Adapted forwards as_path_controller_mut")
-            .set_path(&["home".to_string(), "user".to_string()]);
-        assert!(elem.as_path_controller().is_some(), "shared-ref downcast forwards too");
-        assert_eq!(elem.as_path_controller_mut().unwrap().path_click(), Some(2));
+        // The controller trait is reached by deref through the concrete Adapted<W>...
+        PathController::set_path(&mut **w, &["home".to_string(), "user".to_string()]);
+        assert_eq!(PathController::path_click(&mut **w), Some(2));
 
-        // …and lands on the same state the concrete widget sees.
+        // ...and lands on the same state the concrete widget sees.
         assert_eq!(w.inner().segs, vec!["home".to_string(), "user".to_string()]);
-        assert_eq!(w.inner().clicked, None, "path_click drained through the forward");
-
-        // Capabilities the widget does not expose stay None (the Element defaults).
-        let elem: &dyn Element = w.as_ref();
-        assert!(elem.as_menu_controller().is_none());
-        assert!(elem.as_graph_controller().is_none());
+        assert_eq!(w.inner().clicked, None, "path_click drained through the deref");
     }
     /// Phase 6: the paint walk's text prims carry the widget's font and clip rect (what the
     /// display-list text path renders), not the bare `Paint::paint` text.