╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ───────────╮ │src/ui/Repo/Refs.zig│ ╰────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │48 .branches = .{ .label = try ui.SubTitle.init(arena, "branches") }, │ │49 .tags = .{ .label = try ui.SubTitle.init(arena, "tags") }, │ │50 }; │ │51 +} │ │52 + │ │53 +// both columns empty with no previous row, which shows the empty message │ │54 +fn isEmpty(self: *const Self) bool { │ │55 + return self.branches.names.len == 0 and self.branches.prev == null and self.tags.names.len == 0 and self.tags.prev == null;│ │56 } │ │57 │ │58 // read one window of each column from an opened repo. the ref names are duped │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │182 try outer.children.put(allocator, header.getFocus().id, .{ .widget = .{ .box = header }, .rect = null, .min_size = .{ .width = null, .height = 4 } });│ │183 } │ │184 │ │180 - { │ │185 + // with both columns empty, the empty message stands in for them │ │186 + if (data.isEmpty()) { │ │187 + var center = try ui.widget.initEmpty(allocator); │ │188 + errdefer center.deinit(allocator); │ │189 + try outer.children.put(allocator, center.getFocus().id, .{ .widget = .{ .center = center }, .rect = null, .min_size = null }); │ │190 + } else { │ │191 var split = try wgt.Box(ui.Widget).init(allocator, .{ .border = null, .direction = .horiz }); │ │192 errdefer split.deinit(allocator); │ │193 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │276 │ │277 // split the available width evenly between the two columns when it's│ │278 // known; otherwise let them size to their content. │ │269 - if (constraint.max_size.width) |w| { │ │270 - const half = w / 2; │ │271 - for (self.columns().children.values()) |*child| { │ │272 - child.min_size = .{ .width = half, .height = null }; │ │273 - child.max_size = .{ .width = half, .height = null }; │ │274 - } │ │275 - } else { │ │276 - for (self.columns().children.values()) |*child| { │ │277 - child.min_size = null; │ │278 - child.max_size = null; │ │279 + if (!self.data.isEmpty()) { │ │280 + if (constraint.max_size.width) |w| { │ │281 + const half = w / 2; │ │282 + for (self.columns().children.values()) |*child| { │ │283 + child.min_size = .{ .width = half, .height = null }; │ │284 + child.max_size = .{ .width = half, .height = null }; │ │285 + } │ │286 + } else { │ │287 + for (self.columns().children.values()) |*child| { │ │288 + child.min_size = null; │ │289 + child.max_size = null; │ │290 + } │ │291 } │ │292 } │ │293 │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │302 pub fn input(self: *View, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { │ │303 if (self.headerActive()) { │ │304 const search_box = self.searchBox(); │ │293 - if (inp.vertDirection(key) == .down) return root_focus.setFocus(self.columns().getFocus().id);│ │305 + if (inp.vertDirection(key) == .down) { │ │306 + // the empty message has nothing to focus │ │307 + if (!self.data.isEmpty()) root_focus.setFocus(self.columns().getFocus().id); │ │308 + return; │ │309 + } │ │310 if (key == .enter) { │ │311 const text = try search_box.text(allocator); │ │312 defer allocator.free(text); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────╮ │src/ui/Repo/Undo.zig│ ╰────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │48 // the clear-history confirmation stands in for the list │ │49 clear: bool = false, │ │50 │ │51 +// no history and no failure outside the clear form, which shows the empty message │ │52 +fn isEmpty(self: *const Self, session: *ui.Session) bool { │ │53 + return !self.clear and self.items.len == 0 and (session.data.undo_failure orelse self.failure) == null; │ │54 +} │ │55 + │ │56 pub fn init(comptime opts: rp.RepoOpts(.xit), arena: *std.heap.ArenaAllocator, repo: *rp.Repo(.xit, opts), haxy_moment: ?evt.AdminDB.HashMap(.read_only), identity: []const u8, selected: ?u64, handle: ui.RepoHandle) !Self {│ │57 const aa = arena.allocator(); │ │58 const DB = rp.Repo(.xit, opts).DB; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │406 try outer.children.put(allocator, header.getFocus().id, .{ .widget = .{ .box = header }, .rect = null, .min_size = .{ .width = null, .height = 4 } });│ │407 } │ │408 │ │404 - if (data.clear) { │ │405 - var center = try initClearForm(allocator, data, session); │ │409 + // the clear form, or the empty message when there is no history or failure to show │ │410 + if (data.clear or data.isEmpty(session)) { │ │411 + var center = if (data.clear) try initClearForm(allocator, data, session) else try ui.widget.initEmpty(allocator); │ │412 errdefer center.deinit(allocator); │ │413 try outer.children.put(allocator, center.getFocus().id, .{ .widget = .{ .center = center }, .rect = null, .min_size = null }); │ │408 - outer.getFocus().child_id = outer.children.keys()[content_index]; │ │414 + // the empty message has nothing to focus, so focus rests on the sub-header │ │415 + outer.getFocus().child_id = outer.children.keys()[if (data.clear) content_index else header_index]; │ │416 return .{ .box = outer, .data = data, .session = session }; │ │417 } │ │418 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │431 const route = ui.RoutablePage.repoUndoRoute(data.identity, next) orelse return error.RouteTooLong; │ │432 try addText(allocator, &rows, "next →", try session.page_arena.allocator().print("a:{s}", .{try route.toUrl(session.page_arena)}), .hidden); │ │433 } │ │427 - if (data.items.len == 0) try addText(allocator, &rows, "no undo history", null, .hidden); │ │434 + // the failure row, filled in by build │ │435 + if (data.items.len == 0) try addText(allocator, &rows, "", null, .hidden); │ │436 rows.getFocus().child_id = rows.children.keys()[0]; │ │437 break :blk try wgt.Scroll(ui.Widget).init(allocator, .{ .box = rows }, .{ .direction = .vert, .web_native = !session.is_terminal, .fill = true });│ │438 }; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │578 │ │579 pub fn build(self: *View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void { │ │580 self.clearGrid(); │ │573 - if (self.data.clear) return try self.box.build(allocator, constraint, root_focus); │ │581 + // the clear form or the empty message stands in for the list │ │582 + if (self.data.clear or self.data.isEmpty(self.session)) return try self.box.build(allocator, constraint, root_focus);│ │583 try self.refreshDetail(allocator); │ │584 const rows = &self.listScroll().child.box; │ │585 if (self.data.items.len == 0) { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │577 - const failure = self.session.data.undo_failure orelse self.data.failure; │ │586 + // an empty list only shows to report its failure │ │587 + const failure = self.session.data.undo_failure orelse self.data.failure orelse unreachable; │ │588 if (!std.meta.eql(self.shown_failure, failure)) { │ │579 - const message = if (failure) |name| try self.session.page_arena.allocator().print("error: {s}", .{name}) else "no undo history"; │ │580 - try rows.children.values()[0].widget.text_box.setContent(allocator, message); │ │589 + try rows.children.values()[0].widget.text_box.setContent(allocator, try self.session.page_arena.allocator().print("error: {s}", .{failure}));│ │590 self.shown_failure = failure; │ │591 } │ │592 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │605 // scrolling crosses the boundary the same way the arrow keys do │ │606 const direction = inp.vertDirection(key); │ │607 if (self.headerActive()) { │ │599 - if (direction == .down) root_focus.setFocus(self.box.children.values()[content_index].widget.getFocus().id); │ │608 + // the empty message has nothing to focus │ │609 + if (direction == .down and !self.data.isEmpty(self.session)) root_focus.setFocus(self.box.children.values()[content_index].widget.getFocus().id);│ │610 return; │ │611 } │ │612 if (self.data.clear) { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯