╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Events.zig│ ╰──────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │406 return .{ .box = outer, .data = data, .session = session, .detailed_index = .{ null, null } }; │ │407 } │ │408 │ │404 - fn initContent(allocator: std.mem.Allocator, data: *const Self, session: *ui.Session, view: ui.RoutablePage.EventsView) !wgt.Box(ui.Widget) {│ │409 + // an empty window shows the empty message in place of the list and detail │ │410 + fn initContent(allocator: std.mem.Allocator, data: *const Self, session: *ui.Session, view: ui.RoutablePage.EventsView) !ui.Widget { │ │411 + const event_window = data.window(view); │ │412 + if (event_window.isEmpty()) return .{ .center = try ui.widget.initEmpty(allocator) }; │ │413 + │ │414 var content_box = try wgt.Box(ui.Widget).init(allocator, .{ .border = null, .direction = .horiz }); │ │415 errdefer content_box.deinit(allocator); │ │416 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │417 var list_box = try wgt.Box(ui.Widget).init(allocator, .{ .border = null, .direction = .vert, .stretch = true }); │ │418 errdefer list_box.deinit(allocator); │ │410 - const event_window = data.window(view); │ │419 for (event_window.events) |event| { │ │420 const route = ui.RoutablePage.repoEventsRoute(data.identity, view, event.kind, event.id, data.moment) orelse return error.RouteTooLong;│ │421 const link = try session.page_arena.allocator().print("ai:{s}", .{try route.toUrl(session.page_arena)}); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │446 } │ │447 try content_box.children.put(allocator, frame.getFocus().id, .{ .widget = .{ .box = frame }, .rect = null, .min_size = .{ .width = detail_min_width, .height = null } });│ │448 │ │441 - content_box.getFocus().child_id = content_box.children.keys()[if (event_window.events.len > 0) detail_index else list_index]; │ │442 - return content_box; │ │449 + content_box.getFocus().child_id = content_box.children.keys()[detail_index]; │ │450 + return .{ .box = content_box }; │ │451 } │ │452 │ │453 fn addRow(allocator: std.mem.Allocator, box: *wgt.Box(ui.Widget), text: []const u8, link: []const u8) !void { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │523 self.clearGrid(); │ │524 if (self.header().getSelectedIndex()) |index| │ │525 self.contentStack().getFocus().child_id = self.contentStack().children.keys()[index]; │ │526 + if (self.eventWindow().isEmpty()) return self.box.build(allocator, constraint, root_focus);│ │527 try self.refreshDetail(allocator); │ │528 │ │529 const list = self.listBox(); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │583 // scrolling crosses the sub header boundary the same way arrows do │ │584 const direction = inp.vertDirection(key); │ │585 if (self.headerActive()) { │ │577 - if (direction == .down) root_focus.setFocus(self.content().getFocus().id) else try self.header().input(allocator, key, root_focus);│ │586 + if (direction == .down) { │ │587 + // the empty message has nothing to focus │ │588 + if (!self.eventWindow().isEmpty()) root_focus.setFocus(self.content().getFocus().id); │ │589 + } else try self.header().input(allocator, key, root_focus); │ │590 return; │ │591 } │ │592 // the way back to the list, by enter or a click │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────────╮ │src/ui/Repo/Files.zig│ ╰─────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │103 }; │ │104 const route = self.handle.location.filesRoute(self.ref_or_oid, self.ref_or_oid_value, path, line) orelse return null;│ │105 return route.withFind(self.find orelse return route); │ │106 +} │ │107 + │ │108 +// an empty root listing, which shows the empty message │ │109 +fn isEmpty(self: *const Self) bool { │ │110 + return self.entries.len == 0 and self.dir.len == 0; │ │111 } │ │112 │ │113 // build the listing for an opened repo. generic over the repo's backend and │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │384 errdefer header_view.deinit(allocator); │ │385 // a row taller than the header, leaving a blank line beneath it │ │386 try outer.children.put(allocator, header_view.getFocus().id, .{ .widget = .{ .search_header = header_view }, .rect = null, .min_size = .{ .width = null, .height = 4 } });│ │387 + } │ │388 + │ │389 + // an empty root listing shows the empty message in place of the split │ │390 + if (data.isEmpty()) { │ │391 + var center = try ui.widget.initEmpty(allocator); │ │392 + errdefer center.deinit(allocator); │ │393 + try outer.children.put(allocator, center.getFocus().id, .{ .widget = .{ .center = center }, .rect = null, .min_size = null }); │ │394 + // with nothing below to focus, focus rests on the sub-header │ │395 + outer.getFocus().child_id = outer.children.keys()[header_index]; │ │396 + return .{ .box = outer, .data = data, .session = session }; │ │397 } │ │398 │ │399 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border = null, .direction = .horiz }); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │617 │ │618 pub fn build(self: *View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void {│ │619 self.clearGrid(); │ │605 - if (self.data.no_commits) return self.box.build(allocator, constraint, root_focus); │ │620 + if (self.data.no_commits or self.data.isEmpty()) return self.box.build(allocator, constraint, root_focus); │ │621 │ │622 // whether the detail pane fits beside the list, which decides the list │ │623 // cap below and whether the detail carries a way back to the list │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────╮ │784 // active means there is one │ │785 const header_view = self.header() orelse unreachable; │ │786 if (direction == .down) { │ │772 - root_focus.setFocus(self.contentBox().getFocus().id); │ │787 + // the empty message has nothing to focus │ │788 + if (!self.data.isEmpty()) root_focus.setFocus(self.contentBox().getFocus().id);│ │789 return; │ │790 } │ │791 if (key == .enter) if (try header_view.submittedText(allocator)) |term| { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 998 │ │ 999 // only the header's own row sits directly below the repository header. │ │1000 pub fn atTop(self: *View) bool { │ │ 985 - if (self.data.no_commits) return true; │ │1001 + if (self.data.no_commits or self.data.isEmpty()) return true; │ │1002 const focusable = if (self.header()) |header_view| header_view.hasFocusable() else false;│ │1003 return self.headerActive() or (!focusable and self.contentAtTop()); │ │1004 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯