╭ changed ───────────────╮ │src/ui/widget/thread.zig│ ╰────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────╮ │← previous│ ╰──────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │377 } else { │ │337 - // issues and patches list in the index's own order, so the listed set │ │338 - // narrows the query and the results stream out already sorted │ │339 - var results = try srch_thrd.query(DB, hash_kind, kind, haxy_moment, aa, query_text, &.{ set, extra_set }); │ │378 + // issues and patches list in the index's own order, so the listed sets │ │379 + // narrow the query and the results stream out already sorted │ │380 + var results: srch.Query(DB) = if (words.len == 0) .{ .terms = .empty } else try srch_thrd.query(DB, hash_kind, kind, haxy_moment, aa, words);│ │381 + for (sets) |set| try results.require(aa, set); │ │382 while (try results.next()) |key| { │ │383 try keys.append(aa, try resultKey(key)); │ │384 if (windowKnown(Data, keys.items, root)) break; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │472 assignee.* = .{ .email = email, .name = if (user) |u| u.name else null }; │ │473 } │ │474 return assignees; │ │433 -} │ │434 - │ │435 -// the url-encoded assignee filter a thread page carries, "" for kinds without one│ │436 -fn assigneeFilter(data: anytype) []const u8 { │ │437 - return if (@hasField(@TypeOf(data.*), "assignee")) data.assignee else ""; │ │475 } │ │476 │ │477 // a tab link's route, carrying the page's query when it has one │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │486 return std.fmt.bufPrint(buffer, "{s} ({d}{s})", .{ name, win.count, if (win.more) "+" else "" });│ │487 } │ │488 │ │452 -// the common sub-header used by each thread page: the search box, the │ │453 -// assignee box on the issues page, then the tab strip │ │489 +// the common sub-header used by each thread page: the search box, then the │ │490 +// tab strip │ │491 pub const Header = struct { │ │492 box: wgt.Box(Widget), │ │493 // focus id -> semantic stack index; some pages omit unavailable tabs │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │494 tab_ids: std.AutoArrayHashMapUnmanaged(usize, usize), │ │458 - // the tab whose view the stack shows. focus lands on the boxes too, │ │495 + // the tab whose view the stack shows. focus lands on the search box too, │ │496 // so the selection is remembered rather than read off the focus chain. │ │497 selected_tab: ?usize = null, │ │461 - // how many text boxes come before the tabs │ │462 - box_count: usize, │ │498 │ │464 - // which box enter submitted from │ │465 - pub const Submission = struct { kind: enum { search, assignee }, text: []u8 }; │ │499 + // the search box's place in the box, ahead of the tabs │ │500 + const search_index: usize = 0; │ │501 │ │467 - // the assignee box's label and prefilled text │ │468 - pub const AssigneeBox = struct { label: []const u8, text: []const u8 }; │ │469 - │ │470 - // a null `assignee` leaves out the assignee box │ │471 - pub fn init(allocator: std.mem.Allocator, session: *ui.Session, search: ?[]const u8, assignee: ?AssigneeBox) !Header { │ │502 + pub fn init(allocator: std.mem.Allocator, session: *ui.Session, search: ?[]const u8, search_label: []const u8) !Header {│ │503 var box = try wgt.Box(Widget).init(allocator, .{ .border = null, .direction = .horiz }); │ │504 errdefer box.deinit(allocator); │ │505 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │506 { │ │476 - var search_box = try widget.SearchBox.init(allocator, session, " search ", "search", search); │ │507 + var search_box = try widget.SearchBox.init(allocator, session, search_label, "search", search); │ │508 errdefer search_box.deinit(allocator); │ │509 box.getFocus().child_id = search_box.getFocus().id; │ │510 try box.children.put(allocator, search_box.getFocus().id, .{ .widget = .{ .search_box = search_box }, .rect = null, .min_size = widget.SearchBox.min_size });│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │511 } │ │512 │ │482 - if (assignee) |value| { │ │483 - var assignee_box = try widget.SearchBox.init(allocator, session, value.label, "assignee", value.text); │ │484 - errdefer assignee_box.deinit(allocator); │ │485 - try box.children.put(allocator, assignee_box.getFocus().id, .{ .widget = .{ .search_box = assignee_box }, .rect = null, .min_size = widget.SearchBox.min_size });│ │486 - } │ │487 - │ │488 - return .{ .box = box, .tab_ids = .empty, .box_count = box.children.count() }; │ │513 + return .{ .box = box, .tab_ids = .empty }; │ │514 } │ │515 │ │516 pub fn addTab(self: *Header, allocator: std.mem.Allocator, label: []const u8, link: []const u8, view_index: usize) !void { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │561 } │ │562 │ │563 pub fn input(self: *Header, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { │ │539 - if (self.focusedBox()) |index| { │ │540 - const search_box = self.searchBox(index); │ │541 - // tab, and right-arrow past the last character, step to the next box or the tabs │ │564 + const search_box = self.searchBox(); │ │565 + if (self.box.getFocus().child_id == search_box.getFocus().id) { │ │566 + // tab, and right-arrow past the last character, step to the tabs │ │567 if (key == .tab or (key == .arrow_right and search_box.atEnd())) { │ │543 - root_focus.setFocus(if (index + 1 < self.box_count) self.searchBox(index + 1).getFocus().id else self.tab_ids.keys()[0]);│ │544 - return; │ │545 - } │ │546 - // back-tab, and left-arrow before the first character, step to the previous box │ │547 - if (index > 0 and (key == .back_tab or (key == .arrow_left and search_box.text_input.cursor == 0))) { │ │548 - root_focus.setFocus(self.searchBox(index - 1).getFocus().id); │ │568 + root_focus.setFocus(self.tab_ids.keys()[0]); │ │569 return; │ │570 } │ │571 return search_box.input(allocator, key, root_focus); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │574 if (key == .back_tab or (key == .arrow_left and current_tab == 0)) { │ │575 // remembered here as well as in build, which may not run between keys│ │576 self.selected_tab = current_tab; │ │557 - root_focus.setFocus(self.searchBox(self.box_count - 1).getFocus().id);│ │577 + root_focus.setFocus(search_box.getFocus().id); │ │578 return; │ │579 } │ │580 if (inp.moveTab(key, current_tab, self.tab_ids.count())) |new_tab| { │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │582 } │ │583 } │ │584 │ │565 - // the typed text when enter should submit it: a box holds focus. the │ │585 + // the typed query when enter should submit it: the box holds focus. the │ │586 // caller owns the returned text. │ │567 - pub fn submittedText(self: *Header, allocator: std.mem.Allocator) !?Submission { │ │568 - const index = self.focusedBox() orelse return null; │ │569 - return .{ .kind = if (index == 0) .search else .assignee, .text = try self.searchBox(index).text(allocator) };│ │587 + pub fn submittedText(self: *Header, allocator: std.mem.Allocator) !?[]u8 { │ │588 + const search_box = self.searchBox(); │ │589 + if (self.box.getFocus().child_id != search_box.getFocus().id) return null; │ │590 + return try search_box.text(allocator); │ │591 } │ │592 │ │572 - // the index of the box holding focus, if one does │ │573 - fn focusedBox(self: *Header) ?usize { │ │574 - const child_id = self.box.getFocus().child_id orelse return null; │ │575 - const index = self.box.children.getIndex(child_id) orelse return null; │ │576 - return if (index < self.box_count) index else null; │ │593 + fn searchBox(self: *Header) *widget.SearchBox { │ │594 + return &self.box.children.values()[search_index].widget.search_box; │ │595 } │ │596 │ │579 - fn searchBox(self: *Header, index: usize) *widget.SearchBox { │ │580 - return &self.box.children.values()[index].widget.search_box; │ │581 - } │ │582 - │ │597 pub fn clearGrid(self: *Header) void { │ │598 self.box.clearGrid(); │ │599 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │839 return null; │ │840 } │ │841 │ │828 - fn listRoute(identity: []const u8, status: Status, label: []const u8, selected: []const u8) ?ui.RoutablePage {│ │829 - return Data.listRoute(identity, status, label, selected); │ │842 + fn listRoute(identity: []const u8, status: Status, selected: []const u8) ?ui.RoutablePage { │ │843 + return Data.listRoute(identity, status, selected); │ │844 } │ │845 │ │846 fn draftsRoute(identity: []const u8) ?ui.RoutablePage { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯