git.lucas.co / cce-gallery
widget gallery and compositor test bench

commit072349052e73154a2d70ae5587b025077f73a2e5
parentc87a9f0b8c
authorLucas Galante <[email protected]>
date2026-09-06 11:51
fix: only a Ramp child focuses the preset dropdown at startup

Application::new's child branch downcast slot 1 to Ramp unconditionally and
expect()ed it, but slot 1 is a Ramp only for `--type Ramp`: ColorRamp hosts a
ColorRamp there and Toplevel/Popup/LayerTop/LayerOverlay/LayerBackground host
the description Label (ChildMain). Every non-Ramp child therefore died at
startup with "panicked at main.rs: child ramp widget" — Create Window on the
Windows page spawned processes that never mapped. The panic predates the
roster retype in 0507f49 (its message records it as pre-existing) and
survived it.

The preset-dropdown focus setup is now gated on the downcast succeeding. The
other child kinds start with nothing focused: handle_key's roster sweep
already delivers keys to every visible child slot, and a click focuses the
slot it lands on, so no behaviour is lost.

Verified in a private shadow session (cce-shadow agent-14): the pre-fix
binary panics at main.rs:1248 for `--type Toplevel`; with the fix,
`cce-shadow ctl windows` lists app_id=clear-test-child-toplevel
title="Simulated Toplevel Window" at 400x250, and Ramp, ColorRamp, Popup,
LayerTop, LayerOverlay and LayerBackground children all run past startup.

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

 src/main.rs | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 585e0f7..e7995ed 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1243,10 +1243,17 @@ cascades in cce."
                     &mut state.ui_context,
                 );
             }
-        } else {
-            state.focused_widget = Some(1);
-            let ramp = state.roster.get_dyn_mut(1).as_any_mut().downcast_mut::<Ramp>().expect("child ramp widget");
+        } else if let Some(ramp) = state.roster.get_dyn_mut(1).as_any_mut().downcast_mut::<Ramp>() {
+            // Only a `--type Ramp` child hosts a Ramp in slot 1: it opens with the
+            // preset dropdown focused so the keyboard drives it at once. Every other
+            // child kind (ColorRamp, or the description Label of the Toplevel /
+            // Popup / Layer* windows) starts with nothing focused — the key sweep
+            // in handle_key reaches every visible child slot anyway, and a click
+            // focuses whatever it lands on. This used to downcast unconditionally
+            // and panic for those kinds ("child ramp widget"), which is why Create
+            // Window on the Windows page spawned children that died at startup.
             let preset_ptr = ramp.preset_dropdown.as_ptr_mut();
+            state.focused_widget = Some(1);
             state.ui_context.set_focused_ptr(preset_ptr);
             unsafe {
                 (*preset_ptr).focus();