Two pane markers do not match the live TUI: "esc to interrupt" is inert, and StatusRefiner's caret has no start-of-line rule #767

Open
opened 2026-10-05 11:20:54 +02:00 by ltms · 0 comments
Owner

Follow-up to #761. Both items are about the same thing: a string we match against a terminal pane, where nobody checked the match on a real pane.

1. PromptBox.ACTIVE_TURN_MARKER looks inert

PromptBox.classify returns UNREADABLE when "esc to interrupt" appears at or below the box line. The idea is right: a box drawn under a generating turn is not a settled prompt. But I have not been able to make that string appear.

Evidence I have:

  • A lead pane that was generating drew ✢ Whirlpooling… (29s · ↓ 1.5k tokens). No esc to interrupt.
  • A census on 7 live panes today found 0 hits. That part proves little on its own, because none of those 7 was mid-turn.
  • The 3 hits I first saw on my own pane were my own echoed command text, not the TUI. See "a probe can match itself".

So the marker is probably dead weight. It is not causing harm today, because #761 restricted the search to the box line and below. Under the earlier whole-region search, three copies of the string in one pane's scrollback made that pane permanently UNREADABLE, which would hold every lead delivery for ever.

What to do:

  1. Measure what the current TUI really draws while a turn is generating. Capture herdr agent read <pane> --source detection --format text on a pane that is mid-turn, several times, including while a tool call runs.
  2. Then either replace the marker with what is really there, or delete it and say in the javadoc that the box-line restriction is what makes a generating turn safe.
  3. Do not add a second guess. One measured marker beats two hopeful ones.

Note for whoever picks this up: the fixture is part of the problem. FakeHerdr.IDLE_PROMPT_BOX draws the older bordered UI. #761 added IDLE_PROMPT_CARET / DRAFTED_PROMPT_CARET from a real capture and left the old fixtures byte-identical, because 5 tests in StatusPollerRoutingTest, ClaudeCodeLauncherTest and SessionManagerTest depend on the old one being unclassifiable. Any new fixture here must come from a real capture, not from the production constant.

2. StatusRefiner.java:100 matches ❯ anywhere on a line

boolean readyPrompt = pane.contains("❯")
        || pane.contains("│ >")
        || lower.contains("auto mode on");

PromptBox matches a marker only as a line's first characters, because a caret further along a line is transcript text — for example a caret inside something the operator quoted. StatusRefiner has no such rule. So a pane whose scrollback holds a quoted ❯ can read as a ready prompt when it is not one.

This was raised as caveat 6 by the #761 worker and left out of scope on purpose, so the typing fix stayed small. It should be fixed the same way, and the two should share one matcher rather than carrying two copies of the rule. A shared PromptBox helper is the obvious home; a second copy will drift exactly the way these two already did.

Why one ticket

The same mistake made both: a string copied into a check without a capture of the thing it is supposed to match. Fixing them together makes the shared matcher the natural result.

Follow-up to #761. Both items are about the same thing: a string we match against a terminal pane, where nobody checked the match on a real pane. ## 1. `PromptBox.ACTIVE_TURN_MARKER` looks inert `PromptBox.classify` returns `UNREADABLE` when `"esc to interrupt"` appears at or below the box line. The idea is right: a box drawn under a generating turn is not a settled prompt. But I have not been able to make that string appear. Evidence I have: - A lead pane that **was** generating drew `✢ Whirlpooling… (29s · ↓ 1.5k tokens)`. No `esc to interrupt`. - A census on 7 live panes today found 0 hits. That part proves little on its own, because none of those 7 was mid-turn. - The 3 hits I first saw on my own pane were my own echoed command text, not the TUI. See "a probe can match itself". So the marker is probably dead weight. It is **not** causing harm today, because #761 restricted the search to the box line and below. Under the earlier whole-region search, three copies of the string in one pane's scrollback made that pane permanently `UNREADABLE`, which would hold every lead delivery for ever. What to do: 1. Measure what the current TUI really draws while a turn is generating. Capture `herdr agent read <pane> --source detection --format text` on a pane that is mid-turn, several times, including while a tool call runs. 2. Then either replace the marker with what is really there, or delete it and say in the javadoc that the box-line restriction is what makes a generating turn safe. 3. Do not add a second guess. One measured marker beats two hopeful ones. Note for whoever picks this up: the fixture is part of the problem. `FakeHerdr.IDLE_PROMPT_BOX` draws the older bordered UI. #761 added `IDLE_PROMPT_CARET` / `DRAFTED_PROMPT_CARET` from a real capture and left the old fixtures byte-identical, because 5 tests in `StatusPollerRoutingTest`, `ClaudeCodeLauncherTest` and `SessionManagerTest` depend on the old one being unclassifiable. Any new fixture here must come from a real capture, not from the production constant. ## 2. `StatusRefiner.java:100` matches `❯` anywhere on a line ```java boolean readyPrompt = pane.contains("❯") || pane.contains("│ >") || lower.contains("auto mode on"); ``` `PromptBox` matches a marker only as a line's **first** characters, because a caret further along a line is transcript text — for example a caret inside something the operator quoted. `StatusRefiner` has no such rule. So a pane whose scrollback holds a quoted `❯` can read as a ready prompt when it is not one. This was raised as caveat 6 by the #761 worker and left out of scope on purpose, so the typing fix stayed small. It should be fixed the same way, and the two should share one matcher rather than carrying two copies of the rule. A shared `PromptBox` helper is the obvious home; a second copy will drift exactly the way these two already did. ## Why one ticket The same mistake made both: a string copied into a check without a capture of the thing it is supposed to match. Fixing them together makes the shared matcher the natural result.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: fleet/fleetd#767