diff --git a/bin/backends/herdr.sh b/bin/backends/herdr.sh index 7367a8db5c..de0fbad514 100644 --- a/bin/backends/herdr.sh +++ b/bin/backends/herdr.sh @@ -2587,14 +2587,22 @@ fm_backend_herdr_send_key() { # # the composer-state guard/fallback reads around submit and injection). Workaround: # always request a generous fetch far above any realistic viewport height, then # trim to the caller's requested bound ourselves with `tail`. +# +# Fetches --format ansi and strips locally instead of asking for text: on herdr +# 0.8.0 a text-format `pane read --source recent` against an idle alt-screen +# agent triggers the wheel-event history harvest (src/server/alt_screen_read.rs) +# — the pane VISIBLY scrolls up for seconds and snaps back on every poll +# (herdrdev/herdr#2669, closed as dup of #2387). The ansi path skips the +# harvest entirely and returns herdr's retained rows instantly, which matches +# the pre-0.8.0 text behavior this capture was written against. fm_backend_herdr_capture() { # fm_backend_herdr_target_ready "$1" || return 1 local lines=${2:-200} fetch out case "$lines" in ''|*[!0-9]*) lines=200 ;; esac fetch=$lines case "$fetch" in ''|*[!0-9]*) fetch=200 ;; *) [ "$fetch" -ge 200 ] || fetch=200 ;; esac - out=$(fm_backend_herdr_cli "$FM_BACKEND_HERDR_SESSION" pane read "$FM_BACKEND_HERDR_PANE" --source recent --lines "$fetch" 2>/dev/null) || return 1 - printf '%s' "$out" | tail -n "$lines" + out=$(fm_backend_herdr_cli "$FM_BACKEND_HERDR_SESSION" pane read "$FM_BACKEND_HERDR_PANE" --source recent --lines "$fetch" --format ansi 2>/dev/null) || return 1 + printf '%s' "$out" | tail -n "$lines" | fm_composer_strip_ansi } fm_backend_herdr_capture_ansi() { # diff --git a/tests/fm-backend-herdr.test.sh b/tests/fm-backend-herdr.test.sh index 1adeed3645..2e24098ce2 100755 --- a/tests/fm-backend-herdr.test.sh +++ b/tests/fm-backend-herdr.test.sh @@ -2856,9 +2856,12 @@ test_capture_calls_pane_read() { out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_capture default:w1:p2 250' "$ROOT" ) [ "$out" = $'line one\nline two\nline three' ] || fail "capture did not pass through pane read output, got '$out'" - assert_contains "$(cat "$log")" "HERDR_SESSION=default"$'\x1f''pane'$'\x1f''read'$'\x1f''w1:p2'$'\x1f''--source'$'\x1f''recent'$'\x1f''--lines'$'\x1f''250' \ - "capture did not call pane read with the right pane id and line bound" - pass "fm_backend_herdr_capture: calls 'pane read --source recent --lines N' with the session set" + # --format ansi is load-bearing, not cosmetic: on herdr 0.8.0 a text-format + # recent read of an idle alt-screen agent triggers the wheel-event history + # harvest and the pane visibly scrolls for seconds (herdrdev/herdr#2669). + assert_contains "$(cat "$log")" "HERDR_SESSION=default"$'\x1f''pane'$'\x1f''read'$'\x1f''w1:p2'$'\x1f''--source'$'\x1f''recent'$'\x1f''--lines'$'\x1f''250'$'\x1f''--format'$'\x1f''ansi' \ + "capture did not call pane read with the right pane id, line bound, and the harvest-skipping ansi format" + pass "fm_backend_herdr_capture: calls 'pane read --source recent --lines N --format ansi' with the session set" } test_capture_works_around_small_lines_bug() {