╭ changed ──╮ │src/try.zig│ ╰───────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │533 │ │534 var issue_events: [issue_data.len]evt.EventWithId = undefined; │ │535 for (issue_data, 0..) |issue, i| { │ │536 - // the newest issue's description runs past what the detail │ │536 + // the fifth-newest issue's description runs past what the detail│ │537 // pane shows, so it shows the truncated description and its │ │538 // link. │ │539 - const description = if (i == issue_data.len - 1) desc: { │ │539 + const description = if (i == issue_data.len - 5) desc: { │ │540 var desc_writer = std.Io.Writer.Allocating.init(allocator); │ │541 defer desc_writer.deinit(); │ │542 try desc_writer.writer.print("{s}", .{issue.description}); │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │678 │ │679 try evt.consume(.xit, .{}, io, allocator, &template_repo, evt.events_ref, &.{});│ │680 │ │681 - // seed a small comment tree on the second-newest issue │ │681 + // seed a small comment tree on the newest issue │ │682 var comment_ids: [5][evt.event_id_size]u8 = undefined; │ │683 for (&comment_ids) |*id| id.* = evt.EventWithId.randomId(prng.random()); │ │684 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │697 .timestamp = @intCast(200 + i), │ │698 .author_email = user_data[(i + 1) % user_data.len].email, │ │699 .event = .{ .comment = .{ │ │700 - .thread_id = issue_events[issue_events.len - 2].id, │ │700 + .thread_id = issue_events[issue_events.len - 1].id, │ │701 .parent_id = switch (i) { │ │702 - 0, 1 => issue_events[issue_events.len - 2].id, │ │702 + 0, 1 => issue_events[issue_events.len - 1].id, │ │703 2, 4 => std.fmt.bytesToHex(comment_ids[0], .lower),│ │704 3 => std.fmt.bytesToHex(comment_ids[2], .lower), │ │705 else => unreachable, │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │51 }; │ │52 }, │ │53 .repo => switch (route) { │ │54 - .repo_files, .repo_commits, .repo_refs, .repo_issues, .repo_settings, .repo_auth => .{ .repo = try Repo.init(arena, session, route) }, │ │54 + .repo_files, .repo_commits, .repo_refs, .repo_issues, .repo_comments, .repo_settings, .repo_auth => .{ .repo = try Repo.init(arena, session, route) },│ │55 else => return error.UnexpectedRoute, │ │56 }, │ │57 }; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │ 99 // the view the url names; a route with `selected` derives its view│ │100 // from that issue's status instead. │ │101 view: IssuesView = .open, │ │102 + // the selected issue's comment window (0 = the first window). │ │103 + comments_start: usize = 0, │ │104 + }, │ │105 + repo_comments: struct { │ │106 + name: Array(repo_route_max_len), │ │107 + thread: Array(evt.event_id_size * 2), │ │108 + selected: Array(evt.event_id_size * 2), │ │109 + start: usize = 0, │ │110 }, │ │111 repo_settings: Array(repo_route_max_len), │ │112 repo_auth: Array(repo_route_max_len), │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │353 } }; │ │354 } │ │355 │ │356 + // build a selected issue route at one of its comment windows. │ │357 + pub fn repoIssueCommentsRoute(identity: []const u8, selected: []const u8, start: usize) ?RoutablePage { │ │358 + var route = repoIssuesRoute(identity, .open, "", selected) orelse return null; │ │359 + route.repo_issues.comments_start = start; │ │360 + return route; │ │361 + } │ │362 + │ │363 + // build a comment permalink route at one of its immediate-reply windows. │ │364 + pub fn repoCommentsRoute(identity: []const u8, thread: []const u8, selected: []const u8, start: usize) ?RoutablePage {│ │365 + if (thread.len == 0 or selected.len == 0) return null; │ │366 + return .{ .repo_comments = .{ │ │367 + .name = Array(repo_route_max_len).from(identity) orelse return null, │ │368 + .thread = Array(evt.event_id_size * 2).from(thread) orelse return null, │ │369 + .selected = Array(evt.event_id_size * 2).from(selected) orelse return null, │ │370 + .start = start, │ │371 + } }; │ │372 + } │ │373 + │ │374 // build a `.repo_issues` route showing tags view, keeping the url-encoded │ │375 // `tag` filter ("" = unfiltered). │ │376 pub fn repoIssuesTagsRoute(identity: []const u8, tag: []const u8) ?RoutablePage { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │524 // a selected issue's url carries its id instead of a view │ │525 // name, so it survives status changes │ │526 const word = if (i.selected.len != 0) i.selected.slice() else @tagName(i.view); │ │501 - break :blk if (i.tag.len == 0) │ │527 + break :blk if (i.tag.len == 0 and i.comments_start == 0) │ │528 try std.fmt.allocPrint(arena.allocator(), "{s}/issues/{s}", .{ prefix, word }) │ │529 + else if (i.tag.len == 0) │ │530 + try std.fmt.allocPrint(arena.allocator(), "{s}/issues/{s}/" ++ start_seg ++ "{d}", .{ prefix, word, i.comments_start }) │ │531 else │ │532 try std.fmt.allocPrint(arena.allocator(), "{s}/issues/{s}/" ++ tag_filter_seg ++ "{s}", .{ prefix, word, i.tag.slice() }); │ │533 + }, │ │534 + .repo_comments => |c| blk: { │ │535 + const prefix = try repoUrlPrefix(arena, c.name.slice()); │ │536 + break :blk if (c.start == 0) │ │537 + try std.fmt.allocPrint(arena.allocator(), "{s}/issues/{s}/comments/{s}", .{ prefix, c.thread.slice(), c.selected.slice() }) │ │538 + else │ │539 + try std.fmt.allocPrint(arena.allocator(), "{s}/issues/{s}/comments/{s}/" ++ start_seg ++ "{d}", .{ prefix, c.thread.slice(), c.selected.slice(), c.start });│ │540 }, │ │541 .repo_settings => |name| try std.fmt.allocPrint(arena.allocator(), "{s}/settings", .{try repoUrlPrefix(arena, name.slice())}), │ │542 .repo_auth => |name| try std.fmt.allocPrint(arena.allocator(), "{s}/auth", .{try repoUrlPrefix(arena, name.slice())}), │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────╮ │600 pub fn fullHeight(self: RoutablePage) bool { │ │601 return switch (self) { │ │602 .repo_issues => |i| i.view == .resolve,│ │603 + .repo_comments => true, │ │604 else => false, │ │605 }; │ │606 } │ ╰────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────╮ │613 .repo_commits => |*c| c.name.slice(), │ │614 .repo_refs => |*r| r.name.slice(), │ │615 .repo_issues => |*i| i.name.slice(), │ │616 + .repo_comments => |*c| c.name.slice(), │ │617 .repo_settings, .repo_auth => |*name| name.slice(),│ │618 else => null, │ │619 }; │ ╰────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │638 return switch (self) { │ │639 .home_users, .home_repos, .home_settings, .home_auth => .home, │ │640 .user_repos, .user_settings, .user_auth => .user, │ │604 - .repo_files, .repo_commits, .repo_refs, .repo_issues, .repo_settings, .repo_auth => .repo, │ │641 + .repo_files, .repo_commits, .repo_refs, .repo_issues, .repo_comments, .repo_settings, .repo_auth => .repo,│ │642 }; │ │643 } │ │644 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯