╭ changed ───────────────╮ │src/ui/widget/thread.zig│ ╰────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1913 inline for (@typeInfo(Status).@"enum".field_values) |value| { │ │1914 var split = try initSplit(allocator, session, data, @fromBackingInt(@intCast(value)), false);│ │1915 errdefer split.deinit(allocator); │ │1916 - try stack.children.put(allocator, split.getFocus().id, .{ .box = split }); │ │1916 + try stack.children.put(allocator, split.getFocus().id, split); │ │1917 } │ │1918 │ │1919 - // the labels view │ │1920 - { │ │1919 + // the labels view, or the empty message without labels │ │1920 + if (labelsEmpty(data)) { │ │1921 + var center = try widget.initEmpty(allocator); │ │1922 + errdefer center.deinit(allocator); │ │1923 + try stack.children.put(allocator, center.getFocus().id, .{ .center = center }); │ │1924 + } else { │ │1925 var tf = try WordFlow.init(allocator); │ │1926 errdefer tf.deinit(allocator); │ │1927 var items: std.ArrayList(WordFlow.Item) = .empty; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │2052 if (supports_drafts) { │ │2053 var split = try initSplit(allocator, session, data, null, true); │ │2054 errdefer split.deinit(allocator); │ │2051 - try stack.children.put(allocator, split.getFocus().id, .{ .box = split });│ │2055 + try stack.children.put(allocator, split.getFocus().id, split); │ │2056 } │ │2057 │ │2058 // the conflicts split; its tab exists only when the repo has │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │2061 if (supports_conflicts and data.conflicts.count > 0) { │ │2062 var split = try initSplit(allocator, session, data, null, false); │ │2063 errdefer split.deinit(allocator); │ │2060 - try stack.children.put(allocator, split.getFocus().id, .{ .box = split });│ │2064 + try stack.children.put(allocator, split.getFocus().id, split); │ │2065 } │ │2066 │ │2067 // the stack starts on the page's view. │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2078 } │ │2079 │ │2080 // the master-detail split showing `status`'s window, or the conflicts │ │2077 - // window when null. │ │2078 - fn initSplit(allocator: std.mem.Allocator, session: *ui.Session, data: *const Self, status_maybe: ?Status, drafts: bool) !wgt.Box(Widget) { │ │2081 + // window when null. an empty first window shows the empty message instead. │ │2082 + fn initSplit(allocator: std.mem.Allocator, session: *ui.Session, data: *const Self, status_maybe: ?Status, drafts: bool) !Widget { │ │2083 + const win = if (status_maybe) |status| data.window(status) else if (drafts and supports_drafts) &data.drafts else if (supports_conflicts) &data.conflicts else unreachable;│ │2084 + if (windowEmpty(win)) return .{ .center = try widget.initEmpty(allocator) }; │ │2085 + │ │2086 var box = try wgt.Box(Widget).init(allocator, .{ .border = null, .direction = .horiz }); │ │2087 errdefer box.deinit(allocator); │ │2081 - │ │2082 - const win = if (status_maybe) |status| data.window(status) else if (drafts and supports_drafts) &data.drafts else if (supports_conflicts) &data.conflicts else unreachable;│ │2088 │ │2089 // the thread list (one focusable row per title), plus a "next" link that │ │2090 // reloads the page rooted at the following thread. │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2131 │ │2132 // the page opens on the list, unless the url names a thread │ │2133 box.getFocus().child_id = box.children.keys()[if (data.selected_id.len > 0) detail_index else list_index];│ │2129 - return box; │ │2134 + return .{ .box = box }; │ │2135 } │ │2136 │ │2137 // a thread form, plus the target branch for patches, prefilled from │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │2496 // the labels view's flow inside the stack. │ │2497 fn labelsView(self: *This) *WordFlow { │ │2498 return &self.viewStack().children.values()[labels_view_index].word_flow;│ │2499 + } │ │2500 + │ │2501 + // no labels and no filter to clear, which shows the empty message │ │2502 + fn labelsEmpty(data: *const Self) bool { │ │2503 + return data.label.len == 0 and data.labels.len == 0; │ │2504 } │ │2505 │ │2506 // the non-confirmation form inside the stack │ ╰──────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2574 if (supports_conflicts and index == conflict_view_index) return &self.data.conflicts;│ │2575 if (supports_drafts and index == drafts_view_index) return &self.data.drafts; │ │2576 return self.data.window(splitStatus(index)); │ │2577 + } │ │2578 + │ │2579 + // an empty first window, which shows the empty message │ │2580 + fn windowEmpty(win: *const Window) bool { │ │2581 + return win.items.len == 0 and win.prev_id == null; │ │2582 } │ │2583 │ │2584 fn stackSelectedIndex(self: *This) ?usize { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2587 return stack.children.getIndex(cid); │ │2588 } │ │2589 │ │2575 - // the stack's selected master-detail split (null when the labels view or the │ │2576 - // new-thread form shows). │ │2590 + // the stack's selected master-detail split (null when the labels view, the │ │2591 + // new-thread form, or the empty message shows). │ │2592 fn selectedSplitIndex(self: *This) ?usize { │ │2593 const idx = self.stackSelectedIndex() orelse return null; │ │2579 - return if (idx < status_count or (supports_drafts and idx == drafts_view_index) or idx == conflict_view_index) idx else null;│ │2594 + const split = idx < status_count or (supports_drafts and idx == drafts_view_index) or idx == conflict_view_index; │ │2595 + return if (split and !windowEmpty(self.window(idx))) idx else null; │ │2596 } │ │2597 │ │2598 fn detailActive(self: *This, index: usize) bool { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2723 // view has something to focus; enter in the search box submits │ │2724 // the query; other keys move within the sub-header. │ │2725 if (inp.vertDirection(key) == .down) { │ │2710 - const enterable = if (self.viewStack().getSelected()) |selected| switch (selected.*) {│ │2711 - .word_flow => |*tf| tf.text_boxes.items.len > 0, │ │2712 - // nothing focusable in the unauthorized view │ │2713 - .unauthorized => false, │ │2714 - else => true, │ │2715 - } else false; │ │2726 + const enterable = if (self.stackSelectedIndex()) |idx| │ │2727 + if (idx == labels_view_index) │ │2728 + !labelsEmpty(self.data) │ │2729 + else if (idx == form_view_index) │ │2730 + // nothing focusable in the unauthorized view │ │2731 + self.viewStack().children.values()[idx] != .unauthorized │ │2732 + else │ │2733 + !windowEmpty(self.window(idx)) │ │2734 + else │ │2735 + false; │ │2736 if (enterable) root_focus.setFocus(self.box.children.keys()[stack_index]); │ │2737 } else if (key == .enter) { │ │2738 if (try self.header().submittedText(allocator)) |submission| { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯