1191127df903
fix debounce crash: clamp selection in search widget — navigating during the 250ms debounce window grew selection against the old result set; when the debounced search returned fewer results, renderWidget indexed packages[selection] out of bounds (segfault in ReleaseFast). Now reset selection=0 when a debounced search lands, and clamp selection to count-1 in renderWidget as defense — build+tests green
- time
- 2026-06-05 02:53 UTC · 4 months ago
- machine
- koh2
- agent
- claude-opus-4-8
- session
- nina finishing brief
- parent
- 7abf027a795e
↓ nina-1191127d.tar.gzatlas at this save
files changed1 file
src/search.zig+12 −4src/search.zig+12 −4
@@ -80,6 +80,10 @@8080 query_dirty = false;8181 _ = arena.reset(.free_all);8282 packages = doSearch(arena.allocator(), io, query_buf[0..query_len], mode, &opts);83+ // New result set: reset selection to the top. Navigating during the84+ // debounce window could otherwise leave selection past the new end,85+ // and renderWidget would index out of bounds.86+ selection = 0;8387 output.cursorUp(WIDGET_LINES);8488 renderWidget(query_buf[0..query_len], packages, opts, selection, mode);8589 output.flush();@@ -234,6 +238,10 @@234238 const DIM = output.C_DIM;235239 const CYN = output.C_CYAN;236240 241+ // Clamp to the current result count: a caller could hand us a selection from a242+ // larger, now-replaced result set. Never index past the end.243+ const sel = if (count == 0) 0 else @min(selection, count - 1);244+237245 // Line 1: header238246 output.raw(CLR);239247 const mode_str = if (mode == .packages) "nixpkgs" else "options";@@ -245,13 +253,13 @@245253 output.raw(CLR ++ "\n");246254 247255 // Lines 3-7: results (up to RESULT_ROWS), scrolled so selection stays visible248- const scroll: usize = if (selection >= RESULT_ROWS) selection - RESULT_ROWS + 1 else 0;256+ const scroll: usize = if (sel >= RESULT_ROWS) sel - RESULT_ROWS + 1 else 0;249257 var i: usize = 0;250258 while (i < RESULT_ROWS) : (i += 1) {251259 const idx = scroll + i;252260 output.raw(CLR);253261 if (idx < count) {254- const selected = (idx == selection);262+ const selected = (idx == sel);255263 switch (mode) {256264 .packages => {257265 const pkg = packages[idx];@@ -276,7 +284,7 @@276284 277285 // Lines 9-12: selected package detail278286 if (count > 0 and mode == .packages) {279- const pkg = packages[selection];287+ const pkg = packages[sel];280288 output.raw(CLR);281289 output.p(" {s}{s}{s}{s} {s}{s}{s}\n", .{ PKN, BLD, pkg.pname, RST, CYN, pkg.version, RST });282290 output.raw(CLR);@@ -285,7 +293,7 @@285293 output.raw(CLR);286294 output.p(" {s}attr{s} {s}{s}{s}\n", .{ DIM, RST, CYN, pkg.attr, RST });287295 } else if (count > 0 and mode == .options) {288- const opt = opts[selection];296+ const opt = opts[sel];289297 output.raw(CLR);290298 output.p(" {s} {s}{s}{s}\n", .{ opt.name, DIM, opt.type_str, RST });291299 output.raw(CLR);