╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────────────╮ │src/ui/Repo/Files.zig│ ╰─────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │834 // an "a:" link to the files route at `path`, pinned to the listing's ref, so │ │835 // following it navigates to that directory's listing. │ │836 fn dirLink(page_arena: *std.heap.ArenaAllocator, data: *const Self, path: []const u8) ![]const u8 { │ │807 - const route = data.location.filesRoute(data.ref_or_oid, data.ref_or_oid_value, path, 0) orelse return error.RouteTooLong;│ │837 + const route = data.filesRoute(path, 0) orelse return error.RouteTooLong; │ │838 return std.fmt.allocPrint(page_arena.allocator(), "a:{s}", .{try route.toUrl(page_arena)}); │ │839 } │ │840 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │843 // contents); an unloaded file gets an "a:" link, so activating it reloads the │ │844 // page with that file selected and its content read. │ │845 fn fileLink(page_arena: *std.heap.ArenaAllocator, data: *const Self, path: []const u8, loaded: bool, line: usize) ![]const u8 { │ │816 - const route = data.location.filesRoute(data.ref_or_oid, data.ref_or_oid_value, path, line) orelse return error.RouteTooLong;│ │846 + const route = data.filesRoute(path, line) orelse return error.RouteTooLong; │ │847 const prefix = if (loaded) "ai:" else "a:"; │ │848 return std.fmt.allocPrint(page_arena.allocator(), "{s}{s}", .{ prefix, try route.toUrl(page_arena) }); │ │849 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Header.zig│ ╰──────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │18 name: []const u8, │ │19 owner_name: []const u8, │ │20 title: ui.Title, │ │21 -// the ref/oid this page is viewing, so the files and commits tabs both link to│ │22 -// it (switching tabs keeps the same ref). the value is url-encoded. │ │21 +// the ref/oid this page is viewing; the value is url-encoded │ │22 ref_or_oid: RefOrOid, │ │23 ref_or_oid_value: []const u8, │ │24 // the issues tab's tag filter, url-encoded ("" = unfiltered), so the tab links│ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │49 first_group_width: usize, │ │50 session: *ui.Session, │ │51 │ │53 - pub fn init(allocator: std.mem.Allocator, data: *const Self, commit_count: ?u64, base_oid: []const u8, session: *ui.Session) !View {│ │52 + pub fn init(allocator: std.mem.Allocator, page: *const ui.Repo, session: *ui.Session) !View { │ │53 + const data = &page.header; │ │54 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = .hidden, .rounded_corners = true, .direction = .horiz }); │ │55 errdefer box.deinit(allocator); │ │56 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │66 errdefer tab_ids.deinit(allocator); │ │67 │ │68 const aa = session.page_arena.allocator(); │ │69 - const ref_name = std.Uri.percentDecodeInPlace(try aa.dupe(u8, data.ref_or_oid_value)); │ │69 + const ref_name = std.Uri.percentDecodeInPlace(try aa.dupe(u8, page.files.patchrev_id orelse data.ref_or_oid_value)); │ │70 const bottom_label = try ui.clippedBottomLabel(try aa.alloc(u8, ui.clipped_bottom_label_max_len), ref_name); │ │71 const bottom_label_width = try xitui.width.displayWidth(bottom_label); │ │72 - const commits_label = if (commit_count) |count| try std.fmt.allocPrint(aa, "commits ({d})", .{count}) else "commits";│ │73 - const commits_label_width = try xitui.width.displayWidth(commits_label); │ │72 + const changes_label = switch (page.changes) { │ │73 + .diff => "diff", │ │74 + .commits => |c| if (c.commit_count) |count| try std.fmt.allocPrint(aa, "commits ({d})", .{count}) else "commits",│ │75 + }; │ │76 + const changes_label_width = try xitui.width.displayWidth(changes_label); │ │77 var first_group_width = try data.title.width(); │ │78 │ │79 try ui.widget.addBackButton(allocator, &title_box, session); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │133 // tabs (the page already holds every tab's content), while the href is │ │134 // still followed with js off. the files tab routes through the shared │ │135 // helper so the /repo/.../files url format lives in one place. │ │133 - // both tabs link to the ref/oid this page is viewing, so switching tabs │ │134 - // keeps the same ref (the files tab opens at its root directory). │ │136 + // files and changes keep the same ref or revision when switching tabs. │ │137 + // the files tab opens at its root directory. │ │138 const current_page = session.data.current_page; │ │139 const current_tag = std.meta.activeTag(current_page); │ │137 - const files_route = ui.RoutablePage.repoFilesRoute(identity, data.ref_or_oid, data.ref_or_oid_value, "", 0) orelse return error.RouteTooLong; │ │140 + const files_route = page.files.filesRoute("", 0) orelse return error.RouteTooLong; │ │141 const files_link = try ui.inPageTabLink(session, files_route, current_tag == .repo_files); │ │139 - const commits_route = ui.RoutablePage.repoCommitsRoute(identity, data.ref_or_oid, data.ref_or_oid_value, 0, "", base_oid) orelse return error.RouteTooLong;│ │140 - const commits_link = try ui.inPageTabLink(session, commits_route, current_tag == .repo_commits); │ │142 + const changes_route = (switch (page.changes) { │ │143 + .diff => |d| d.route.page(d.window.start, d.path), │ │144 + .commits => |c| ui.RoutablePage.repoCommitsRoute(identity, data.ref_or_oid, data.ref_or_oid_value, 0, "", c.base_oid), │ │145 + }) orelse return error.RouteTooLong; │ │146 + const changes_tag = std.meta.activeTag(changes_route); │ │147 + const changes_link = try ui.inPageTabLink(session, changes_route, current_tag == changes_tag); │ │148 const refs_route = ui.RoutablePage.repoRefsRoute(identity, .branch, "") orelse return error.RouteTooLong; │ │149 const refs_link = try ui.inPageTabLink(session, refs_route, current_tag == .repo_refs); │ │150 const issues_route = ui.RoutablePage.repoIssuesRoute(identity, .open, data.issues_tag, "") orelse return error.RouteTooLong; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │179 }); │ │180 } │ │181 │ │175 - // commits tab │ │182 + // commits, or the comparison selected by a diff / patchrev url │ │183 { │ │177 - var text_box = try wgt.TextBox.init(allocator, commits_label, .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none, .bottom_label = bottom_label });│ │184 + var text_box = try wgt.TextBox.init(allocator, changes_label, .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none, .bottom_label = bottom_label });│ │185 errdefer text_box.deinit(allocator); │ │186 text_box.getFocus().mode = .all; │ │180 - text_box.getFocus().kind = .{ .custom = commits_link }; │ │187 + text_box.getFocus().kind = .{ .custom = changes_link }; │ │188 try tab_ids.put(allocator, text_box.getFocus().id, {}); │ │182 - if (current_tag == .repo_commits) selected_tab = text_box.getFocus().id; │ │189 + if (current_tag == changes_tag) selected_tab = text_box.getFocus().id; │ │190 try tabs_box.children.put(allocator, text_box.getFocus().id, .{ │ │191 .widget = .{ .text_box = text_box }, │ │192 .rect = null, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │186 - .min_size = .{ .width = @max(commits_label_width, bottom_label_width) + 2, .height = null },│ │193 + .min_size = .{ .width = @max(changes_label_width, bottom_label_width) + 2, .height = null },│ │194 }); │ │195 } │ │196 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────────╮ │src/ui/Repo/Patches.zig│ ╰───────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │273 return ui.RoutablePage.repoCommitsRoute(identity, .object, entry.revision_oid, 0, "", entry.base_oid) orelse error.RouteTooLong; │ │274 } │ │275 │ │276 +pub fn diffRoute(allocator: std.mem.Allocator, identity: []const u8, entry: PatchWithId) !?ui.RoutablePage { │ │277 + // merged patches show what actually landed, including conflict resolutions │ │278 + if (entry.record.event.status == .merged) { │ │279 + return ui.RoutablePage.repoPatchRevDiffRoute(identity, &entry.record.event.status.merged.patchrev_id, 0, "") orelse error.RouteTooLong;│ │280 + } │ │281 + const route = (try commitsRoute(allocator, identity, entry)) orelse return null; │ │282 + return switch (route) { │ │283 + .fork_commits => ui.RoutablePage.forkDiffRoute(identity, entry.id, 0, ""), │ │284 + .repo_commits => |c| ui.RoutablePage.repoDiffRoute(identity, c.ref_or_oid, c.value.slice(), 0, "", c.base_oid.slice()), │ │285 + else => unreachable, │ │286 + } orelse error.RouteTooLong; │ │287 +} │ │288 + │ │289 pub fn draftsRoute(identity: []const u8) ?ui.RoutablePage { │ │290 return ui.RoutablePage.repoPatchesDraftsRoute(identity); │ │291 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/ui/widget.zig│ ╰─────────────────╯ ╭────────────────────────────────────────────────────────────╮ │45 repo_discussions_header: ui.Repo.Discussions.Header,│ │46 repo_files: ui.Repo.Files.View, │ │47 repo_commits: ui.Repo.Commits.View, │ │48 - diff_view: ui.Repo.Commits.Diff.View, │ │48 + diff_view: ui.Repo.Diff.View, │ │49 repo_refs: ui.Repo.Refs.View, │ │50 repo_issues: ui.Repo.Issues.View, │ │51 repo_patches: ui.Repo.Patches.View, │ ╰────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯