From 9126dbe7a09d463c4eac99b8cc56b14a033b291a Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 29 Sep 2026 04:58:30 -0300 Subject: follow new: a log follower that starts at the end A follower read the ring's whole history before anything new, and there was no way to skip it. Writing follow new instead of follow skips what the open froze and waits for what comes after (tail -n0 -f); fs.md and the skill say that follow replays first. Co-Authored-By: Claude Opus 5.5 --- src/ninep/events.zig | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/ninep/events.zig b/src/ninep/events.zig index a3f8c278..1067b60e 100644 --- a/src/ninep/events.zig +++ b/src/ninep/events.zig @@ -371,7 +371,12 @@ pub fn readLog(p: *Pardes, req: Req) Reply { /// records instead of ending; closing it is the way back. pub fn writeLog(p: *Pardes, req: Req) Reply { const slot = &(tree.openOf(p, req) orelse return Reply.fail(req.tag, E.INVAL)).what.log; - if (!std.mem.eql(u8, std.mem.trim(u8, req.data, " \t\r\n"), "follow")) return Reply.fail(req.tag, E.INVAL); + const word = std.mem.trim(u8, req.data, " \t\r\n"); + // `follow` reads what the open froze, then waits; `follow new` skips + // what was there and waits for what comes after, as tail -n0 -f does. + if (std.mem.eql(u8, word, "follow new")) { + slot.pos = slot.bytes.len; + } else if (!std.mem.eql(u8, word, "follow")) return Reply.fail(req.tag, E.INVAL); slot.follow = true; return .{ .tag = req.tag, .written = @intCast(req.data.len) }; } @@ -1110,3 +1115,18 @@ test "a click in the body reports the offsets of the word it takes, so the recor _ = call(p, .{ .tag = 3, .op = .release, .node = event, .handle = h }); } } + +test "follow new skips what the log held and waits for what comes after" { + const p = try withFile(testing.allocator, "x\n"); + defer p.deinit(); + p.update(.tick); // the pane's `new` is logged first + while (p.nextEffect()) |_| {} + p.setMessage(0, "before the open"); + const log = @intFromEnum(tree.TopFile.log); + const h = call(p, .{ .tag = 1, .op = .open, .node = log }).reply.handle; + try testing.expectEqual(Status.ok, call(p, .{ .tag = 2, .op = .write, .node = log, .handle = h, .data = "follow new\n" }).reply.status); + try testing.expectEqual(Status.again, call(p, .{ .tag = 3, .op = .read, .node = log, .handle = h, .size = 4096 }).reply.status); + p.setMessage(0, "after it"); + try testing.expect(std.mem.endsWith(u8, call(p, .{ .tag = 4, .op = .read, .node = log, .handle = h, .size = 4096 }).bytes, " after it\n")); + _ = call(p, .{ .tag = 5, .op = .release, .node = log, .handle = h }); +} -- cgit v1.3