From 2663a4d1b3170cb2a4dca5e6f1f3535f44b30e44 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 16:47:26 -0300 Subject: No panic from a notice wider than its band, or from the rest of its class The showcase GUI died in joinNotice on a long shader-error notice: with the tagline narrower than the body, the band hides more columns (skip) than a long message has padding (pad), and `skip..@min(pad, ..)` ran backwards. An audit of draw.zig, Layer.zig, Presentation.zig and animation.zig found the same class elsewhere, all fixed: - a multi-row pane tag or header scrolled for its caret's line sliced a shorter row past its end (paintPaneTag, renderHeaderLayer); - a zero-width column tag kept an earlier frame's viewport, and a column move's clip took it below zero (now saturating, and the early return resets the viewport and caret); - a tag line past 65535 columns overflowed tag_scroll's u16; - a hover left over from a bigger window dashed past the grid; - a border drag over a column that went away, a move preview past a shrunken grid, a column-move rail at zero columns; - Layer.pointAt cast a host's unchecked float metrics (web) to u16; - Text.insertVerticalCursor read past its lines for a cursor whose text changed under it. Regressions: a test of a notice wider than its band through Msg and through setMessage (the Post.zig compile-error path), which panics without the fix; and a monkey over the core at a gui's metrics (random resizes to a cell, long notices, prompts, multi-row tags, clicks, keys), 400 steps in the suite, PARDES_FUZZ_SEED/STEPS for longer runs; 60 seeds x 3000 steps clean. Shared files touched: none of the list (draw.zig, Layer.zig, Text.zig). --- src/Layer.zig | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src/Layer.zig') diff --git a/src/Layer.zig b/src/Layer.zig index e979eab7..a9827304 100644 --- a/src/Layer.zig +++ b/src/Layer.zig @@ -106,9 +106,12 @@ const Point = struct { index: u16, col: u16, metrics: Metrics }; fn pointAt(layer: *const Layer, x: f32, y: f32, body_w: f32, body_h: f32, tagline_w: f32, tagline_h: f32) ?Point { if (layer.rows == 0 or !std.math.isFinite(x) or !std.math.isFinite(y)) return null; // A band's hit is refused outright on metrics no host could have; a - // body's has always been floored to a pixel instead. - if (layer.kind != .body) for ([_]f32{ body_w, body_h, tagline_w, tagline_h }) |metric| - if (!std.math.isFinite(metric) or metric < 1 or metric > std.math.maxInt(u16)) return null; + // body's has always been floored to a pixel instead, and both refuse + // what is no number or past a u16 (a host's floats: web's exports). + for ([_]f32{ body_w, body_h, tagline_w, tagline_h }) |metric| { + if (!std.math.isFinite(metric) or metric > std.math.maxInt(u16)) return null; + if (layer.kind != .body and metric < 1) return null; + } const bw = @max(1, body_w); const bh = @max(1, body_h); const tw = std.math.clamp(tagline_w, 1, bw); -- cgit v1.3