From 77c7d0bae3b69dc6fccc7cfea7d30aabc1543409 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 13:35:24 -0300 Subject: With no frontend attached, a detached session's messages go after MessageLinger A message lingers until input dismisses it, and a detached session with nobody attached has no input: its messages, and /screen, kept what was said long ago. The session now marks the core unattended while no client is attached, and the core drops a pane's messages once the newest has been up MessageLinger on the clock, checked as it is stepped or served. A pane's older lines go with its newest rather than each on its own clock; the render pipeline's clock is the place for that. Co-Authored-By: Claude Opus 5.5 --- src/Messages.zig | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) (limited to 'src/Messages.zig') diff --git a/src/Messages.zig b/src/Messages.zig index 72b3b63c..0652f974 100644 --- a/src/Messages.zig +++ b/src/Messages.zig @@ -184,6 +184,7 @@ fn showMessage(p: *Pardes, id: usize, text: []const u8, stack: bool) void { const arrives = pane.msg_len == 0 or pane.msg_life.phase == .leaving; for (text[0..n], pane.msg[0..n]) |c, *cell| cell.* = if (c < 0x20 or c == 0x7f) ' ' else c; pane.msg_len = @intCast(n); + pane.msg_said_ms = nowMs(); p.needs_frame = true; if (arrives) { pane.msg_life = .{ .phase = if (p.settings.message_animation and n > 0) .entering else .shown }; @@ -197,6 +198,33 @@ fn showMessage(p: *Pardes, id: usize, text: []const u8, stack: bool) void { } } +fn nowMs() i64 { + if (comptime !pardes.hosted) return 0; + var ts: std.c.timespec = undefined; + if (std.c.clock_gettime(.MONOTONIC, &ts) != 0) return 0; + return @as(i64, ts.sec) * 1000 + @divTrunc(@as(i64, ts.nsec), 1_000_000); +} + +/// With no frontend attached no input dismisses a message, and a detached +/// session's /screen and messages kept what was said an hour ago: each +/// pane's lines go once its newest has been up `message_linger_ms`, on the +/// clock, checked as the core is stepped or served. +/// ponytail: a pane's older lines go with its newest, not each on its own +/// clock; the render pipeline's clock will give every line its own. +pub fn expireUnattended(p: *Pardes) void { + if (!p.unattended) return; + const now = nowMs(); + for (p.panes) |slot| { + const pane = slot orelse continue; + if (pane.msg_len == 0 and pane.msg_older_len == 0) continue; + if (now - pane.msg_said_ms < p.settings.message_linger_ms) continue; + pane.msg_len = 0; + pane.msg_life = .{}; + pane.msg_older_len = 0; + p.needs_frame = true; + } +} + /// The key or click that used to clear the message row now starts each /// line's exit: it stays `message_linger_ms`, then dissolves. A line /// already on its way out keeps its own timing. @@ -529,6 +557,28 @@ test "Msg writes the transient row by hand, bare or with text, and input ends it try std.testing.expectEqual(p.panes[2].?.msg.len, row(p, 2).len); } +test "with no frontend attached a message goes once it has lingered, on the clock" { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true }); + defer p.deinit(); + const pane = p.panes[p.active].?; + p.setMessage(p.active, "said"); + p.setMessage(p.active, "said again"); + // Attached: it waits for input, however long. + pane.msg_said_ms -= 60_000; + p.update(.tick); + try std.testing.expect(pane.msg_len > 0); + // Detached with nobody attached: past its linger it is gone, and the + // line above it with it. + p.unattended = true; + _ = p.serveFs(.{ .tag = 1, .op = .read, .node = @intFromEnum(ctlfs.TopFile.index), .size = 64 }); + try std.testing.expectEqual(@as(u16, 0), pane.msg_len); + try std.testing.expectEqual(@as(u8, 0), pane.msg_older_len); + // A fresh one stays until its linger is up. + p.setMessage(p.active, "fresh"); + p.update(.tick); + try std.testing.expect(pane.msg_len > 0); +} + test "a message falls in, lingers past the input that dismisses it, and dissolves" { const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 80, .rows = 24 }); defer p.deinit(); -- cgit v1.3