╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Issues.zig│ ╰──────────────────────╯ ╭────────────────────────────────────────────────────────╮ │212 admin_moment: ?evt.AdminDB.HashMap(.read_only),│ │213 identity: []const u8, │ │214 label: []const u8, │ │215 + assignee: []const u8, │ │216 search: []const u8, │ │217 selected_id: []const u8, │ │218 comment_id: []const u8, │ ╰────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │221 view: ui.RoutablePage.IssuesView, │ │222 viewer: ?ui.Viewer, │ │223 ) !Self { │ │218 - const empty = try emptyResult(arena.allocator(), identity, label, search, selected_id, comment_id, comments_start, theirs_picks, view); │ │224 + const empty = try emptyResult(arena.allocator(), identity, label, assignee, search, selected_id, comment_id, comments_start, theirs_picks, view);│ │225 │ │226 const aa = arena.allocator(); │ │227 const DB = evt.EventDB(repo_opts.hash); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │263 closed_set = try thread.statusSet(Self, DB, status_to_issues, .closed); │ │264 } else if (rooted) return error.NotFound; │ │265 │ │266 + // the assignee's issues, intersected with each status's set when windowed. │ │267 + // a name resolves to its user's email. an unknown name or an email no │ │268 + // issue carries lists nothing and marks the box, like the assign input. │ │269 + var assignee_set: ?DB.SortedSet(.read_only) = null; │ │270 + const assignee_found = empty.assignee.len == 0 or found: { │ │271 + const decoded = std.Uri.percentDecodeInPlace(try aa.dupe(u8, empty.assignee)); │ │272 + const email = switch (try ui.assigneeEmail(admin_moment, arena, decoded)) { │ │273 + .email => |email| email, │ │274 + .failure => break :found false, │ │275 + }; │ │276 + const by_assignee_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Assignment.assignee_to_issue_id_set_key)) orelse break :found false;│ │277 + const by_assignee = try DB.HashMap(.read_only).init(by_assignee_cursor); │ │278 + const set_cursor = try by_assignee.getCursor(hash.hashInt(repo_opts.hash, email)) orelse break :found false; │ │279 + assignee_set = try DB.SortedSet(.read_only).init(set_cursor); │ │280 + break :found true; │ │281 + }; │ │282 + if (!assignee_found) { │ │283 + open_set = null; │ │284 + closed_set = null; │ │285 + } │ │286 + │ │287 const event_id_to_issue_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Issue.record_map_key)) orelse { │ │288 if (strict) return error.NotFound; │ │289 return empty; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │326 .closed => closed_set, │ │327 }) orelse return error.NotFound; │ │328 if (!try set.contains(order_key)) return error.NotFound; │ │329 + if (assignee_set) |assignee_issues| if (!try assignee_issues.contains(order_key)) return error.NotFound;│ │330 │ │331 switch (issue_event.event.status) { │ │332 .open => open_root = order_key, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │357 } │ │358 │ │359 const thread_comments_start = if (empty.comment_id.len == 0) comments_start else 0; │ │332 - 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, viewer); │ │333 - 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, viewer); │ │334 - 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, viewer); │ │360 + 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, assignee_set, viewer); │ │361 + 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, assignee_set, viewer);│ │362 + 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, null, viewer); │ │363 if (view == .conflicts and conflicts_window.count > 0) resolved_view = .conflicts; │ │364 │ │365 const comment_page = if (empty.comment_id.len == 0) │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────╮ │372 return .{ │ │373 .identity = empty.identity, │ │374 .label = empty.label, │ │375 + .assignee = empty.assignee, │ │376 + .assignee_found = assignee_found,│ │377 .search = empty.search, │ │378 .selected_id = empty.selected_id,│ │379 .comment_id = empty.comment_id, │ ╰──────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │409 │ │410 // tabs switching between the issues page's views │ │411 pub fn initHeader(allocator: std.mem.Allocator, session: *ui.Session, data: *const Self) !Header { │ │382 - var header = try Header.init(allocator, session, data.search); │ │412 + var header = try Header.init(allocator, session, data.search, .{ │ │413 + .label = if (data.assignee_found) " assignee " else " assignee (not found) ", │ │414 + .text = std.Uri.percentDecodeInPlace(try session.page_arena.allocator().dupe(u8, data.assignee)),│ │415 + }); │ │416 errdefer header.deinit(allocator); │ │417 const selected_index = View.viewIndex(data.view); │ │418 const page_selected = std.meta.activeTag(session.data.current_page) == .repo_issues; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │420 // a list tab per status, labeled with its listing's issue count │ │421 const status_labels = [_][]const u8{ open_tab_label, closed_tab_label }; │ │422 for ([_]evt.Issue.Status{ .open, .closed }, status_labels, 0..) |status, status_label, index| { │ │390 - const route = try thread.searchRoute(ui.RoutablePage.repoIssuesRoute(data.identity, status, data.label, ""), data.search); │ │423 + const route = try thread.searchRoute(ui.RoutablePage.withEncodedAssignee(ui.RoutablePage.repoIssuesRoute(data.identity, status, data.label, ""), data.assignee), data.search);│ │424 const link = try ui.inPageTabLink(session, route, page_selected and selected_index == index); │ │425 var label_buf: [64]u8 = undefined; │ │426 const label = try thread.countLabel(&label_buf, status_label, data.window(status).*); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │429 │ │430 // labels tab │ │431 { │ │399 - const labels_route = try thread.searchRoute(ui.RoutablePage.repoThreadLabelsRoute(.issue, data.identity, data.label), data.search); │ │432 + const labels_route = try thread.searchRoute(ui.RoutablePage.withEncodedAssignee(ui.RoutablePage.repoThreadLabelsRoute(.issue, data.identity, data.label), data.assignee), data.search);│ │433 const labels_link = try ui.inPageTabLink(session, labels_route, page_selected and selected_index == View.viewIndex(.labels)); │ │434 try header.addTab(allocator, labels_tab_label, labels_link, View.viewIndex(.labels)); │ │435 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────────╮ │src/ui/Repo/Patches.zig│ ╰───────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────╮ │147 conflicted: bool = false, │ │148 comments: Comment.Window = .empty, │ │149 attachments: []const Attachment.WithId = &.{}, │ │150 - assignees: []const []const u8 = &.{}, │ │150 + assignees: []const ui.Assignee = &.{}, │ │151 // whether the viewer may change the thread, computed here so the web ui has it│ │152 can_modify: bool = false, │ │153 draft: bool = false, │ ╰────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯