╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ───────────╮ │src/ui/Repo/Undo.zig│ ╰────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │120 return .{ │ │121 .action = try std.fmt.allocPrint(aa, "events ({d})", .{count}), │ │122 .description = "updated the event database", │ │111 - .events = .{ │ │112 - .label = try std.fmt.allocPrint(aa, "view events ({d})", .{count}), │ │123 + .button = .{ │ │124 + .label = " events ", │ │125 + .text = try std.fmt.allocPrint(aa, "view events ({d})", .{count}), │ │126 .link = try std.fmt.allocPrint(aa, "a:{s}", .{try route.toUrl(arena)}), │ │127 }, │ │128 + }; │ │129 +} │ │130 + │ │131 +// the push record stores the email of the user whose key the server accepted. │ │132 +// resolving it names them as they are now, and leaves the email when no user │ │133 +// holds it, which is also what local mode sees with no admin db to read │ │134 +fn pushUser(arena: *std.heap.ArenaAllocator, haxy_moment: ?evt.AdminDB.HashMap(.read_only), payload: []const u8) !?DetailButton { │ │135 + const Payload = struct { author: ?struct { email: []const u8 } = null }; │ │136 + const parsed = std.json.parseFromSlice(Payload, arena.child_allocator, payload, .{ .ignore_unknown_fields = true }) catch |err| switch (err) {│ │137 + error.OutOfMemory => return err, │ │138 + else => return null, │ │139 + }; │ │140 + defer parsed.deinit(); │ │141 + const author = parsed.value.author orelse return null; │ │142 + return switch (try ui.Author.initFromEmail(haxy_moment, arena, author.email)) { │ │143 + .unknown => null, │ │144 + .email => |email| .{ .label = " user ", .text = email }, │ │145 + .user_name => |name| .{ .label = " user ", .text = name, .link = try ui.userLink(arena, name) }, │ │146 }; │ │147 } │ │148 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │390 const button = &details.children.values()[0].widget.text_box; │ │391 const description = &details.children.values()[2].widget.text_box; │ │392 try button.setContent(allocator, if (item.index == 0) "initial state cannot be undone" else if (item.index == self.data.count - 1) "undo this" else "undo this and all above it");│ │362 - // a transaction that wrote events links to them instead of describing itself │ │363 - if (item.events) |events| { │ │364 - try description.setContent(allocator, events.label); │ │365 - description.options.label = " events "; │ │366 - description.getFocus().kind = .{ .custom = events.link }; │ │393 + // some actions put a button where the description would go │ │394 + if (item.button) |detail_button| { │ │395 + try description.setContent(allocator, detail_button.text); │ │396 + description.options.label = detail_button.label; │ │397 + description.getFocus().kind = if (detail_button.link) |link| .{ .custom = link } else .text_box; │ │398 } else { │ │399 try description.setContent(allocator, item.description); │ │400 description.options.label = " description "; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯