╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────╮ │← previous│ ╰──────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │281 .identity = rf.identity, │ │282 .files_dir = if (std.meta.activeTag(r) == .repo) rf.dir else "", │ │283 .commits_start = if (std.meta.activeTag(r) == .repo_commits) repoCommitsStart(name) else "",│ │284 + .commits_after = switch (r) { │ │285 + .repo_commits => |c| c.after, │ │286 + else => 0, │ │287 + }, │ │288 }; │ │289 } │ │290 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │294 const cb = repoContent(b) orelse return false; │ │295 return !std.mem.eql(u8, ca.identity, cb.identity) or │ │296 !std.mem.eql(u8, ca.files_dir, cb.files_dir) or │ │276 - !std.mem.eql(u8, ca.commits_start, cb.commits_start); │ │297 + !std.mem.eql(u8, ca.commits_start, cb.commits_start) or │ │298 + ca.commits_after != cb.commits_after; │ │299 } │ │300 │ │279 - // serialize as { "kind": <tag>, "name"?: <user name> }. the default │ │301 + // serialize as { "kind": <tag>, "name"?: <name>, "after"?: <n> }. the default│ │302 // union(enum) codec doesn't round-trip here: Stringify emits a void │ │303 // variant as a bare tag string, but the parser expects an object — so the │ │304 // server->wasm Snapshot JSON would fail to parse. │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │311 try jw.objectField("name"); │ │312 try jw.write(name.slice()); │ │313 }, │ │292 - .repo, .repo_commits, .repo_settings, .repo_auth => |name| {│ │314 + .repo, .repo_settings, .repo_auth => |name| { │ │315 try jw.objectField("name"); │ │316 try jw.write(name.slice()); │ │317 + }, │ │318 + .repo_commits => |c| { │ │319 + try jw.objectField("name"); │ │320 + try jw.write(c.name.slice()); │ │321 + try jw.objectField("after"); │ │322 + try jw.write(c.after); │ │323 }, │ │324 else => {}, │ │325 } │ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │327 } │ │328 │ │329 pub fn jsonParse(allocator: std.mem.Allocator, source: anytype, options: std.json.ParseOptions) !RoutablePage {│ │302 - const Helper = struct { kind: std.meta.Tag(RoutablePage), name: ?[]const u8 = null }; │ │330 + const Helper = struct { kind: std.meta.Tag(RoutablePage), name: ?[]const u8 = null, after: usize = 0 }; │ │331 const helper = try std.json.innerParse(Helper, allocator, source, options); │ │332 const parseName = struct { │ │333 // errors must stay within std.json's ParseError set; ValueTooLong │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │345 .user_settings => .{ .user_settings = try parseName(evt.User.name_max_len, helper.name) }, │ │346 .user_auth => .{ .user_auth = try parseName(evt.User.name_max_len, helper.name) }, │ │347 .repo => .{ .repo = try parseName(repo_route_max_len, helper.name) }, │ │320 - .repo_commits => .{ .repo_commits = try parseName(repo_route_max_len, helper.name) }, │ │348 + .repo_commits => .{ .repo_commits = .{ .name = try parseName(repo_route_max_len, helper.name), .after = helper.after } },│ │349 .repo_settings => .{ .repo_settings = try parseName(repo_route_max_len, helper.name) }, │ │350 .repo_auth => .{ .repo_auth = try parseName(repo_route_max_len, helper.name) }, │ │351 }; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │591 }; │ │592 const a_prefix = "a:"; │ │593 if (!std.mem.startsWith(u8, custom, a_prefix)) return null; │ │566 - const route = RoutablePage.fromUrl(custom[a_prefix.len..]) orelse return null; │ │594 + const url = custom[a_prefix.len..]; │ │595 + const q = std.mem.indexOfScalar(u8, url, '?'); │ │596 + const route = RoutablePage.fromUrl(if (q) |i| url[0..i] else url, if (q) |i| url[i + 1 ..] else null) orelse return null;│ │597 // a link to a different parent page always navigates; within the repo page, │ │598 // a files-directory link navigates while settings/auth tab links stay │ │599 // in-page (header-handled). │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯