╭ changed ─────────────╮ │src/ui/Repo/Events.zig│ ╰──────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────╮ │43 active: Window, │ │44 removed: Window, │ │45 header: Header, │ │46 +// set when the list is limited to the events one transaction wrote│ │47 +moment: ?u64 = null, │ │48 │ │49 const Self = @This(); │ │50 │ ╰───────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────╮ │59 requested_view: ui.RoutablePage.EventsView,│ │60 selected_kind: ?evt.EventKind, │ │61 selected: []const u8, │ │62 + moment_index: ?u64, │ │63 local: bool, │ │64 failure: ?[]const u8, │ │65 ) !Self { │ ╰───────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │66 const aa = arena.allocator(); │ │67 var result = try empty(aa, identity, requested_view, local, failure); │ │68 + result.moment = moment_index; │ │69 if (local) result.header = try Header.local(repo_kind, repo_opts, aa, repo, io, failure);│ │70 const require_selected_event = selected_kind != null or selected.len != 0; │ │71 if ((selected_kind == null) != (selected.len == 0)) return error.NotFound; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │107 root_key = evt.orderKeyDesc(meta.updated_order, &id); │ │108 } │ │109 │ │110 + if (moment_index) |index| { │ │111 + try readMomentEvents(repo_kind, repo_opts, arena, &result, haxy_moment, kind_map, admin_moment, identity, index, selected);│ │112 + return result; │ │113 + } │ │114 + │ │115 for ([_]ui.RoutablePage.EventsView{ .active, .removed }) |view| { │ │116 const id_set_cursor = switch (view) { │ │117 .active => active_cursor, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │144 event_window.events = events.items; │ │145 } │ │146 return result; │ │147 +} │ │148 + │ │149 +// the moment's set is keyed on the event order, not the update order the │ │150 +// active and removed sets use, so it gets its own walk. one transaction's │ │151 +// events are few, so the whole set is scanned to count both views. │ │152 +fn readMomentEvents( │ │153 + comptime repo_kind: rp.RepoKind, │ │154 + comptime repo_opts: rp.RepoOpts(repo_kind), │ │155 + arena: *std.heap.ArenaAllocator, │ │156 + result: *Self, │ │157 + haxy_moment: evt.EventDB(repo_opts.hash).HashMap(.read_only), │ │158 + kind_map: evt.EventDB(repo_opts.hash).HashMap(.read_only), │ │159 + admin_moment: ?evt.AdminDB.HashMap(.read_only), │ │160 + identity: []const u8, │ │161 + moment_index: u64, │ │162 + selected: []const u8, │ │163 +) !void { │ │164 + const DB = evt.EventDB(repo_opts.hash); │ │165 + const aa = arena.allocator(); │ │166 + const ids = try evt.momentEventIds(DB, repo_opts.hash, haxy_moment, moment_index) orelse return error.NotFound; │ │167 + │ │168 + var events: [2]std.ArrayList(Item) = .{ .empty, .empty }; │ │169 + // the selected event's window starts at it, not at the top │ │170 + var awaiting_selected = selected.len != 0; │ │171 + │ │172 + var iter = try ids.iterator(); │ │173 + while (try iter.next()) |cursor_value| { │ │174 + const id = try evt.readOrderKeyId(DB, cursor_value); │ │175 + const kind = (try evt.readEventKind(repo_opts.hash, kind_map, &id)) orelse continue; │ │176 + const meta = try readMeta(repo_opts.hash, arena, haxy_moment, kind, &id); │ │177 + const view: ui.RoutablePage.EventsView = if (meta.removed) .removed else .active; │ │178 + const event_window = switch (view) { │ │179 + .active => &result.active, │ │180 + .removed => &result.removed, │ │181 + }; │ │182 + event_window.count += 1; │ │183 + if (awaiting_selected and view == result.view) { │ │184 + if (!std.mem.eql(u8, selected, &std.fmt.bytesToHex(id, .lower))) continue; │ │185 + awaiting_selected = false; │ │186 + } │ │187 + const window_events = &events[@intFromEnum(view)]; │ │188 + if (window_events.items.len == page_size) { │ │189 + if (event_window.next == null) event_window.next = .{ .id = try formatId(aa, &id), .kind = kind }; │ │190 + continue; │ │191 + } │ │192 + const item = (try readItem(repo_opts.hash, arena, haxy_moment, kind_map, admin_moment, identity, view == .active, kind, &id)) orelse continue;│ │193 + try window_events.append(aa, item); │ │194 + } │ │195 + if (awaiting_selected) return error.NotFound; │ │196 + result.active.events = events[@intFromEnum(ui.RoutablePage.EventsView.active)].items; │ │197 + result.removed.events = events[@intFromEnum(ui.RoutablePage.EventsView.removed)].items; │ │198 } │ │199 │ │200 pub fn empty(aa: std.mem.Allocator, identity: []const u8, view: ui.RoutablePage.EventsView, local: bool, failure: ?[]const u8) !Self { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │392 errdefer list_box.deinit(allocator); │ │393 const event_window = data.window(view); │ │394 for (event_window.events) |event| { │ │335 - const route = ui.RoutablePage.repoEventsRoute(data.identity, view, event.kind, event.id) orelse return error.RouteTooLong; │ │395 + const route = ui.RoutablePage.repoEventsRoute(data.identity, view, event.kind, event.id, data.moment) orelse return error.RouteTooLong;│ │396 const link = try std.fmt.allocPrint(session.page_arena.allocator(), "ai:{s}", .{try route.toUrl(session.page_arena)}); │ │397 try addRow(allocator, &list_box, @tagName(event.kind), link); │ │398 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │399 if (event_window.next) |next| { │ │340 - const route = ui.RoutablePage.repoEventsRoute(data.identity, view, next.kind, next.id) orelse return error.RouteTooLong; │ │400 + const route = ui.RoutablePage.repoEventsRoute(data.identity, view, next.kind, next.id, data.moment) orelse return error.RouteTooLong;│ │401 const link = try std.fmt.allocPrint(session.page_arena.allocator(), "a:{s}", .{try route.toUrl(session.page_arena)}); │ │402 try addRow(allocator, &list_box, "next →", link); │ │403 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │643 var tab_ids: [2]usize = undefined; │ │644 const view_labels = [_][]const u8{ active_tab_label, removed_tab_label }; │ │645 inline for ([_]ui.RoutablePage.EventsView{ .active, .removed }, view_labels, 0..) |view, view_label, i| { │ │586 - const route = ui.RoutablePage.repoEventsRoute(data.identity, view, null, "") orelse return error.RouteTooLong; │ │646 + const route = ui.RoutablePage.repoEventsRoute(data.identity, view, null, "", data.moment) orelse return error.RouteTooLong;│ │647 const count = data.window(view).count; │ │648 var label_buf: [64]u8 = undefined; │ │649 const label = try std.fmt.bufPrint(&label_buf, "{s} ({d})", .{ view_label, count }); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │766 session.data.sync_failure = null; │ │767 switch (session.data.current_page) { │ │768 .repo_events => |route| { │ │709 - if (ui.RoutablePage.repoEventsRoute(route.name.slice(), route.view, null, "")) |first| │ │769 + if (ui.RoutablePage.repoEventsRoute(route.name.slice(), route.view, null, "", route.moment)) |first|│ │770 session.data.current_page = first; │ │771 }, │ │772 else => {}, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯