╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1661 // focus id -> the live TextInput, refreshed each frame by the views that own │ │1662 // inputs. web/wasm form handling looks widgets up here by focus id. │ │1663 text_inputs: std.AutoHashMapUnmanaged(usize, *wgt.TextInput) = .empty, │ │1664 - nav_back: bool = false, // set by input (escape) to request the native TUI pop a page; see Nav│ │1664 + back: enum { unavailable, available, requested } = .unavailable, │ │1665 refresh_requested: bool = false, // set by input (ctrl+r) │ │1666 // a requested forward navigation. set this (via navigate) to move to a new │ │1667 // page; Nav.sync builds it and then copies it into current_page, clearing │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │1989 // request a navigation pop; the host's Nav.sync goes back a page, or│ │1990 // quits when there's no history left. │ │1991 .escape => { │ │1992 - session.nav_back = true; │ │1992 + session.back = .requested; │ │1993 return; │ │1994 }, │ │1995 .ctrl => |letter| switch (letter) { │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2016 var iter = root_focus.children.iterator(); │ │2017 while (iter.next()) |entry| { │ │2018 const child = entry.value_ptr.*; │ │2019 - if (!child.focus.focusable) continue; │ │2019 + if (!child.focus.focusable and !widget.isBackButton(child.focus)) continue;│ │2020 const r = child.rect; │ │2021 if (mouse.x >= r.x and mouse.y >= r.y and │ │2022 mouse.x < r.x + r.size.width and mouse.y < r.y + r.size.height) │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2026 } │ │2027 } │ │2028 if (clicked) |focus_id| { │ │2029 + if (widget.isBackButton((root_focus.children.get(focus_id) orelse return).focus)) {│ │2030 + if (session.back == .available) session.back = .requested; │ │2031 + return; │ │2032 + } │ │2033 // follow a cross-page link │ │2034 if (crossPageLink(root_focus, focus_id, session.data)) |route| { │ │2035 return session.navigate(route); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │2209 const max_history: usize = 16; │ │2210 │ │2211 pub fn init(allocator: std.mem.Allocator, session: *Session) !Nav {│ │2212 + session.back = .unavailable; │ │2213 const arena = try allocator.create(std.heap.ArenaAllocator); │ │2214 arena.* = std.heap.ArenaAllocator.init(allocator); │ │2215 errdefer { │ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │2275 return; │ │2276 } │ │2277 │ │2273 - if (session.nav_back) { │ │2274 - session.nav_back = false; │ │2278 + if (session.back == .requested) { │ │2279 if (self.history.pop()) |entry| { │ │2280 + session.back = if (self.history.items.len == 0) .unavailable else .available;│ │2281 self.root.deinit(allocator); │ │2282 freeArena(allocator, self.arena); │ │2283 self.root = entry.root; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────╮ │2288 chooseAnsiArtForNavigation(session); │ │2289 return; │ │2290 } │ │2291 + session.back = .unavailable; │ │2292 // nothing to go back to; switch to the quit confirmation│ │2293 const root_focus = self.root.getFocus(); │ │2294 var iter = root_focus.children.iterator(); │ ╰───────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │2321 if (session.haxy_moment == null and session.local == null) return;│ │2322 // the page we navigated to becomes the current page │ │2323 session.data.current_page = route; │ │2324 + session.back = .available; │ │2325 │ │2326 const arena = try allocator.create(std.heap.ArenaAllocator); │ │2327 arena.* = std.heap.ArenaAllocator.init(allocator); │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Fork/Header.zig│ ╰──────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │35 tab_ids: std.AutoArrayHashMapUnmanaged(usize, void), │ │36 tabs_id: usize, │ │37 first_group_width: usize, │ │38 + session: *ui.Session, │ │39 │ │40 pub fn init(allocator: std.mem.Allocator, data: *const Self, commit_count: ?u64, session: *ui.Session) !View { │ │41 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = .hidden, .rounded_corners = true, .direction = .horiz });│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │57 const commits_label = if (commit_count) |count| try std.fmt.allocPrint(aa, "commits ({d})", .{count}) else "commits";│ │58 var first_group_width = try data.title.width(); │ │59 │ │60 + try ui.widget.addBackButton(allocator, &title_box, session); │ │61 + │ │62 // the user's name links to their page. │ │63 { │ │64 var text_buf: [evt.User.name_max_len + 1]u8 = undefined; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯