╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──────────────╮ │src/ui/Repo/Patches.zig│ ╰───────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │821 selected_id: []const u8, │ │822 target_repo: *rp.Repo(repo_kind, repo_opts), │ │823 ) !Window { │ │800 - const user_id = session.data.user_id orelse return .empty; │ │801 - if (user_id.len != evt.event_id_size) return .empty; │ │824 + const user_id = session.userId() orelse return .empty; │ │825 + const user_name = session.data.user_name orelse return .empty; │ │826 const repos_dir = session.repos_dir orelse return .empty; │ │827 const io = session.io orelse return .empty; │ │804 - const key = evt.Fork.draftKey(repo_id, user_id); │ │805 - const drafts_cursor = try admin_moment.getCursor(hash.hashInt(evt.admin_repo_opts.hash, evt.Fork.repo_user_to_draft_id_set_key)) orelse return .empty;│ │806 - const drafts = try evt.AdminDB.HashMap(.read_only).init(drafts_cursor); │ │807 - const set_cursor = try drafts.getCursor(hash.hashInt(evt.admin_repo_opts.hash, &key)) orelse return .empty; │ │808 - const set = try evt.AdminDB.SortedSet(.read_only).init(set_cursor); │ │828 + │ │829 + // the viewer's drafts live in their user repo │ │830 + var user_repo = (try evt.openUserRepo(io, arena.child_allocator, repos_dir, &user_id)) orelse return .empty; │ │831 + defer user_repo.deinit(io, arena.child_allocator); │ │832 + const user_moment = (try evt.userMoment(&user_repo)) orelse return .empty; │ │833 + const drafts_cursor = try user_moment.getCursor(hash.hashInt(evt.user_repo_opts.hash, evt.Fork.repo_to_draft_id_set_key)) orelse return .empty; │ │834 + const drafts = try evt.UserDB.HashMap(.read_only).init(drafts_cursor); │ │835 + const set_cursor = try drafts.getCursor(hash.hashInt(evt.user_repo_opts.hash, repo_id)) orelse return .empty; │ │836 + const set = try evt.UserDB.SortedSet(.read_only).init(set_cursor); │ │837 const aa = arena.allocator(); │ │838 │ │839 var root_key: ?[]const u8 = null; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │840 if (selected_id.len != 0) { │ │841 const id = evt.parseEventId(selected_id) catch return error.NotFound; │ │814 - if (try evt.Fork.readById(evt.AdminDB, evt.admin_repo_opts.hash, admin_moment, arena, &id)) |record| {│ │842 + if (try evt.Fork.readById(evt.UserDB, evt.user_repo_opts.hash, user_moment, arena, &id)) |record| { │ │843 if (!record.removed and record.event.stage == .draft) { │ │844 const order_key = try aa.dupe(u8, &evt.orderKeyDesc(record.created_order, &id)); │ │845 if (!try set.contains(order_key)) return error.NotFound; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────╮ │866 var items: std.ArrayList(PatchWithId) = .empty; │ │867 var next_id: ?[]const u8 = null; │ │868 while (try iter.next()) |cursor| { │ │841 - const id = try evt.readOrderKeyId(evt.AdminDB, cursor);│ │869 + const id = try evt.readOrderKeyId(evt.UserDB, cursor); │ │870 const id_hex = std.fmt.bytesToHex(id, .lower); │ │871 if (items.items.len == page_size) { │ │872 next_id = try aa.dupe(u8, &id_hex); │ ╰────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │875 const path = try fork.forkPath(aa, repos_dir, &id_hex); │ │876 var fork_repo = rp.Repo(.xit, .{}).open(io, arena.child_allocator, .{ .path = path, .require_repo_root = true }) catch continue; │ │877 defer fork_repo.deinit(io, arena.child_allocator); │ │850 - const entry = (try loadDraftEntry(repo_kind, repo_opts, arena, io, admin_moment, &fork_repo, target_repo, id)) orelse continue; │ │878 + const entry = (try loadDraftEntry(repo_kind, repo_opts, arena, io, admin_moment, &fork_repo, target_repo, id, user_name)) orelse continue;│ │879 try items.append(aa, entry); │ │880 } │ │881 return .{ │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────╮ │895 fork_repo: *rp.Repo(.xit, .{}), │ │896 target_repo: *rp.Repo(repo_kind, repo_opts), │ │897 id: [evt.event_id_size]u8, │ │898 + forker: []const u8, │ │899 ) !?PatchWithId { │ │900 const aa = arena.allocator(); │ │901 const id_hex = std.fmt.bytesToHex(id, .lower);│ ╰───────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────╮ │917 .commit_count = commit_count, │ │918 .no_changes = if (target_oid) |oid| std.mem.eql(u8, &oid, &fork_oid) else false,│ │919 .fork_exists = true, │ │920 + .forker = try aa.dupe(u8, forker), │ │921 }; │ │922 } │ │923 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────╮ │src/ui/Repo/Undo.zig│ ╰────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │375 const admin_repo = session.admin_repo orelse return error.NotFound; │ │376 const moment = try evt.currentMoment(evt.admin_repo_opts, admin_repo); │ │377 const pair = ui.RoutablePage.RepoIdentity.parse(identity) orelse return error.NotFound; │ │378 - const found = try evt.Repo.readByOwnerAndName(evt.AdminDB, evt.admin_repo_opts.hash, moment, session.page_arena, pair.owner, pair.name) orelse return error.NotFound; │ │378 + const io = session.io orelse return error.NotFound; │ │379 + const repos_dir = session.repos_dir orelse return error.NotFound; │ │380 + const found = try evt.readRepoByOwnerAndName(io, session.page_arena.child_allocator, session.page_arena, moment, repos_dir, pair.owner, pair.name) orelse return error.NotFound;│ │381 const hex = std.fmt.bytesToHex(found.event_id, .lower); │ │380 - return .{ .path = try std.fs.path.join(session.page_arena.allocator(), &.{ session.repos_dir orelse return error.NotFound, &hex }), .repo_kind = .xit }; │ │382 + return .{ .path = try std.fs.path.join(session.page_arena.allocator(), &.{ repos_dir, &hex }), .repo_kind = .xit }; │ │383 } │ │384 │ │385 pub const View = struct { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/User.zig│ ╰───────────────╯ ╭────────────────────────────────────────────────────────────╮ │22 │ │23 pub const ForkItem = struct { │ │24 id: []const u8, │ │25 + // the fork route's identity, which names the forker│ │26 + identity: []const u8, │ │27 target: []const u8, │ │28 title: []const u8, │ │29 }; │ ╰────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────╮ │50 repos_start: usize, │ │51 forks_start: usize, │ │52 ) !Self { │ │53 + // admin and user repos share one db type │ │54 const DB = evt.AdminDB; │ │55 const hash_kind = evt.admin_repo_opts.hash;│ │56 │ ╰───────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │54 - // a route identifies a user by name; resolve it to the user's event id via │ │55 - // the name->user-id index, which everything below keys off of. │ │56 - const name_to_user_id_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, "name->user-id")) orelse return error.NotFound;│ │57 - const name_to_user_id = try DB.HashMap(.read_only).init(name_to_user_id_cursor); │ │58 - const user_id_cursor = try name_to_user_id.getCursor(hash.hashInt(hash_kind, name.slice())) orelse return error.NotFound; │ │59 - var user_id_buf: [evt.event_id_size]u8 = undefined; │ │60 - _ = try user_id_cursor.readBytes(&user_id_buf); │ │61 - const user_id: []const u8 = &user_id_buf; │ │57 + // a route identifies a user by name, which everything below keys off of │ │58 + const user_id = (try evt.User.readIdByName(DB, hash_kind, haxy_moment, name.slice())) orelse return error.NotFound; │ │59 + const user = (try evt.User.readById(DB, hash_kind, haxy_moment, arena, &user_id)) orelse return error.NotFound; │ │60 │ │63 - const user = (try evt.User.readById(DB, hash_kind, haxy_moment, arena, user_id)) orelse return error.NotFound; │ │61 + const io = session.io orelse return error.NoMoment; │ │62 + const repos_dir = session.repos_dir orelse return error.NoMoment; │ │63 + const gpa = arena.child_allocator; │ │64 + │ │65 + // a user with no user repo or no events has empty lists │ │66 + var user_repo_maybe = try evt.openUserRepo(io, gpa, repos_dir, &user_id); │ │67 + defer if (user_repo_maybe) |*user_repo| user_repo.deinit(io, gpa); │ │68 + const user_moment = if (user_repo_maybe) |*user_repo| try evt.userMoment(user_repo) else null; │ │69 │ │70 var repos: std.ArrayList(evt.Repo.Record) = .empty; │ │71 var repos_next_start: ?usize = null; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯