╭ changed ──────────────╮ │src/ui/Repo/Commits.zig│ ╰───────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │310 try addRow(allocator, &list_box, "next →", try commitsLink(session.page_arena, data.identity, next, 0)); │ │311 } │ │312 if (list_box.children.count() > 0) list_box.getFocus().child_id = list_box.children.keys()[0]; │ │313 - break :blk try wgt.Scroll(ui.Widget).init(allocator, .{ .box = list_box }, .{ .direction = .vert }); │ │313 + break :blk try wgt.Scroll(ui.Widget).init(allocator, .{ .box = list_box }, .{ .direction = .vert, .web_native = !session.is_terminal }); │ │314 }; │ │315 errdefer list_scroll.deinit(allocator); │ │316 try box.children.put(allocator, list_scroll.getFocus().id, .{ .widget = .{ .scroll = list_scroll }, .rect = null, .min_size = .{ .width = list_max_width, .height = null }, .max_size = .{ .width = list_max_width, .height = null } });│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │322 var diff_scroll = blk2: { │ │323 var diff_inner = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .direction = .vert }); │ │324 errdefer diff_inner.deinit(allocator); │ │325 - break :blk2 try wgt.Scroll(ui.Widget).init(allocator, .{ .box = diff_inner }, .{ .direction = .both }); │ │325 + break :blk2 try wgt.Scroll(ui.Widget).init(allocator, .{ .box = diff_inner }, .{ .direction = .both, .web_native = !session.is_terminal });│ │326 }; │ │327 errdefer diff_scroll.deinit(allocator); │ │328 var outer = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = .hidden, .direction = .vert }); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │472 const both_panes_fit = if (constraint.max_size.width) |w| w >= list_max_width + diff_min_width else true; │ │473 self.box.children.values()[list_index].max_size = if (both_panes_fit) .{ .width = list_max_width, .height = null } else null;│ │474 │ │475 - // web browsers provide scrolling, so let every widget grow to │ │476 - // its natural size instead of clipping inside the diff scroll │ │477 - const build_constraint = if (self.session.is_terminal) constraint else layout.Constraint{ │ │478 - .min_size = .{ .width = null, .height = null }, │ │479 - .max_size = .{ .width = null, .height = null }, │ │480 - }; │ │481 - try self.box.build(allocator, build_constraint, root_focus); │ │475 + // the web bounds the layout to the browser viewport like the terminal; │ │476 + // each Scroll's web-native mode hands its full content to a real │ │477 + // scrollable element, so we no longer build unbounded here. │ │478 + try self.box.build(allocator, constraint, root_focus); │ │479 │ │480 // if the diff pane is selected but focus is still elsewhere in this view, │ │481 // the pane was too narrow to lay out when focus crossed over. it's laid │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │514 } │ │515 if (commit.has_more) try self.addNavLink(allocator, inner, "next →", commit.oid, commit.window_start + diff_page);│ │516 │ │517 + // reset the scroll to the top for the newly-shown commit: directly on the │ │518 + // terminal (the wasm offset), and via a version bump on the web (so the │ │519 + // renderer's scroll id changes and JS drops the preserved position). │ │520 const sc = self.diffScroll(); │ │521 sc.x = 0; │ │522 sc.y = 0; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │523 + sc.getFocus().version +%= 1; │ │524 } │ │525 │ │526 pub fn input(self: *View, allocator: std.mem.Allocator, key: inp.Key, root_focus: *Focus) !void {│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────╮ │537 // jump a fixed amount. right/Enter cross into the diff pane. Enter/clicks │ │538 // on the "next" row become navigation in the host before reaching here. │ │539 switch (key) { │ │539 - .arrow_right, .enter => try self.focusDiff(root_focus), │ │540 + .enter => if (self.selectedCommitIndex() != null) │ │541 + try self.focusDiff(root_focus) │ │542 + else if (self.data.next_start) |next| { │ │543 + if (ui.RoutablePage.repoCommitsRoute(self.data.identity, next, 0)) |route|│ │544 + try self.session.navigate(route); │ │545 + }, │ │546 + .arrow_right => try self.focusDiff(root_focus), │ │547 .arrow_down => try self.moveSelection(root_focus, 1), │ │548 .arrow_up => try self.moveSelection(root_focus, -1), │ │549 .page_down => try self.moveSelection(root_focus, page_rows), │ ╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │584 } │ │585 } │ │586 │ │580 - // move focus one hunk in `dir` (+1 down, -1 up). if the adjacent hunk is │ │581 - // already visible, just move focus to it — don't scroll. only when it isn't │ │582 - // visible yet do we scroll one line toward it (and focus it once it appears),│ │583 - // so scrolling never skips past an on-screen hunk. when nothing fits to │ │584 - // scroll (e.g. the web, where the browser scrolls the page), the clamp keeps │ │585 - // the offset put and focus simply steps to the adjacent hunk. │ │587 + // move focus one hunk in `dir` (+1 down, -1 up). on the web the browser owns │ │588 + // scrolling, so just step focus to the adjacent hunk and let scrollToFocus │ │589 + // bring it into view. on the terminal, if the adjacent hunk is already │ │590 + // visible just move focus; otherwise scroll one line toward it (focusing it │ │591 + // once it appears) so scrolling never skips past an on-screen hunk. │ │592 fn moveDiff(self: *View, root_focus: *Focus, dir: isize) !void { │ │593 const inner = self.diffInner(); │ │594 const keys = inner.children.keys(); │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │597 const target = @as(isize, @intCast(cur)) + dir; │ │598 const in_range = target >= 0 and target < @as(isize, @intCast(keys.len));│ │599 │ │600 + if (!self.session.is_terminal) { │ │601 + if (in_range) try root_focus.setFocus(keys[@intCast(target)]); │ │602 + return; │ │603 + } │ │604 + │ │605 if (in_range and self.hunkVisible(@intCast(target))) { │ │606 try root_focus.setFocus(keys[@intCast(target)]); │ │607 return; │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │615 } │ │616 │ │617 // page a fixed number of lines, then focus the leading visible hunk │ │607 - // (bottom-most when paging down, top-most when paging up). │ │618 + // (bottom-most when paging down, top-most when paging up). on the web the │ │619 + // browser scrolls, so jump focus by a page's worth of hunks instead. │ │620 fn pageDiff(self: *View, root_focus: *Focus, delta: isize) !void { │ │621 + if (!self.session.is_terminal) { │ │622 + const inner = self.diffInner(); │ │623 + const keys = inner.children.keys(); │ │624 + if (keys.len == 0) return; │ │625 + const cur: isize = if (root_focus.grandchild_id) |g| @intCast(inner.children.getIndex(g) orelse 0) else 0;│ │626 + const target: usize = @intCast(std.math.clamp(cur + delta, 0, @as(isize, @intCast(keys.len - 1)))); │ │627 + try root_focus.setFocus(keys[target]); │ │628 + return; │ │629 + } │ │630 const sc = self.diffScroll(); │ │631 sc.y += delta; │ │632 self.clampDiffScroll(); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯