a73x

a6f3c66c

refactor: the removed-tile flag is not called gone

a73x   2026-09-01 09:40

Commit message
refactor: the removed-tile flag is not called gone

The spec about to land (docs/superpowers/specs/2026-09-01-gone-panes-design.md)
adds a .gone TILE STATE meaning the pane stands and its session does not.
The atomic that meant the opposite - the tile itself has left the wall -
gives up the word first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

src/tui/wall_pump.zig
Old New
@@ -43,7 +43,7 @@ pub fn copySelection(
43 /// this tile has not been forgotten. `paint_mu` is held for the whole paint. 43 /// this tile has not been forgotten. `paint_mu` is held for the whole paint.
44 pub fn tilePaintBegin(ctx: ?*anyopaque) bool { 44 pub fn tilePaintBegin(ctx: ?*anyopaque) bool {
45 const t: *Tile = @ptrCast(@alignCast(ctx.?)); 45 const t: *Tile = @ptrCast(@alignCast(ctx.?));
46 if (t.gone.load(.acquire)) return false; 46 if (t.removed.load(.acquire)) return false;
47 t.shared.paint_mu.lock(); 47 t.shared.paint_mu.lock();
48 // Under the lock, not before: a pump that loses the race paints over the 48 // Under the lock, not before: a pump that loses the race paints over the
49 // popup, and `picker_stamp` suppresses the repaint that would repair it. 49 // popup, and `picker_stamp` suppresses the repaint that would repair it.
@@ -216,9 +216,9 @@ pub fn askOn(target: client.Target, shared: *const Shared) client.Target {
216 fn dial(alloc: std.mem.Allocator, t: *Tile, target_in: client.Target) ?client.Transport { 216 fn dial(alloc: std.mem.Allocator, t: *Tile, target_in: client.Target) ?client.Transport {
217 var target = askOn(target_in, t.shared); 217 var target = askOn(target_in, t.shared);
218 var backoff_ms: u64 = 0; 218 var backoff_ms: u64 = 0;
219 // `gone` as well as `running`: a tile forgotten while retrying a dead 219 // `removed` as well as `running`: a tile forgotten while retrying a dead
220 // host must not keep a thread and a backoff alive. 220 // host must not keep a thread and a backoff alive.
221 while (t.shared.running.load(.acquire) and !t.gone.load(.acquire)) { 221 while (t.shared.running.load(.acquire) and !t.removed.load(.acquire)) {
222 // The reason is not kept — the picker row polls the same host and 222 // The reason is not kept — the picker row polls the same host and
223 // carries it. The PID is: only it separates a refusal from a dead box. 223 // carries it. The PID is: only it separates a refusal from a dead box.
224 var d: handoff.Dial = .{}; 224 var d: handoff.Dial = .{};
@@ -241,7 +241,7 @@ fn dial(alloc: std.mem.Allocator, t: *Tile, target_in: client.Target) ?client.Tr
241 // Sliced sleep so quit is never behind a full backoff. 241 // Sliced sleep so quit is never behind a full backoff.
242 var slept: u64 = 0; 242 var slept: u64 = 0;
243 while (slept < backoff_ms and t.shared.running.load(.acquire) and 243 while (slept < backoff_ms and t.shared.running.load(.acquire) and
244 !t.gone.load(.acquire)) : (slept += 50) 244 !t.removed.load(.acquire)) : (slept += 50)
245 { 245 {
246 std.Thread.sleep(50 * std.time.ns_per_ms); 246 std.Thread.sleep(50 * std.time.ns_per_ms);
247 } 247 }
@@ -501,9 +501,9 @@ pub fn pumpTile(t: *Tile) void {
501 // What this tile's paint is worth: while it matches the wall's 501 // What this tile's paint is worth: while it matches the wall's
502 // generation the terminal still holds what this thread drew. 502 // generation the terminal still holds what this thread drew.
503 var painted_gen = t.shared.repaint_gen.load(.acquire); 503 var painted_gen = t.shared.repaint_gen.load(.acquire);
504 // `gone` ends this thread exactly as `running` does: the defers free the 504 // `removed` ends this thread exactly as `running` does: the defers free the
505 // daemon slot and nothing else. The session goes on running. 505 // daemon slot and nothing else. The session goes on running.
506 outer: while (t.shared.running.load(.acquire) and !t.gone.load(.acquire)) { 506 outer: while (t.shared.running.load(.acquire) and !t.removed.load(.acquire)) {
507 // The link, the doorbell, then one fd per live agent channel — one 507 // The link, the doorbell, then one fd per live agent channel — one
508 // poll, because the transport has exactly one owning thread. 508 // poll, because the transport has exactly one owning thread.
509 var fdbuf: [3 + agent_locals.len]std.posix.pollfd = undefined; 509 var fdbuf: [3 + agent_locals.len]std.posix.pollfd = undefined;
src/tui/wall_test_wall.zig
Old New
@@ -1043,7 +1043,7 @@ test "setFocus: the marker moves between two tiles with no pump to move it" {
1043 .state = .refused, 1043 .state = .refused,
1044 .alive = std.atomic.Value(bool).init(false), 1044 .alive = std.atomic.Value(bool).init(false),
1045 }; 1045 };
1046 // Forgotten, not merely dead: `x` clears `present` and sets `gone` 1046 // Forgotten, not merely dead: `x` clears `present` and sets `removed`
1047 // together, and a bar painted into a rect the layout no longer owns 1047 // together, and a bar painted into a rect the layout no longer owns
1048 // writes over a neighbour. 1048 // writes over a neighbour.
1049 tiles[2] = Tile{ 1049 tiles[2] = Tile{
@@ -1055,7 +1055,7 @@ test "setFocus: the marker moves between two tiles with no pump to move it" {
1055 .wake_w = -1, 1055 .wake_w = -1,
1056 .state = .exited, 1056 .state = .exited,
1057 .alive = std.atomic.Value(bool).init(false), 1057 .alive = std.atomic.Value(bool).init(false),
1058 .gone = std.atomic.Value(bool).init(true), 1058 .removed = std.atomic.Value(bool).init(true),
1059 }; 1059 };
1060 shared.sel = 0; 1060 shared.sel = 0;
1061 1061
@@ -1122,9 +1122,9 @@ test "a vanished tile is stepped over" {
1122 // through stepPresent, and a click's rectHit and the Ctrl-\ l and 1-9 1122 // through stepPresent, and a click's rectHit and the Ctrl-\ l and 1-9
1123 // chords check the same present array a vanish clears. Tiles are never 1123 // chords check the same present array a vanish clears. Tiles are never
1124 // compacted — pumps hold pointers into the array — so a vanished tile is 1124 // compacted — pumps hold pointers into the array — so a vanished tile is
1125 // a hole focus steps over, not a gap it fills. gone and present clear 1125 // a hole focus steps over, not a gap it fills. `removed` and present clear
1126 // together in `vanishTile`, so stepping on present is stepping past 1126 // together in `vanishTile`, so stepping on present is stepping past
1127 // gone. 1127 // `removed`.
1128 const mid = [_]bool{ true, false, true }; 1128 const mid = [_]bool{ true, false, true };
1129 try std.testing.expectEqual(@as(?usize, 2), wv.stepPresent(&mid, 0, true)); 1129 try std.testing.expectEqual(@as(?usize, 2), wv.stepPresent(&mid, 0, true));
1130 try std.testing.expectEqual(@as(?usize, 0), wv.stepPresent(&mid, 2, true)); 1130 try std.testing.expectEqual(@as(?usize, 0), wv.stepPresent(&mid, 2, true));
src/tui/wallview.zig
Old New
@@ -257,8 +257,8 @@ pub const Tile = struct {
257 pump_done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), 257 pump_done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
258 /// Forgotten by `Ctrl-\ x`: off the wall, and off the wire as soon as the 258 /// Forgotten by `Ctrl-\ x`: off the wall, and off the wire as soon as the
259 /// pump notices. "Remove is detach" — the session is untouched. Distinct 259 /// pump notices. "Remove is detach" — the session is untouched. Distinct
260 /// from `alive`: a gone tile the user removed, a dead one still narrates. 260 /// from `alive`: a tile the user removed, a dead one still narrates.
261 gone: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), 261 removed: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
262 /// The doorbell. The keyboard writes one byte here to wake this tile's 262 /// The doorbell. The keyboard writes one byte here to wake this tile's
263 /// pump; the bytes themselves carry nothing, the mailbox does. Both 263 /// pump; the bytes themselves carry nothing, the mailbox does. Both
264 /// ends are non-blocking, which is what makes a bell that is already 264 /// ends are non-blocking, which is what makes a bell that is already
@@ -436,7 +436,7 @@ pub fn endKey(t: *Tile, now: i64) EndKey {
436 // Nothing is asked of the daemon: the session keeps running and the 436 // Nothing is asked of the daemon: the session keeps running and the
437 // host's next list births the tile back. A pending pane has no pump. 437 // host's next list births the tile back. A pending pane has no pump.
438 if (!t.alive.load(.acquire)) return .drop; 438 if (!t.alive.load(.acquire)) return .drop;
439 // A pump on its FIRST dial is parked in `dial`, which polls `gone` and 439 // A pump on its FIRST dial is parked in `dial`, which polls `removed` and
440 // never `ask`: the key would be swallowed until the box came back, then 440 // never `ask`: the key would be swallowed until the box came back, then
441 // end the session. Birthing onto an `unreachable` row is a designed path. 441 // end the session. Birthing onto an `unreachable` row is a designed path.
442 if (!t.ever_up.load(.acquire) and awaitingSession(t) != null) return .drop; 442 if (!t.ever_up.load(.acquire) and awaitingSession(t) != null) return .drop;
@@ -554,11 +554,11 @@ pub fn paintLabelLocked(t: *Tile) void {
554 554
555 // The bars of the tiles whose pump has ended. Caller holds `paint_mu`. 555 // The bars of the tiles whose pump has ended. Caller holds `paint_mu`.
556 // A dead pump answers no doorbell, so the keyboard is the only thread that 556 // A dead pump answers no doorbell, so the keyboard is the only thread that
557 // can move its focus marker. `gone` rather than `present`, because a focus 557 // can move its focus marker. `removed` rather than `present`, because a focus
558 // move has no `present` slice and a vanished tile's rect is a neighbour's. 558 // move has no `present` slice and a vanished tile's rect is a neighbour's.
559 pub fn paintDeadBarsLocked(tiles: []Tile) void { 559 pub fn paintDeadBarsLocked(tiles: []Tile) void {
560 for (tiles) |*t| { 560 for (tiles) |*t| {
561 if (!t.alive.load(.acquire) and !t.gone.load(.acquire)) paintLabelLocked(t); 561 if (!t.alive.load(.acquire) and !t.removed.load(.acquire)) paintLabelLocked(t);
562 } 562 }
563 } 563 }
564 564
@@ -750,7 +750,7 @@ pub fn paintEmptyWallLocked(shared: *Shared) void {
750 /// focus hand-off is written once. `to` overrides where the focus goes. 750 /// focus hand-off is written once. `to` overrides where the focus goes.
751 pub fn vanishTile(tiles: []Tile, present: []bool, shared: *Shared, i: usize, to: ?usize) void { 751 pub fn vanishTile(tiles: []Tile, present: []bool, shared: *Shared, i: usize, to: ?usize) void {
752 present[i] = false; 752 present[i] = false;
753 tiles[i].gone.store(true, .release); 753 tiles[i].removed.store(true, .release);
754 ring(&tiles[i]); 754 ring(&tiles[i]);
755 shared.tree.remove(@intCast(i)); 755 shared.tree.remove(@intCast(i));
756 // Through `setFocus`, never by writing `sel`: a focus that arrives 756 // Through `setFocus`, never by writing `sel`: a focus that arrives