╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │46 else => return error.UnexpectedRoute, │ │47 }, │ │48 .repo => switch (route) { │ │49 - .repo_files, .repo_commits, .repo_refs, .repo_settings, .repo_auth => .{ .repo = try Repo.init(arena, session, route) }, │ │49 + .repo_files, .repo_commits, .repo_refs, .repo_issues, .repo_settings, .repo_auth => .{ .repo = try Repo.init(arena, session, route) },│ │50 else => return error.UnexpectedRoute, │ │51 }, │ │52 }; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │75 repo_files: struct { name: Array(repo_route_max_len), after: usize = 0 }, │ │76 repo_commits: struct { name: Array(repo_route_max_len), after: usize = 0 }, │ │77 repo_refs: struct { name: Array(repo_route_max_len), kind: RefKind = .branch, after: usize = 0 }, │ │78 + repo_issues: struct { name: Array(repo_route_max_len), selected: Array(evt.event_id_size * 2) = .{} },│ │79 repo_settings: Array(repo_route_max_len), │ │80 repo_auth: Array(repo_route_max_len), │ │81 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │198 // column to `after` (0 = the first window, the other column always at 0). │ │199 pub fn repoRefsRoute(identity: []const u8, kind: RefKind, after: usize) ?RoutablePage { │ │200 return .{ .repo_refs = .{ .name = Array(repo_route_max_len).from(identity) orelse return null, .kind = kind, .after = after } };│ │201 + } │ │202 + │ │203 + // build a `.repo_issues` route for "owner/name" (identity) rooted at the │ │204 + // issue with hex event id `selected` ("" = the first window). │ │205 + pub fn repoIssuesRoute(identity: []const u8, selected: []const u8) ?RoutablePage { │ │206 + return .{ .repo_issues = .{ │ │207 + .name = Array(repo_route_max_len).from(identity) orelse return null, │ │208 + .selected = Array(evt.event_id_size * 2).from(selected) orelse return null, │ │209 + } }; │ │210 } │ │211 │ │212 // an inline, owned array of data. keeping it in the route (rather than a │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │254 try std.fmt.allocPrint(arena.allocator(), repo_segment ++ "{s}/refs", .{r.name.slice()}) │ │255 else │ │256 try std.fmt.allocPrint(arena.allocator(), repo_segment ++ "{s}/refs?kind={s}&after={d}", .{ r.name.slice(), @tagName(r.kind), r.after }),│ │257 + .repo_issues => |i| if (i.selected.len == 0) │ │258 + try std.fmt.allocPrint(arena.allocator(), repo_segment ++ "{s}/issues", .{i.name.slice()}) │ │259 + else │ │260 + try std.fmt.allocPrint(arena.allocator(), repo_segment ++ "{s}/issues/{s}", .{ i.name.slice(), i.selected.slice() }), │ │261 .repo_settings => |name| try std.fmt.allocPrint(arena.allocator(), repo_segment ++ "{s}/settings", .{name.slice()}), │ │262 .repo_auth => |name| try std.fmt.allocPrint(arena.allocator(), repo_segment ++ "{s}/auth", .{name.slice()}), │ │263 }; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │267 return switch (self) { │ │268 .home_users, .home_repos, .home_settings, .home_auth => .home, │ │269 .user_repos, .user_settings, .user_auth => .user, │ │256 - .repo_files, .repo_commits, .repo_refs, .repo_settings, .repo_auth => .repo, │ │270 + .repo_files, .repo_commits, .repo_refs, .repo_issues, .repo_settings, .repo_auth => .repo,│ │271 }; │ │272 } │ │273 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │315 .kind = refsKind(query), │ │316 .after = after, │ │317 } }; │ │318 + if (std.mem.eql(u8, sub, "issues")) return repoIssuesRoute(pair, ""); │ │319 + if (std.mem.startsWith(u8, sub, "issues/")) { │ │320 + // "issues/<issue event id>" │ │321 + const id = sub["issues/".len..]; │ │322 + if (id.len == 0) return null; │ │323 + return repoIssuesRoute(pair, id); │ │324 + } │ │325 if (std.mem.eql(u8, sub, "settings")) return .{ .repo_settings = Array(repo_route_max_len).from(pair) orelse return null }; │ │326 if (std.mem.eql(u8, sub, "auth")) return .{ .repo_auth = Array(repo_route_max_len).from(pair) orelse return null }; │ │327 if (std.mem.eql(u8, sub, files_seg)) return repoFilesRoute(pair, null, "", "", after); // trailing /files == default-branch root│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │389 .repo_files => |a_f| std.mem.eql(u8, a_f.name.slice(), b.repo_files.name.slice()) and a_f.after == b.repo_files.after, │ │390 .repo_commits => |a_c| std.mem.eql(u8, a_c.name.slice(), b.repo_commits.name.slice()) and a_c.after == b.repo_commits.after, │ │391 .repo_refs => |a_r| std.mem.eql(u8, a_r.name.slice(), b.repo_refs.name.slice()) and a_r.kind == b.repo_refs.kind and a_r.after == b.repo_refs.after, │ │392 + .repo_issues => |a_i| std.mem.eql(u8, a_i.name.slice(), b.repo_issues.name.slice()) and std.mem.eql(u8, a_i.selected.slice(), b.repo_issues.selected.slice()),│ │393 .repo_settings => |a_name| std.mem.eql(u8, a_name.slice(), b.repo_settings.slice()), │ │394 .repo_auth => |a_name| std.mem.eql(u8, a_name.slice(), b.repo_auth.slice()), │ │395 else => true, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────╮ │459 try jw.objectField("name"); │ │460 try jw.write(name.slice()); │ │461 }, │ │462 + .repo_issues => |i| { │ │463 + try jw.objectField("name"); │ │464 + try jw.write(i.name.slice()); │ │465 + try jw.objectField("selected"); │ │466 + try jw.write(i.selected.slice());│ │467 + }, │ │468 .repo_files => |f| { │ │469 try jw.objectField("name"); │ │470 try jw.write(f.name.slice()); │ ╰──────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │497 } │ │498 │ │499 pub fn jsonParse(allocator: std.mem.Allocator, source: anytype, options: std.json.ParseOptions) !RoutablePage { │ │472 - const Helper = struct { kind: std.meta.Tag(RoutablePage), name: ?[]const u8 = null, after: usize = 0, ref_kind: ?[]const u8 = null }; │ │500 + const Helper = struct { kind: std.meta.Tag(RoutablePage), name: ?[]const u8 = null, after: usize = 0, ref_kind: ?[]const u8 = null, selected: ?[]const u8 = null };│ │501 const helper = try std.json.innerParse(Helper, allocator, source, options); │ │502 const parseName = struct { │ │503 // errors must stay within std.json's ParseError set; ValueTooLong │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │521 .kind = if (helper.ref_kind) |k| (if (std.mem.eql(u8, k, "tag")) .tag else .branch) else .branch, │ │522 .after = helper.after, │ │523 } }, │ │524 + .repo_issues => .{ .repo_issues = .{ │ │525 + .name = try parseName(repo_route_max_len, helper.name), │ │526 + .selected = Array(evt.event_id_size * 2).from(helper.selected orelse "") orelse return error.ValueTooLong,│ │527 + } }, │ │528 .repo_settings => .{ .repo_settings = try parseName(repo_route_max_len, helper.name) }, │ │529 .repo_auth => .{ .repo_auth = try parseName(repo_route_max_len, helper.name) }, │ │530 }; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯