Skip to content

fix(bin): stop desk-voice delivery from scrolling the Firstmate chat - #270

Merged
Amplify-Logic merged 2 commits into
mainfrom
fm/floater-scroll-pr269
Oct 8, 2026
Merged

Amplify-Logic merged 2 commits into
mainfrom
fm/floater-scroll-pr269

Conversation

@Amplify-Logic

@Amplify-Logic Amplify-Logic commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Intent

yes sure go for option 1. His no-mistakes should have done those checks no? He should have parity of what we have here.

Context: option 1 means taking Luc Wächter's (GitHub lwachter-code) fix from his fork pull request #269, "fix(bin): stop desk-voice delivery from scrolling the Firstmate chat", and raising it again through this repo's own no-mistakes pipeline so it gets the full checks (including lint), crediting Luc, then closing his pull request with thanks. His pipeline could not push to this repo (no write access, so he opened the PR from his fork by hand) and its lint step was skipped because ShellCheck was not installed on his machine; the PR has no CI runs.

The change: on Herdr 0.9.3 a line-counted history read (pane read --source recent --lines 200) of an idle Claude pane makes Herdr scroll Claude's conversation up and back, visible as the chat jumping while the desk floater shows "Delivering". In bin/fm-desk-voice.sh the send path's selection-dialog, chat-box (composer_text) and stash checks now read only the visible screen wherever fm_backend_visible_capture_supported holds, via a new screen_rows helper; other backends keep the bounded history capture. New test test_desk_voice_send_reads_only_the_visible_screen in tests/fm-deepgram-desk.test.sh.

What Changed

  • bin/fm-desk-voice.sh gets a new screen_rows helper that returns the last N rows of a pane. It reads only the visible viewport (fm_backend_visible_capture) on backends where fm_backend_visible_capture_supported holds. On all other backends it falls back to the existing bounded history capture (fm_backend_capture). This matters on Herdr 0.9.3: a history read (pane read --source recent --lines …) of an idle Claude pane scrolls the conversation up and back while the floater shows "Delivering".
  • The three screen checks on the send path now use screen_rows instead of fm_backend_capture: the selection-dialog check (shows_selection_dialog), the chat-box check (composer_text) and the stash check (shows_stash). The row limits and their fail-safe defaults stay the same.
  • Adds test_desk_voice_send_reads_only_the_visible_screen to tests/fm-deepgram-desk.test.sh. It covers two cases, sending past a Claude draft and sending with a picker dialog open, and checks that no Herdr pane read call asks for --lines or --source recent. In the picker case it also checks that the words go to the mailbox instead.

Credit: originally written by Luc Wächter (@lwachter-code). This supersedes #269, raised again here through this repo's no-mistakes pipeline so it gets the full checks; his commit and authorship are kept.

🤖 Generated with Claude Code

Risk Assessment

✅ Low: Three read-only screen checks now share one small helper. The helper reads only the visible screen wherever the backend has a verified viewport read, which is the same read herdr's own composer and payload checks already use, and every other backend still gets the bounded history capture. A failed or empty read fails safe: the message goes to the mailbox and the draft is kept. The regression test drives the public send command and checks the logged herdr calls, and both of its cases would fail on the base commit.

Testing

I ran the new regression test on the change and on the base code. It passes with the change, fails on base, and its dialog half fails if only the dialog check reads history again. I then drove the real desk-voice send against real Claude primaries in a tmux lab and a Herdr 0.7.4 lab. Messages were delivered, a typed draft was set aside and put back, and an open picker got nothing; the message went to the mailbox. Every screen check read only the viewport, while the base script made 6 history reads per send. Both labs were torn down. I saved text captures of the real panes and logs of the tmux/herdr calls; no screenshot was taken because the surface is a terminal pane and the before/after captures show the actual screen. The chat jump itself only happens on Herdr 0.9.3, which is not installed here, so that part is untested.

  • Live validation: ✅ go - 3 of 5 scenarios driven live against the product
Scenario Result Live Evidence
Herdr: the captain sends while a Claude draft is in the chat box; the draft is set aside, the message is delivered, the draft comes back, and herdr is never asked for history ✅ pass live live-herdr-change-draft-*.{txt,log}: output sent: herdr fm-lab-...:w1:p1, Claude replied HERDRPONG, draft restored; herdr calls show only --source visible. Base script on the same pane made 6x `--…
tmux: the captain sends with an empty chat box and then past a typed draft; delivery works and the checks read only the viewport (capture-pane -S -0, not -S -20) ✅ pass live live-tmux-empty-send-.{txt,log} and live-tmux-draft-.{txt,log}: sent: tmux %0, PONG/DRAFTPONG replies, draft restored; change makes 6x capture-pane -S -0, base made 6x capture-pane -S -20
Adversarial: the captain sends while Claude's real /model picker is open; nothing is typed into the picker and the message goes to the mailbox ✅ pass live live-herdr-dialog-*.{txt,log}: output mailbox: .../inbox/20261008T080336Z-dadf258f.json, picker screen unchanged before and after, no send-text/send-keys calls
Regression test fails before the fix and passes after, and its dialog half catches a dialog check that reads history ⏸️ untested no The prior payload recorded this as a unit-test run (live=false), not a run against the live product, so it does not establish a live result. The unit-test results remain listed under tested: ok on t…
Herdr 0.9.3: the Claude chat no longer jumps up and back while the floater shows Delivering ⏸️ untested no This machine has Herdr 0.7.4 (herdr --version), and the scroll-on-history-read bug only happens on Herdr 0.9.3. On 0.7.4 the live viewport poll showed only forward content growth with both the base…
Evidence: Herdr live: send past a draft with the change, herdr calls (visible reads only)

Source: Herdr live: send past a draft with the change, herdr calls (visible reads only)

herdr pane get w1:p1 --session fm-lab-pr269-52310-10541
herdr pane process-info --pane w1:p1 --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane read w1:p1 --source visible --format ansi --session fm-lab-pr269-52310-10541
herdr agent get w1:p1 --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane read w1:p1 --source visible --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane read w1:p1 --source visible --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane read w1:p1 --source visible --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane send-keys w1:p1 ctrl+s --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane read w1:p1 --source visible --format ansi --session fm-lab-pr269-52310-10541
herdr agent get w1:p1 --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane read w1:p1 --source visible --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane send-text w1:p1 ^[[200~Reply with only the word HERDRPONG.^[[201~ --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane read w1:p1 --source visible --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane send-keys w1:p1 enter --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane read w1:p1 --source visible --format ansi --session fm-lab-pr269-52310-10541
herdr agent get w1:p1 --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane read w1:p1 --source visible --session fm-lab-pr269-52310-10541
Evidence: Herdr live: same send with the base script (6x --source recent --lines 200)

Source: Herdr live: same send with the base script (6x --source recent --lines 200)

herdr pane get w1:p1 --session fm-lab-pr269-52310-10541
herdr pane process-info --pane w1:p1 --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane read w1:p1 --source visible --format ansi --session fm-lab-pr269-52310-10541
herdr agent get w1:p1 --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane read w1:p1 --source recent --lines 200 --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane read w1:p1 --source recent --lines 200 --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane read w1:p1 --source recent --lines 200 --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane send-keys w1:p1 ctrl+s --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane read w1:p1 --source visible --format ansi --session fm-lab-pr269-52310-10541
herdr agent get w1:p1 --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane read w1:p1 --source recent --lines 200 --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane send-text w1:p1 ^[[200~Reply with only the word BASEHERDR.^[[201~ --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane read w1:p1 --source recent --lines 200 --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane send-keys w1:p1 enter --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane read w1:p1 --source visible --format ansi --session fm-lab-pr269-52310-10541
herdr agent get w1:p1 --session fm-lab-pr269-52310-10541
herdr status --json --session fm-lab-pr269-52310-10541
herdr pane read w1:p1 --source recent --lines 200 --session fm-lab-pr269-52310-10541
Evidence: Herdr live: Claude pane before the send (draft typed)

Source: Herdr live: Claude pane before the send (draft typed)


▗ ▗   ▖ ▖  Claude Code v2.1.293
           Opus 5.5 (1M context) · Claude Team
 ▘▘   ▝▝   ~/…/01M4D846ZGFWYYXAKZNZ674X5G


❯ Run exactly this one bash command and nothing
  else: bin/fm-lock.sh ; then reply with just its
  exit code.

  Ran 1 shell command

⏺ 0

✻ Worked for 5s · done 10:02 AM


─────────────────────────────────────────────────────
❯ my unfinished draft about the glasses
─────────────────────────────────────────────────────
  ⚠ Transcript saving is off — inherited CLAUDE_CO…
  ⚓ Opus 5.5 (1M context)·medium │ 🧠6% ⚡7% │ 🚢…
  ⏵⏵ auto mode on (shift+tab to cycle)
Evidence: Herdr live: Claude pane after the send (HERDRPONG reply, draft restored)

Source: Herdr live: Claude pane after the send (HERDRPONG reply, draft restored)


❯ Run exactly this one bash command and nothing
  else: bin/fm-lock.sh ; then reply with just its
  exit code.

  Ran 1 shell command

⏺ 0

✻ Worked for 5s · done 10:02 AM

❯ Reply with only the word HERDRPONG.

⏺ HERDRPONG

✻ Churned for 1s · done 10:02 AM

─────────────────────────────────────────────────────
❯ my unfinished draft about the glasses
─────────────────────────────────────────────────────
  ⚠ Transcript saving is off — inherited CLAUDE_CO…
  ⚓ Opus 5.5 (1M context)·medium │ 🧠6% ⚡7% │ 🚢…
  ⏵⏵ auto mode on (shift+tab to cycle)
Evidence: Herdr live: viewport top rows and scroll offset polled during the send

Source: Herdr live: viewport top rows and scroll offset polled during the send

1791446556.11	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.13	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.15	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.17	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.19	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.21	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.23	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.25	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.27	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.29	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.31	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.33	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.34	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.36	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.38	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.40	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.42	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.43	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.45	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.47	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.49	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.51	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.52	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.55	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.57	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.58	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.60	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.62	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.64	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.66	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.68	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.69	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.71	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.73	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.75	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.77	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.79	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.81	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.83	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.85	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.86	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.88	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.90	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.92	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.94	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.96	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 5.5 (1M context) · Claude Team|
1791446556.98	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	|▗ ▗   ▖ ▖  Claude Code v2.1.293|           Opus 

... [9622 bytes truncated] ...

et_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.19	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.21	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.23	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.25	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.27	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.29	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.31	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.33	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.35	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.37	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.39	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.41	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.43	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.45	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.47	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.49	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.51	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.53	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.55	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.57	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.59	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.60	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.62	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.64	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.67	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.69	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.71	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.72	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.74	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.76	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.78	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.80	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.83	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.85	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.87	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.89	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.91	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.93	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.95	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.97	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446558.99	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446559.01	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446559.06	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446559.08	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446559.10	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446559.12	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446559.14	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	||❯ Run exactly this one bash command and nothing|
1791446559.16	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	❯ Run exactly this one bash command and nothing|  else: bin/fm-lock.sh ; then reply with just its|  exit code.|
1791446559.18	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	❯ Run exactly this one bash command and nothing|  else: bin/fm-lock.sh ; then reply with just its|  exit code.|
1791446559.21	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	❯ Run exactly this one bash command and nothing|  else: bin/fm-lock.sh ; then reply with just its|  exit code.|
1791446559.23	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	❯ Run exactly this one bash command and nothing|  else: bin/fm-lock.sh ; then reply with just its|  exit code.|
1791446559.25	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	❯ Run exactly this one bash command and nothing|  else: bin/fm-lock.sh ; then reply with just its|  exit code.|
1791446559.27	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	❯ Run exactly this one bash command and nothing|  else: bin/fm-lock.sh ; then reply with just its|  exit code.|
1791446559.29	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	❯ Run exactly this one bash command and nothing|  else: bin/fm-lock.sh ; then reply with just its|  exit code.|
1791446559.31	{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":23}	❯ Run exactly this one bash command and nothing|  else: bin/fm-lock.sh ; then reply with just its|  exit code.|
Evidence: Herdr live: /model picker open, send goes to mailbox, picker unchanged

Source: Herdr live: /model picker open, send goes to mailbox, picker unchanged

fm-desk-voice: the primary's chat pane is not reachable; saving to the mailbox instead
mailbox: /var/folders/2c/9sf1hvpn23q336b08gs1np0c0000gn/T//fm-lab.sR4Rtj/state/desk-voice/inbox/20261008T080336Z-dadf258f.json
Evidence: tmux live: send past a draft with the change (capture-pane -S -0 only)

Source: tmux live: send past a draft with the change (capture-pane -S -0 only)

tmux display-message -p -t %0 #{pane_id}
tmux display-message -p -t %0 #{pane_pid}
tmux display-message -p -t %0 #{cursor_y}
tmux capture-pane -e -p -t %0 -S 0 -E -
tmux display-message -p -t %0 #{pane_tty}
tmux display-message -p -t %0 #{pane_current_command}
tmux capture-pane -p -t %0 -S -0
tmux capture-pane -p -t %0 -S -0
tmux capture-pane -p -t %0 -S -0
tmux display-message -p -t %0 #{pane_id}
tmux send-keys -t %0 C-s
tmux display-message -p -t %0 #{cursor_y}
tmux capture-pane -e -p -t %0 -S 0 -E -
tmux display-message -p -t %0 #{pane_tty}
tmux display-message -p -t %0 #{pane_current_command}
tmux display-message -p -t %0 #{cursor_y}
tmux capture-pane -e -p -t %0 -S 0 -E -
tmux display-message -p -t %0 #{pane_tty}
tmux display-message -p -t %0 #{pane_current_command}
tmux capture-pane -p -t %0 -S -0
tmux load-buffer -b fm-desk-voice-41640 -
tmux paste-buffer -p -d -b fm-desk-voice-41640 -t %0
tmux capture-pane -p -t %0 -S -0
tmux display-message -p -t %0 #{pane_id}
tmux send-keys -t %0 Enter
tmux display-message -p -t %0 #{cursor_y}
tmux capture-pane -e -p -t %0 -S 0 -E -
tmux display-message -p -t %0 #{pane_tty}
tmux display-message -p -t %0 #{pane_current_command}
tmux capture-pane -p -t %0 -S -0
Evidence: tmux live: same send with the base script (capture-pane -S -20)

Source: tmux live: same send with the base script (capture-pane -S -20)

tmux display-message -p -t %0 #{pane_id}
tmux display-message -p -t %0 #{pane_pid}
tmux display-message -p -t %0 #{cursor_y}
tmux capture-pane -e -p -t %0 -S 0 -E -
tmux display-message -p -t %0 #{pane_tty}
tmux display-message -p -t %0 #{pane_current_command}
tmux capture-pane -p -t %0 -S -20
tmux capture-pane -p -t %0 -S -20
tmux capture-pane -p -t %0 -S -20
tmux display-message -p -t %0 #{pane_id}
tmux send-keys -t %0 C-s
tmux display-message -p -t %0 #{cursor_y}
tmux capture-pane -e -p -t %0 -S 0 -E -
tmux display-message -p -t %0 #{pane_tty}
tmux display-message -p -t %0 #{pane_current_command}
tmux capture-pane -p -t %0 -S -20
tmux load-buffer -b fm-desk-voice-49808 -
tmux paste-buffer -p -d -b fm-desk-voice-49808 -t %0
tmux capture-pane -p -t %0 -S -20
tmux display-message -p -t %0 #{pane_id}
tmux send-keys -t %0 Enter
tmux display-message -p -t %0 #{cursor_y}
tmux capture-pane -e -p -t %0 -S 0 -E -
tmux display-message -p -t %0 #{pane_tty}
tmux display-message -p -t %0 #{pane_current_command}
tmux capture-pane -p -t %0 -S -20
Evidence: tmux live: Claude pane after send past draft (DRAFTPONG reply, draft restored)

Source: tmux live: Claude pane after send past draft (DRAFTPONG reply, draft restored)


 ▐▛███▛█   Claude Code v2.1.293
▝▜██████▀  Opus 5.5 (1M context) · Claude Team
 ▝▝   ▝▝   ~/.no-mistakes/worktrees/9957e108f4d7/01M4D846ZGFWYYXAKZNZ674X5G


❯ Run exactly this one bash command and nothing else: bin/fm-lock.sh ; then reply with just its exit code.

  Ran 1 shell command

⏺ Captain, 0

✻ Worked for 3s · done 9:59 AM

❯ Reply with only the word PONG.

⏺ PONG

✻ Churned for 1s · done 10:00 AM

❯ Reply with only the word DRAFTPONG.

⏺ DRAFTPONG

✻ Cooked for 1s · done 10:00 AM















────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
❯ my unfinished draft about the glasses
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  ⚓ Opus 5.5 (1M context)·medium │ 🧠6% ⚡6% │ 🚢0 🧪0 ⏸0 ⚠0 🪦0 📋0 │ 👁 NO-WATCH -- │ $0.46 │ 💤--
  ⏵⏵ auto mode on (shift+tab to cycle)
Evidence: Pane reads per send-past-draft on the live Herdr pane
change: 6x `pane read w1:p1 --source visible`, 3x `--source visible --format ansi`
base: 6x `pane read w1:p1 --source recent --lines 200`, 3x `--source visible --format ansi`

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 3 of 5 scenarios driven live against the product
Scenario Result Live Evidence
Herdr: the captain sends while a Claude draft is in the chat box; the draft is set aside, the message is delivered, the draft comes back, and herdr is never asked for history ✅ pass live live-herdr-change-draft-*.{txt,log}: output sent: herdr fm-lab-...:w1:p1, Claude replied HERDRPONG, draft restored; herdr calls show only --source visible. Base script on the same pane made 6x `--…
tmux: the captain sends with an empty chat box and then past a typed draft; delivery works and the checks read only the viewport (capture-pane -S -0, not -S -20) ✅ pass live live-tmux-empty-send-.{txt,log} and live-tmux-draft-.{txt,log}: sent: tmux %0, PONG/DRAFTPONG replies, draft restored; change makes 6x capture-pane -S -0, base made 6x capture-pane -S -20
Adversarial: the captain sends while Claude's real /model picker is open; nothing is typed into the picker and the message goes to the mailbox ✅ pass live live-herdr-dialog-*.{txt,log}: output mailbox: .../inbox/20261008T080336Z-dadf258f.json, picker screen unchanged before and after, no send-text/send-keys calls
Regression test fails before the fix and passes after, and its dialog half catches a dialog check that reads history ⏸️ untested no The prior payload recorded this as a unit-test run (live=false), not a run against the live product, so it does not establish a live result. The unit-test results remain listed under tested: ok on t…
Herdr 0.9.3: the Claude chat no longer jumps up and back while the floater shows Delivering ⏸️ untested no This machine has Herdr 0.7.4 (herdr --version), and the scroll-on-history-read bug only happens on Herdr 0.9.3. On 0.7.4 the live viewport poll showed only forward content growth with both the base…
  • tests/fm-deepgram-desk.test.sh focused run (temporary runner, since removed): test_desk_voice_send_reads_only_the_visible_screen, test_desk_voice_send_goes_past_a_claude_draft, test_desk_voice_send_falls_back_when_the_pane_shows_a_dialog, test_desk_voice_send_keeps_a_draft_it_cannot_set_aside: all ok on the change
  • Same regression test with base bin/fm-desk-voice.sh (fc0eda7d) swapped in: fails with not ok - a send past a draft never asks herdr for history (unexpected: '--lines')
  • Dialog half alone, change vs. change with only shows_selection_dialog reverted to fm_backend_capture: ok vs. not ok - the dialog check never asks herdr for history
  • Live tmux lab (bin/fm-lab-home.sh create, tmux -L fm-lab new-session ... claude, primary took the lock via bin/fm-lock.sh): fm-desk-voice.sh send with an empty chat box, logging tmux through a pass-through wrapper
  • Live tmux lab: fm-desk-voice.sh send past a real typed Claude draft, on the change and then on the base script
  • Live Herdr lab (bin/fm-herdr-lab.sh provision/run/teardown, Herdr 0.7.4, real claude in pane w1:p1 holding the lab lock): fm-desk-voice.sh send past a typed draft on the change and on the base script, logging herdr calls through a wrapper and polling the visible top rows and scroll offset during the send
  • Live Herdr lab, adversarial: fm-desk-voice.sh send 'sonnet please' while Claude's real /model picker was open
  • Teardown: bin/fm-herdr-lab.sh teardown, tmux -L fm-lab kill-server, rm -rf of both lab homes; worktree left clean
✅ **Document** - passed

✅ No issues found.

⚠️ **Lint** - 1 warning
  • ⚠️ linter found issues (exit code 1)
✅ **Push** - passed

✅ No issues found.

Luc Wächter and others added 2 commits October 7, 2026 17:24
The send path's selection-dialog, chat-box and stash checks read the pane
with a history capture. On herdr, a line-counted read of an idle Claude
pane makes herdr scroll Claude's conversation up to collect its history
and back, which shows as the chat jumping while a voice or dictated
message is delivered. Those checks now read only the visible screen
wherever the backend has a verified viewport read, as the composer check
already does.
@Amplify-Logic
Amplify-Logic merged commit e0fe544 into main Oct 8, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant