You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Put wrapping text inside a max_w container inside a gpui::list item, and the item's height is computed as if the text wrapped at the uncapped pane width, while the text is laid out (and painted) at the capped width. Every paragraph that wraps to more lines at the capped width leaves the item one line short; over a long list this accumulates and the next items paint straight over the overflow — headings buried mid-paragraph, lines of two blocks interleaved. Wrong on the first frame, no interaction needed.
The root cause is in taffy, and it's already fixed upstream: when determining a flex child's basis, taffy ≤ 0.12.2 used the child's style size as a known dimension unclamped by the child's own min/max — so width: 100%; max_width: 560px is content-measured at the parent's full width, then laid out at 560. DioxusLabs/taffy#989 (merged 2026-07-25, released in taffy 0.13.0 on 2026-08-08) added the missing clamp — incidentally: its objective was aspect-ratio transferred sizes and it doesn't mention this failure mode. Zed's main currently pins taffy 0.12.2, which is affected. Bumping to 0.13.0 fixes this class of bug. Regression tests for the measure-function case: DioxusLabs/taffy#1095.
Reproduction
Side-by-side gpui app, corruption visible on launch: the left pane puts the cap inside each list item (w_full item → w_full().max_w(560.) column), the right pane renders identical content with the cap on the list element instead. In the left pane the red "NEXT BLOCK" heading paints on top of row 08 and the item after it lands inside row 09, while rows 09–11 continue underneath. Reproduces at rev b05f40c554 (taffy 0.12.1) and at d637307b (taffy 0.4.4).
use gpui::{
div, list, prelude::*, px, rgb, size,App,Bounds,Context,Div,FontWeight,ListAlignment,ListState,TitlebarOptions,Window,WindowBounds,WindowOptions,};use gpui_platform::application;constCOLUMN:f32 = 560.0;constPADDING:f32 = 20.0;constROWS:usize = 12;constPARAGRAPH:&str = "the quick brown fox jumps over the lazy dog while the \ measure cache hands back a size that was shaped against a width this row \ will never be laid out at, which is how the rows below end up too short";fnbullet_row(text:String) -> Div{div().flex().items_start().gap(px(8.0)).child(div().mt(px(8.0)).w(px(16.0)).flex_none().text_color(rgb(0x6b7280)).child("•"),).child(div().flex_1().min_w(px(1.0)).flex().flex_col().child(div().mt(px(8.0)).child(text)),)}fnitem_body(index:usize) -> Div{match index {0 => div().flex().flex_col().children((0..ROWS).map(|row| bullet_row(format!("row {row:02} — {PARAGRAPH}"))),),1 => div().mt(px(20.0)).child(div().text_size(px(17.0)).font_weight(FontWeight::SEMIBOLD).text_color(rgb(0xff5f56)).child("NEXT BLOCK — should start below every numbered row"),),
_ => bullet_row(String::from("after the heading — stacking continues here")),}}structRepro{broken:ListState,fixed:ListState,}implRenderforRepro{fnrender(&mutself, _:&mutWindow, _:&mutContext<Self>) -> implIntoElement{let pane = |label:&'staticstr,color:u32,body:Div| {div().flex_1().min_w(px(1.0)).h_full().flex().flex_col().child(div().flex_none().h(px(30.0)).px(px(12.0)).flex().items_center().bg(rgb(0x21242b)).font_weight(FontWeight::SEMIBOLD).text_color(rgb(color)).child(label),).child(body.flex_1().min_h(px(1.0)))};// BROKEN: the width cap lives inside each item, under an auto width.let broken = list(self.broken.clone(), |index, _, _| {div().w_full().flex().flex_col().items_center().child(div().w_full().max_w(px(COLUMN)).px(px(PADDING)).child(item_body(index)),).into_any_element()});// FIXED: identical content; the cap sits on the list element, whose// bounds are resolved before any item lays out.let fixed = list(self.fixed.clone(), |index, _, _| {div().w_full().px(px(PADDING)).child(item_body(index)).into_any_element()});div().size_full().bg(rgb(0x16181d)).text_color(rgb(0xc8ccd4)).text_size(px(12.5)).flex().child(pane("BROKEN — max_w inside each list item",0xff5f56,div().child(broken.size_full()),)).child(div().flex_none().w(px(1.0)).h_full().bg(rgb(0x3a3f4b))).child(pane("FIXED — cap on the list element",0x27c93f,div().flex().justify_center().child(fixed.h_full().w_full().max_w(px(COLUMN))),))}}fnmain(){application().run(|cx:&mutApp| {let bounds = Bounds::centered(None,size(px(1400.0),px(900.0)), cx);
cx.open_window(WindowOptions{window_bounds:Some(WindowBounds::Windowed(bounds)),titlebar:Some(TitlebarOptions{title:Some("gpui-text-overlap-repro".into()),
..Default::default()}),
..Default::default()},
|_, cx| {
cx.new(|_| Repro{broken:ListState::new(3,ListAlignment::Top,px(400.0)),fixed:ListState::new(3,ListAlignment::Top,px(400.0)),})},).unwrap();
cx.activate(true);});}
There is also a 45-line pure-taffy repro (no gpui) in DioxusLabs/taffy#1095: fails on taffy 0.4.4–0.12.2, passes on 0.13.0.
What we verified
Not gpui's text element. The measure cache in elements/text.rs does answer min/max-content queries with sizes shaped at the wrong width (wrap_width.is_none() || wrap_width == cached), but disabling that cache entirely reproduces a bit-for-bit identical wrong frame. The stale height comes from taffy's flex sizing, not the text element.
Node-level taffy logs show the capped column content-measured at known.width = 700 (the parent's width; its own max_width is 560) → 12 two-line rows → height 581. The final pass wraps the text at 496 → 12 three-line rows → 824 painted. The item root keeps 581, gpui::list stacks the next items at 581, and ~240px of content is painted over.
The taffy clamp fixes the app with stock gpui: applying the Dropping frames when moving the cursor up and down #989 clamp to taffy 0.4.4 makes the broken pane render identically to the fixed one; the pure repro passes on stock taffy 0.13.0.
Spec and Chrome agree the clamp is required (flexbox §9.2 step 3 item E sizes the item at its used cross size; Chrome renders the analog DOM correctly).
Relation to previous reports
#30002 / #30097 ("Agent Panel: Overlapping text following mis-sized Code block") look like this exact failure — one commenter notes it appearing while wheel-scrolling, which is when a tall item's corrupted bottom boundary scrolls into view. #30377 fixed them by removing the triggering layout in active_thread.rs ("Seems that h_full was causing it to use the height of the overall list item for some reason") — the mechanism stayed, and any max_w-over-auto-width around wrapped text inside a list item still hits it on taffy ≤ 0.12.2.
Suggested action
Bump taffy to 0.13.0 (or cherry-pick the child_known_dimensions clamp from DioxusLabs/taffy#989). Until then, the app-side workaround for gpui users is to keep width caps on the list element itself — its bounds are resolved before items lay out as roots — and never between an item's root and wrapped text.
Environment
macOS 15 (Darwin 24.6.0), Apple Silicon. Verified at zed revs b05f40c5546b47bcf9561136dc0fcdcd9968cb63 (taffy 0.12.1) and d637307bd75389f63adbb7d86bc66174ee817d0d (taffy 0.4.4); zed main pins taffy 0.12.2 as of 2026-08-11.
TL;DR
Put wrapping text inside a
max_wcontainer inside agpui::listitem, and the item's height is computed as if the text wrapped at the uncapped pane width, while the text is laid out (and painted) at the capped width. Every paragraph that wraps to more lines at the capped width leaves the item one line short; over a long list this accumulates and the next items paint straight over the overflow — headings buried mid-paragraph, lines of two blocks interleaved. Wrong on the first frame, no interaction needed.The root cause is in taffy, and it's already fixed upstream: when determining a flex child's basis, taffy ≤ 0.12.2 used the child's style size as a known dimension unclamped by the child's own min/max — so
width: 100%; max_width: 560pxis content-measured at the parent's full width, then laid out at 560. DioxusLabs/taffy#989 (merged 2026-07-25, released in taffy 0.13.0 on 2026-08-08) added the missing clamp — incidentally: its objective was aspect-ratio transferred sizes and it doesn't mention this failure mode. Zed's main currently pins taffy 0.12.2, which is affected. Bumping to 0.13.0 fixes this class of bug. Regression tests for the measure-function case: DioxusLabs/taffy#1095.Reproduction
Side-by-side gpui app, corruption visible on launch: the left pane puts the cap inside each list item (
w_fullitem →w_full().max_w(560.)column), the right pane renders identical content with the cap on the list element instead. In the left pane the red "NEXT BLOCK" heading paints on top of row 08 and the item after it lands inside row 09, while rows 09–11 continue underneath. Reproduces at revb05f40c554(taffy 0.12.1) and atd637307b(taffy 0.4.4).main.rs + Cargo.toml (builds against rev b05f40c)
There is also a 45-line pure-taffy repro (no gpui) in DioxusLabs/taffy#1095: fails on taffy 0.4.4–0.12.2, passes on 0.13.0.
What we verified
elements/text.rsdoes answer min/max-content queries with sizes shaped at the wrong width (wrap_width.is_none() || wrap_width == cached), but disabling that cache entirely reproduces a bit-for-bit identical wrong frame. The stale height comes from taffy's flex sizing, not the text element.known.width = 700(the parent's width; its ownmax_widthis 560) → 12 two-line rows → height 581. The final pass wraps the text at 496 → 12 three-line rows → 824 painted. The item root keeps 581,gpui::liststacks the next items at 581, and ~240px of content is painted over.Relation to previous reports
#30002 / #30097 ("Agent Panel: Overlapping text following mis-sized Code block") look like this exact failure — one commenter notes it appearing while wheel-scrolling, which is when a tall item's corrupted bottom boundary scrolls into view. #30377 fixed them by removing the triggering layout in
active_thread.rs("Seems thath_fullwas causing it to use the height of the overall list item for some reason") — the mechanism stayed, and anymax_w-over-auto-width around wrapped text inside alistitem still hits it on taffy ≤ 0.12.2.Suggested action
Bump taffy to 0.13.0 (or cherry-pick the
child_known_dimensionsclamp from DioxusLabs/taffy#989). Until then, the app-side workaround for gpui users is to keep width caps on thelistelement itself — its bounds are resolved before items lay out as roots — and never between an item's root and wrapped text.Environment
macOS 15 (Darwin 24.6.0), Apple Silicon. Verified at zed revs
b05f40c5546b47bcf9561136dc0fcdcd9968cb63(taffy 0.12.1) andd637307bd75389f63adbb7d86bc66174ee817d0d(taffy 0.4.4); zed main pins taffy 0.12.2 as of 2026-08-11.Visual-aid