From ad98ca774658053cd6dcd74a2ae9dfb47349a825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 28 Sep 2026 16:30:47 -0400 Subject: [PATCH] =?UTF-8?q?Capture=20=C2=B7=20Roots:=20the=20twelve=20root?= =?UTF-8?q?s=20round=201=20found?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eleven more roots drop w_full: the docked ActionBar, MessageBubble's three arms, PromptInput, TitleBar, Tabs, the horizontal Divider with and without a label, DropZone and Playlist. Their hosts in the crate are blocks or flex columns. The gallery's docked ActionBar sits in an items_center column, so the demo gives it the width. AspectRatio keeps w_full, and AGENTS.md says why: taffy 0.9 follows a ratio from a set width, not from a stretch, and without one the image gallery's tiles and the ratio demo collapsed. Every page and scripted state was shot again at full width and at 280px and compared with the shots from before T44a. Nothing collapsed or shrank. The mail reader's attachments now wrap side by side, as their row asks. --- AGENTS.md | 2 +- TASKS.md | 1 + examples/gallery/pages/buttons/actions.rs | 1 + src/buttons/bars.rs | 3 +-- src/chat/composer.rs | 1 - src/chat/message.rs | 3 --- src/forms/files.rs | 1 - src/media/listen.rs | 2 +- src/navigation/tabs.rs | 2 +- src/primitives/divider.rs | 3 +-- src/shell/chrome.rs | 1 - 11 files changed, 7 insertions(+), 13 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d281d30..1855b6f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -153,7 +153,7 @@ Ely GPUI Component. A component library for GPUI, in light and dark. - A row that holds text beside fixed parts lets the text give way: the text takes `flex_1().min_w_0()` and an `Ellipsis`; counts, icons, dates and chevrons take `flex_none`. A label that must stay whole wraps in that block instead, as `forms::Checkbox`'s does. - A mark over a text field reads the field at paint time, from a canvas laid over it (`collab::RemoteCursor`): by then `TextInput::bounds_for` holds this frame's layout, and a still page never renders again to catch up. - A callback field holds a named `type` alias; clippy's type_complexity rejects `Option>`. A chapter shares one alias per shape. -- A component that fills its container takes no width on its root: a block or a flex column stretches it. Percents fill badly in taffy 0.9. `w_full` in a flex column that a block measures by content keeps its content's width: taffy lays the column out once at unknown width and keeps that layout when the width comes out the same. `min_w_full` resolves against a block's outer width, padding and all (`forms::FormField`). In a flex row the host gives it `flex_1`; as a window's root it sits in a `size_full` box. A root centered under a cap keeps `w_full`, since `mx_auto` needs a width to fill (`layout::Container`). A row that hands a text field a `flex_1` share keeps `w_full` too: stretched without a width, taffy 0.9 can keep a pass that laid the share at no width, and the field draws nothing (`forms::Input`, `InputGroup`, `TagInput`). +- A component that fills its container takes no width on its root: a block or a flex column stretches it. Percents fill badly in taffy 0.9. `w_full` in a flex column that a block measures by content keeps its content's width: taffy lays the column out once at unknown width and keeps that layout when the width comes out the same. `min_w_full` resolves against a block's outer width, padding and all (`forms::FormField`). In a flex row the host gives it `flex_1`; as a window's root it sits in a `size_full` box. A root centered under a cap keeps `w_full`, since `mx_auto` needs a width to fill (`layout::Container`); so does one that keeps a ratio, since taffy 0.9 follows a ratio from a set width, not a stretch (`layout::AspectRatio`). A row that hands a text field a `flex_1` share keeps `w_full` too: stretched without a width, taffy 0.9 can keep a pass that laid the share at no width, and the field draws nothing (`forms::Input`, `InputGroup`, `TagInput`). - A field's `Highlight` sets color, wash, weight, slant and strike per span; measuring and painting share one set of runs, so wrapped heights match. - A block editor keeps one undo for the document: text and structure together, as snapshots through `forms::History`; it takes Undo and Redo before its fields do. - gpui calls focus listeners only while the window is active. A test that needs focus events activates its window with `window.activate_window()`. diff --git a/TASKS.md b/TASKS.md index 5f29fff..9ec4b8f 100644 --- a/TASKS.md +++ b/TASKS.md @@ -472,3 +472,4 @@ Per-component lines live in `tasks/`. Tags there: | T43b | 2 | PASS | | | T43c | 1 | FAIL | the audit left fg_subtle on active and every syntax color unread, both under 4.5:1; the viewer left out avatar and container sizes while claiming every token | | T43c | 2 | PASS | the AGENTS.md decision narrowed to the pairs the audit holds, in the pass commit | +| T44a | 1 | FAIL | twelve roots in nine components still said w_full: a root held in a variable, a match arm or an animation passed the sweep | diff --git a/examples/gallery/pages/buttons/actions.rs b/examples/gallery/pages/buttons/actions.rs index 2dedeb9..8cf8487 100644 --- a/examples/gallery/pages/buttons/actions.rs +++ b/examples/gallery/pages/buttons/actions.rs @@ -171,6 +171,7 @@ pub fn action_bar(cx: &App) -> impl IntoElement + use<> { ) .child( ActionBar::new() + .w_full() .child(Button::new("bar-cancel", "Cancel").variant(ButtonVariant::Ghost)) .child(Button::new("bar-save", "Save changes").primary()), ), diff --git a/src/buttons/bars.rs b/src/buttons/bars.rs index 8045120..b6164fb 100644 --- a/src/buttons/bars.rs +++ b/src/buttons/bars.rs @@ -69,8 +69,7 @@ impl RenderOnce for ActionBar { .border_color(colors.border) .shadow(theme.elevation(Elevation::Floating)) } else { - bar.w_full() - .justify_end() + bar.justify_end() .px_4() .py_3() .bg(colors.surface) diff --git a/src/chat/composer.rs b/src/chat/composer.rs index 5d0b05b..23f4c2b 100644 --- a/src/chat/composer.rs +++ b/src/chat/composer.rs @@ -364,7 +364,6 @@ impl RenderOnce for PromptInput { let body = div() .id(self.id.clone()) .relative() - .w_full() .flex() .flex_col() .gap_2() diff --git a/src/chat/message.rs b/src/chat/message.rs index 68d12ad..d539eca 100644 --- a/src/chat/message.rs +++ b/src/chat/message.rs @@ -238,7 +238,6 @@ impl RenderOnce for MessageBubble { .children(self.body); match self.role { Role::System => div() - .w_full() .flex() .justify_center() .py_1() @@ -247,7 +246,6 @@ impl RenderOnce for MessageBubble { .child(body.text_size(theme.text_size(TextSize::Xs))) .into_any_element(), Role::User => div() - .w_full() .flex() .gap_3() .child( @@ -282,7 +280,6 @@ impl RenderOnce for MessageBubble { .children(self.avatar) .into_any_element(), Role::Assistant | Role::Error => div() - .w_full() .flex() .gap_3() .children(self.avatar) diff --git a/src/forms/files.rs b/src/forms/files.rs index 53f7732..6dae1c7 100644 --- a/src/forms/files.rs +++ b/src/forms/files.rs @@ -298,7 +298,6 @@ impl RenderOnce for DropZone { .flex_col() .items_center() .gap_2() - .w_full() .px_6() .py_8() .rounded(theme.radius(Radius::Lg)) diff --git a/src/media/listen.rs b/src/media/listen.rs index 305a226..3baf317 100644 --- a/src/media/listen.rs +++ b/src/media/listen.rs @@ -115,7 +115,7 @@ impl RenderOnce for Playlist { let theme = cx.theme(); let (muted, lead) = (theme.colors.fg_muted, theme.icon_size(IconSize::Lg)); let small = theme.text_size(TextSize::Sm); - let mut list = SelectableList::new(self.id.clone()).w_full(); + let mut list = SelectableList::new(self.id.clone()); for (ix, track) in self.tracks.into_iter().enumerate() { let now = self.current.as_ref() == Some(&track.key); let mark = if now { diff --git a/src/navigation/tabs.rs b/src/navigation/tabs.rs index a0136a3..35c9580 100644 --- a/src/navigation/tabs.rs +++ b/src/navigation/tabs.rs @@ -237,7 +237,7 @@ impl RenderOnce for Tabs { move |panel, t| if turn == 0 { panel } else { panel.opacity(t) }, ) }); - let frame = div().id(self.id).flex().gap_4().w_full(); + let frame = div().id(self.id).flex().gap_4(); match placement { TabPlacement::Top => frame.flex_col().child(strip).children(panel), TabPlacement::Bottom => frame.flex_col().children(panel).child(strip), diff --git a/src/primitives/divider.rs b/src/primitives/divider.rs index 077786c..ea554cb 100644 --- a/src/primitives/divider.rs +++ b/src/primitives/divider.rs @@ -39,12 +39,11 @@ impl RenderOnce for Divider { return line().h_full().border_l_1(); } match self.label { - None => line().w_full().border_t_1(), + None => line().border_t_1(), Some(label) => div() .flex() .items_center() .gap_3() - .w_full() .child(line().flex_1().border_t_1()) .child( div() diff --git a/src/shell/chrome.rs b/src/shell/chrome.rs index 392f570..7bba17f 100644 --- a/src/shell/chrome.rs +++ b/src/shell/chrome.rs @@ -376,7 +376,6 @@ impl RenderOnce for TitleBar { .flex_none() .items_center() .gap_2() - .w_full() .h(theme.titlebar_height()) .bg(theme.colors.bg) .border_b_1()