╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │242 .user_settings => |a_name| std.mem.eql(u8, a_name.slice(), b.user_settings.slice()),│ │243 .user_auth => |a_name| std.mem.eql(u8, a_name.slice(), b.user_auth.slice()), │ │244 .repo => |a_name| std.mem.eql(u8, a_name.slice(), b.repo.slice()), │ │245 + .repo_commits => |a_name| std.mem.eql(u8, a_name.slice(), b.repo_commits.slice()), │ │246 .repo_settings => |a_name| std.mem.eql(u8, a_name.slice(), b.repo_settings.slice()),│ │247 .repo_auth => |a_name| std.mem.eql(u8, a_name.slice(), b.repo_auth.slice()), │ │248 else => true, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │249 }; │ │250 } │ │251 │ │225 - // true when `a` and `b` are both `.repo` files routes pointing at different │ │226 - // directories. a repo directory change is a navigation (its own page); │ │227 - // settings/auth tab switches are not, so they don't satisfy this. │ │228 - pub fn repoDirChanged(a: RoutablePage, b: RoutablePage) bool { │ │229 - return std.meta.activeTag(a) == .repo and std.meta.activeTag(b) == .repo and !a.eql(b); │ │252 + // the content a repo route renders: which repo, plus the files directory and │ │253 + // commits page. tab switches keep this fixed (the page already holds every │ │254 + // tab's data); a files-directory or commits-page change alters it. │ │255 + const RepoContent = struct { identity: []const u8, files_dir: []const u8, commits_start: []const u8 };│ │256 + │ │257 + fn repoContent(r: RoutablePage) ?RepoContent { │ │258 + const name = switch (r) { │ │259 + .repo, .repo_commits, .repo_settings, .repo_auth => |n| n.slice(), │ │260 + else => return null, │ │261 + }; │ │262 + const rf = RepoFiles.parse(name) orelse return null; │ │263 + return .{ │ │264 + .identity = rf.identity, │ │265 + .files_dir = if (std.meta.activeTag(r) == .repo) rf.dir else "", │ │266 + .commits_start = if (std.meta.activeTag(r) == .repo_commits) repoCommitsStart(name) else "", │ │267 + }; │ │268 + } │ │269 + │ │270 + // true when `a` and `b` are both repo routes that render different content │ │271 + pub fn repoPageChanged(a: RoutablePage, b: RoutablePage) bool { │ │272 + const ca = repoContent(a) orelse return false; │ │273 + const cb = repoContent(b) orelse return false; │ │274 + return !std.mem.eql(u8, ca.identity, cb.identity) or │ │275 + !std.mem.eql(u8, ca.files_dir, cb.files_dir) or │ │276 + !std.mem.eql(u8, ca.commits_start, cb.commits_start); │ │277 } │ │278 │ │279 // serialize as { "kind": <tag>, "name"?: <user name> }. the default │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │289 try jw.objectField("name"); │ │290 try jw.write(name.slice()); │ │291 }, │ │245 - .repo, .repo_settings, .repo_auth => |name| { │ │292 + .repo, .repo_commits, .repo_settings, .repo_auth => |name| {│ │293 try jw.objectField("name"); │ │294 try jw.write(name.slice()); │ │295 }, │ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │317 .user_settings => .{ .user_settings = try parseName(evt.User.name_max_len, helper.name) },│ │318 .user_auth => .{ .user_auth = try parseName(evt.User.name_max_len, helper.name) }, │ │319 .repo => .{ .repo = try parseName(repo_route_max_len, helper.name) }, │ │320 + .repo_commits => .{ .repo_commits = try parseName(repo_route_max_len, helper.name) }, │ │321 .repo_settings => .{ .repo_settings = try parseName(repo_route_max_len, helper.name) }, │ │322 .repo_auth => .{ .repo_auth = try parseName(repo_route_max_len, helper.name) }, │ │323 }; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │553 // a link to a different parent page always navigates; within the repo page, │ │554 // a files-directory link navigates while settings/auth tab links stay │ │555 // in-page (header-handled). │ │508 - if (route.parent() != current.parent() or RoutablePage.repoDirChanged(route, current)) return route; │ │556 + if (route.parent() != current.parent() or RoutablePage.repoPageChanged(route, current)) return route;│ │557 return null; │ │558 } │ │559 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮ │658 // — a different files directory (each directory is its own page, while │ │659 // settings/auth tab switches stay in-page and are not pushed here). │ │660 const cp = session.data.current_page; │ │613 - if (cp.parent() != self.route.parent() or RoutablePage.repoDirChanged(cp, self.route)) { │ │661 + if (cp.parent() != self.route.parent() or RoutablePage.repoPageChanged(cp, self.route)) {│ │662 if (session.haxy_moment == null) return; │ │663 const arena = try allocator.create(std.heap.ArenaAllocator); │ │664 arena.* = std.heap.ArenaAllocator.init(allocator); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────╮ │730 user_header: User.Header.View, │ │731 repo_header: Repo.Header.View, │ │732 repo_files: Repo.Files.View, │ │733 + repo_commits: Repo.Commits.View, │ │734 home_users: Home.Users.View, │ │735 home_repos: Home.Repos.View, │ │736 auth_tab: Home.Header.AuthTab.View,│ ╰────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/Repo.zig│ ╰───────────────╯ ╭──────────────────────────────────────────────────────╮ │11 │ │12 pub const Header = @import("./Repo/Header.zig"); │ │13 pub const Files = @import("./Repo/Files.zig"); │ │14 +pub const Commits = @import("./Repo/Commits.zig");│ │15 pub const Settings = @import("./Settings.zig"); │ │16 pub const Auth = @import("./Auth.zig"); │ │17 pub const Quit = @import("./Quit.zig"); │ ╰──────────────────────────────────────────────────────╯ ╭───────────────────────╮ │21 header: Header, │ │22 repo: evt.Repo, │ │23 files: Files, │ │24 +commits: Commits, │ │25 settings: Settings,│ │26 auth: Auth, │ │27 quit: Quit, │ ╰───────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │28 // the full files route this page renders ("owner/name" or "owner/name/files/<dir>"),│ │29 // mirrored into current_page when the files tab is selected. │ │30 route_name: Array, │ │31 +// the commits route this page renders ("owner/name/commits[/<oid>]"), mirrored │ │32 +// when the commits tab is selected. │ │33 +commits_route_name: Array, │ │34 // "owner/name" alone, mirrored when the settings/auth tab is selected. │ │35 identity: Array, │ │36 │ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯