╭ changed ─────────────╮ │src/ui/Repo/Issues.zig│ ╰──────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │67 next_id: ?[]const u8, │ │68 // how many issues the listing holds across all windows. │ │69 count: usize, │ │70 + // whether a search stopped counting at its cap, so `count` is a floor. │ │71 + more: bool = false, │ │72 │ │73 pub const empty: Window = .{ .items = &.{}, .prev_id = null, .next_id = null, .count = 0 };│ │74 }; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────╮ │77 identity: []const u8, │ │78 // the url-encoded tag the lists are filtered to ("" = unfiltered). │ │79 tag: []const u8, │ │80 +// the decoded query the lists are filtered to, or null when unfiltered. │ │81 +search: ?[]const u8 = null, │ │82 // the hex event id of the issue its status's window is rooted at ("" = the│ │83 // first window), mirrored into the url. │ │84 selected_id: []const u8, │ ╰───────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │162 } │ │163 │ │164 // an empty listing, for the wasm / no-repo paths. │ │161 -pub fn emptyResult(aa: std.mem.Allocator, identity: []const u8, tag: []const u8, selected_id: []const u8, comment_id: []const u8, comments_start: usize, theirs_picks: []const u8, view: ui.RoutablePage.IssuesView) !Self { │ │165 +pub fn emptyResult(aa: std.mem.Allocator, identity: []const u8, tag: []const u8, search: []const u8, selected_id: []const u8, comment_id: []const u8, comments_start: usize, theirs_picks: []const u8, view: ui.RoutablePage.IssuesView) !Self {│ │166 return .{ │ │167 .identity = try aa.dupe(u8, identity), │ │168 .tag = try aa.dupe(u8, tag), │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │169 + .search = if (search.len == 0) null else std.Uri.percentDecodeInPlace(try aa.dupe(u8, search)),│ │170 .selected_id = try aa.dupe(u8, selected_id), │ │171 .comment_id = try aa.dupe(u8, comment_id), │ │172 .comments_start = comments_start, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────╮ │202 admin_moment: ?evt.AdminDB.HashMap(.read_only),│ │203 identity: []const u8, │ │204 tag: []const u8, │ │205 + search: []const u8, │ │206 selected_id: []const u8, │ │207 comment_id: []const u8, │ │208 comments_start: usize, │ ╰────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │209 theirs_picks: []const u8, │ │210 view: ui.RoutablePage.IssuesView, │ │211 ) !Self { │ │206 - const empty = try emptyResult(arena.allocator(), identity, tag, selected_id, comment_id, comments_start, theirs_picks, view); │ │212 + const empty = try emptyResult(arena.allocator(), identity, tag, search, selected_id, comment_id, comments_start, theirs_picks, view);│ │213 │ │214 const aa = arena.allocator(); │ │215 const DB = evt.EventDB(repo_opts.hash); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │323 } │ │324 │ │325 const thread_comments_start = if (empty.comment_id.len == 0) comments_start else 0; │ │320 - const open_window = try thread.loadWindow(Self, repo_opts.hash, arena, admin_moment, haxy_moment, event_id_to_issue, open_set, open_root, conflict_set, empty.selected_id, thread_comments_start); │ │321 - const closed_window = try thread.loadWindow(Self, repo_opts.hash, arena, admin_moment, haxy_moment, event_id_to_issue, closed_set, closed_root, conflict_set, empty.selected_id, thread_comments_start); │ │322 - const conflicts_window = try thread.loadWindow(Self, repo_opts.hash, arena, admin_moment, haxy_moment, event_id_to_issue, conflict_set, conflicts_root, conflict_set, empty.selected_id, thread_comments_start); │ │326 + const open_window = try thread.loadWindow(Self, .issue, repo_opts.hash, arena, admin_moment, haxy_moment, event_id_to_issue, open_set, open_root, conflict_set, empty.selected_id, thread_comments_start, empty.search); │ │327 + const closed_window = try thread.loadWindow(Self, .issue, repo_opts.hash, arena, admin_moment, haxy_moment, event_id_to_issue, closed_set, closed_root, conflict_set, empty.selected_id, thread_comments_start, empty.search);│ │328 + const conflicts_window = try thread.loadWindow(Self, .issue, repo_opts.hash, arena, admin_moment, haxy_moment, event_id_to_issue, conflict_set, conflicts_root, conflict_set, empty.selected_id, thread_comments_start, null);│ │329 if (view == .conflicts and conflicts_window.count > 0) resolved_view = .conflicts; │ │330 │ │331 const comment_page = if (empty.comment_id.len == 0) │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────╮ │338 return .{ │ │339 .identity = empty.identity, │ │340 .tag = empty.tag, │ │341 + .search = empty.search, │ │342 .selected_id = empty.selected_id,│ │343 .comment_id = empty.comment_id, │ │344 .comments_start = comments_start,│ ╰──────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │372 │ │373 // tabs switching between the issues page's views │ │374 pub fn initHeader(allocator: std.mem.Allocator, session: *ui.Session, data: *const Self) !Header {│ │368 - var header = try Header.init(allocator); │ │375 + var header = try Header.init(allocator, session, data.search); │ │376 errdefer header.deinit(allocator); │ │377 const aa = session.page_arena.allocator(); │ │378 const selected_index = View.viewIndex(data.view); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │381 // a list tab per status, labeled with its listing's issue count │ │382 const status_labels = [_][]const u8{ open_tab_label, closed_tab_label }; │ │383 for ([_]evt.Issue.Status{ .open, .closed }, status_labels, 0..) |status, status_label, index| { │ │377 - const route = ui.RoutablePage.repoIssuesRoute(data.identity, status, data.tag, "") orelse return error.RouteTooLong; │ │384 + const route = try thread.searchRoute(ui.RoutablePage.repoIssuesRoute(data.identity, status, data.tag, ""), data.search);│ │385 const link = try ui.inPageTabLink(session, route, page_selected and selected_index == index); │ │386 var label_buf: [64]u8 = undefined; │ │380 - const label = try std.fmt.bufPrint(&label_buf, "{s} ({d})", .{ status_label, data.window(status).count }); │ │387 + const label = try thread.countLabel(&label_buf, status_label, data.window(status).*); │ │388 try header.addTab(allocator, label, link, index); │ │389 } │ │390 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯