╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭────────────────────────────────────────────────────────────╮ │ 95 │ │ 96 pub const RefKind = enum { branch, tag }; │ │ 97 │ │ 98 + pub const IssuesView = enum { open, closed, tags };│ │ 99 + │ │100 pub const RefOrOid = enum { │ │101 branch, │ │102 tag, │ ╰────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │232 return .{ .repo_refs = .{ .name = Array(repo_route_max_len).from(identity) orelse return null, .kind = kind, .after = after } };│ │233 } │ │234 │ │231 - // build a `.repo_issues` route for "owner/name" (identity) filtered to the │ │232 - // url-encoded `tag` ("" = unfiltered) and rooted at the issue with hex │ │233 - // event id `selected` ("" = the first window). │ │234 - pub fn repoIssuesRoute(identity: []const u8, tag: []const u8, selected: []const u8) ?RoutablePage { │ │235 + // build a `.repo_issues` route for "owner/name" (identity) showing │ │236 + // `status`'s list, filtered to the url-encoded `tag` ("" = unfiltered) and │ │237 + // rooted at the issue with hex event id `selected` ("" = the first window). │ │238 + pub fn repoIssuesRoute(identity: []const u8, status: evt.Issue.Status, tag: []const u8, selected: []const u8) ?RoutablePage { │ │239 return .{ .repo_issues = .{ │ │240 .name = Array(repo_route_max_len).from(identity) orelse return null, │ │241 .tag = Array(issue_tag_route_max_len).from(tag) orelse return null, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────╮ │242 .selected = Array(evt.event_id_size * 2).from(selected) orelse return null,│ │243 + .view = switch (status) { │ │244 + .open => .open, │ │245 + .closed => .closed, │ │246 + }, │ │247 } }; │ │248 } │ │249 │ ╰────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │242 - // build a `.repo_issues` route showing "owner/name" (identity)'s tags view. │ │243 - pub fn repoIssuesTagsRoute(identity: []const u8) ?RoutablePage { │ │250 + // build a `.repo_issues` route showing "owner/name" (identity)'s tags view, │ │251 + // keeping the url-encoded `tag` filter ("" = unfiltered). │ │252 + pub fn repoIssuesTagsRoute(identity: []const u8, tag: []const u8) ?RoutablePage {│ │253 return .{ .repo_issues = .{ │ │254 .name = Array(repo_route_max_len).from(identity) orelse return null, │ │255 + .tag = Array(issue_tag_route_max_len).from(tag) orelse return null, │ │256 .view = .tags, │ │257 } }; │ │258 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │318 }, │ │319 .repo_issues => |i| blk: { │ │320 const prefix = try repoUrlPrefix(arena, i.name.slice()); │ │321 + // list urls name the view; a selected issue's url is status-free │ │322 + // so it survives the issue changing status. │ │323 break :blk switch (i.view) { │ │312 - .tags => try std.fmt.allocPrint(arena.allocator(), "{s}/issues/tags", .{prefix}), │ │313 - .results => if (i.tag.len == 0) │ │314 - (if (i.selected.len == 0) │ │315 - try std.fmt.allocPrint(arena.allocator(), "{s}/issues", .{prefix}) │ │324 + .tags => if (i.tag.len == 0) │ │325 + try std.fmt.allocPrint(arena.allocator(), "{s}/issues/tags", .{prefix}) │ │326 + else │ │327 + try std.fmt.allocPrint(arena.allocator(), "{s}/issues/tags/{s}", .{ prefix, i.tag.slice() }), │ │328 + .open, .closed => |view| if (i.selected.len != 0) │ │329 + (if (i.tag.len == 0) │ │330 + try std.fmt.allocPrint(arena.allocator(), "{s}/issues/{s}", .{ prefix, i.selected.slice() }) │ │331 else │ │317 - try std.fmt.allocPrint(arena.allocator(), "{s}/issues/{s}", .{ prefix, i.selected.slice() })) │ │318 - else if (i.selected.len == 0) │ │319 - try std.fmt.allocPrint(arena.allocator(), "{s}/issues/tags/{s}", .{ prefix, i.tag.slice() }) │ │332 + try std.fmt.allocPrint(arena.allocator(), "{s}/issues/tags/{s}/{s}", .{ prefix, i.tag.slice(), i.selected.slice() }))│ │333 + else if (i.tag.len == 0) │ │334 + try std.fmt.allocPrint(arena.allocator(), "{s}/issues/{s}", .{ prefix, @tagName(view) }) │ │335 else │ │321 - try std.fmt.allocPrint(arena.allocator(), "{s}/issues/tags/{s}/{s}", .{ prefix, i.tag.slice(), i.selected.slice() }), │ │336 + try std.fmt.allocPrint(arena.allocator(), "{s}/issues/{s}/tags/{s}", .{ prefix, @tagName(view), i.tag.slice() }), │ │337 }; │ │338 }, │ │339 .repo_settings => |name| try std.fmt.allocPrint(arena.allocator(), "{s}/settings", .{try repoUrlPrefix(arena, name.slice())}), │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────╮ │426 .after = after, │ │427 } }; │ │428 if (std.mem.eql(u8, sub, "issues") or std.mem.startsWith(u8, sub, "issues/")) {│ │414 - // "issues[/<issue event id>]", "issues/tags" (the tags view), or │ │415 - // "issues/tags/<tag>[/<issue event id>]" (the list filtered to <tag>) │ │429 + // "issues[/<issue event id>]", "issues/open|closed[/tags/<tag>]", │ │430 + // "issues/tags[/<tag>]" (the tags view, optionally filtered), or │ │431 + // "issues/tags/<tag>/<issue event id>" │ │432 var segments = std.mem.splitScalar(u8, sub, '/'); │ │433 _ = segments.next(); // "issues" │ │434 + var status: evt.Issue.Status = .open; │ │435 var tag_value: []const u8 = ""; │ │436 var id: []const u8 = ""; │ │437 if (segments.next()) |segment| { │ ╰────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │421 - if (std.mem.eql(u8, segment, "tags")) { │ │422 - tag_value = segments.next() orelse return repoIssuesTagsRoute(pair); │ │438 + if (std.meta.stringToEnum(evt.Issue.Status, segment)) |s| { │ │439 + status = s; │ │440 + if (segments.next()) |tags_segment| { │ │441 + if (!std.mem.eql(u8, tags_segment, "tags")) return null; │ │442 + tag_value = segments.next() orelse return null; │ │443 + if (tag_value.len == 0) return null; │ │444 + } │ │445 + } else if (std.mem.eql(u8, segment, "tags")) { │ │446 + tag_value = segments.next() orelse return repoIssuesTagsRoute(pair, "");│ │447 if (tag_value.len == 0) return null; │ │424 - id = segments.next() orelse ""; │ │448 + id = segments.next() orelse return repoIssuesTagsRoute(pair, tag_value);│ │449 + if (id.len == 0) return null; │ │450 } else { │ │451 if (segment.len == 0) return null; │ │452 id = segment; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │453 } │ │454 } │ │455 if (segments.next() != null) return null; │ │431 - return repoIssuesRoute(pair, tag_value, id); │ │456 + return repoIssuesRoute(pair, status, tag_value, id); │ │457 } │ │458 if (std.mem.eql(u8, sub, "settings")) return .{ .repo_settings = Array(repo_route_max_len).from(pair) orelse return null };│ │459 if (std.mem.eql(u8, sub, "auth")) return .{ .repo_auth = Array(repo_route_max_len).from(pair) orelse return null }; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/Repo.zig│ ╰───────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │107 .repo_refs => |r| r.after, │ │108 else => 0, │ │109 }; │ │110 - // the issues tab's tag filter and the issue its window is rooted at. │ │110 + // the issues tab's tag filter, the issue its window is rooted at, and the│ │111 + // view it shows. │ │112 const issues_tag: []const u8 = switch (route) { │ │113 .repo_issues => |i| i.tag.slice(), │ │114 else => "", │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │117 .repo_issues => |i| i.selected.slice(), │ │118 else => "", │ │119 }; │ │120 + const issues_view: ui.RoutablePage.IssuesView = switch (route) { │ │121 + .repo_issues => |i| i.view, │ │122 + else => .open, │ │123 + }; │ │124 │ │125 // where the on-disk repo lives (null keeps the views' empty fallback), plus│ │126 // the repo and owner-name metadata the header shows. local mode already │ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯