╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭─────────────────────────────────────────────────────────────────────────╮ │428 + .user => |*p| .{ .user = try .init(allocator, p, session) },│ │429 }; │ │430 │ │431 const demon_art = @embedFile("embed/demon.ans"); │ ╰─────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────╮ │434 errdefer root.deinit(allocator); │ │435 │ │436 try root.build(allocator, .{ │ │227 - .min_size = .{ .width = null, .height = null },│ │437 + .min_size = .{ .width = null, .height = 40 }, │ │438 .max_size = .{ .width = 80, .height = null }, │ │439 }, root.getFocus()); │ │440 if (root.getFocus().child_id) |child_id| { │ ╰────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────╮ │452 scroll: wgt.Scroll(Widget), │ │453 stack: wgt.Stack(Widget), │ │454 flow_box: FlowBox, │ │455 + flow_box_scroll: FlowBox.Scroll,│ │456 spacer: Spacer, │ │457 center: Center, │ │458 ansi_art: AnsiArt, │ ╰─────────────────────────────────────────╯ ╭────────────────────────────────────────────╮ │459 background: AnsiBackground, │ │460 home: Home.View, │ │461 + user: User.View, │ │462 title: Title.View, │ │463 home_header: Home.Header.View, │ │464 + user_header: User.Header.View, │ │465 home_users: Home.Users.View, │ │466 home_repos: Home.Repos.View, │ │467 auth_tab: Home.Header.AuthTab.View,│ ╰────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────╮ │255 - settings: Home.Settings.View, │ │256 - auth: Home.Auth.View, │ │468 + home_settings: Home.Settings.View, │ │469 + home_auth: Home.Auth.View, │ │470 │ │471 pub fn deinit(self: *Widget, allocator: std.mem.Allocator) void {│ │472 switch (self.*) { │ ╰──────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │509 focus: Focus, │ │510 grid: ?Grid, │ │511 text_boxes: std.ArrayList(wgt.TextBox(Widget)), │ │299 - lines: std.ArrayList([]const u8), │ │512 + // backs the per-item strings the text boxes borrow: each box's `content` │ │513 + // and, for link items, its `.custom` focus kind (e.g. "a:/user/foo"). reset│ │514 + // wholesale on each setItems so there's no per-string ownership to track. │ │515 + arena: std.heap.ArenaAllocator, │ │516 // column count from the last build — FlowBox.Scroll.input uses it so arrow │ │517 // up/down can step by a row's worth of items. │ │518 last_cols: usize, │ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────╮ │525 cell_height: usize = 3, │ │526 }; │ │527 │ │312 - pub fn init(options: Options) FlowBox { │ │528 + pub fn init(allocator: std.mem.Allocator, options: Options) FlowBox {│ │529 return .{ │ │530 .focus = Focus.init(.container), │ │531 .grid = null, │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────╮ │532 .text_boxes = .empty, │ │317 - .lines = .empty, │ │533 + .arena = std.heap.ArenaAllocator.init(allocator),│ │534 .last_cols = 1, │ │535 .options = options, │ │536 }; │ ╰──────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │544 } │ │545 for (self.text_boxes.items) |*tb| tb.deinit(allocator); │ │546 self.text_boxes.deinit(allocator); │ │331 - for (self.lines.items) |line| allocator.free(line); │ │332 - self.lines.deinit(allocator); │ │547 + self.arena.deinit(); │ │548 } │ │549 │ │335 - pub fn setItems(self: *FlowBox, allocator: std.mem.Allocator, items: []const []const u8) !void { │ │550 + // `links`, when given, is parallel to `items`: a non-empty entry sets that │ │551 + // item's focus kind to `.{ .custom = link }` (e.g. "a:/user/foo"), which the │ │552 + // web renderer turns into an anchor. pass null when items aren't links. │ │553 + // both the item text and the link are copied into the arena, so the caller's │ │554 + // slices needn't outlive this call. │ │555 + pub fn setItems(self: *FlowBox, allocator: std.mem.Allocator, items: []const []const u8, links: ?[]const []const u8) !void {│ │556 for (self.text_boxes.items) |*tb| tb.deinit(allocator); │ │557 self.text_boxes.clearAndFree(allocator); │ │558 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │339 - for (self.lines.items) |line| allocator.free(line); │ │340 - self.lines.clearAndFree(allocator); │ │559 + // the old text boxes (and their borrowed content/links) are gone now, so│ │560 + // the arena that backed those strings can be reclaimed. │ │561 + _ = self.arena.reset(.retain_capacity); │ │562 + const aa = self.arena.allocator(); │ │563 │ │564 self.focus.clear(); │ │565 self.focus.child_id = null; │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯