╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1145 inline for (struct_info.fields) |field| { │ │1118 - if (comptime std.mem.eql(u8, field.name, "created_ts")) continue; │ │1146 + if (comptime derivedField(field.name)) continue; │ │1147 if (!fieldEqual(field.type, @field(a, field.name), @field(b, field.name))) return false;│ │1148 } │ │1149 }, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1302 try map.put(key, .{ .bytes_object = .{ .value = bytes, .format_tag = "bl".* } }); │ │1303 }, │ │1304 .@"enum" => try upsertBytes(DB, hash_kind, map, key, @tagName(value)), │ │1305 + .optional => |optional_info| { │ │1306 + if (optional_info.child != []const u8) @compileError("unsupported upsert field type: " ++ @typeName(Field));│ │1307 + // a missing key is null │ │1308 + if (value) |bytes| { │ │1309 + try upsertBytes(DB, hash_kind, map, key, bytes); │ │1310 + } else { │ │1311 + _ = try map.remove(key); │ │1312 + } │ │1313 + }, │ │1314 else => @compileError("unsupported upsert field type: " ++ @typeName(Field)), │ │1315 } │ │1316 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────╮ │src/event/Issue.zig│ ╰───────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────╮ │ 8 description: []const u8, │ │ 9 tags: []const u8, // space-separated │ │10 status: Status = .open, │ │11 +author_email: ?[]const u8 = null, │ │12 created_ts: u64 = 0, // the commit timestamp of the event that first created this issue│ │13 │ │14 pub const Status = enum { │ ╰───────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────╮ │53 return true; │ │54 } │ │55 │ │55 -// `created_ts` comes from the commit timestamp, not the event │ │56 -// payload, so it must not appear in the JSON we output │ │56 +// the commit-derived fields must not appear in the JSON we output│ │57 pub fn jsonStringify(self: @This(), jw: anytype) !void { │ │58 try jw.beginObject(); │ │59 inline for (std.meta.fields(@This())) |field| { │ ╰──────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │60 - if (comptime std.mem.eql(u8, field.name, "created_ts")) continue;│ │60 + if (comptime evt.derivedField(field.name)) continue; │ │61 try jw.objectField(field.name); │ │62 try jw.write(@field(self, field.name)); │ │63 } │ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────╮ │71 event_id: *const [evt.event_id_size]u8, │ │72 event_maybe: ?@This(), │ │73 arena: *std.heap.ArenaAllocator, │ │74 - created_ts: u64, │ │74 // the commit this event came from, recorded against every field it │ │75 // changes. null from `merge`, which sets oids and conflicts itself.│ │76 event_oid: ?[]const u8, │ ╰────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────╮ │101 var existing_event_maybe: ?@This() = null; │ │102 const existing_cursor_maybe = try event_id_to_issue.getCursor(issue_key); │ │103 if (existing_cursor_maybe) |existing_cursor| { │ │105 - // updates preserve the original creation timestamp │ │104 + // updates preserve the original creation timestamp and author │ │105 const existing_issue = try DB.HashMap(.read_only).init(existing_cursor); │ │106 const existing_event = try evt.read(@This(), DB, hash_kind, arena, existing_issue);│ │107 existing_event_maybe = existing_event; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │108 event_to_write.created_ts = existing_event.created_ts; │ │109 + event_to_write.author_email = existing_event.author_email; │ │110 │ │111 // drop the old status's and tags' entries; the current ones are re-added below│ │112 const order_key = evt.orderKeyDesc(existing_event.created_ts, event_id); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │117 // any event settles the conflict, since resolving in the ui may │ │118 // keep our own values and so change nothing to detect │ │119 if (event_oid != null) _ = try conflicts.remove(&order_key); │ │120 - } else { │ │121 - // first time we've seen this issue: stamp it with the commit timestamp │ │122 - event_to_write.created_ts = created_ts; │ │120 } │ │121 │ │122 try evt.writeOid(@This(), DB, hash_kind, issue_id_to_field_to_oid, issue_key, existing_event_maybe, event, event_oid);│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────╮ │src/event/Repo.zig│ ╰──────────────────╯ ╭──────────────────────────────────────────────────────────────────────╮ │36 } │ │37 } │ │38 │ │39 -// `created_ts` comes from the commit timestamp, not the event │ │40 -// payload, so it must not appear in the JSON we output │ │39 +// the commit-derived fields must not appear in the JSON we output│ │40 pub fn jsonStringify(self: @This(), jw: anytype) !void { │ │41 try jw.beginObject(); │ │42 inline for (std.meta.fields(@This())) |field| { │ ╰──────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯