╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──────╮ │src/ui/Repo.zig│ ╰───────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │182 try Files.init(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, rf.identity, requested_ref_or_oid, requested_ref_value, files_dir, files_after),│ │183 try Commits.init(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, rf.identity, requested_ref_or_oid, requested_ref_value, commits_after), │ │184 try Refs.init(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, rf.identity, refs_kind, refs_after), │ │180 - try Issues.init(repo_kind, opened.self_repo_opts, arena, opened, io, is_local, rf.identity, issues_tag, issues_selected), │ │185 + try Issues.init(repo_kind, opened.self_repo_opts, arena, opened, io, is_local, rf.identity, issues_tag, issues_selected, issues_view), │ │186 }; │ │187 }, │ │188 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │194 try Files.emptyResult(aa, rf.identity, requested_ref_or_oid orelse .branch, requested_ref_value, files_dir),│ │195 try Commits.emptyResult(aa, rf.identity, requested_ref_or_oid orelse .branch, requested_ref_value), │ │196 try Refs.emptyResult(arena, rf.identity, refs_kind, refs_after), │ │192 - try Issues.emptyResult(aa, rf.identity, issues_tag, issues_selected), │ │197 + try Issues.emptyResult(aa, rf.identity, issues_tag, issues_selected, issues_view), │ │198 }; │ │199 }; │ │200 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │334 .repo_refs => self.session.data.current_page = .{ .repo_refs = .{ .name = self.data.identity, .kind = self.data.refs.kind, .after = self.data.refs.after } }, │ │335 // the issues tab mirrors this page's tag filter (issue urls │ │336 // themselves never carry the tag). │ │332 - .repo_issues => self.session.data.current_page = ui.RoutablePage.repoIssuesRoute(self.data.identity.slice(), self.data.issues.tag, "") orelse self.session.data.current_page, │ │337 + .repo_issues => self.session.data.current_page = ui.RoutablePage.repoIssuesRoute(self.data.identity.slice(), .open, self.data.issues.tag, "") orelse self.session.data.current_page,│ │338 .home_settings => self.session.data.current_page = .{ .repo_settings = self.data.identity }, │ │339 .home_auth => self.session.data.current_page = .{ .repo_auth = self.data.identity }, │ │340 // the quit tab is tty-only and not a route, so leave current_page │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Header.zig│ ╰──────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │118 const commits_link = try std.fmt.allocPrint(aa, "ai:{s}", .{try commits_route.urlAlloc(session.page_arena)}); │ │119 const refs_route = ui.RoutablePage.repoRefsRoute(identity, .branch, 0) orelse return error.RouteTooLong; │ │120 const refs_link = try std.fmt.allocPrint(aa, "ai:{s}", .{try refs_route.urlAlloc(session.page_arena)}); │ │121 - const issues_route = ui.RoutablePage.repoIssuesRoute(identity, data.issues_tag, "") orelse return error.RouteTooLong; │ │121 + const issues_route = ui.RoutablePage.repoIssuesRoute(identity, .open, data.issues_tag, "") orelse return error.RouteTooLong;│ │122 const issues_link = try std.fmt.allocPrint(aa, "ai:{s}", .{try issues_route.urlAlloc(session.page_arena)}); │ │123 const settings_route = ui.RoutablePage{ .repo_settings = Array.from(identity) orelse return error.RouteTooLong }; │ │124 const settings_link = try std.fmt.allocPrint(aa, "ai:{s}", .{try settings_route.urlAlloc(session.page_arena)}); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Issues.zig│ ╰──────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────╮ │23 pub const IssueWithId = struct { │ │24 id: []const u8, │ │25 issue: evt.Issue, │ │26 +}; │ │27 + │ │28 +// one status's windowed listing. │ │29 +pub const Window = struct { │ │30 + issues: []const IssueWithId, │ │31 + // the id of the previous window's first issue, or null when this window │ │32 + // is already the first. │ │33 + prev_id: ?[]const u8, │ │34 + // the id of the next window's first issue, or null when this is the last │ │35 + // window. │ │36 + next_id: ?[]const u8, │ │37 + // how many issues the listing holds across all windows. │ │38 + count: usize, │ │39 + │ │40 + pub const empty: Window = .{ .issues = &.{}, .prev_id = null, .next_id = null, .count = 0 };│ │41 }; │ │42 │ │43 // "owner/name", so the view can build /repo/owner/name/issues/... links. │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │44 identity: []const u8, │ │30 -// the url-encoded tag the list is filtered to ("" = unfiltered). │ │45 +// the url-encoded tag the lists are filtered to ("" = unfiltered). │ │46 tag: []const u8, │ │32 -// the hex event id of the issue this window is rooted at ("" = the first │ │33 -// window), mirrored into the url. │ │47 +// the hex event id of the issue its status's window is rooted at ("" = the │ │48 +// first window), mirrored into the url. │ │49 selected_id: []const u8, │ │35 -issues: []const IssueWithId, │ │36 -// the id of the previous window's first issue, or null when this window is │ │37 -// already the first. │ │38 -prev_id: ?[]const u8, │ │39 -// the id of the next window's first issue, or null when this is the last window. │ │40 -next_id: ?[]const u8, │ │41 -// how many issues the listing holds across all windows. │ │42 -count: usize, │ │50 +open: Window, │ │51 +closed: Window, │ │52 +// the view the page shows initially (a selected issue's status overrides the route's)│ │53 +view: ui.RoutablePage.IssuesView, │ │54 // every tag in the repo, in sorted order, for the tags view. │ │55 tags: []const []const u8, │ │56 │ ╰──────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │57 const Self = @This(); │ │58 │ │59 +// `status`'s windowed listing. │ │60 +pub fn window(self: *const Self, status: evt.Issue.Status) *const Window { │ │61 + return switch (status) { │ │62 + .open => &self.open, │ │63 + .closed => &self.closed, │ │64 + }; │ │65 +} │ │66 + │ │67 // an empty listing, for the wasm / no-repo paths. │ │49 -pub fn emptyResult(aa: std.mem.Allocator, identity: []const u8, tag: []const u8, selected_id: []const u8) !Self { │ │68 +pub fn emptyResult(aa: std.mem.Allocator, identity: []const u8, tag: []const u8, selected_id: []const u8, view: ui.RoutablePage.IssuesView) !Self {│ │69 return .{ │ │70 .identity = try aa.dupe(u8, identity), │ │71 .tag = try aa.dupe(u8, tag), │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────╮ │72 .selected_id = try aa.dupe(u8, selected_id),│ │54 - .issues = &.{}, │ │55 - .prev_id = null, │ │56 - .next_id = null, │ │57 - .count = 0, │ │73 + .open = .empty, │ │74 + .closed = .empty, │ │75 + .view = view, │ │76 .tags = &.{}, │ │77 }; │ │78 } │ ╰────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │79 │ │62 -// read one window of an opened repo's issues (filtered to `tag` when set), │ │63 -// ordered by creation time (oldest first), starting at the issue │ │64 -// `selected_id` names ("" = the beginning). a local session reads the event │ │65 -// db next to the repo (synced from the events branch on each page build); │ │66 -// a server repo reads its own db. │ │80 +// read one window per status of an opened repo's issues (filtered to `tag` │ │81 +// when set), ordered by creation time (oldest first). the window of the issue│ │82 +// `selected_id` names starts at it ("" = the beginning). a local session │ │83 +// reads the event db next to the repo (synced from the events branch on each │ │84 +// page build); a server repo reads its own db. │ │85 pub fn init( │ │86 comptime repo_kind: rp.RepoKind, │ │87 comptime repo_opts: rp.RepoOpts(repo_kind), │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────╮ │ 92 identity: []const u8, │ │ 93 tag: []const u8, │ │ 94 selected_id: []const u8, │ │ 95 + view: ui.RoutablePage.IssuesView, │ │ 96 ) !Self { │ │ 78 - const empty = try emptyResult(arena.allocator(), identity, tag, selected_id); │ │ 97 + const empty = try emptyResult(arena.allocator(), identity, tag, selected_id, view);│ │ 98 │ │ 99 const aa = arena.allocator(); │ │100 const DB = evt.EventDB(repo_opts.hash); │ ╰────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯