╭ changed ──────────╮ │src/embed/script.js│ ╰───────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │368 if (!span) return; │ │369 // leave modified / non-left clicks (open-in-new-tab, etc.) to the browser │ │370 if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;│ │371 - // wasm routes the click: an in-page link just moves focus and re-renders, │ │371 + // wasm routes the click: an in-page link updates focus and re-renders, │ │372 // a cross-page one calls _navigate. suppress the <a>'s own navigation so │ │373 // an in-page link doesn't also trigger a redundant full page load. │ │374 event.preventDefault(); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/main_wasm.zig│ ╰─────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────╮ │63 .max_size = .{ .width = max_width, .height = max_height }, │ │64 }, root_ptr.getFocus()); │ │65 │ │66 - // mirror the page Header settled on into the browser URL. these are │ │67 - // passive same-page changes, like moving down the commits list, so we │ │68 - // replace the URL in place rather than pushing a history entry. real │ │69 - // cross-page navigations go through _navigate. │ │66 + // push input-driven in-page route changes into the browser url without │ │67 + // adding history entries. real cross-page navigations go through _navigate. │ │68 const current_page = session.data.current_page; │ │69 const should_push = if (last_pushed_page_maybe) |lp| !lp.eql(current_page) else true;│ │70 if (should_push) { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────╮ │125 40 => .arrow_down, │ │126 else => return, │ │127 }; │ │128 + const root_focus = root_ptr.getFocus(); │ │129 + const focused_before = root_focus.grandchild_id; │ │130 │ │131 if (key == .enter) { │ │132 - if (root_ptr.getFocus().grandchild_id) |gid| { │ │132 + if (root_focus.grandchild_id) |gid| { │ │133 // follow a cross-page link │ │134 - if (ui.crossPageLink(root_ptr.getFocus(), gid, session.data)) |route| {│ │134 + if (ui.crossPageLink(root_focus, gid, session.data)) |route| { │ │135 const url = try route.toUrl(&page_arena); │ │136 _navigate(url.ptr, @intCast(url.len)); │ │137 return; │ ╰────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────╮ │138 } │ │139 // follow a link to bytes the server serves directly│ │140 - if (ui.rawLink(root_ptr.getFocus(), gid)) |url| { │ │140 + if (ui.rawLink(root_focus, gid)) |url| { │ │141 _navigate(url.ptr, @intCast(url.len)); │ │142 return; │ │143 } │ ╰─────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │144 } │ │145 } │ │146 │ │147 - try root_ptr.input(allocator, key, root_ptr.getFocus()); │ │147 + try root_ptr.input(allocator, key, root_focus); │ │148 + const focused_after = root_focus.grandchild_id; │ │149 + if (key != .enter and focused_before == focused_after) return; │ │150 + const focus_id = focused_after orelse return; │ │151 + if (ui.inPageLink(root_focus, focus_id, session.data)) |route| session.data.current_page = route;│ │152 } │ │153 │ │154 fn onMouseClick(focus_id: usize) !void { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────╮ │164 _navigate(url.ptr, @intCast(url.len));│ │165 return; │ │166 } │ │163 - root_ptr.getFocus().setFocus(focus_id); │ │167 + try setFocus(focus_id); │ │168 } │ │169 │ │170 fn setFocus(focus_id: usize) !void { │ ╰───────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │171 const root_ptr = if (root) |*root_value| root_value else return error.NotStarted; │ │168 - root_ptr.getFocus().setFocus(focus_id); │ │172 + const root_focus = root_ptr.getFocus(); │ │173 + root_focus.setFocus(focus_id); │ │174 + if (ui.inPageLink(root_focus, focus_id, session.data)) |route| session.data.current_page = route;│ │175 } │ │176 │ │177 fn consoleLog(arg: []const u8) void { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │1194 } │ │1195 │ │1196 // true when `a` and `b` are the same user paginated to a different repos │ │1197 - // window. switching between a user's tabs is in-page (header-handled), so│ │1197 + // window. switching between a user's tabs is in-page, so │ │1198 // only a changed `start` on the repos list navigates. │ │1199 pub fn userPageChanged(a: RoutablePage, b: RoutablePage) bool { │ │1200 return switch (a) { │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1654 } │ │1655 │ │1656 pub fn inputKey(allocator: std.mem.Allocator, root: *Widget, key: Key, session: *Session) !void {│ │1657 + const root_focus = root.getFocus(); │ │1658 + const focused_before = root_focus.grandchild_id; │ │1659 + var activate_in_page_link = false; │ │1660 switch (key) { │ │1661 // request a navigation pop; the host's Nav.sync goes back a page, or │ │1662 // quits when there's no history left. │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │1660 - .escape => session.nav_back = true, │ │1663 + .escape => { │ │1664 + session.nav_back = true; │ │1665 + return; │ │1666 + }, │ │1667 .ctrl => |letter| switch (letter) { │ │1662 - 'r' => session.refresh_requested = true, │ │1663 - else => try root.input(allocator, key, root.getFocus()), │ │1668 + 'r' => { │ │1669 + session.refresh_requested = true; │ │1670 + return; │ │1671 + }, │ │1672 + else => try root.input(allocator, key, root_focus), │ │1673 }, │ │1674 .enter => { │ │1666 - const root_focus = root.getFocus(); │ │1675 if (root_focus.grandchild_id) |gid| { │ │1676 // follow a cross-page link │ │1677 if (crossPageLink(root_focus, gid, session.data)) |route| {│ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯