From 9184ce603905dc30032ec5fe4ea03e2a39cd8e4c Mon Sep 17 00:00:00 2001 From: slicenfer Date: Thu, 27 Aug 2026 17:11:33 +0800 Subject: [PATCH] Raise retained widget layout budget 1024 -> 2048 nodes per view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A production three-pane app (session rail ~50 rows, streaming detail timeline, model-picker dialog listing ~60 rows) measures 1036 retained widget nodes on its dialog frame — 12 over the 1024 cap. The WidgetLayoutListFull adoption failure discards the whole frame, so opening the dialog leaves every control visibly dead, and a view that sits over budget wedges interactions on every invalidated frame. Doubling the budget covers the measured view with ~2x headroom. The automation snapshot cap moves in lockstep (the existing lockstep test enforces it). 4096 was ruled out: session record/replay tests inline record+replay harnesses into one test frame (~11 MiB at 1024 on macOS), and 4096 pushes those frames past the main-thread stack limit (53 crashes); 2048 keeps zig build test green. Fixes #408 --- src/automation/snapshot.zig | 2 +- src/runtime/canvas_limits.zig | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/automation/snapshot.zig b/src/automation/snapshot.zig index 2ac61b4a3..101a4b21a 100644 --- a/src/automation/snapshot.zig +++ b/src/automation/snapshot.zig @@ -10,7 +10,7 @@ pub const max_views: usize = platform.max_windows + platform.max_views + platfor // cannot import the runtime); a lockstep test in // canvas_widget_layout_tests.zig fails if they drift, so snapshots never // silently truncate widget enumeration below the node budget. -pub const max_widgets_per_view: usize = 1024; +pub const max_widgets_per_view: usize = 2048; pub const max_widgets: usize = platform.max_views * max_widgets_per_view; pub const Window = struct { diff --git a/src/runtime/canvas_limits.zig b/src/runtime/canvas_limits.zig index 9022c3c9e..ecb9805a0 100644 --- a/src/runtime/canvas_limits.zig +++ b/src/runtime/canvas_limits.zig @@ -8,9 +8,13 @@ const canvas = @import("canvas"); // markdown detail pane + run surface): it spent more design effort // budgeting nodes than building UI at the old 256-node cap. The // widget-node budget quadrupled (256 -> 1024: the measured worst realistic -// three-pane view is ~500 nodes, so 1024 leaves comfortable headroom), and -// the frame-content budgets doubled (commands 1024 -> 2048, glyphs -// 4096 -> 8192, text 16 KiB -> 32 KiB: visible content per frame grows with +// three-pane view is ~500 nodes, so 1024 leaves comfortable headroom), later +// doubled (1024 -> 2048) after a production three-pane app — a ~50-row +// session sidebar, a streaming detail timeline, and a dialog enumerating +// ~60 model rows — measured 1036 nodes on its dialog frame; at the 1024 +// cap the adoption failure discarded whole frames, leaving controls +// visibly dead. The frame-content budgets doubled (commands 1024 -> 2048, +// glyphs 4096 -> 8192, text 16 KiB -> 32 KiB: visible content per frame grows with // surface density, not with the retained-node cap — a full-height monospace // diff pane is ~7200 glyphs). Memory cost is fixed-capacity address space // in the Runtime (in-place constructed, large fields left uninitialized), @@ -174,13 +178,13 @@ pub const max_media_surface_pixel_bytes: usize = 8 * 1024 * 1024; pub const max_registered_canvas_fonts: usize = 8; pub const max_registered_canvas_font_bytes: usize = 24 * 1024 * 1024; -// The retained widget-tree budgets (raised 256 -> 1024; see the header +// The retained widget-tree budgets (raised 256 -> 1024 -> 2048; see the // comment). `automation.snapshot.max_widgets_per_view` // mirrors the node cap so snapshots never silently truncate widget // enumeration; a test in canvas_widget_layout_tests.zig keeps them in // lockstep. -pub const max_canvas_widget_nodes_per_view: usize = 1024; -pub const max_canvas_widget_semantics_per_view: usize = 1024; +pub const max_canvas_widget_nodes_per_view: usize = 2048; +pub const max_canvas_widget_semantics_per_view: usize = 2048; // Raised from 2048 with the inline-span/markdown work, then from 64 KiB for // editable code surfaces: a simple editor must retain a practical source // file (roughly 10k ordinary code lines) plus the surrounding view chrome. @@ -210,7 +214,7 @@ pub const max_canvas_widget_spans_per_view: usize = 1024; // fast — a 24-row sidebar with 4 items + separator per row, a detail-pane // menu, and per-step ledger menus measured 124/128 before the app was // finished. Quadrupled (128 -> 512) -// so declared menus scale with the 1024-node budget instead of becoming +// so declared menus scale with the 2048-node budget instead of becoming // the next design-effort cliff. Memory cost is one 24-byte entry (a // 16-byte label slice + enabled/separator flags) per slot: 24 B x 512 = // 12 KiB per view (was 3 KiB at 128), x 32 view slots = 384 KiB total