a73x

a3d0585e

test: the heal legs read the wall a gone pane leaves standing

a73x   2026-09-01 10:49

Commit message
test: the heal legs read the wall a gone pane leaves standing

A reachable host that no longer names a saved session now dresses that
pane `gone` instead of collapsing it out. Two legs pinned the collapse:

  * layout-heal asserted two tiles and read the newcomer's bar for
    `#c [up]`. The wall is three tiles now, so a stripe is 26 columns and
    `labelText` truncates the socket path long before the `#c`. The tree
    run 2 SAVES says the same thing exactly and at any width — root still
    `beside`, leaves `0 c b`, the survivor still weighing 43 — so the
    assertion moved to the sidecar. That also retires the leg's note that
    a heal loses the orientation and the weights: nothing collapses now,
    so nothing re-wraps, and both survive.

  * hosts-restart asserted that no tile named `#c` exists after the
    daemon comes back. A pane named `c` is not a session named `c`: it is
    the rect the user saved, wearing `[gone]`, offering to start one. The
    leg's real oracle was always the `mux d stats` count beside it, which
    is untouched; the tile grep now reads `[gone]` for presence and `[up]`
    for the resurrection it was written to refuse.

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

test/e2e_09_hosts.sh
Old New
@@ -522,7 +522,12 @@ ok "x on a wall ends the focused tile's session and no other, and that tile leav
522 # * back: its own default session and nothing else. `c`, which was live 522 # * back: its own default session and nothing else. `c`, which was live
523 # when the daemon died, is not there — asked of `mux d stats` on the 523 # when the daemon died, is not there — asked of `mux d stats` on the
524 # real daemon, not of the wall that would be reporting on its own 524 # real daemon, not of the wall that would be reporting on its own
525 # resurrection. 525 # resurrection. The PANE for `c` is still on the wall, and that is not
526 # a contradiction: it stands wearing [gone] because the host answered
527 # and did not name the session (gone panes, 2026-09-01), which is a
528 # rect the user saved and an offer to start it anew, not a session.
529 # `x` closes it and Enter creates it; until then the daemon holds one
530 # session and the wall says so.
526 assert_stopped "$SOCKH2" "$DH2PID" "hosts restart" "$OUT.h2stop" 531 assert_stopped "$SOCKH2" "$DH2PID" "hosts restart" "$OUT.h2stop"
527 DH2PID="" 532 DH2PID=""
528 set +e 533 set +e
@@ -576,9 +581,17 @@ grep -q -- "--sock $SOCKH2#0 \[up\]" "$OUT.hrcap" || {
576 echo "e2e FAIL: hosts restart: the reborn daemon's own session never became a tile:" 581 echo "e2e FAIL: hosts restart: the reborn daemon's own session never became a tile:"
577 cat "$OUT.hrpc"; exit 1; } 582 cat "$OUT.hrpc"; exit 1; }
578 "$RENDER" --cols 80 --rows 44 < "$OUT.hrcap" > "$OUT.hrcap.final" 583 "$RENDER" --cols 80 --rows 44 < "$OUT.hrcap" > "$OUT.hrcap.final"
579 if grep -q -- "--sock $SOCKH2#c" "$OUT.hrcap.final"; then 584 # The pane the dead session left behind stands, and says why: the host
585 # ANSWERED and did not name `c`, which is [gone] and not [unreachable].
586 grep -q -- "--sock $SOCKH2#c \[gone\]" "$OUT.hrcap.final" || {
587 echo "e2e FAIL: hosts restart: the pane for the session the dead daemon held did"
588 echo " not stand wearing [gone] on the reborn daemon:"
589 cat "$OUT.hrcap.final"; exit 1; }
590 # ...and it is a rect, not a session: nothing attached to a `c` that does
591 # not exist. A pane the wall had resurrected would read [up].
592 if grep -- "--sock $SOCKH2#c" "$OUT.hrcap.final" | grep -q '\[up\]'; then
580 echo "e2e FAIL: hosts restart: the wall resurrected the session the dead daemon held:" 593 echo "e2e FAIL: hosts restart: the wall resurrected the session the dead daemon held:"
581 cat "$OUT.hrpc"; exit 1 594 cat "$OUT.hrcap.final"; exit 1
582 fi 595 fi
583 # The daemon's own account of it, which is the one that matters: a session 596 # The daemon's own account of it, which is the one that matters: a session
584 # the wall re-created would be here whether or not it ever got a tile. 597 # the wall re-created would be here whether or not it ever got a tile.
test/e2e_12_panes.sh
Old New
@@ -697,9 +697,15 @@ ok "a resized layout survives a detach/reattach round trip via the sidecar"
697 # The drift a wall of daemons can actually suffer, and it is no longer a 697 # The drift a wall of daemons can actually suffer, and it is no longer a
698 # file being edited: a 2-pane sidecar is saved, and then one of those 698 # file being edited: a 2-pane sidecar is saved, and then one of those
699 # sessions ENDS while another is born. On reattach, restoreLayout heals — 699 # sessions ENDS while another is born. On reattach, restoreLayout heals —
700 # the survivor leaf keeps its session, the newcomer inserts beside it, and 700 # the survivor leaf keeps its session and the newcomer inserts beside it —
701 # the unmatched saved leaf collapses out. Two tiles, not three, and the 701 # and the unmatched saved leaf STANDS, wearing `gone`. Three tiles, and the
702 # wall did not refuse. 702 # wall did not refuse.
703 #
704 # It used to be two: the unmatched leaf collapsed out on the first poll
705 # answer. Gone panes are the change (2026-09-01) — a reachable host that
706 # no longer names your saved session dresses that pane instead of taking
707 # it, so a reboot cannot silently re-cut a wall. The heal is the same
708 # code; only the fate of the leaf the list disowns is different.
703 LPHSTATE="${TMPDIR:-/tmp}/mux-e2e-lpheal-state-$$" 709 LPHSTATE="${TMPDIR:-/tmp}/mux-e2e-lpheal-state-$$"
704 defer_rm "$LPHSTATE" 710 defer_rm "$LPHSTATE"
705 LPHHOSTS="$LPHSTATE/mux/hosts" 711 LPHHOSTS="$LPHSTATE/mux/hosts"
@@ -789,73 +795,51 @@ set -e
789 cat "$OUT.lphpc2" "$OUT.lphcap2.err"; exit 1; } 795 cat "$OUT.lphpc2" "$OUT.lphcap2.err"; exit 1; }
790 # The wall must not have refused: the ptyclient exited 0 (checked above), 796 # The wall must not have refused: the ptyclient exited 0 (checked above),
791 # and stderr mentions no layout error. 797 # and stderr mentions no layout error.
792 #
793 # The money assertion is that run 2's cut came from the SIDECAR and not
794 # from the aspect rule. At 80x24 the aspect rule cuts BESIDE — a vertical
795 # rail, a reverse-video column somewhere near 40, which run 1's capture
796 # has and this one must not. A restore that reported failure falls through
797 # to `setRootOrient(rootOrient(size))` and puts that rail back, so its
798 # absence is the heal having run AND won.
799 #
800 # What this leg does NOT claim, because the product does not do it —
801 # measured, with the sidecar read either side of the second run:
802 #
803 # saved by run 1: beside 0 / leaf 43 #0 / leaf 36 #b / focus 0
804 # after the heal: stacked 0 / leaf 1 #0 / leaf 1 #c / focus 0
805 #
806 # * the survivor's WEIGHT does not survive (43 -> 1) and neither does the
807 # container's ORIENTATION (beside -> stacked). Dropping the unmatched
808 # leaf collapses the two-child container to a bare leaf, and the
809 # newcomer's `insert` then re-wraps that leaf in a fresh STACKED
810 # container with default weights (`layout.insert`'s root-is-a-leaf
811 # arm). A heal keeps WHICH tiles, never HOW they were cut.
812 # * the focus lands on the newcomer rather than the leaf the record
813 # names, which is why the marker below is typed at a tile this leg
814 # names with `\x1c1` instead of inheriting one.
815 #
816 # Both are defects, not design: a sidecar exists to remember a cut. They
817 # are reported, not fixed here.
818 grep -qi "layout" "$OUT.lphcap2.err" && { 798 grep -qi "layout" "$OUT.lphcap2.err" && {
819 echo "e2e FAIL: layout-heal: stderr mentions layout (heal was not silent):" 799 echo "e2e FAIL: layout-heal: stderr mentions layout (heal was not silent):"
820 cat "$OUT.lphcap2.err"; exit 1; } 800 cat "$OUT.lphcap2.err"; exit 1; }
821 # Two tiles: the survivor and the newcomer. The daemon has 0 and c, and
822 # the tile the sidecar's surviving leaf carries is the one the marker
823 # landed in.
824 # A BESIDE cut puts a rail in a column of its own; a stacked cut has one
825 # only at column 1, which is a label bar and what `rail_cols`' callers
826 # filter out.
827 # Run 1 is the anchor: it really was cut beside and `lll` really moved the 801 # Run 1 is the anchor: it really was cut beside and `lll` really moved the
828 # rail, so run 2's silence below is a difference and not an empty capture. 802 # rail, so run 2's tree below is a comparison and not an empty capture. A
803 # BESIDE cut puts a rail in a column of its own; a stacked cut has one only
804 # at column 1, which is a label bar and what `rail_cols`' callers filter
805 # out.
829 _lph_r1=$(rail_cols "$OUT.lphcap1" | awk '$1 > 1' | tail -1) 806 _lph_r1=$(rail_cols "$OUT.lphcap1" | awk '$1 > 1' | tail -1)
830 [ -n "$_lph_r1" ] && [ "$_lph_r1" -ge 43 ] || { 807 [ -n "$_lph_r1" ] && [ "$_lph_r1" -ge 43 ] || {
831 echo "e2e FAIL: layout-heal: run 1's rail ended at column ${_lph_r1:-none}, want >= 43" 808 echo "e2e FAIL: layout-heal: run 1's rail ended at column ${_lph_r1:-none}, want >= 43"
832 echo " — the resize this leg saves a tree for did not happen" 809 echo " — the resize this leg saves a tree for did not happen"
833 cat "$OUT.lphpc1"; exit 1; } 810 cat "$OUT.lphpc1"; exit 1; }
834 # What run 2 may NOT be is the aspect rule's own cut. Two shapes pass, and 811 # The money assertion is that run 2's cut came from the SIDECAR and not
835 # both of them are the sidecar's: 812 # from the aspect rule, and the tree run 2 SAVES is where that reads
813 # exactly. A restore that reported failure falls through to
814 # `setRootOrient(rootOrient(size))` and builds default weights of 1, so a
815 # root that is still `beside` over a survivor still weighing 43 is the heal
816 # having run AND won. Measured 2026-09-01, sidecar read either side:
836 # 817 #
837 # * no vertical rail — today's heal, which drops the unmatched leaf, 818 # saved by run 1: beside 0 / leaf 43 #0 / leaf 36 #b / focus 0
838 # collapses the container and re-wraps STACKED (measured above). 819 # saved by run 2: beside 0 / leaf 43 #0 / leaf 43 #c / leaf 36 #b
839 # * a rail at the column run 1 dragged the boundary to (>= 43) — a heal
840 # that also kept the saved orientation and weights, which is the fix
841 # the note above says is owed.
842 # 820 #
843 # The aspect rule at 80x24 cuts beside down the middle, around column 40, 821 # Three leaves, in that order: `insert` puts the newcomer immediately after
844 # and that is the one answer neither of those can produce. Asserting "no 822 # its anchor (the focus, `0`) and copies the anchor's weight, and the leaf
845 # rail at all" instead would fail the improvement and blame the restore for 823 # the list disowned keeps its own 36 where it stood. Nothing collapsed, so
846 # not having run, which is the opposite of what would have happened. 824 # nothing re-wrapped — which is why the orientation and the weights now
847 _lph_r2=$(rail_cols "$OUT.lphcap2" | awk '$1 > 1' | tail -1) 825 # survive a heal that used to lose both.
848 [ -z "$_lph_r2" ] || [ "$_lph_r2" -ge 43 ] || { 826 _lph_root=$(sed -n '2p' "$LPHLAYOUT")
849 echo "e2e FAIL: layout-heal: run 2's vertical rail is at column $_lph_r2 — neither" 827 [ "$_lph_root" = "beside 0" ] || {
850 echo " the sidecar's own cut (>= 43, where run 1 left it) nor the bare" 828 echo "e2e FAIL: layout-heal: run 2 saved root '$_lph_root', want 'beside 0'"
851 echo " leaf a heal collapses to. The aspect rule cut this wall, so the" 829 echo " — the aspect rule cut this wall, so the saved tree never reached it"
852 echo " saved tree never reached it" 830 cat "$LPHLAYOUT"; exit 1; }
853 cat "$OUT.lphpc2"; exit 1; } 831 _lph_leaves=$(sed -n 's/^ *leaf [0-9][0-9]* .*#\(.*\)$/\1/p' "$LPHLAYOUT" | tr '\n' ' ')
854 # ...and only now the bars, which the cut above is what makes readable: a 832 [ "$_lph_leaves" = "0 c b " ] || {
855 # stacked stripe is the full 80 columns, and a beside one is 39 — half a 833 echo "e2e FAIL: layout-heal: run 2 saved leaves '$_lph_leaves', want '0 c b ':"
856 # socket path, which `labelText` truncates before it reaches the `#c`. 834 cat "$LPHLAYOUT"; exit 1; }
857 grep -q -- "--sock $SOCK60#c \[up\]" "$OUT.lphcap2" || { 835 grep -q -- "^ leaf 43 .*#0\$" "$LPHLAYOUT" || {
858 echo "e2e FAIL: layout-heal: the newcomer never became a tile:" 836 echo "e2e FAIL: layout-heal: the survivor's weight did not survive the heal:"
837 cat "$LPHLAYOUT"; exit 1; }
838 # ...and the leaf the list disowned says on screen why it is still there.
839 # The state word survives a bar's truncation where the socket path does
840 # not, so this is the needle that reads at any pane width.
841 grep -qa '\[gone\]' "$OUT.lphcap2" || {
842 echo "e2e FAIL: layout-heal: the ended session's pane wore no [gone]:"
859 cat "$OUT.lphpc2"; exit 1; } 843 cat "$OUT.lphpc2"; exit 1; }
860 timeout 20 "$MUX" a capture --sock "$SOCK60" > "$OUT.lphfa" 2>&1 844 timeout 20 "$MUX" a capture --sock "$SOCK60" > "$OUT.lphfa" 2>&1
861 grep -q "lph-heal-marker" "$OUT.lphfa" || { 845 grep -q "lph-heal-marker" "$OUT.lphfa" || {
@@ -864,7 +848,7 @@ grep -q "lph-heal-marker" "$OUT.lphfa" || {
864 assert_stopped "$SOCK60" "$D61PID" "layout-heal" "$OUT.lphstop" 848 assert_stopped "$SOCK60" "$D61PID" "layout-heal" "$OUT.lphstop"
865 D61PID="" 849 D61PID=""
866 rm -rf "$LPHSTATE" 850 rm -rf "$LPHSTATE"
867 ok "the layout heals on live drift: the survivor keeps its session, the newcomer joins" 851 ok "the layout heals on live drift: the survivor keeps its session, the newcomer joins, the ended one's pane stands gone"
868 852
869 # ---- layout sidecar: silent degrade on garbage ----------------------- 853 # ---- layout sidecar: silent degrade on garbage -----------------------
870 # 854 #
@@ -922,3 +906,4 @@ assert_stopped "$SOCK61" "$D62PID" "layout-degrade" "$OUT.lpdstop"
922 D62PID="" 906 D62PID=""
923 rm -rf "$LPDSTATE" 907 rm -rf "$LPDSTATE"
924 ok "a corrupted sidecar degrades silently to the default layout" 908 ok "a corrupted sidecar degrades silently to the default layout"
909