╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──────────────╮ │src/ui/Repo/Patches.zig│ ╰───────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │504 } │ │505 │ │506 // an empty listing, for the wasm / no-repo paths. │ │508 -pub fn emptyResult(aa: std.mem.Allocator, identity: []const u8, label: []const u8, search: []const u8, selected_id: []const u8, comment_id: []const u8, comments_start: usize, theirs_picks: []const u8, view: ui.RoutablePage.PatchesView) !Self {│ │507 +pub fn emptyResult(aa: std.mem.Allocator, identity: []const u8, search: []const u8, selected_id: []const u8, comment_id: []const u8, comments_start: usize, theirs_picks: []const u8, view: ui.RoutablePage.PatchesView) !Self { │ │508 return .{ │ │509 .identity = try aa.dupe(u8, identity), │ │510 .default_target_branch = "", │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │512 - .label = try aa.dupe(u8, label), │ │511 .search = if (search.len == 0) null else std.Uri.percentDecodeInPlace(try aa.dupe(u8, search)),│ │512 .selected_id = try aa.dupe(u8, selected_id), │ │513 .comment_id = try aa.dupe(u8, comment_id), │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮ │531 } │ │532 │ │533 pub fn detailResult(aa: std.mem.Allocator, identity: []const u8, entry: PatchWithId) !Self { │ │536 - var result = try emptyResult(aa, identity, "", "", entry.id, "", 0, "", .open); │ │534 + var result = try emptyResult(aa, identity, "", entry.id, "", 0, "", .open); │ │535 const items = try aa.dupe(PatchWithId, &.{entry}); │ │536 const detail_window = Window{ .items = items, .prev_id = null, .next_id = null, .count = 1 };│ │537 if (entry.draft) { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │551 return result; │ │552 } │ │553 │ │556 -// read one window per status of an opened repo's patches (filtered to `label` │ │557 -// when set), ordered by creation (newest first). the window of the patch │ │554 +// read one window per status of an opened repo's patches (filtered to the │ │555 +// search when set), ordered by creation (newest first). the window of the patch│ │556 // `selected_id` names starts at it ("" = the beginning). a git repo reads the │ │557 // event db next to it (synced from the events branch on each page build); a │ │558 // xit repo reads its own db. │ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────╮ │569 repo_id_maybe: ?[evt.event_id_size]u8,│ │570 identity: []const u8, │ │571 target_branch: []const u8, │ │574 - label: []const u8, │ │572 search: []const u8, │ │573 selected_id: []const u8, │ │574 comment_id: []const u8, │ ╰───────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │578 viewer: ?ui.Viewer, │ │579 ) !Self { │ │580 const allowed_view: ui.RoutablePage.PatchesView = if (session.local != null and view == .drafts) .open else view; │ │584 - var empty = try emptyResult(arena.allocator(), identity, label, search, selected_id, comment_id, comments_start, theirs_picks, allowed_view);│ │581 + var empty = try emptyResult(arena.allocator(), identity, search, selected_id, comment_id, comments_start, theirs_picks, allowed_view); │ │582 empty.default_target_branch = try arena.allocator().dupe(u8, target_branch); │ │583 // the early returns below still let the viewer write │ │584 empty.viewer = viewer; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │586 const aa = arena.allocator(); │ │587 const DB = evt.EventDB(repo_opts.hash); │ │588 const rooted = empty.selected_id.len != 0; │ │592 - const labeled = empty.label.len != 0; │ │589 const drafts_window = if (admin_moment) |admin| │ │590 if (repo_id_maybe) |repo_id| │ │591 if (session.data.user_id != null and session.users_dir != null)│ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │624 return empty; │ │625 }; │ │626 │ │631 - // the sorted sets to window, per status: the label's sets when filtered, │ │632 - // else the top-level per-status sets. a missing set is an empty window. │ │633 - var open_set: ?DB.SortedSet(.read_only) = null; │ │634 - var closed_set: ?DB.SortedSet(.read_only) = null; │ │635 - var merged_set: ?DB.SortedSet(.read_only) = null; │ │636 - if (labeled) { │ │637 - if (try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Patch.label_status_to_id_set_key))) |label_to_patches_cursor| {│ │638 - const label_to_patches = try DB.SortedMap(.read_only).init(label_to_patches_cursor); │ │639 - const decoded = std.Uri.percentDecodeInPlace(try aa.dupe(u8, empty.label)); │ │640 - open_set = try thread.labelStatusSet(Self, DB, label_to_patches, decoded, .open); │ │641 - closed_set = try thread.labelStatusSet(Self, DB, label_to_patches, decoded, .closed); │ │642 - merged_set = try thread.labelStatusSet(Self, DB, label_to_patches, decoded, .merged); │ │643 - } │ │644 - } else if (try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Patch.status_to_id_set_key))) |status_to_patches_cursor| { │ │645 - const status_to_patches = try DB.SortedMap(.read_only).init(status_to_patches_cursor); │ │646 - open_set = try thread.statusSet(Self, DB, status_to_patches, .open); │ │647 - closed_set = try thread.statusSet(Self, DB, status_to_patches, .closed); │ │648 - merged_set = try thread.statusSet(Self, DB, status_to_patches, .merged); │ │649 - } else if (strict) return error.NotFound; │ │627 + // each status's listing intersects every label's set. │ │628 + // patches have no assignees, so an assignee term lists nothing. │ │629 + const filters = try srch_thrd.parseFilters(aa, empty.search orelse ""); │ │630 + const extra_sets: ?[]const DB.SortedSet(.read_only) = if (filters.assignees.len == 0) &.{} else null; │ │631 + const open_sets = try thread.statusSets(Self, repo_opts.hash, aa, haxy_moment, filters.labels, extra_sets, .open); │ │632 + const closed_sets = try thread.statusSets(Self, repo_opts.hash, aa, haxy_moment, filters.labels, extra_sets, .closed); │ │633 + const merged_sets = try thread.statusSets(Self, repo_opts.hash, aa, haxy_moment, filters.labels, extra_sets, .merged); │ │634 │ │635 const event_id_to_patch_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Patch.record_map_key)) orelse { │ │636 if (strict) return error.NotFound; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────╮ │668 if (!try set.contains(order_key)) return error.NotFound; │ │669 conflicts_root = order_key; │ │670 } else { │ │687 - // the named patch must be in its windowed set (a label url can name │ │688 - // an patch that doesn't carry the label). │ │689 - const set = (switch (patch_event.event.status) { │ │690 - .open => open_set, │ │691 - .closed => closed_set, │ │692 - .merged => merged_set, │ │693 - }) orelse return error.NotFound; │ │694 - if (!try set.contains(order_key)) return error.NotFound; │ │671 + // the named patch must be in every set its window intersects (a │ │672 + // filtered url can name a patch the filters leave out). │ │673 + const sets = switch (patch_event.event.status) { │ │674 + .open => open_sets, │ │675 + .closed => closed_sets, │ │676 + .merged => merged_sets, │ │677 + }; │ │678 + if (!try thread.inEverySet(DB, sets, order_key)) return error.NotFound;│ │679 │ │680 switch (patch_event.event.status) { │ │681 .open => open_root = order_key, │ ╰────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │707 } │ │708 } │ │709 const thread_comments_start = if (empty.comment_id.len == 0) comments_start else 0; │ │726 - var open_window = try thread.loadWindow(Self, .patch, repo_opts.hash, arena, admin_moment, haxy_moment, event_id_to_patch, open_set, open_root, conflict_set, empty.selected_id, thread_comments_start, empty.search, null, viewer); │ │727 - var closed_window = try thread.loadWindow(Self, .patch, repo_opts.hash, arena, admin_moment, haxy_moment, event_id_to_patch, closed_set, closed_root, conflict_set, empty.selected_id, thread_comments_start, empty.search, null, viewer); │ │728 - var merged_window = try thread.loadWindow(Self, .patch, repo_opts.hash, arena, admin_moment, haxy_moment, event_id_to_patch, merged_set, merged_root, conflict_set, empty.selected_id, thread_comments_start, empty.search, null, viewer); │ │729 - var conflicts_window = try thread.loadWindow(Self, .patch, repo_opts.hash, arena, admin_moment, haxy_moment, event_id_to_patch, conflict_set, conflicts_root, conflict_set, empty.selected_id, thread_comments_start, null, null, viewer); │ │710 + var open_window = try thread.loadWindow(Self, .patch, repo_opts.hash, arena, admin_moment, haxy_moment, event_id_to_patch, open_sets, open_root, conflict_set, empty.selected_id, thread_comments_start, filters.words, viewer); │ │711 + var closed_window = try thread.loadWindow(Self, .patch, repo_opts.hash, arena, admin_moment, haxy_moment, event_id_to_patch, closed_sets, closed_root, conflict_set, empty.selected_id, thread_comments_start, filters.words, viewer); │ │712 + var merged_window = try thread.loadWindow(Self, .patch, repo_opts.hash, arena, admin_moment, haxy_moment, event_id_to_patch, merged_sets, merged_root, conflict_set, empty.selected_id, thread_comments_start, filters.words, viewer); │ │713 + var conflicts_window = try thread.loadWindow(Self, .patch, repo_opts.hash, arena, admin_moment, haxy_moment, event_id_to_patch, if (conflict_set) |*set| set[0..1] else &.{}, conflicts_root, conflict_set, empty.selected_id, thread_comments_start, "", viewer);│ │714 try setPatchDetails(repo_kind, repo_opts, io, arena, admin_moment, session.users_dir, haxy_moment, repo, &open_window); │ │715 try setPatchDetails(repo_kind, repo_opts, io, arena, admin_moment, session.users_dir, haxy_moment, repo, &closed_window); │ │716 try setPatchDetails(repo_kind, repo_opts, io, arena, admin_moment, session.users_dir, haxy_moment, repo, &merged_window); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯