Universal-Debloater-Alliance/universal-android-debloater-next-generation · error

Layout must have at least 1 child

Error message

Layout must have at least 1 child

What it means

This is a Rust `Option::expect` panic inside a modal widget's `update` in `uad-gui` (Iced). The code asks the iced `Layout` for its first child via `layout.children().next().expect("Layout must have at least 1 child")` to get the content bounds and check whether the cursor clicked outside the modal. The library/widget assumes the modal layout always renders at least one child; if the modal content is empty or not yet laid out, the iterator yields `None` and the app panics.

Solutions

  1. Replace `.expect(...)` with `if let Some(child) = layout.children().next() { let content_bounds = child.bounds(); ... }` so clicks outside without content just publish the close message
  2. Ensure the modal always renders at least one child (e.g. a non-empty container) for every state where cursor handling is active
  3. Gate the cursor-handling branch on the modal actually having content (a `is_open && content.is_some()` style condition)
  4. Log/ignore clicks when children are empty instead of panicking, since a click on an empty modal should simply dismiss it

Example fix

// before
let content_bounds = layout
    .children()
    .next()
    .expect("Layout must have at least 1 child")
    .bounds();
// after
if let Some(child) = layout.children().next() {
    let content_bounds = child.bounds();
    if !content_bounds.contains(cursor_position) {
        shell.publish(message.clone());
        return;
    }
}
Defensive patterns

Strategy: type-guard

Validate before calling

if layout.children().next().is_none() {
    // no content laid out yet; skip outside-click handling
    return;
}

Type guard

fn first_child<'a>(layout: &Layout<'a>) -> Option<&'a crate::widgets::modal::Child> {
    layout.children().next()
}

Try / catch

// Use iterator combinator instead of expect:
let content_bounds = layout.children().next().map(|c| c.bounds());

Prevention

When it happens

Trigger: A mouse/cursor press event is processed by the modal's `update` while the modal's layout has zero children — e.g. the modal content widget failed to build, was cleared, or the event fires before the first layout pass populates children.

Common situations: Rendering a modal with empty/conditional content that produced no children this frame; a click arriving during the same frame the modal opens before layout completes; a refactor that removes the modal's container/child for certain states; iced version changes altering layout child behavior.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of Universal-Debloater-Alliance/universal-android-debloater-next-generation@64465c850c (2026-09-12). Data as JSON: /api/errors/8a20e0ca66d0de86. Report an issue: GitHub.

Appendix: source

Thrown at crates/uad-gui/src/widgets/modal.rs:206

        &mut self,
        event: &Event,
        layout: Layout<'_>,
        cursor: Cursor,
        renderer: &Renderer,
        clipboard: &mut dyn Clipboard,
        shell: &mut Shell<'_, Message>,
    ) {
        if let Some(message) = self.on_blur.as_ref()
            && matches!(
                event,
                Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
            )
            && let Some(cursor_position) = cursor.position()
        {
            let content_bounds = layout
                .children()
                .next()
                .expect("Layout must have at least 1 child")
                .bounds();
            if !content_bounds.contains(cursor_position) {
                shell.publish(message.clone());
                return;
            }
        }

        self.content.as_widget_mut().update(
            self.tree,
            event,
            layout.children().next().unwrap(),
            cursor,
            renderer,
            clipboard,
            shell,
            &self.viewport,
        );
    }

View on GitHub (pinned to 64465c850c)