005be213
feat: an unreachable pane says it is retrying, and Enter brings it back
a73x 2026-09-02 08:41
Commit message
src/tui/wall_test_host.zig
| Old | New | ||
|---|---|---|---|
| @@ -1013,31 +1013,77 @@ test "setFocus: landing on a gone pane says what the two keys are" { | |||
| 1013 | try std.testing.expectEqualStrings(wv.gone_notice, shared.notice[0..shared.notice_len]); | 1013 | try std.testing.expectEqualStrings(wv.gone_notice, shared.notice[0..shared.notice_len]); |
| 1014 | } | 1014 | } |
| 1015 | 1015 | ||
| 1016 | test "armRevive: Enter turns a gone pane into a creating attach in the same rect" { | 1016 | test "setFocus: landing on an unreachable pane says it is retrying and Enter retries" { |
| 1017 | // The pane the wall used to be SILENT about: no notice on focus, so the | ||
| 1018 | // user pressed Enter (the gone reflex) into nothing. It now says the | ||
| 1019 | // host is retrying and that Enter hurries it, the mirror of the gone | ||
| 1020 | // notice one state over. | ||
| 1017 | var arena = std.heap.ArenaAllocator.init(std.testing.allocator); | 1021 | var arena = std.heap.ArenaAllocator.init(std.testing.allocator); |
| 1018 | defer arena.deinit(); | 1022 | defer arena.deinit(); |
| 1019 | const alloc = arena.allocator(); | 1023 | const alloc = arena.allocator(); |
| 1020 | var shared: Shared = undefined; | 1024 | var shared: Shared = undefined; |
| 1021 | fixture.stoppedWall(alloc, &shared); | 1025 | fixture.stoppedWall(alloc, &shared); |
| 1022 | var tiles: [2]Tile = undefined; | 1026 | var tiles: [4]Tile = undefined; |
| 1023 | try wv.seedTile(&tiles[0], .{ .target = .{ .sock = "/tmp/box.sock" }, .label = "box#a", .session = "a" }, .{ .top = 1, .left = 41, .rows = 23, .cols = 39 }, &shared, 0, 0); | 1027 | var present = [_]bool{false} ** 4; |
| 1024 | tiles[0].state = .gone; | 1028 | var live: usize = 0; |
| 1029 | defer fixture.endPumps(tiles[0..live]); | ||
| 1030 | try wv.seedTile(&tiles[0], .{ .target = .{ .sock = "/tmp/box.sock" }, .label = "box#a", .session = "a" }, .{ .top = 1, .left = 0, .rows = 23, .cols = 40 }, &shared, 0, 0); | ||
| 1031 | try wv.seedTile(&tiles[1], .{ .target = .{ .sock = "/tmp/dark.sock" }, .label = "dark#b", .session = "b" }, .{ .top = 1, .left = 41, .rows = 23, .cols = 39 }, &shared, 1, 1); | ||
| 1032 | present[0] = true; | ||
| 1033 | present[1] = true; | ||
| 1034 | live = 2; | ||
| 1035 | tiles[1].state = .@"unreachable"; | ||
| 1025 | 1036 | ||
| 1026 | wv.armRevive(&tiles[0]); | 1037 | wv.setFocus(tiles[0..live], &shared, 1); |
| 1038 | try std.testing.expectEqualStrings(wv.unreachable_notice, shared.notice[0..shared.notice_len]); | ||
| 1039 | } | ||
| 1027 | 1040 | ||
| 1028 | // The tile is no longer a placeholder: it CREATES (the daemon has no | 1041 | test "armPane: the three wakes differ only in create and start" { |
| 1029 | // such session - that is what gone means), and it does so exactly | 1042 | var arena = std.heap.ArenaAllocator.init(std.testing.allocator); |
| 1030 | // where it stood. | 1043 | defer arena.deinit(); |
| 1031 | try std.testing.expect(!tiles[0].pending); | 1044 | const alloc = arena.allocator(); |
| 1032 | try std.testing.expect(tiles[0].creates); | 1045 | var shared: Shared = undefined; |
| 1033 | try std.testing.expectEqual(wv.State.connecting, tiles[0].state); | 1046 | fixture.stoppedWall(alloc, &shared); |
| 1034 | try std.testing.expect(tiles[0].alive.load(.acquire)); | 1047 | |
| 1035 | try std.testing.expect(!tiles[0].pump_done.load(.acquire)); | 1048 | // A gone pane on a HAND host, so `asked` has somewhere to land. Gone |
| 1036 | try std.testing.expectEqual(@as(u16, 41), tiles[0].rect.left); | 1049 | // means the host is up, so Enter re-creates ({create, no start}) and |
| 1037 | try std.testing.expectEqualStrings("a", tiles[0].r.session); | 1050 | // does so exactly where the pane stood. |
| 1051 | var gone: Tile = undefined; | ||
| 1052 | const hand = client.Target{ .hand = .{ .host = "box", .ssh_argv = &.{"ssh"}, .cache_path = null } }; | ||
| 1053 | try wv.seedTile(&gone, .{ .target = hand, .label = "box#a", .session = "a" }, .{ .top = 1, .left = 41, .rows = 23, .cols = 39 }, &shared, 0, 0); | ||
| 1054 | gone.state = .gone; | ||
| 1055 | wv.armPane(&gone, .{ .creates = true, .asked = false }); | ||
| 1056 | try std.testing.expect(!gone.pending); | ||
| 1057 | try std.testing.expect(gone.creates); | ||
| 1058 | try std.testing.expect(!gone.r.target.hand.asked); | ||
| 1059 | try std.testing.expectEqual(wv.State.connecting, gone.state); | ||
| 1060 | try std.testing.expect(gone.alive.load(.acquire)); | ||
| 1061 | try std.testing.expect(!gone.pump_done.load(.acquire)); | ||
| 1062 | try std.testing.expectEqual(@as(u16, 41), gone.rect.left); | ||
| 1063 | try std.testing.expectEqualStrings("a", gone.r.session); | ||
| 1064 | |||
| 1065 | // An unreachable pane on the same kind of host: its daemon may be dark, | ||
| 1066 | // so Enter STARTS it first ({create, start}) — the one field that | ||
| 1067 | // differs from the gone wake. | ||
| 1068 | var unreach: Tile = undefined; | ||
| 1069 | const hand2 = client.Target{ .hand = .{ .host = "box", .ssh_argv = &.{"ssh"}, .cache_path = null } }; | ||
| 1070 | try wv.seedTile(&unreach, .{ .target = hand2, .label = "box#b", .session = "b" }, .{ .top = 1, .left = 0, .rows = 23, .cols = 40 }, &shared, 1, 0); | ||
| 1071 | unreach.state = .@"unreachable"; | ||
| 1072 | wv.armPane(&unreach, .{ .creates = true, .asked = true }); | ||
| 1073 | try std.testing.expect(unreach.creates); | ||
| 1074 | try std.testing.expect(unreach.r.target.hand.asked); | ||
| 1075 | |||
| 1076 | // A poll bind: attach only, neither create nor start. | ||
| 1077 | var bind: Tile = undefined; | ||
| 1078 | const hand3 = client.Target{ .hand = .{ .host = "box", .ssh_argv = &.{"ssh"}, .cache_path = null } }; | ||
| 1079 | try wv.seedTile(&bind, .{ .target = hand3, .label = "box#c", .session = "c" }, .{ .top = 1, .left = 0, .rows = 23, .cols = 40 }, &shared, 2, 0); | ||
| 1080 | bind.state = .waiting; | ||
| 1081 | wv.armPane(&bind, .{ .creates = false, .asked = false }); | ||
| 1082 | try std.testing.expect(!bind.creates); | ||
| 1083 | try std.testing.expect(!bind.r.target.hand.asked); | ||
| 1038 | } | 1084 | } |
| 1039 | 1085 | ||
| 1040 | test "keysToFocused: a gone pane eats every byte except Enter" { | 1086 | test "keysToFocused: a gone pane eats every byte except Enter, which revives without starting" { |
| 1041 | var arena = std.heap.ArenaAllocator.init(std.testing.allocator); | 1087 | var arena = std.heap.ArenaAllocator.init(std.testing.allocator); |
| 1042 | defer arena.deinit(); | 1088 | defer arena.deinit(); |
| 1043 | const alloc = arena.allocator(); | 1089 | const alloc = arena.allocator(); |
| @@ -1048,7 +1094,8 @@ test "keysToFocused: a gone pane eats every byte except Enter" { | |||
| 1048 | // `shared` is this frame too: the test may not return until that thread | 1094 | // `shared` is this frame too: the test may not return until that thread |
| 1049 | // has stored `alive = false`, or the next test's stack is what it reads. | 1095 | // has stored `alive = false`, or the next test's stack is what it reads. |
| 1050 | defer fixture.endPumps(tiles[0..1]); | 1096 | defer fixture.endPumps(tiles[0..1]); |
| 1051 | try wv.seedTile(&tiles[0], .{ .target = .{ .sock = "/tmp/box.sock" }, .label = "box#a", .session = "a" }, .{ .top = 1, .left = 0, .rows = 23, .cols = 40 }, &shared, 0, 0); | 1097 | const hand = client.Target{ .hand = .{ .host = "box", .ssh_argv = &.{"ssh"}, .cache_path = null } }; |
| 1098 | try wv.seedTile(&tiles[0], .{ .target = hand, .label = "box#a", .session = "a" }, .{ .top = 1, .left = 0, .rows = 23, .cols = 40 }, &shared, 0, 0); | ||
| 1052 | tiles[0].state = .gone; | 1099 | tiles[0].state = .gone; |
| 1053 | 1100 | ||
| 1054 | // Ordinary typing reaches no session and queues nowhere. | 1101 | // Ordinary typing reaches no session and queues nowhere. |
| @@ -1056,10 +1103,40 @@ test "keysToFocused: a gone pane eats every byte except Enter" { | |||
| 1056 | try std.testing.expectEqual(@as(usize, 0), tiles[0].in_len); | 1103 | try std.testing.expectEqual(@as(usize, 0), tiles[0].in_len); |
| 1057 | try std.testing.expect(tiles[0].pending); | 1104 | try std.testing.expect(tiles[0].pending); |
| 1058 | 1105 | ||
| 1059 | // Enter revives. (`running` is false in this fixture, so the spawned | 1106 | // Enter revives, and does NOT start the daemon — a gone host is up. |
| 1060 | // pump exits at its gate; the transitions are armRevive's, pinned above.) | 1107 | // (`running` is false in this fixture, so the spawned pump exits at its |
| 1108 | // gate; the transitions are armPane's, pinned above.) | ||
| 1061 | wv.keysToFocused(&tiles[0], "\r"); | 1109 | wv.keysToFocused(&tiles[0], "\r"); |
| 1062 | try std.testing.expect(!tiles[0].pending); | 1110 | try std.testing.expect(!tiles[0].pending); |
| 1111 | try std.testing.expect(!tiles[0].r.target.hand.asked); | ||
| 1112 | tiles[0].pump_done.store(true, .release); | ||
| 1113 | } | ||
| 1114 | |||
| 1115 | test "keysToFocused: Enter on an unreachable pane revives it AND starts the daemon" { | ||
| 1116 | var arena = std.heap.ArenaAllocator.init(std.testing.allocator); | ||
| 1117 | defer arena.deinit(); | ||
| 1118 | const alloc = arena.allocator(); | ||
| 1119 | var shared: Shared = undefined; | ||
| 1120 | fixture.stoppedWall(alloc, &shared); | ||
| 1121 | var tiles: [2]Tile = undefined; | ||
| 1122 | defer fixture.endPumps(tiles[0..1]); | ||
| 1123 | // A dark host's saved pane. The old behaviour re-polled and could never | ||
| 1124 | // start a rebooted box's daemon; Enter now wakes a pump that dials | ||
| 1125 | // `--start`, the one thing that boots it. | ||
| 1126 | const hand = client.Target{ .hand = .{ .host = "box", .ssh_argv = &.{"ssh"}, .cache_path = null } }; | ||
| 1127 | try wv.seedTile(&tiles[0], .{ .target = hand, .label = "box#a", .session = "a" }, .{ .top = 1, .left = 0, .rows = 23, .cols = 40 }, &shared, 0, 0); | ||
| 1128 | tiles[0].state = .@"unreachable"; | ||
| 1129 | |||
| 1130 | // Ordinary typing is not the verb: the pane stays a pending drawing. | ||
| 1131 | wv.keysToFocused(&tiles[0], "ls -la"); | ||
| 1132 | try std.testing.expect(tiles[0].pending); | ||
| 1133 | try std.testing.expect(!tiles[0].r.target.hand.asked); | ||
| 1134 | |||
| 1135 | // Enter wakes it with the start armed. | ||
| 1136 | wv.keysToFocused(&tiles[0], "\r"); | ||
| 1137 | try std.testing.expect(!tiles[0].pending); | ||
| 1138 | try std.testing.expect(tiles[0].creates); | ||
| 1139 | try std.testing.expect(tiles[0].r.target.hand.asked); | ||
| 1063 | tiles[0].pump_done.store(true, .release); | 1140 | tiles[0].pump_done.store(true, .release); |
| 1064 | } | 1141 | } |
| 1065 | 1142 | ||
src/tui/wallview.zig
| Old | New | ||
|---|---|---|---|
| @@ -68,7 +68,7 @@ pub const State = enum { | |||
| 68 | return switch (s) { | 68 | return switch (s) { |
| 69 | .waiting => "waiting", | 69 | .waiting => "waiting", |
| 70 | .@"unreachable" => "unreachable", | 70 | .@"unreachable" => "unreachable", |
| 71 | .gone => "gone", | 71 | .gone => "session ended", |
| 72 | .connecting => "connecting", | 72 | .connecting => "connecting", |
| 73 | .up => "up", | 73 | .up => "up", |
| 74 | .reconnecting => "reconnecting", | 74 | .reconnecting => "reconnecting", |
| @@ -730,7 +730,13 @@ pub fn setNoticeIdle(shared: *Shared, text: []const u8) void { | |||
| 730 | 730 | ||
| 731 | /// What a gone pane can do, said where the user is looking. Under the | 731 | /// What a gone pane can do, said where the user is looking. Under the |
| 732 | /// 96-byte notice buffer; `setNoticeIdle` truncates silently past it. | 732 | /// 96-byte notice buffer; `setNoticeIdle` truncates silently past it. |
| 733 | pub const gone_notice: []const u8 = "[session gone - Enter starts it anew, x closes]"; | 733 | pub const gone_notice: []const u8 = "[session ended - Enter restarts it, x closes]"; |
| 734 | |||
| 735 | /// The same, for an unreachable pane — the one the wall was silent about, so | ||
| 736 | /// the user pressed Enter (the gone reflex) and nothing answered. It says | ||
| 737 | /// the host is retrying on its own AND that Enter hurries it, because both | ||
| 738 | /// are true: the shared poller retries every cycle, and Enter pokes it now. | ||
| 739 | pub const unreachable_notice: []const u8 = "[host unreachable, retrying - Enter retries now, x closes]"; | ||
| 734 | 740 | ||
| 735 | /// Take it, once. | 741 | /// Take it, once. |
| 736 | pub fn takeNotice(shared: *Shared, out: []u8) []const u8 { | 742 | pub fn takeNotice(shared: *Shared, out: []u8) []const u8 { |
| @@ -772,18 +778,22 @@ pub fn setFocus(tiles: []Tile, shared: *Shared, next: usize) void { | |||
| 772 | // Scoped rather than deferred to the end of the function: the notice at | 778 | // Scoped rather than deferred to the end of the function: the notice at |
| 773 | // the tail goes through `setNoticeIdle`, which takes `paint_mu` itself, | 779 | // the tail goes through `setNoticeIdle`, which takes `paint_mu` itself, |
| 774 | // and the mutex is not reentrant. | 780 | // and the mutex is not reentrant. |
| 775 | var landed_gone = false; | 781 | var landed: State = .waiting; |
| 776 | { | 782 | { |
| 777 | shared.paint_mu.lock(); | 783 | shared.paint_mu.lock(); |
| 778 | defer shared.paint_mu.unlock(); | 784 | defer shared.paint_mu.unlock(); |
| 779 | moveFocusLocked(tiles, shared, prev, next); | 785 | moveFocusLocked(tiles, shared, prev, next); |
| 780 | // Read HERE because `state` is a `paint_mu` field a pump writes | 786 | // Read HERE because `state` is a `paint_mu` field a pump writes |
| 781 | // through `paintLabel`, and the keyboard is not its owner. | 787 | // through `paintLabel`, and the keyboard is not its owner. |
| 782 | landed_gone = next < tiles.len and tiles[next].state == .gone; | 788 | if (next < tiles.len) landed = tiles[next].state; |
| 783 | } | 789 | } |
| 784 | // A gone pane's bar says "gone"; this says what to do about it. Idle | 790 | // The pane's bar names the state; this says what to do about it. Idle |
| 785 | // only: a refusal or an exit sentence already on screen outranks a hint. | 791 | // only: a refusal or an exit sentence already on screen outranks a hint. |
| 786 | if (landed_gone) setNoticeIdle(shared, gone_notice); | 792 | switch (landed) { |
| 793 | .gone => setNoticeIdle(shared, gone_notice), | ||
| 794 | .@"unreachable" => setNoticeIdle(shared, unreachable_notice), | ||
| 795 | else => {}, | ||
| 796 | } | ||
| 787 | } | 797 | } |
| 788 | 798 | ||
| 789 | /// The focus move itself. Caller holds `paint_mu`. | 799 | /// The focus move itself. Caller holds `paint_mu`. |
| @@ -1058,39 +1068,59 @@ pub fn wakePending(t: *Tile, creates: bool) void { | |||
| 1058 | t.alive.store(true, .release); | 1068 | t.alive.store(true, .release); |
| 1059 | } | 1069 | } |
| 1060 | 1070 | ||
| 1061 | /// A pending pane the host's own list confirmed: wake it in place. The | 1071 | /// How a pending pane comes to life — the two axes every wake differs on, |
| 1062 | /// rect was cut at seed time, so binding moves nothing and re-cuts | 1072 | /// and nothing else. `creates`: the daemon MAKES the session (a nonzero |
| 1063 | /// nothing — which is the whole point of seeding. | 1073 | /// attach size) rather than only attaching to one it already lists. |
| 1064 | pub fn bindTile(w: Wall, i: usize) void { | 1074 | /// `asked`: the dial STARTS the daemon (`mux d endpoint --start`) rather |
| 1065 | const t = &w.tiles[i]; | 1075 | /// than reading what is already there — the field the entry attach and a |
| 1066 | wakePending(t, false); | 1076 | /// picker birth set, worn on a `.hand` target. Every road onto a pending |
| 1067 | spawnPump(t); | 1077 | /// pane — a poll bind, a gone-pane Enter, an unreachable-pane Enter — is one |
| 1078 | /// of these three combos, so they share one primitive and diverge only in | ||
| 1079 | /// the struct. | ||
| 1080 | pub const ReviveOpts = struct { creates: bool, asked: bool }; | ||
| 1081 | |||
| 1082 | /// Set the dial's start-the-daemon flag where the target can carry one. A | ||
| 1083 | /// `.sock`/`.quic`/`.via` target has no daemon to start over ssh, so the | ||
| 1084 | /// flag has nowhere to live and the ask is a no-op — a local daemon that is | ||
| 1085 | /// down is `mux d start`, not a wall pane's to boot. | ||
| 1086 | fn armAsked(t: *Tile, asked: bool) void { | ||
| 1087 | if (t.r.target == .hand) t.r.target.hand.asked = asked; | ||
| 1068 | } | 1088 | } |
| 1069 | 1089 | ||
| 1070 | /// The state half of a revive, split from the thread so a test can pin the | 1090 | /// The state half of a wake, split from the thread so a test can pin the |
| 1071 | /// transitions without racing a pump. `reviveTile` is the only production | 1091 | /// transitions without racing a pump. |
| 1072 | /// caller. | 1092 | pub fn armPane(t: *Tile, opts: ReviveOpts) void { |
| 1073 | pub fn armRevive(t: *Tile) void { | 1093 | armAsked(t, opts.asked); |
| 1074 | wakePending(t, true); | 1094 | wakePending(t, opts.creates); |
| 1075 | } | 1095 | } |
| 1076 | 1096 | ||
| 1077 | /// A gone pane, revived on the user's Enter: the same tile re-arms as a | 1097 | /// The one wake: arm the pane, then give it its pump. Every caller names its |
| 1078 | /// CREATING attach — same host, same session name, same rect — so the | 1098 | /// intent in the struct — a bind attaches ({false,false}), a gone-pane Enter |
| 1079 | /// daemon makes the session anew where the old one stood. No tree edit and | 1099 | /// re-creates on a live host ({true,false}), an unreachable-pane Enter starts |
| 1080 | /// no flatten: reviving re-cuts nothing, which is the promise gone panes | 1100 | /// the daemon first and then re-creates ({true,true}). No tree edit and no |
| 1081 | /// exist to keep. | 1101 | /// flatten: waking re-cuts nothing, which is the promise a seeded rect keeps. |
| 1082 | pub fn reviveTile(t: *Tile) void { | 1102 | pub fn revivePane(t: *Tile, opts: ReviveOpts) void { |
| 1083 | armRevive(t); | 1103 | armPane(t, opts); |
| 1084 | spawnPump(t); | 1104 | spawnPump(t); |
| 1085 | } | 1105 | } |
| 1086 | 1106 | ||
| 1087 | /// The one door for bytes aimed at the focused tile. A gone pane has no | 1107 | /// A pending pane the host's own list confirmed: wake it in place, attaching |
| 1108 | /// to the session the poll just saw — never creating, never starting. | ||
| 1109 | pub fn bindTile(w: Wall, i: usize) void { | ||
| 1110 | revivePane(&w.tiles[i], .{ .creates = false, .asked = false }); | ||
| 1111 | } | ||
| 1112 | |||
| 1113 | /// The one door for bytes aimed at the focused tile. A pending pane has no | ||
| 1088 | /// pump, so no byte could reach a session — eaten here rather than queued | 1114 | /// pump, so no byte could reach a session — eaten here rather than queued |
| 1089 | /// into `in` where they would greet the NEXT session this tile carries. | 1115 | /// into `in` where they would greet the NEXT session this tile carries. |
| 1090 | /// Enter is the pane's one verb. | 1116 | /// Enter is the pane's one verb: it wakes the pane through `revivePane`, and |
| 1117 | /// the only thing that differs by WHY the pane was pending is whether the | ||
| 1118 | /// daemon must be started first — a gone pane's host is up, an unreachable | ||
| 1119 | /// pane's may be down. | ||
| 1091 | pub fn keysToFocused(t: *Tile, keys: []const u8) void { | 1120 | pub fn keysToFocused(t: *Tile, keys: []const u8) void { |
| 1092 | if (t.pending and t.state == .gone) { | 1121 | if (t.pending and (t.state == .gone or t.state == .@"unreachable")) { |
| 1093 | if (std.mem.indexOfAny(u8, keys, "\r\n") != null) reviveTile(t); | 1122 | if (std.mem.indexOfAny(u8, keys, "\r\n") != null) |
| 1123 | revivePane(t, .{ .creates = true, .asked = t.state == .@"unreachable" }); | ||
| 1094 | return; | 1124 | return; |
| 1095 | } | 1125 | } |
| 1096 | sendKeys(t, keys); | 1126 | sendKeys(t, keys); |
test/e2e_09_hosts.sh
| Old | New | ||
|---|---|---|---|
| @@ -513,7 +513,7 @@ ok "x on a wall ends the focused tile's session and no other, and that tile leav | |||
| 513 | # when the daemon died, is not there — asked of `mux d stats` on the | 513 | # when the daemon died, is not there — asked of `mux d stats` on the |
| 514 | # real daemon, not of the wall that would be reporting on its own | 514 | # real daemon, not of the wall that would be reporting on its own |
| 515 | # resurrection. The PANE for `c` is still on the wall, and that is not | 515 | # resurrection. The PANE for `c` is still on the wall, and that is not |
| 516 | # a contradiction: it stands wearing [gone] because the host answered | 516 | # a contradiction: it stands wearing [session ended] because the host answered |
| 517 | # and did not name the session (gone panes, 2026-09-01), which is a | 517 | # and did not name the session (gone panes, 2026-09-01), which is a |
| 518 | # rect the user saved and an offer to start it anew, not a session. | 518 | # rect the user saved and an offer to start it anew, not a session. |
| 519 | # `x` closes it and Enter creates it; until then the daemon holds one | 519 | # `x` closes it and Enter creates it; until then the daemon holds one |
| @@ -570,10 +570,10 @@ grep -q -- "--sock $SOCKH2#0 \[up\]" "$OUT.hrcap" || { | |||
| 570 | cat "$OUT.hrpc"; exit 1; } | 570 | cat "$OUT.hrpc"; exit 1; } |
| 571 | "$RENDER" --cols 80 --rows 44 < "$OUT.hrcap" > "$OUT.hrcap.final" | 571 | "$RENDER" --cols 80 --rows 44 < "$OUT.hrcap" > "$OUT.hrcap.final" |
| 572 | # The pane the dead session left behind stands, and says why: the host | 572 | # The pane the dead session left behind stands, and says why: the host |
| 573 | # ANSWERED and did not name `c`, which is [gone] and not [unreachable]. | 573 | # ANSWERED and did not name `c`, which is [session ended] and not [unreachable]. |
| 574 | grep -q -- "--sock $SOCKH2#c \[gone\]" "$OUT.hrcap.final" || { | 574 | grep -q -- "--sock $SOCKH2#c \[session ended\]" "$OUT.hrcap.final" || { |
| 575 | echo "e2e FAIL: hosts restart: the pane for the session the dead daemon held did" | 575 | echo "e2e FAIL: hosts restart: the pane for the session the dead daemon held did" |
| 576 | echo " not stand wearing [gone] on the reborn daemon:" | 576 | echo " not stand wearing [session ended] on the reborn daemon:" |
| 577 | cat "$OUT.hrcap.final"; exit 1; } | 577 | cat "$OUT.hrcap.final"; exit 1; } |
| 578 | # ...and it is a rect, not a session: nothing attached to a `c` that does | 578 | # ...and it is a rect, not a session: nothing attached to a `c` that does |
| 579 | # not exist. A pane the wall had resurrected would read [up]. | 579 | # not exist. A pane the wall had resurrected would read [up]. |
test/e2e_12_panes.sh
| Old | New | ||
|---|---|---|---|
| @@ -818,8 +818,8 @@ grep -q -- "^ leaf 43 .*#0\$" "$LPHLAYOUT" || { | |||
| 818 | # ...and the leaf the list disowned says on screen why it is still there. | 818 | # ...and the leaf the list disowned says on screen why it is still there. |
| 819 | # The state word survives a bar's truncation where the socket path does | 819 | # The state word survives a bar's truncation where the socket path does |
| 820 | # not, so this is the needle that reads at any pane width. | 820 | # not, so this is the needle that reads at any pane width. |
| 821 | grep -qa '\[gone\]' "$OUT.lphcap2" || { | 821 | grep -qa '\[session ended\]' "$OUT.lphcap2" || { |
| 822 | echo "e2e FAIL: layout-heal: the ended session's pane wore no [gone]:" | 822 | echo "e2e FAIL: layout-heal: the ended session's pane wore no [session ended]:" |
| 823 | cat "$OUT.lphpc2"; exit 1; } | 823 | cat "$OUT.lphpc2"; exit 1; } |
| 824 | timeout 20 "$MUX" a capture --sock "$SOCK60" > "$OUT.lphfa" 2>&1 | 824 | timeout 20 "$MUX" a capture --sock "$SOCK60" > "$OUT.lphfa" 2>&1 |
| 825 | grep -q "lph-heal-marker" "$OUT.lphfa" || { | 825 | grep -q "lph-heal-marker" "$OUT.lphfa" || { |
| @@ -992,10 +992,10 @@ RC=$? | |||
| 992 | set -e | 992 | set -e |
| 993 | rc0 "gone-panes: run 2's ptyclient exited $RC:" "$OUT.gppc2" "$OUT.gpcap2.err" | 993 | rc0 "gone-panes: run 2's ptyclient exited $RC:" "$OUT.gppc2" "$OUT.gpcap2.err" |
| 994 | # The word reached the grid. A bar is byte-truncated from the LABEL end and | 994 | # The word reached the grid. A bar is byte-truncated from the LABEL end and |
| 995 | # the state survives that cut (`labelText`), so `[gone]` is on screen for | 995 | # the state survives that cut (`labelText`), so `[session ended]` is on screen for |
| 996 | # any pane width this leg could produce. | 996 | # any pane width this leg could produce. |
| 997 | grep -qa '\[gone\]' "$OUT.gpcap2" || { | 997 | grep -qa '\[session ended\]' "$OUT.gpcap2" || { |
| 998 | echo "e2e FAIL: gone-panes: no pane wore [gone] after the reboot:" | 998 | echo "e2e FAIL: gone-panes: no pane wore [session ended] after the reboot:" |
| 999 | cat "$OUT.gppc2"; exit 1; } | 999 | cat "$OUT.gppc2"; exit 1; } |
| 1000 | # The rails did not move. A capture is a stream and holds every rail the | 1000 | # The rails did not move. A capture is a stream and holds every rail the |
| 1001 | # run ever painted, so this catches a re-cut that MOVES a boundary; the | 1001 | # run ever painted, so this catches a re-cut that MOVES a boundary; the |