4a25816d
feat: a declined prompt leaves its reason on the tile's own bar
a73x 2026-08-30 10:59
Commit message
src/tui/wall_pump.zig
| Old | New | ||
|---|---|---|---|
| @@ -284,6 +284,10 @@ fn dial(alloc: std.mem.Allocator, t: *Tile, target_in: client.Target) ?client.Tr | |||
| 284 | // from the picker, which is a thing they can choose to do. | 284 | // from the picker, which is a thing they can choose to do. |
| 285 | if (t.shared.prompts) |l| { | 285 | if (t.shared.prompts) |l| { |
| 286 | if (l.declined(d.ssh_pid)) { | 286 | if (l.declined(d.ssh_pid)) { |
| 287 | // The bar says it, before the pump goes: an unfocused tile | ||
| 288 | // that ends without exiting narrates on its own bar and | ||
| 289 | // asks the keyboard for nothing (`wallview.endedTile`). | ||
| 290 | wv.paintLabel(t, .declined); | ||
| 287 | endWith(t, .declined, 1); | 291 | endWith(t, .declined, 1); |
| 288 | return null; | 292 | return null; |
| 289 | } | 293 | } |
src/tui/wall_test_wall.zig
| Old | New | ||
|---|---|---|---|
| @@ -167,9 +167,18 @@ test "endKey: x on a tile that never came up closes the TILE — there is no ses | |||
| 167 | try std.testing.expectEqual(EndKey{ .ask = .end_force }, wv.endKey(&t, 999)); | 167 | try std.testing.expectEqual(EndKey{ .ask = .end_force }, wv.endKey(&t, 999)); |
| 168 | try std.testing.expectEqual(EndKey{ .ask = .end }, wv.endKey(&t, 1000)); | 168 | try std.testing.expectEqual(EndKey{ .ask = .end }, wv.endKey(&t, 1000)); |
| 169 | 169 | ||
| 170 | // A pump that has ended has nothing to ask over. | 170 | // A pump that has ended has nothing to ASK over, so the tile goes — |
| 171 | // whatever it once was. Without this a tile parked on a host no poll | ||
| 172 | // can log into (a declined prompt, a box that is down) sits on the wall | ||
| 173 | // for the wall's life with no key that removes it: `ringLive` is false, | ||
| 174 | // so the ask reaches nobody, and the list that would vanish it never | ||
| 175 | // comes. | ||
| 171 | t.alive.store(false, .release); | 176 | t.alive.store(false, .release); |
| 172 | try std.testing.expectEqual(EndKey.none, wv.endKey(&t, 0)); | 177 | try std.testing.expectEqual(EndKey.drop, wv.endKey(&t, 0)); |
| 178 | // ...including one that never came up at all, which is the same answer | ||
| 179 | // by the other road. | ||
| 180 | t.ever_up.store(false, .release); | ||
| 181 | try std.testing.expectEqual(EndKey.drop, wv.endKey(&t, 0)); | ||
| 173 | } | 182 | } |
| 174 | 183 | ||
| 175 | test "x on a tile whose daemon refused arms a second press, and the window closes after 3 s" { | 184 | test "x on a tile whose daemon refused arms a second press, and the window closes after 3 s" { |
src/tui/wallview.zig
| Old | New | ||
|---|---|---|---|
| @@ -536,8 +536,13 @@ pub const EndKey = union(enum) { | |||
| 536 | /// Whether `Ctrl-\ x` has anything to ask, and what. | 536 | /// Whether `Ctrl-\ x` has anything to ask, and what. |
| 537 | pub fn endKey(t: *Tile, now: i64) EndKey { | 537 | pub fn endKey(t: *Tile, now: i64) EndKey { |
| 538 | // A pump that has ended is the only thread that would ever swap `ask` | 538 | // A pump that has ended is the only thread that would ever swap `ask` |
| 539 | // out again, so an ask stored on one goes nowhere for the wall's life. | 539 | // out again, so an ask stored on one goes nowhere for the wall's life: |
| 540 | if (!t.alive.load(.acquire)) return .none; | 540 | // the TILE goes instead. Nothing is asked of the daemon — a session it |
| 541 | // still holds keeps running, and the host's next list births the tile | ||
| 542 | // back — but a tile whose host no poll can reach (a declined prompt, a | ||
| 543 | // box that is down) was otherwise on the wall for the wall's life with | ||
| 544 | // no key that could remove it. | ||
| 545 | if (!t.alive.load(.acquire)) return .drop; | ||
| 541 | // A pump still on its FIRST dial is alive and parked in `dial`, which | 546 | // A pump still on its FIRST dial is alive and parked in `dial`, which |
| 542 | // polls `gone` and never `ask`: the key would be swallowed for as long | 547 | // polls `gone` and never `ask`: the key would be swallowed for as long |
| 543 | // as the box is down — the picker advertises `unreachable` rows as a | 548 | // as the box is down — the picker advertises `unreachable` rows as a |
| @@ -2351,7 +2356,14 @@ pub fn run(alloc: std.mem.Allocator, host_specs: []const HostSpec, entry: Entry) | |||
| 2351 | exit_msg = "mux: aborted before attaching"; | 2356 | exit_msg = "mux: aborted before attaching"; |
| 2352 | break :keys; | 2357 | break :keys; |
| 2353 | } | 2358 | } |
| 2354 | setNotice(&shared, "[nothing attached there yet - tile closed]"); | 2359 | // Two truths, and the tile knows which: one that |
| 2360 | // never attached leaves nothing behind, while one | ||
| 2361 | // whose pump died still has its session on its | ||
| 2362 | // daemon — `x` closed a tile, not a shell. | ||
| 2363 | setNotice(&shared, if (tiles[z].ever_up.load(.acquire)) | ||
| 2364 | "[tile closed - the session is still on its daemon]" | ||
| 2365 | else | ||
| 2366 | "[nothing attached there yet - tile closed]"); | ||
| 2355 | wall_layout.relayout(w, shared.sel); | 2367 | wall_layout.relayout(w, shared.sel); |
| 2356 | }, | 2368 | }, |
| 2357 | .none => {}, | 2369 | .none => {}, |