From 38a3367087b5867c5809dab97c661fae189149fb Mon Sep 17 00:00:00 2001 From: "fizzy-linux[bot]" <346940526+fizzy-linux[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 11:46:20 -0500 Subject: [PATCH 1/2] tracks: reopening a document never writes on the frame pickUp flushed every closed document's pending track (pack, write, spin on the checkpoint's writer) before reading a kept file. It now reads a waiting copy from memory and leaves the write to the checkpoint's thread; only quitting flushes. Agent-Team: linux Worker: claude Co-Authored-By: Claude Sonnet 5.5 --- app/Tracks.zig | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/app/Tracks.zig b/app/Tracks.zig index c07deca3..da379ba8 100644 --- a/app/Tracks.zig +++ b/app/Tracks.zig @@ -548,8 +548,7 @@ fn keepLater(self: *Tracks, t: *Track) bool { return true; } -/// Write what `keepLater` left, here: for a read of a kept file (`pickUp`) or the app quitting, -/// neither of which can wait for the checkpoint. +/// Write what `keepLater` left, here: for the app quitting, which cannot wait for the checkpoint. fn flushPending(self: *Tracks) void { if (comptime !can_keep) return; // The checkpoint's thread may hold older copies of the same files. @@ -625,9 +624,15 @@ fn pickUp(self: *Tracks, t: *Track) bool { if (!keepable(path)) return false; const file = self.keptFile(self.gpa, path) orelse return false; defer self.gpa.free(file); - self.flushPending(); - const bytes = std.Io.Dir.cwd().readFileAlloc(dvui.io, file, self.gpa, .unlimited) catch return false; - defer self.gpa.free(bytes); + // A closed document's track still waiting for the checkpoint's thread is read where it is, not + // written first: a reopen never packs or writes on the frame. The last is the newest. + var waiting: ?[]const u8 = null; + for (self.pending.items) |k| { + if (std.mem.eql(u8, k.file, file)) waiting = k.bytes; + } + const read_bytes = if (waiting) |_| null else std.Io.Dir.cwd().readFileAlloc(dvui.io, file, self.gpa, .unlimited) catch return false; + defer if (read_bytes) |b| self.gpa.free(b); + const bytes = waiting orelse read_bytes.?; const prof = core.profile.begin("fizzy", "tracks.pickUp"); defer prof.end(); var kept = track_file.read(self.gpa, bytes) catch return false; @@ -1389,10 +1394,14 @@ const KeptStand = struct { return true; } + // lint-allow(anyerror): the vtable's signature + fn restoreHistory(_: *anyopaque, _: sdk.DocHandle, _: sdk.Plugin.RestoredHistory) anyerror!void {} + const vtable: sdk.Plugin.VTable = v: { var v = Stand.vtable; v.documentPath = documentPath; v.isDirty = isDirty; + v.restoreHistory = restoreHistory; break :v v; }; var plugin: sdk.Plugin = .{ .state = &Stand.state, .vtable = &vtable, .id = "kept", .display_name = "Kept", .step_format = 1 }; @@ -1464,7 +1473,22 @@ test "a closed document's track waits unpacked for the checkpoint's thread" { try testing.expect(size < Stand.bytes.len / 2); } -test "a read of a kept file writes the closed documents' tracks first" { +test "reopening a document reads its closed track from memory and writes nothing" { + var tmp = testing.tmpDir(.{}); + defer tmp.cleanup(); + var t = try KeptStand.tracks(&tmp); + defer t.deinitAll(); + t.closed(1); + try testing.expectEqual(@as(usize, 1), t.pending.items.len); + t.opened(.{ .ptr = &Stand.state, .owner = &KeptStand.plugin, .id = 1 }); + t.tick(); + // The track came back, and the frame neither wrote nor dropped the copy waiting for the thread. + try testing.expectEqual(@as(usize, 1), t.open.get(1).?.steps.items.len); + try testing.expectEqual(@as(usize, 1), t.pending.items.len); + try testing.expectEqual(@as(?u64, null), try KeptStand.keptFileSize(&t, &tmp)); +} + +test "the app quitting writes the closed documents' tracks" { var tmp = testing.tmpDir(.{}); defer tmp.cleanup(); var t = try KeptStand.tracks(&tmp); From 5dd049d10f1c469aa6e7d8503b6c5c32792a3ae0 Mon Sep 17 00:00:00 2001 From: "fizzy-linux[bot]" <346940526+fizzy-linux[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 13:53:20 -0500 Subject: [PATCH 2/2] tracks: a reopen waits out a checkpoint write of its track A track the checkpoint's thread took (takeChanged) is no longer in pending and not yet renamed into place, so a reopen read an absent or older file and lost the restored history. pickUp now waits for writing to drain when its file is not in pending: rare, so the common reopen stays off the writer. Agent-Team: linux Worker: claude Co-Authored-By: Claude Sonnet 5.5 --- app/Tracks.zig | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/app/Tracks.zig b/app/Tracks.zig index da379ba8..997dfe5f 100644 --- a/app/Tracks.zig +++ b/app/Tracks.zig @@ -630,6 +630,9 @@ fn pickUp(self: *Tracks, t: *Track) bool { for (self.pending.items) |k| { if (std.mem.eql(u8, k.file, file)) waiting = k.bytes; } + // One the thread already took is on its way to the file: wait it out, which is rare, so the + // read finds the newest history and not an absent or older file. + if (waiting == null) while (self.writing.load(.acquire) != 0) std.Thread.yield() catch {}; const read_bytes = if (waiting) |_| null else std.Io.Dir.cwd().readFileAlloc(dvui.io, file, self.gpa, .unlimited) catch return false; defer if (read_bytes) |b| self.gpa.free(b); const bytes = waiting orelse read_bytes.?; @@ -1488,6 +1491,30 @@ test "reopening a document reads its closed track from memory and writes nothing try testing.expectEqual(@as(?u64, null), try KeptStand.keptFileSize(&t, &tmp)); } +test "reopening a document while the checkpoint's thread writes its track keeps the history" { + const Writer = struct { + fn run(t: *Tracks, k: KeptFile) void { + dvui.io.sleep(.fromMilliseconds(50), .awake) catch {}; + _ = writeKept(testing.allocator, k, "checkpoint"); + t.doneWriting(); + } + }; + var tmp = testing.tmpDir(.{}); + defer tmp.cleanup(); + var t = try KeptStand.tracks(&tmp); + defer t.deinitAll(); + t.closed(1); + const taken = t.takeChanged(false); + defer testing.allocator.free(taken); + defer taken[0].deinit(testing.allocator); + const thread = try std.Thread.spawn(.{}, Writer.run, .{ &t, taken[0] }); + defer thread.join(); + // Taken, and not yet renamed into place: a reopen now must not start a fresh track. + t.opened(.{ .ptr = &Stand.state, .owner = &KeptStand.plugin, .id = 1 }); + t.tick(); + try testing.expectEqual(@as(usize, 1), t.open.get(1).?.steps.items.len); +} + test "the app quitting writes the closed documents' tracks" { var tmp = testing.tmpDir(.{}); defer tmp.cleanup();