a73x

113a7e2c

test: the restore leg asserts the focus came back

a73x   2026-08-25 09:10

Commit message
test: the restore leg asserts the focus came back

The layout-restore e2e leg now moves focus right (Ctrl-\ l) to pane b
before the first detach, so the sidecar's focus record names leaf 1.
The second ptyclient reattach types a marker with no focus movement;
the capture asserts flip — the marker must be in session b (where the
focus was saved) and must NOT be in session a.

The spec's "Focus is NOT persisted" bullet is replaced: focus IS
persisted via the optional `focus K` line, mapped through the spelling
match on restore, falling back to tile 0 when the leaf did not survive.
The Ctrl-\ w fold never moves focus. The grammar section and example
gain the focus line kind.

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

docs/superpowers/specs/2026-08-24-layout-persistence-design.md
Old New
@@ -49,6 +49,7 @@ beside 0
49 stacked 39 49 stacked 39
50 leaf 12 --sock /tmp/x#b 50 leaf 12 --sock /tmp/x#b
51 leaf 11 --sock /tmp/x#c 51 leaf 11 --sock /tmp/x#c
52 focus 2
52 ``` 53 ```
53 54
54 - First line: `mux-layout 1`. Any other first line means "not mine": 55 - First line: `mux-layout 1`. Any other first line means "not mine":
@@ -66,12 +67,20 @@ beside 0
66 - Leaf line: `leaf CELLS SPELLING`. `flatten` treats weights 67 - Leaf line: `leaf CELLS SPELLING`. `flatten` treats weights
67 proportionally, so a different terminal restores the same ratios at 68 proportionally, so a different terminal restores the same ratios at
68 its own size; absolute cells are a snapshot, not a promise. 69 its own size; absolute cells are a snapshot, not a promise.
70 - Focus line: `focus K`, optional, after all node lines. K is the
71 focused leaf's encounter index (0..n-1 in depth-first order). A
72 present but malformed focus record (K out of range, a second focus
73 line, any node line after it) makes the whole parse return null.
69 - The root line's `beside`/`stacked` IS the persisted root orientation. 74 - The root line's `beside`/`stacked` IS the persisted root orientation.
70 When a restore applies, `rootOrient` (the aspect rule) is not 75 When a restore applies, `rootOrient` (the aspect rule) is not
71 consulted. 76 consulted.
72 - Fullscreen is NOT persisted. It is a transient view op; reattach is 77 - Fullscreen is NOT persisted. It is a transient view op; reattach is
73 always un-fullscreened. 78 always un-fullscreened.
74 - Focus is NOT persisted. Focus starts at tile 0, as today. 79 - Focus IS persisted (amended 2026-08-25 after user hands-on). The
80 sidecar's optional `focus K` line names the focused leaf by encounter
81 index in the depth-first walk; restore maps it through the spelling
82 match and falls back to tile 0 when the leaf did not survive. The
83 Ctrl-\ w fold never moves focus — the user is typing when they fold.
75 - Size cap mirrors the wall file's load cap (1 MiB); a bigger file is 84 - Size cap mirrors the wall file's load cap (1 MiB); a bigger file is
76 malformed. 85 malformed.
77 86
test/e2e.sh
Old New
@@ -1447,7 +1447,7 @@ cleanup() {
1447 "$OUT.lpr.d" "$OUT.lpra" "$OUT.lpra.err" "$OUT.lprb" "$OUT.lprb.err" \ 1447 "$OUT.lpr.d" "$OUT.lpra" "$OUT.lpra.err" "$OUT.lprb" "$OUT.lprb.err" \
1448 "$OUT.lprcap1" "$OUT.lprcap1.err" "$OUT.lprpc1" \ 1448 "$OUT.lprcap1" "$OUT.lprcap1.err" "$OUT.lprpc1" \
1449 "$OUT.lprcap2" "$OUT.lprcap2.err" "$OUT.lprpc2" \ 1449 "$OUT.lprcap2" "$OUT.lprcap2.err" "$OUT.lprpc2" \
1450 "$OUT.lprfa" "$OUT.lprstop" "$SOCK59" \ 1450 "$OUT.lprfa" "$OUT.lprfb" "$OUT.lprstop" "$SOCK59" \
1451 "$OUT.lph.d" "$OUT.lpha" "$OUT.lpha.err" "$OUT.lphb" "$OUT.lphb.err" \ 1451 "$OUT.lph.d" "$OUT.lpha" "$OUT.lpha.err" "$OUT.lphb" "$OUT.lphb.err" \
1452 "$OUT.lphc" "$OUT.lphc.err" "$OUT.lphcap1" "$OUT.lphcap1.err" \ 1452 "$OUT.lphc" "$OUT.lphc.err" "$OUT.lphcap1" "$OUT.lphcap1.err" \
1453 "$OUT.lphpc1" "$OUT.lphcap2" "$OUT.lphcap2.err" "$OUT.lphpc2" \ 1453 "$OUT.lphpc1" "$OUT.lphcap2" "$OUT.lphcap2.err" "$OUT.lphpc2" \
@@ -8071,6 +8071,8 @@ send l
8071 settle 500 5000 8071 settle 500 5000
8072 send \x1b 8072 send \x1b
8073 settle 500 5000 8073 settle 500 5000
8074 send \x1cl
8075 settle 500 5000
8074 send \x1cd 8076 send \x1cd
8075 waitexit 10000 8077 waitexit 10000
8076 EOF 8078 EOF
@@ -8095,6 +8097,10 @@ case "$_head2" in
8095 *) echo "e2e FAIL: layout-restore: sidecar root is '$_head2', want 'beside 0'" 8097 *) echo "e2e FAIL: layout-restore: sidecar root is '$_head2', want 'beside 0'"
8096 cat "$LPLAYOUT"; exit 1 ;; 8098 cat "$LPLAYOUT"; exit 1 ;;
8097 esac 8099 esac
8100 # The focus record: focus-right moved focus to pane b (leaf 1).
8101 grep -q '^focus 1$' "$LPLAYOUT" || {
8102 echo "e2e FAIL: layout-restore: sidecar carries no focus record"
8103 cat "$LPLAYOUT"; exit 1; }
8098 8104
8099 set +e 8105 set +e
8100 # Second ptyclient: reattach. The sidecar restores the resized tree — 8106 # Second ptyclient: reattach. The sidecar restores the resized tree —
@@ -8127,11 +8133,15 @@ _max_rail=$(grep -ao "$_rail_re" "$OUT.lprcap2" | \
8127 [ "$_max_rail" -ge 43 ] || { 8133 [ "$_max_rail" -ge 43 ] || {
8128 echo "e2e FAIL: layout-restore: rail column $_max_rail < 43 (resize did not survive)" 8134 echo "e2e FAIL: layout-restore: rail column $_max_rail < 43 (resize did not survive)"
8129 cat "$OUT.lprcap2"; exit 1; } 8135 cat "$OUT.lprcap2"; exit 1; }
8130 # The marker typed into the restored wall landed in session a (tile 0, 8136 # The marker typed into the restored wall landed in session b — the
8131 # the default focus). 8137 # focus was saved on pane b and restore put it back there, not tile 0.
8138 timeout 20 "$MUXA" capture --sock "$SOCK59" --session b > "$OUT.lprfb" 2>&1
8139 grep -q "lpr-rt-marker" "$OUT.lprfb" || {
8140 echo "e2e FAIL: layout-restore: marker not in session b (focus did not survive):"
8141 cat "$OUT.lprfb"; exit 1; }
8132 timeout 20 "$MUXA" capture --sock "$SOCK59" --session a > "$OUT.lprfa" 2>&1 8142 timeout 20 "$MUXA" capture --sock "$SOCK59" --session a > "$OUT.lprfa" 2>&1
8133 grep -q "lpr-rt-marker" "$OUT.lprfa" || { 8143 ! grep -q "lpr-rt-marker" "$OUT.lprfa" || {
8134 echo "e2e FAIL: layout-restore: marker not in session a after round trip:" 8144 echo "e2e FAIL: layout-restore: marker leaked into session a (focus was tile 0, not b):"
8135 cat "$OUT.lprfa"; exit 1; } 8145 cat "$OUT.lprfa"; exit 1; }
8136 assert_stopped "$SOCK59" "$D60PID" "layout-restore" "$OUT.lprstop" 8146 assert_stopped "$SOCK59" "$D60PID" "layout-restore" "$OUT.lprstop"
8137 D60PID="" 8147 D60PID=""