╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ───────────╮ │src/ui/Repo/Refs.zig│ ╰────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │176 try header_box.children.put(allocator, space.getFocus().id, .{ .widget = .{ .text = space }, .rect = null, .min_size = null }); │ │177 } │ │178 { │ │157 - var header = try ui.SubTitle.View.init(allocator, label); │ │179 + var header = try ui.SubTitle.View.init(allocator, &data.label); │ │180 errdefer header.deinit(allocator); │ │181 try header_box.children.put(allocator, header.getFocus().id, .{ .widget = .{ .sub_title = header }, .rect = null, .min_size = null });│ │182 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │192 │ │193 // window-navigation rows bracket the names: "← previous" off the │ │194 // first window, "next →" when more remain. each is a full reload. │ │173 - if (after > 0) try addRow(allocator, &rows, "← previous", try windowLink(session.page_arena, identity, kind, after -| page_size));│ │195 + if (data.prev) |p| try addRow(allocator, &rows, "← previous", try windowLink(session.page_arena, identity, kind, p)); │ │196 // each ref name links to the files tab at that ref's root. │ │175 - for (names) |name| try addRow(allocator, &rows, name, try refLink(session.page_arena, identity, kind, name)); │ │176 - if (next_after) |na| try addRow(allocator, &rows, "next →", try windowLink(session.page_arena, identity, kind, na)); │ │197 + for (data.names) |name| try addRow(allocator, &rows, name, try refLink(session.page_arena, identity, kind, name)); │ │198 + if (data.next) |n| try addRow(allocator, &rows, "next →", try windowLink(session.page_arena, identity, kind, n)); │ │199 │ │200 if (rows.children.count() > 0) rows.getFocus().child_id = rows.children.keys()[0]; │ │201 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │347 } │ │348 }; │ │349 │ │328 -// the "a:" link to `kind`'s column paginated to `after` within `identity` │ │329 -// ("owner/name"). the other column resets to its first window. │ │330 -fn windowLink(page_arena: *std.heap.ArenaAllocator, identity: []const u8, kind: ui.RoutablePage.RefKind, after: usize) ![]const u8 { │ │331 - const route = ui.RoutablePage.repoRefsRoute(identity, kind, after) orelse return error.RouteTooLong; │ │350 +// the "a:" link to `kind`'s column windowed from ref `name` (raw; "" = the │ │351 +// first window) within `identity` ("owner/name"). the other column resets to │ │352 +// its first window. │ │353 +fn windowLink(page_arena: *std.heap.ArenaAllocator, identity: []const u8, kind: ui.RoutablePage.RefKind, name: []const u8) ![]const u8 {│ │354 + const encoded = try ui.urlEncodeRef(page_arena.allocator(), name); │ │355 + const route = ui.RoutablePage.repoRefsRoute(identity, kind, encoded) orelse return error.RouteTooLong; │ │356 const url = try route.urlAlloc(page_arena); │ │357 return std.fmt.allocPrint(page_arena.allocator(), "a:{s}", .{url}); │ │358 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/User.zig│ ╰───────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────╮ │21 header: Header, │ │22 user: evt.User.Safe, │ │23 repos: []const evt.Repo, │ │24 -repos_after: usize, // the repos window this page was built with, mirrored into the url│ │25 -repos_next_after: ?usize, // the `after` for the "next" row, or null on the last window│ │24 +repos_start: usize, // the repos window this page was built with, mirrored into the url│ │25 +repos_next_start: ?usize, // the `start` for the "next" row, or null on the last window│ │26 settings: Settings, │ │27 auth: Auth, │ │28 quit: Quit, │ ╰───────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────╮ │34 arena: *std.heap.ArenaAllocator, │ │35 haxy_moment: evt.AdminDB.HashMap(.read_only), │ │36 name: ui.RoutablePage.Array(evt.User.name_max_len),│ │37 - after: usize, │ │37 + start: usize, │ │38 ) !Self { │ │39 const DB = evt.AdminDB; │ │40 const hash_kind = evt.admin_repo_opts.hash; │ ╰───────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │51 const user = (try evt.User.readById(DB, hash_kind, haxy_moment, arena, user_id)) orelse return error.NotFound;│ │52 │ │53 var repos: std.ArrayList(evt.Repo) = .empty; │ │54 - var repos_next_after: ?usize = null; │ │54 + var repos_next_start: ?usize = null; │ │55 │ │56 // the user-id->repo-id-set index maps each user to a set of their repo event │ │57 // ids ordered by creation time (oldest first); it only exists once a repo has │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │65 const event_id_to_repo_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, "event-id->repo")) orelse return error.NotFound;│ │66 const event_id_to_repo = try DB.HashMap(.read_only).init(event_id_to_repo_cursor); │ │67 │ │68 - // read the window [after, after+page_size) with one seek to the start │ │68 + // read the window [start, start+page_size) with one seek to the start │ │69 // rank, then a sequential walk. │ │70 - const end = @min(after + page_size, count); │ │71 - var repos_iter = try user_repos.iteratorFromIndex(after); │ │72 - var i = after; │ │70 + const end = @min(start + page_size, count); │ │71 + var repos_iter = try user_repos.iteratorFromIndex(start); │ │72 + var i = start; │ │73 while (i < end) : (i += 1) { │ │74 var kv_cursor = (try repos_iter.next()) orelse break; │ │75 const kv = try kv_cursor.readKeyValuePair(); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │83 const repo_event = try evt.read(evt.Repo, DB, hash_kind, arena, repo_map);│ │84 try repos.append(arena.allocator(), repo_event); │ │85 } │ │86 - repos_next_after = if (end < count) end else null; │ │86 + repos_next_start = if (end < count) end else null; │ │87 } │ │88 } │ │89 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────╮ │91 .header = try Header.init(arena, user.name),│ │92 .user = evt.User.Safe.init(user), │ │93 .repos = repos.items, │ │94 - .repos_after = after, │ │95 - .repos_next_after = repos_next_after, │ │94 + .repos_start = start, │ │95 + .repos_next_start = repos_next_start, │ │96 .settings = Settings.init(), │ │97 .auth = Auth.init(), │ │98 .quit = Quit.init(), │ ╰────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │139 // then a trailing "next" row when more remain. each window row │ │140 // navigates to the adjacent window of this user's repos. │ │141 var items: std.ArrayList(ui.FlowBox.Item) = .empty; │ │142 - if (data.repos_after > 0) │ │143 - try items.append(aa, .{ .text = "← previous", .link = try std.fmt.allocPrint(aa, "a:/user/{s}?after={d}", .{ data.user.name, data.repos_after -| page_size }) }); │ │142 + if (data.repos_start > 0) │ │143 + try items.append(aa, .{ .text = "← previous", .link = try std.fmt.allocPrint(aa, "a:/user/{s}/repos/start:{d}", .{ data.user.name, data.repos_start -| page_size }) });│ │144 for (data.repos) |repo| │ │145 // clicking a repo opens its page; the "a:" prefix makes the web │ │146 // renderer emit an <a href="/repo/alice/foo"> anchor. │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯