╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │679 // "<id>/edit" is the edit form for that issue, with no params; │ │680 // "<id>/description" is its whole-description page. │ │681 const after_tab = segments.rest(); │ │682 + // "<thread-id>/comments/<comment-id>[/start:<n>]" is a comment │ │683 + // permalink with one window of its immediate replies. │ │684 + { │ │685 + var comment_segments = std.mem.splitScalar(u8, after_tab, '/'); │ │686 + const thread = comment_segments.next() orelse return null; │ │687 + const marker = comment_segments.next(); │ │688 + if (marker) |word| { │ │689 + if (std.mem.eql(u8, word, "comments")) { │ │690 + const comment = comment_segments.next() orelse return null; │ │691 + if (thread.len == 0 or comment.len == 0) return null; │ │692 + return repoCommentsRoute(pair, thread, comment, listStart(&comment_segments) orelse return null);│ │693 + } │ │694 + } │ │695 + } │ │696 if (std.mem.endsWith(u8, after_tab, "/edit")) { │ │697 const id = after_tab[0 .. after_tab.len - "/edit".len]; │ │698 if (id.len == 0 or std.mem.indexOfScalar(u8, id, '/') != null) return null; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │721 if (!params.only(&.{.start})) return null; │ │722 return repoIssuesConflictsRoute(pair, params.values.get(.start) orelse ""); │ │723 } │ │724 + if (params.values.get(.start) != null) { │ │725 + if (!params.only(&.{.start})) return null; │ │726 + return repoIssueCommentsRoute(pair, w, params.start() orelse return null); │ │727 + } │ │728 if (!params.only(&.{.tag})) return null; │ │729 if (std.meta.stringToEnum(evt.Issue.Status, w)) |status| return repoIssuesRoute(pair, status, tag_value, "");│ │730 if (std.mem.eql(u8, w, "tags")) return repoIssuesTagsRoute(pair, tag_value); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │785 std.mem.eql(u8, a_i.tag.slice(), b.repo_issues.tag.slice()) and │ │786 std.mem.eql(u8, a_i.selected.slice(), b.repo_issues.selected.slice()) and │ │787 std.mem.eql(u8, a_i.theirs.slice(), b.repo_issues.theirs.slice()) and │ │733 - a_i.view == b.repo_issues.view, │ │788 + a_i.view == b.repo_issues.view and │ │789 + a_i.comments_start == b.repo_issues.comments_start, │ │790 + .repo_comments => |a_c| std.mem.eql(u8, a_c.name.slice(), b.repo_comments.name.slice()) and│ │791 + std.mem.eql(u8, a_c.thread.slice(), b.repo_comments.thread.slice()) and │ │792 + std.mem.eql(u8, a_c.selected.slice(), b.repo_comments.selected.slice()) and │ │793 + a_c.start == b.repo_comments.start, │ │794 .repo_settings => |a_name| std.mem.eql(u8, a_name.slice(), b.repo_settings.slice()), │ │795 .repo_auth => |a_name| std.mem.eql(u8, a_name.slice(), b.repo_auth.slice()), │ │796 else => true, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────╮ │1507 repo_commits: Repo.Commits.View, │ │1508 repo_refs: Repo.Refs.View, │ │1509 repo_issues: Repo.Issues.View, │ │1510 + repo_comments: Repo.Comments.View, │ │1511 + repo_comment: Repo.Comments.Item, │ │1512 home_users: Home.Users.View, │ │1513 home_repos: Home.Repos.View, │ │1514 auth_tab: Home.Header.AuthTab.View,│ ╰─────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/Repo.zig│ ╰───────────────╯ ╭────────────────────────────────────────────────────────╮ │16 pub const Commits = @import("./Repo/Commits.zig"); │ │17 pub const Refs = @import("./Repo/Refs.zig"); │ │18 pub const Issues = @import("./Repo/Issues.zig"); │ │19 +pub const Comments = @import("./Repo/Comments.zig");│ │20 pub const Settings = @import("./Settings.zig"); │ │21 pub const Auth = @import("./Auth.zig"); │ │22 pub const Quit = @import("./Quit.zig"); │ ╰────────────────────────────────────────────────────────╯ ╭────────────────────────╮ │29 commits: Commits, │ │30 refs: Refs, │ │31 issues: Issues, │ │32 +comments: ?Comments,│ │33 settings: Settings, │ │34 auth: Auth, │ │35 quit: Quit, │ ╰────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │117 const issues_view: ui.RoutablePage.IssuesView = switch (route) { │ │118 .repo_issues => |i| i.view, │ │119 else => .open, │ │120 + }; │ │121 + const issues_comments_start: usize = switch (route) { │ │122 + .repo_issues => |i| i.comments_start, │ │123 + else => 0, │ │124 }; │ │125 │ │126 // where the on-disk repo lives (null keeps the views' empty fallback), plus│ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │164 // route named none), so they end up viewing the same one and either's │ │165 // resolved ref can canonicalize the tab mirror urls below. no filesystem │ │166 // (wasm), nowhere to look, or a failed open: empty tabs. │ │161 - const files, const commits, const refs, var issues = blk: { │ │167 + const files, const commits, const refs, var issues, const comments = blk: {│ │168 read: { │ │169 const io = session.io orelse break :read; │ │170 const src = source orelse break :read; │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │177 inline else => |*opened| { │ │178 // local mode: bring the event db up to date with the events branch │ │179 if (session.local != null) try evt.consume(repo_kind, opened.self_repo_opts, io, gpa, opened, evt.events_ref, &.{}); │ │180 + const comments = switch (route) { │ │181 + .repo_comments => |*c| try Comments.initFromRepo(repo_kind, opened.self_repo_opts, arena, opened, io, session.haxy_moment, repo_identity.identity, c.thread.slice(), c.selected.slice(), c.start),│ │182 + else => null, │ │183 + }; │ │184 break :blk .{ │ │185 try Files.init(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, repo_identity.identity, requested_ref_or_oid, requested_ref_value, files_dir, files_line), │ │186 try Commits.init(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, session.haxy_moment, repo_identity.identity, requested_ref_or_oid, requested_ref_value, commits_content), │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │187 try Refs.init(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, repo_identity.identity, refs_kind, refs_from), │ │178 - try Issues.init(repo_kind, opened.self_repo_opts, arena, opened, io, session.haxy_moment, repo_identity.identity, issues_tag, issues_selected, issues_theirs, issues_view), │ │188 + if (comments == null) │ │189 + try Issues.init(repo_kind, opened.self_repo_opts, arena, opened, io, session.haxy_moment, repo_identity.identity, issues_tag, issues_selected, issues_comments_start, issues_theirs, issues_view)│ │190 + else │ │191 + try Issues.emptyResult(arena.allocator(), repo_identity.identity, "", "", 0, "", .open), │ │192 + comments, │ │193 }; │ │194 }, │ │195 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯