Capture · Roots: the roots round 2 missed

A sweep that read every render found 28 roots still on w_full,
reached by 64 components: the field button under Select and every
picker, the checked and mention fields, the slider track,
NumberInput, Steps, the state views, the agent previews, and every
chart and finance frame. All drop it. LivePreview's size_full becomes
h_full, since its card no longer fills.

Every host of those roots was listed and read, 231 in all. Five
tests that mounted a picker as the window root sit in a size_full
box, the index manager gives its method select a flex_1 box, and the
notification center and the gallery's state frames stack their state
views in a column so they stretch.

Each dropped root carries a debug selector, and six chapter tests
mount 43 components in a padded card whose column is measured by
content, through layout::tests::narrow_width: each root must take the
full 240px. With w_full back on the roots, all six fail.

AGENTS.md names the roots that keep w_full and why: picture frames
that keep a ratio, and rows that hand a text field its share.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-09-28 19:34:01 -04:00
1 parent ca5c967f17
commit e482bac865
43 files changed
+465 -108

No files matched your search

+1 -1
View File
@@ -43,10 +43,10 @@ fn checked(
let theme = cx.theme();
let (subtle, danger) = (theme.colors.fg_subtle, theme.colors.danger);
div()
.debug_selector(|| "checked-root".into())
.flex()
.flex_col()
.gap_1()
.w_full()
.child(
Input::new(state)
.size(size)
+1 -1
View File
@@ -172,9 +172,9 @@ impl Suggestions {
let (up_rows, down_rows, enter_rows) = (rows.clone(), rows.clone(), rows.clone());
let (enter, trigger) = (self.pick.clone(), self.trigger);
div()
.debug_selector(|| "mention-root".into())
.id(self.id)
.relative()
.w_full()
.capture_action(move |_: &Up, _, cx| {
if open {
cx.stop_propagation();
+1 -1
View File
@@ -401,10 +401,10 @@ impl RenderOnce for NumberInput {
.child(stepper),
);
div()
.debug_selector(|| "number-root".into())
.flex()
.items_center()
.gap_2()
.w_full()
.children(label)
.child(
div()
+1 -1
View File
@@ -88,13 +88,13 @@ pub(crate) fn field_button(
let theme = cx.theme();
let colors = &theme.colors;
div()
.debug_selector(|| "field-root".into())
.id(id)
.track_focus(focus)
.relative()
.flex()
.items_center()
.gap_2()
.w_full()
.h(theme.control_height(size))
.px(theme.control_padding(size))
.rounded(theme.radius(Radius::Md))
+2 -1
View File
@@ -243,13 +243,14 @@ impl Track {
})
});
div()
.debug_selector(|| "slider-root".into())
.id(self.id)
.flex_none()
.map(|outer| {
if vertical {
outer.h_full().w(thumb).py(thumb * 0.5)
} else {
outer.w_full().h(thumb).px(thumb * 0.5)
outer.h(thumb).px(thumb * 0.5)
}
})
.when(self.disabled, |outer| outer.opacity(0.5))
+15 -11
View File
@@ -67,14 +67,16 @@ struct Picker {
impl Render for Picker {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.entity();
Select::new("select", abc(false))
.selected(self.chosen.clone())
.on_change(move |value, _, cx| {
view.update(cx, |view, cx| {
view.chosen = value.clone();
cx.notify();
})
})
div().size_full().child(
Select::new("select", abc(false))
.selected(self.chosen.clone())
.on_change(move |value, _, cx| {
view.update(cx, |view, cx| {
view.chosen = value.clone();
cx.notify();
})
}),
)
}
}
@@ -425,9 +427,11 @@ struct Empty(bool);
impl Render for Empty {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
Select::new("empty", [])
.placeholder("Nothing yet")
.disabled(self.0)
div().size_full().child(
Select::new("empty", [])
.placeholder("Nothing yet")
.disabled(self.0),
)
}
}
+37 -26
View File
@@ -1,4 +1,7 @@
use gpui::{Context, IntoElement, KeyUpEvent, Keystroke, Render, TestAppContext, Window};
use gpui::{
Context, IntoElement, KeyUpEvent, Keystroke, ParentElement, Render, Styled, TestAppContext,
Window, div,
};
use jiff::civil::{Date, Time, date, time};
use super::setup;
@@ -14,15 +17,17 @@ struct Day {
impl Render for Day {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.entity();
DatePicker::new("day", self.value)
.today(TODAY)
.disabled(self.disabled)
.on_change(move |picked, _, cx| {
view.update(cx, |view, cx| {
view.value = Some(picked);
cx.notify();
})
})
div().size_full().child(
DatePicker::new("day", self.value)
.today(TODAY)
.disabled(self.disabled)
.on_change(move |picked, _, cx| {
view.update(cx, |view, cx| {
view.value = Some(picked);
cx.notify();
})
}),
)
}
}
@@ -48,14 +53,16 @@ struct Span {
impl Render for Span {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.entity();
DateRangePicker::new("span", self.value)
.today(TODAY)
.on_change(move |picked, _, cx| {
view.update(cx, |view, cx| {
view.value = Some(picked);
cx.notify();
})
})
div().size_full().child(
DateRangePicker::new("span", self.value)
.today(TODAY)
.on_change(move |picked, _, cx| {
view.update(cx, |view, cx| {
view.value = Some(picked);
cx.notify();
})
}),
)
}
}
@@ -78,14 +85,18 @@ struct Clock {
impl Render for Clock {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.entity();
TimePicker::new("clock", self.value)
.step(15)
.on_change(move |picked, _, cx| {
view.update(cx, |view, cx| {
view.value = Some(picked);
cx.notify();
})
})
div()
.size_full()
.child(
TimePicker::new("clock", self.value)
.step(15)
.on_change(move |picked, _, cx| {
view.update(cx, |view, cx| {
view.value = Some(picked);
cx.notify();
})
}),
)
}
}
+38 -2
View File
@@ -1,12 +1,16 @@
use gpui::{
AppContext as _, Context, Entity, InteractiveElement, IntoElement, KeyUpEvent, Keystroke,
App, AppContext as _, Context, Entity, InteractiveElement, IntoElement, KeyUpEvent, Keystroke,
ParentElement, Render, Styled, TestAppContext, VisualTestContext, Window, div, px,
};
use super::setup;
use crate::{
buttons::Button,
forms::{FieldArray, Form, FormField, Input, InputEvent, TextInput},
forms::{
Choice, DatePicker, EmailInput, FieldArray, Form, FormField, Input, InputEvent,
MentionInput, NumberInput, Select, Slider, TextInput,
},
layout::tests::narrow_width,
};
struct Signup {
@@ -195,3 +199,35 @@ fn a_field_keeps_inside_its_cards_padding(cx: &mut TestAppContext) {
"a percent min would take the card's outer width"
);
}
#[gpui::test]
fn fields_fill_a_column_their_block_measures_by_content(cx: &mut TestAppContext) {
setup(cx);
let field =
|window: &mut Window, cx: &mut App| window.use_keyed_state("field", cx, TextInput::new);
let widths = [
narrow_width(cx, "field-root", |_, _| {
Select::new("select", [Choice::new("a", "A")]).into_any_element()
}),
narrow_width(cx, "field-root", |_, _| {
DatePicker::new("date", None).into_any_element()
}),
narrow_width(cx, "checked-root", move |window, cx| {
EmailInput::new(&field(window, cx)).into_any_element()
}),
narrow_width(cx, "mention-root", move |window, cx| {
MentionInput::new("mention", &field(window, cx)).into_any_element()
}),
narrow_width(cx, "slider-root", |_, _| {
Slider::new("slider", 0.5).into_any_element()
}),
narrow_width(cx, "number-root", |_, _| {
NumberInput::new("number", 42.0).into_any_element()
}),
];
assert_eq!(
widths,
[px(240.0); 6],
"each field spans the card inside its padding"
);
}