╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──────╮ │src/ui/User.zig│ ╰───────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │67 - │ │68 - // the user-id->repo-id-set index maps each user to a set of their repo event │ │69 - // ids ordered by creation (newest first); it only exists once a repo has │ │70 - // been consumed, so a user with no repos simply yields an empty list. │ │71 - if (try haxy_moment.getCursor(hash.hashInt(hash_kind, "user-id->repo-id-set"))) |user_id_to_repo_id_set_cursor| { │ │72 - const user_id_to_repo_id_set = try DB.HashMap(.read_only).init(user_id_to_repo_id_set_cursor); │ │73 - if (try user_id_to_repo_id_set.getCursor(hash.hashInt(hash_kind, user_id))) |user_repos_cursor| { │ │72 + if (user_moment) |moment| { │ │73 + if (try moment.getCursor(hash.hashInt(hash_kind, evt.Repo.active_id_set_key))) |user_repos_cursor| { │ │74 const user_repos = try DB.SortedSet(.read_only).init(user_repos_cursor); │ │75 const count = try user_repos.count(); │ │76 - │ │77 - const event_id_to_repo_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, "event-id->repo")) orelse return error.NotFound;│ │78 - const event_id_to_repo = try DB.HashMap(.read_only).init(event_id_to_repo_cursor); │ │76 │ │77 // read the window [start, start+page_size) with one seek to the start │ │78 // rank, then a sequential walk. │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │80 var repos_iter = try user_repos.iteratorFromIndex(repos_start); │ │81 var i = repos_start; │ │82 while (i < end) : (i += 1) { │ │86 - var kv_cursor = (try repos_iter.next()) orelse break; │ │87 - const kv = try kv_cursor.readKeyValuePair(); │ │88 - // the set key is orderKey = [created-order][event-id]; its trailing │ │89 - // bytes are the repo event id. │ │90 - var order_key: [@sizeOf(u64) + evt.event_id_size]u8 = undefined; │ │91 - _ = try kv.key_cursor.readBytes(&order_key); │ │92 - const event_id = order_key[@sizeOf(u64)..]; │ │93 - const repo_cursor = try event_id_to_repo.getCursor(hash.hashInt(hash_kind, event_id)) orelse continue;│ │94 - const repo_map = try DB.HashMap(.read_only).init(repo_cursor); │ │95 - const repo_event = try evt.read(evt.Repo.Record, DB, hash_kind, arena, repo_map); │ │83 + const kv_cursor = (try repos_iter.next()) orelse break; │ │84 + const event_id = try evt.readOrderKeyId(DB, kv_cursor); │ │85 + const repo_event = (try evt.Repo.readById(DB, hash_kind, moment, arena, &event_id)) orelse continue; │ │86 // unreadable repos leave their window short rather than shifting the others │ │87 if (evt.Repo.roleOf(repo_event, session.userId()) == .none) continue; │ │88 try repos.append(arena.allocator(), repo_event); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 93 │ │ 94 var forks: std.ArrayList(ForkItem) = .empty; │ │ 95 var forks_next_start: ?usize = null; │ │106 - if (try haxy_moment.getCursor(hash.hashInt(hash_kind, evt.Fork.user_id_to_fork_id_set_key))) |by_user_cursor| { │ │107 - const by_user = try DB.HashMap(.read_only).init(by_user_cursor); │ │108 - if (try by_user.getCursor(hash.hashInt(hash_kind, user_id))) |user_forks_cursor| { │ │ 96 + if (user_moment) |moment| { │ │ 97 + if (try moment.getCursor(hash.hashInt(hash_kind, evt.Fork.active_id_set_key))) |user_forks_cursor| { │ │ 98 const user_forks = try DB.SortedSet(.read_only).init(user_forks_cursor); │ │110 - const repos_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, evt.Repo.record_map_key)) orelse return error.NotFound;│ │111 - const repo_records = try DB.HashMap(.read_only).init(repos_cursor); │ │ 99 const count = try user_forks.count(); │ │100 const end = @min(forks_start +| page_size, count); │ │101 var iter = try user_forks.iteratorFromIndex(forks_start); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │102 var i = forks_start; │ │103 while (i < end) : (i += 1) { │ │117 - const cursor = (try iter.next()) orelse break; │ │118 - var kv_cursor = cursor; │ │119 - const kv = try kv_cursor.readKeyValuePair(); │ │120 - var order_key: [@sizeOf(u64) + evt.event_id_size]u8 = undefined; │ │121 - _ = try kv.key_cursor.readBytes(&order_key); │ │122 - const fork_id = order_key[@sizeOf(u64)..]; │ │123 - const fork_id_hex = std.fmt.bytesToHex(fork_id.*, .lower); │ │124 - const record = (try evt.Fork.readById(DB, hash_kind, haxy_moment, arena, fork_id)) orelse continue; │ │104 + const kv_cursor = (try iter.next()) orelse break; │ │105 + const fork_id = try evt.readOrderKeyId(DB, kv_cursor); │ │106 + const fork_id_hex = std.fmt.bytesToHex(fork_id, .lower); │ │107 + const record = (try evt.Fork.readById(DB, hash_kind, moment, arena, &fork_id)) orelse continue; │ │108 │ │126 - const repo_cursor = try repo_records.getCursor(hash.hashInt(hash_kind, record.event.repo_id)) orelse continue; │ │127 - const target_repo = try evt.read(evt.Repo.Record, DB, hash_kind, arena, try DB.HashMap(.read_only).init(repo_cursor)); │ │109 + // the target lives in its owner's user repo │ │110 + const owner_id = record.event.repo_user_id[0..evt.event_id_size]; │ │111 + const target_repo = (try evt.readRepoById(io, gpa, arena, repos_dir, owner_id, record.event.repo_id)) orelse continue; │ │112 if (evt.Repo.roleOf(target_repo, session.userId()) == .none) continue; │ │129 - const owner = (try evt.User.readById(DB, hash_kind, haxy_moment, arena, target_repo.event.user_id)) orelse continue; │ │130 - const target = try std.fmt.allocPrint(arena.allocator(), "{s}/{s}", .{ owner.event.name, target_repo.event.name }); │ │113 + const owner = (try evt.User.readById(DB, hash_kind, haxy_moment, arena, owner_id)) orelse continue; │ │114 │ │115 var title: []const u8 = "(unavailable)"; │ │133 - if (session.io) |io| if (session.repos_dir) |repos_dir| { │ │134 - const path = try fork.forkPath(arena.allocator(), repos_dir, &fork_id_hex); │ │135 - if (rp.Repo(.xit, .{}).open(io, arena.child_allocator, .{ .path = path, .require_repo_root = true })) |opened| { │ │136 - var fork_repo = opened; │ │137 - defer fork_repo.deinit(io, arena.child_allocator); │ │138 - if (evt.currentMoment(.{}, &fork_repo)) |moment| { │ │139 - if (try evt.Patch.readById(evt.EventDB(.sha1), .sha1, moment, arena, fork_id)) |patch| title = patch.event.title; │ │140 - } else |_| {} │ │116 + const path = try fork.forkPath(arena.allocator(), repos_dir, &fork_id_hex); │ │117 + if (rp.Repo(.xit, .{}).open(io, gpa, .{ .path = path, .require_repo_root = true })) |opened| { │ │118 + var fork_repo = opened; │ │119 + defer fork_repo.deinit(io, gpa); │ │120 + if (evt.currentMoment(.{}, &fork_repo)) |fork_moment| { │ │121 + if (try evt.Patch.readById(evt.EventDB(.sha1), .sha1, fork_moment, arena, &fork_id)) |patch| title = patch.event.title;│ │122 } else |_| {} │ │142 - }; │ │123 + } else |_| {} │ │124 try forks.append(arena.allocator(), .{ │ │125 .id = try arena.allocator().dupe(u8, &fork_id_hex), │ │145 - .target = target, │ │126 + .identity = try std.fmt.allocPrint(arena.allocator(), "{s}/{s}", .{ user.event.name, target_repo.event.name }), │ │127 + .target = try std.fmt.allocPrint(arena.allocator(), "{s}/{s}", .{ owner.event.name, target_repo.event.name }), │ │128 .title = title, │ │129 }); │ │130 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │210 if (data.forks_start > 0) │ │211 try items.append(aa, .{ .text = "← previous", .link = try std.fmt.allocPrint(aa, "a:/user/{s}/forks/start:{d}", .{ data.user.name, data.forks_start -| page_size }) });│ │212 for (data.forks) |fork_item| { │ │231 - const route = ui.RoutablePage.forkPatchRoute(fork_item.target, fork_item.id) orelse return error.RouteTooLong; │ │213 + const route = ui.RoutablePage.forkPatchRoute(fork_item.identity, fork_item.id) orelse return error.RouteTooLong; │ │214 try items.append(aa, .{ │ │215 .text = try std.fmt.allocPrint(aa, "{s}\n{s}", .{ fork_item.target, fork_item.title }), │ │216 .link = try std.fmt.allocPrint(aa, "a:{s}", .{try route.toUrl(session.page_arena)}), │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/ui/widget.zig│ ╰─────────────────╯ ╭──────────────────────────────────────────────╮ │65 patch_source: ui.Repo.Patches.Source, │ │66 home_about: ui.Home.About.View, │ │67 home_users: ui.Home.Users.View, │ │68 - home_repos: ui.Home.Repos.View, │ │68 auth_tab: ui.Home.Header.AuthTab.View,│ │69 home_settings: ui.Home.Settings.View, │ │70 home_auth: ui.Home.Auth.View, │ ╰──────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │111 // whether moving up should return to the page header │ │112 pub fn atTop(self: *Widget, root_focus: *Focus) bool { │ │113 return switch (self.*) { │ │115 - inline .home_about, .home_users, .home_repos, .flow_box_scroll, .home_settings, .auth_login, .auth_logout, .quit, .repo_files, .repo_commits, .diff_view, .repo_refs, .repo_issues, .repo_patches, .repo_discussions, .repo_events, .repo_undo => |*view| view.atTop(),│ │114 + inline .home_about, .home_users, .flow_box_scroll, .home_settings, .auth_login, .auth_logout, .quit, .repo_files, .repo_commits, .diff_view, .repo_refs, .repo_issues, .repo_patches, .repo_discussions, .repo_events, .repo_undo => |*view| view.atTop(), │ │115 inline .home_auth, .repo_patch_detail => |*view| view.atTop(root_focus), │ │116 else => false, │ │117 }; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────────╮ │src/ui/widget/thread.zig│ ╰────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2704 if (comptime wasm) return; │ │2705 const io = self.session.io orelse return; │ │2706 const src = self.data.repo_source orelse return; │ │2707 - const author = ((try self.session.authorize(self.data.identity, .read)) orelse return).author;│ │2707 + const actor = (try self.session.authorize(self.data.identity, .read)) orelse return; │ │2708 │ │2709 const form = self.threadForm() orelse return; │ │2710 const title_input = try formField(form, "title"); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2732 .branch => try source.field().text(allocator), │ │2733 }; │ │2734 defer if (source_branch) |branch| allocator.free(branch); │ │2735 - const event_id_hex = Data.create(self.data, self.session, allocator, author, title, labels, description, target_branch, source_branch) catch |err| {│ │2735 + const event_id_hex = Data.create(self.data, self.session, allocator, actor, title, labels, description, target_branch, source_branch) catch |err| { │ │2736 const failure = FeedbackFailure.fromError(err) orelse return err; │ │2737 try self.rememberThreadFeedback(failure, title, labels, description, target_branch); │ │2738 return; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │2753 const event = evt.EventWithId{ │ │2754 .id = event_id_hex, │ │2755 .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()),│ │2756 - .author = author, │ │2756 + .author = actor.author, │ │2757 .event = @unionInit(evt.Event, @tagName(kind), Event{ │ │2758 .title = title, │ │2759 .description = description, │ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯