From 32198b59020e295efbc48de343a9de69c952dcfc Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 29 Sep 2026 05:15:57 -0300 Subject: A Save that fails logs its err, not a save record The save record was logged as soon as the host was asked to write, so a Save into a read-only directory logged save and then failed. It is now logged only once the write is done; a failure's err record (the write's, naming the path) stands alone, the msg it was also said as dropped. Co-Authored-By: Claude Opus 5.5 --- src/pardes.zig | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) (limited to 'src/pardes.zig') diff --git a/src/pardes.zig b/src/pardes.zig index 5d709679..b488ec1c 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -5239,9 +5239,12 @@ pub const Pardes = struct { p.hostWriteFile(sf.pane, f.path, f.content); const saved_pane = p.panes[sf.pane] orelse return; if (saved_pane.serial != serial) return; - ctlfs.events.noteLog(p, .save, saved_pane); const saved = if (saved_pane.file) |*file| file else return; - if (saved.saved_revision != saved.revision or !saved.watch_after_save) return; + // A save the host could not do (saveFailed) is its err + // record alone, not a `save`. + if (saved.saved_revision != saved.revision) return; + ctlfs.events.noteLog(p, .save, saved_pane); + if (!saved.watch_after_save) return; saved.watch_after_save = false; if (filesystem.localPath(saved.path) != null) p.emit(.{ .watch = .{ .pane = sf.pane, .on = true } }); @@ -7983,3 +7986,27 @@ test "a pane closed while its question stands ends the question in the log" { var want: [64]u8 = undefined; try std.testing.expect(th.logHas(p, try std.fmt.bufPrint(&want, "answer {d} -\ndel {d}", .{ serial, serial }))); } + +test "a Save the host could not do logs no save record" { + const th = @import("ninep/testing.zig"); + const p = try th.withFile(std.testing.allocator, "x\n"); + defer p.deinit(); + const Failing = struct { + core: *Pardes, + const vtable: Host.VTable = .{ .write_file = write }; + fn write(ctx: ?*anyopaque, pane: u8, path: []const u8, _: []const u8) void { + const f: *@This() = @ptrCast(@alignCast(ctx.?)); + f.core.saveFailed(pane, path, error.AccessDenied); + } + }; + var host: Failing = .{ .core = p }; + p.host = .{ .ctx = &host, .vtable = &Failing.vtable }; + _ = th.wr(p, ctlfs.Node.of(p.panes[0].?.serial, .body), "more\n"); + p.perform(.{ .save_file = .{ .pane = 0 } }); + try std.testing.expect(!th.logHas(p, "\nsave ")); + try std.testing.expect(p.panes[0].?.file.?.saved_revision != p.panes[0].?.file.?.revision); + // With the host's write done, it is logged. + p.host = .{}; + p.perform(.{ .save_file = .{ .pane = 0 } }); + try std.testing.expect(th.logHas(p, "\nsave ")); +} -- cgit v1.3