╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──────╮ │src/ui/Repo.zig│ ╰───────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │214 │ │215 // open the on-disk repo once and read every tab's data from it. files and │ │216 // commits resolve the same requested ref (the default branch when the │ │231 - // route named none), so they end up viewing the same one and either's │ │232 - // resolved ref can canonicalize the tab mirror urls below. no filesystem │ │217 + // route named none), so they end up viewing the same one. no filesystem │ │218 // (wasm), nowhere to look, or a failed open: empty tabs. │ │219 const files, const commits, const refs, var issues, var patches, var discussions, const events = blk: {│ │220 read: { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │260 patches.target_branch = if (files.ref_or_oid == .branch) files.ref_or_oid_value else ""; │ │261 discussions.repo_source = source; │ │262 │ │278 - // each tab mirror carries this page's route for that tab; tabs not targeted │ │279 - // by the incoming route fall back to their root/first-page route. the files │ │280 - // mirror carries the selected file (when the route named one) so the url │ │281 - // keeps it before focus enters the view. │ │282 - const files_path = if (files.selected_file) |f| try Files.childDir(arena.allocator(), files.dir, f) else files.dir; │ │283 - // the content window only applies to a selected file, so the bare │ │284 - // directory route drops it. │ │285 - const files_route_line = if (files.selected_file != null) files_line else 0; │ │286 - const route_name = (ui.RoutablePage.repoFilesRoute(repo_identity.identity, files.ref_or_oid, files.ref_or_oid_value, files_path, files_route_line) orelse return error.NotFound).repo_files;│ │287 - // the message page mirrors its own url, not the log's │ │288 - const commits_route = switch (commits.content) { │ │289 - .message => ui.RoutablePage.repoCommitMessageRoute(repo_identity.identity, commits.ref_or_oid, commits.ref_or_oid_value), │ │290 - .diff => |d| ui.RoutablePage.repoCommitsRoute(repo_identity.identity, commits.ref_or_oid, commits.ref_or_oid_value, commits_start, d.path), │ │291 - }; │ │292 - const commits_route_name = (commits_route orelse return error.NotFound).repo_commits; │ │293 - │ │263 return .{ │ │264 // files and commits resolve the same ref, so either's serves the header, │ │265 // which points both tabs at it. │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │275 .settings = Settings.init(), │ │276 .auth = Auth.init(), │ │277 .quit = Quit.init(), │ │309 - .route_name = route_name, │ │310 - .commits_route_name = commits_route_name, │ │311 - .identity = Array.from(repo_identity.identity) orelse return error.NotFound,│ │278 }; │ │279 } │ │280 │ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────╮ │281 pub const View = struct { │ │282 box: wgt.Box(ui.Widget), │ │317 - data: *const Self, │ │318 - session: *ui.Session, │ │283 │ │284 const header_index: usize = 0;│ │285 const stack_index: usize = 1; │ ╰───────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │372 try box.children.put(allocator, stack.getFocus().id, .{ .widget = .{ .stack = stack }, .rect = null, .min_size = null });│ │373 } │ │374 │ │411 - var self = View{ .box = box, .data = data, .session = session }; │ │375 + var self = View{ .box = box }; │ │376 self.getFocus().child_id = box.children.keys()[header_index]; │ │377 return self; │ │378 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │386 const header = &self.box.children.values()[header_index].widget.repo_header; │ │387 const stack = &self.box.children.values()[stack_index].widget.stack; │ │388 │ │425 - // each header tab maps 1:1 to a stack child by position. mirror the │ │426 - // selection into current_page so the host can push the matching url; │ │427 - // all repo tabs share the .repo parent, so this stays on the page │ │428 - // rather than navigating. │ │429 - if (header.getSelectedIndex()) |index| { │ │389 + // each header tab maps 1:1 to a stack child by position │ │390 + if (header.getSelectedIndex()) |index| │ │391 stack.getFocus().child_id = stack.children.keys()[index]; │ │431 - switch (std.meta.activeTag(stack.children.values()[index])) { │ │432 - // files/commits carry this page's content route (directory / log │ │433 - // page); settings/auth carry only "owner/name". │ │434 - .repo_commits => self.session.data.current_page = .{ .repo_commits = self.data.commits_route_name }, │ │435 - // the refs tab mirrors this page's windowed column. │ │436 - .repo_refs => self.session.data.current_page = ui.RoutablePage.repoRefsRoute(self.data.identity.slice(), self.data.refs.kind, self.data.refs.from) orelse self.session.data.current_page, │ │437 - // the issues tab mirrors this page's tag filter (issue urls │ │438 - // themselves never carry the tag). │ │439 - .repo_issues => self.session.data.current_page = ui.RoutablePage.repoIssuesRoute(self.data.identity.slice(), .open, self.data.issues.tag, "") orelse self.session.data.current_page, │ │440 - .repo_patches => self.session.data.current_page = ui.RoutablePage.repoPatchesRoute(self.data.identity.slice(), .open, self.data.patches.tag, "") orelse self.session.data.current_page, │ │441 - .repo_discussions => self.session.data.current_page = ui.RoutablePage.repoDiscussionsRoute(self.data.identity.slice(), self.data.discussions.tag, "") orelse self.session.data.current_page,│ │442 - .repo_events => self.session.data.current_page = ui.RoutablePage.repoEventsRoute(self.data.identity.slice(), self.data.events.view, null, "") orelse self.session.data.current_page, │ │443 - .home_settings => { │ │444 - if (ui.RoutablePage.Array(ui.RoutablePage.repo_route_max_len).from(self.data.identity.slice())) |identity| │ │445 - self.session.data.current_page = .{ .repo_settings = identity }; │ │446 - }, │ │447 - .home_auth => { │ │448 - if (ui.RoutablePage.Array(ui.RoutablePage.repo_route_max_len).from(self.data.identity.slice())) |identity| │ │449 - self.session.data.current_page = .{ .repo_auth = identity }; │ │450 - }, │ │451 - // the quit tab is tty-only and not a route, so leave current_page │ │452 - // alone (nothing to mirror into the url). │ │453 - .quit => {}, │ │454 - // the files tab (this page's directory route) │ │455 - else => self.session.data.current_page = .{ .repo_files = self.data.route_name }, │ │456 - } │ │457 - } │ │392 try self.box.build(allocator, constraint, root_focus); │ │393 } │ │394 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────────╮ │src/ui/Repo/Commits.zig│ ╰───────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │442 var list_scroll = blk: { │ │443 var list_box = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .direction = .vert, .stretch = true });│ │444 errdefer list_box.deinit(allocator); │ │445 - for (data.commits) |commit| { │ │445 + for (data.commits, 0..) |commit, index| { │ │446 // an in-page "ai:" anchor so a commit row is clickable with │ │447 // js off (the browser follows it, rooting the list there); │ │448 // with wasm the click just selects it and swaps the diff pane. │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │449 - try addRow(allocator, &list_box, firstLine(commit.message), try commitRowLink(session.page_arena, data.identity, commit.oid, "")); │ │449 + const content = if (index == 0) data.content else Content{ .diff = .{} }; │ │450 + try addRow(allocator, &list_box, firstLine(commit.message), try commitRowLink(session.page_arena, data.identity, commit, content));│ │451 } │ │452 if (data.next_start) |next| { │ │453 try addRow(allocator, &list_box, "next →", try commitsLink(session.page_arena, data.identity, next, 0, "")); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │631 │ │632 // swap the diff pane to the selected commit when it changes. │ │633 try self.refreshDiff(allocator); │ │633 - │ │634 - // mirror the focused commit into the url so it updates as the selection │ │635 - // moves, but only while focus is inside this view (the list or diff). when │ │636 - // focus sits on the header tab, use the page's base /commits route. │ │637 - if (root_focus.grandchild_id) |g| { │ │638 - if (self.box.getFocus().children.contains(g)) { │ │639 - if (self.selectedCommitIndex()) |sel| { │ │640 - const oid = self.data.commits[sel].oid; │ │641 - const window_start = self.data.commits[sel].window_start; │ │642 - // mirror the commit's current diff window so the url stays │ │643 - // linkable (0 for any commit but the windowed start one). │ │644 - // selecting another commit leaves the message behind, like │ │645 - // it leaves the path filter behind. │ │646 - const mirrored = switch (self.paneContent(sel)) { │ │647 - .message => ui.RoutablePage.repoCommitMessageRoute(self.data.identity, .object, oid), │ │648 - .diff => |d| ui.RoutablePage.repoCommitsRoute(self.data.identity, .object, oid, window_start, d.path),│ │649 - }; │ │650 - if (mirrored) |route| self.session.data.current_page = route; │ │651 - } │ │652 - } │ │653 - } │ │634 │ │635 // the selected list row shows a border (the focused TextBox upgrades it │ │636 // to a double border itself); the rest stay borderless. │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │969 return std.fmt.allocPrint(page_arena.allocator(), "a:{s}", .{url}); │ │970 } │ │971 │ │992 -// the in-page "ai:" anchor for selecting `oid` in `identity`'s commit list. it │ │993 -// points at the same route as commitsLink but the "ai:" prefix keeps wasm clicks │ │994 -// in-page (crossPageLink ignores it); the href is only followed with js off. │ │995 -fn commitRowLink(page_arena: *std.heap.ArenaAllocator, identity: []const u8, oid: []const u8, path: []const u8) ![]const u8 {│ │996 - const route = ui.RoutablePage.repoCommitsRoute(identity, .object, oid, 0, path) orelse return error.RouteTooLong; │ │972 +// the in-page "ai:" anchor for selecting a commit with its current pane content │ │973 +// and window. the href is only followed with js off. │ │974 +fn commitRowLink(page_arena: *std.heap.ArenaAllocator, identity: []const u8, commit: Commit, content: Content) ![]const u8 { │ │975 + const route = (switch (content) { │ │976 + .message => ui.RoutablePage.repoCommitMessageRoute(identity, .object, commit.oid), │ │977 + .diff => |diff| ui.RoutablePage.repoCommitsRoute(identity, .object, commit.oid, commit.window_start, diff.path), │ │978 + }) orelse return error.RouteTooLong; │ │979 const url = try route.toUrl(page_arena); │ │980 return std.fmt.allocPrint(page_arena.allocator(), "ai:{s}", .{url}); │ │981 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯