Skip to content

fix(ws,composer): restore review fixes dropped from #967 - #972

Merged
devswha merged 2 commits into
mainfrom
fix/restore-967-review-fixes
Oct 13, 2026
Merged

devswha merged 2 commits into
mainfrom
fix/restore-967-review-fixes

Conversation

@devswha

@devswha devswha commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

Summary

Four review fixes from cc86059 and 66599c7 never reached main when #967 merged. I checked each claim against origin/main (a338f8b) and found no equivalent handling for any of them. All four are restored here on main's current probe design (pauseProbe/probePaused). The original endProbe(answered)/probeAnswered scheme is not brought back: main handles the hidden-page case differently on purpose, by keeping submits waiting (ws.test.ts "keeps a submit and a secret waiting when the page hides during a probe").

Dropped fix Gap on main Restored as
Composer send refused beside a held terminal draft (cc86059) submitComposerMessage never checked draftHeldForRef. The message box, quick sends and a queued "Send now" typed text and pressed Enter after a partial line that had already reached the shell. submitComposerMessage answers held_input. submitNote gives the held-input note.
held_input counts as nothing typed (66599c7) submitNotTyped did not list it, so a refused queued row would be marked uncertain. Added to submitNotTyped. A refused "Send now" now stays held.
Probe pong goes on to handlers, and the poll re-reads canInput() (cc86059) ws.ts returned early on every pong. If a frame arrived during the probe, the view stored inputReady=false. On a quiet pane, the "Waiting for terminal input…" banner and the disabled key bar then stayed until some later frame arrived. The pong reaches the handlers after endProbe(). PaneTerminal's 1 s poll calls setInputReady(socket.canInput(pane)).
A snapshot without ping ends a probe (cc86059) A probe sent before the snapshot, on ping support remembered from the last connection, dropped a healthy older bridge after 3 s + 3 s. If the page hid before the snapshot, the paused probe was never cleared, because probe() returns early on !pingKnown. canInput() then stayed false for the whole connection. The snapshot handler calls endProbe() when !pingKnown && this.probing. This covers both the running and the paused case.

Tests

  • ws.test.ts: three tests from cc86059, adapted to main. They cover the pong reaching the handlers, the snapshot without ping ending a running probe while the connection and a waiting submit carry on, and the paused-probe variant. I ran all three against main's unfixed ws.ts and they failed.
  • compose.test.ts: checks the held_input entries for submitNotTyped and submitNote.
  • bun run check fast: typecheck, build and lint pass. server/portal.test.ts failed once in the parallel unit run. It passes on its own, and this PR does not touch that file.
  • Browser runs with CI=1 bun run check run: ui-regression.ts (held draft, composer and terminal input checks), ui-contexts-regression.ts, terminal-input-regression.ts, held-rows-demo-regression.ts and pending-pane-switch-regression.ts all pass.

Docs: I updated src/AGENTS.md (WS client and Held terminal draft rows) and extended changelog.d/ws-liveness-probe.md.

🤖 Generated with Claude Code

Review

Fixes from the review, in a8752f8:

  • Medium: Send now on a server-owned queued row skipped the held-input guard. actOnPending sent pending-action straight away, and the server pastes the text and presses Enter for non-Claude agents, so a partial line left in the pty would run with the message. The guard now runs in actOnPending for every Send now, before begin. It refuses with held_input, does not mark the row uncertain, and leaves Send now (once the draft is settled) and Discard working. The changelog fragment and the src/AGENTS.md row are now accurate as written.
  • Low: no test covered the composer guard or the poll's canInput re-read. scripts/pending-pane-switch-regression.ts now holds a terminal draft and checks three things: Send now on a server-owned row sends no pending-action and shows the held note, a message-box send sends no submit and keeps its text, and Send now delivers exactly once after Discard. scripts/terminal-input-regression.ts holds the probe's pings on a quiet pane. It checks that "Waiting for terminal input…" shows within about a second and clears after the pong. I confirmed each test fails without its fix.
  • Low: a refused Send now on a row showed the raw English reason. PendingMessages now sends held_input through submitNote along with input_draft. A new unit test, src/components/PendingMessages.test.tsx, renders it in Korean.

…overy dropped from #967

Review fixes from cc86059 and 66599c7 were lost when #967 merged. Restored on main's
current probe design (pauseProbe/probePaused), not the original endProbe(answered) scheme:

- submitComposerMessage refuses with held_input while a terminal draft is held, so the
  message box, quick sends and a queued "Send now" never press Enter after a partial line
  that already reached the shell. submitNote words it; submitNotTyped counts it as nothing
  typed, so a refused queued row stays held.
- The probe's pong is passed on to message handlers, and PaneTerminal's 1 s poll re-reads
  canInput(), so the key bar and the "Waiting for terminal input" banner recover on a quiet
  pane after a probe.
- A snapshot without "ping" ends a probe in flight or paused (sent on ping support
  remembered from an earlier connection), so an older bridge after a reconnect is not
  dropped at the deadline and a paused probe no longer refuses input for the whole
  connection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 7 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: devswha/herdr-web-ui/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 2d9cee9a-13e4-4185-a0f4-b29cabf1714e
📥 Commits

Reviewing files that changed from the base of the PR and between a338f8b and a8752f8.

📒 Files selected for processing (11)
  • changelog.d/ws-liveness-probe.md
  • scripts/pending-pane-switch-regression.ts
  • scripts/terminal-input-regression.ts
  • src/AGENTS.md
  • src/components/PaneTerminal.tsx
  • src/components/PendingMessages.test.tsx
  • src/components/PendingMessages.tsx
  • src/lib/compose.test.ts
  • src/lib/compose.ts
  • src/lib/ws.test.ts
  • src/lib/ws.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

… held

Send now on a server-owned queued row called pending-action directly and
never reached the composer's held-input guard, so the server pasted the
text and pressed Enter beside a partial line the draft kept back. The
guard now runs in actOnPending for every Send now, before the row is
marked busy or unconfirmed, so Send now after the draft is settled (or
Discard) still works. A held-input refusal on a row is shown through
submitNote, in the reader's language, rather than the raw English reason.

Tests: the pending pane-switch regression holds a terminal draft and
checks that Send now and a message-box send are both refused with no
frame sent, then that Send now delivers once the draft is discarded; the
terminal input regression checks that the 1 s poll shows a probe's
refused input on a quiet pane and clears it after the pong; a
PendingMessages unit test covers the translated note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@devswha
devswha merged commit 026448e into main Oct 13, 2026
9 checks passed
@devswha
devswha deleted the fix/restore-967-review-fixes branch October 13, 2026 17:59
devswha added a commit that referenced this pull request Oct 15, 2026
#811 had landed under 0.4.7 by a merge; its entry now describes what survived #1104 (Mod+Shift+PageUp/PageDown, one line of overlap). The Hermes Stop note carries #1161, the held-input notes #972, and the folded message note no longer mentions Find, which #1140 removed.
devswha added a commit that referenced this pull request Oct 15, 2026
## Change

Release 0.4.9 from main at 6e575a8 (v0.4.8 + 193 commits), plus one
blocker fix found in pre-release QA.

- **`fix(terminal)` (ededf39)**: an open terminal re-attaches by itself
after herdr restarts or updates. Before, the pane kept "terminal ended"
and dropped typed input until another pane was picked (also in v0.4.8).
The renderer now keys its attachment on the pane's `terminal_id`, which
herdr changes on restore, and a cached tab waits until it is shown
again. Locked by `scripts/herdr-restart-regression.ts` (fails on main,
passes here), added to the browser lane.
- `package.json` and `herdr-plugin.toml`: 0.4.9.
- CHANGELOG: `[Unreleased]` and 147 changelog.d fragments become
`[0.4.9] - 2026-10-16`, with `by @login` for outside contributors (22
PRs) and #1164 by @aisensiy carried in #1169. The reattach entry has no
PR link yet: it is this PR's.
- `release-summaries.json`: 0.4.9 in en/ko/ja/zh (7 new, 7 improved, 8
fixed in each).
- `REMOTE_BUNDLE_VERSION` 27 -> 28: the bundle's inputs changed since
remote-v27.
- `telemetry/deployed.json`: the Worker was redeployed (version
431d6e07) for the settings schema changes since v0.4.8 (#1120, #1123,
#1135); before it, this build's install event got HTTP 400.

## Release notes (as installs will show them)

### New features

- New workspace → Browse makes a new folder and starts in it.
- Shortcuts record chords with their exact modifiers; New tab and splits
can be bound.
- Pin workspaces, worktrees and agents from a row's menu or a
right-click.
- A Jump to bottom button returns a terminal scrolled up into history to
the live end.
- Pick a skill anywhere in a message, not only at its start.
- Connect to remote PCs from a Windows PC.
- Mod+Shift+E puts the cursor in the pane's input.

### Improvements

- Split view halves are independent, and both may show the same tab.
- Phones start in direct typing; the key bar's keyboard key lights for
the input line.
- Claude's whole plan shows from its plan file, and its answer review
reads correctly.
- Send shows progress until the agent takes the message, and Stop always
goes first.
- Recent tabs stay ready, so switching back shows the chat at once.
- Sign out moved into Settings behind a confirm; menus and settings were
trimmed.
- A workspace or tab reopens on the pane you last saw, also after a
reload.

### Bug fixes

- Open terminals reconnect by themselves after herdr restarts or
updates.
- With herdr 0.9.4 or later, a second device no longer types stray
characters into a pane.
- Typing Hangul from an iPad hardware keyboard composes syllables.
- Opening an Android keyboard no longer resizes the shared terminal.
- When the app's folder is replaced under it, it asks for a restart, not
an endless reload.
- Hermes: Stop ends a running turn, and Chat will not send over text
typed in its input.
- The sidebar no longer breaks on a worktree without a branch.
- A click on a lone pane's top right corner reaches the program there.

The full changelog is the [`[0.4.9]` section of CHANGELOG.md on this
branch](https://github.com/devswha/herdr-web-ui/blob/chore/release-0.4.9/CHANGELOG.md)
(too long for a PR body).

## Verification

Scenarios ran on c5f297e. The two commits after it (a978847, 14387e5)
change only CHANGELOG.md, from the gate review: links for #863, #864,
#869, #870, #972 and #1161, #811 moved from 0.4.7 (where a merge put it)
into 0.4.9, and entries no longer name the removed Files at once setting
or Find. No runtime behaviour differs from what was verified.

QA plan with scenarios and pass conditions: user flows install → first
run → daily use → errors, on Linux, macOS, Windows and remote PCs. Each
scenario ran twice: a first pass on main 6e575a8, then an independent
re-run by separate agents on this head (c5f297e), checked artifact by
artifact. Browser checks drove a real Chrome at 1440 px and 390 px
(iPhone emulation), light and dark. Local herdr was 0.9.5 (CI pins
0.9.3), so this also covers the newer herdr users have.

| ID | Flow | Where | Result on c5f297e |
|---|---|---|---|
| L1 | Fresh install: README plugin path and `install.sh` one-liner |
Clean Ubuntu 24.04 (Docker) | PASS: both exit 0, health ok, startup hook
after a herdr restart |
| M1 | Plugin install, start, first terminal | MacBook, macOS 26.6,
herdr 0.9.5 | PASS: installed in ~1 min, health ok, `echo` runs |
| W1(a) | Fresh native install | windows-latest ("Real Windows install"
check) | PASS (CI) |
| W1(b) | Plugin install on an existing herdr 0.9.3, mirrored pane |
Windows 11 PC | PASS; installer unit test PASS |
| L2 | First run: workspace list, new workspace, type in a pane | Linux,
herdr 0.9.5 | PASS |
| L3 | Daily use: split halves on one tab, Browse → New folder, pin,
record a shortcut, Sign out confirm, phone direct typing and input-line
key | Linux | PASS 7/7 |
| L4 | herdr down, server restart, typing while disconnected (kept as a
draft), token-protected server refuses a fresh browser | Linux | PASS |
| FIX1 | herdr stop + start with a pane open: re-attaches without
re-pick or reload | Linux | PASS (screenshots below) |
| R1 | Remote PC over SSH: bundle smoke, key + password logins, desktop
and phone | Linux sshd, Debian container | PASS |
| L5 | In-place update and PWA update | Linux | PASS for the product;
`update-browser-qa.ts` needs a fixture body after a fold (#1178) |
| C1 | Integration suite on herdr 0.9.5 + settings/split/shortcut/chat
browser scripts | Linux | 521 pass / 0 fail; `chat-browser-qa.ts` script
drift (#1178) |
| T1 | 1,484/2,000-char one-line messages into real Claude Code | Linux;
macOS | Linux PASS; **macOS input line: known issue #1174, see below** |

CI on this head: Fast checks, Integration and browser, Native Windows
install, Real Windows install, macOS session identity and the remote
bundle builds all pass. Local `bun run check fast`: 3,825 unit tests
pass; `bun scripts/release-guards.ts` passes.

### Known issue shipped: #1174 (not a regression)

On macOS, a single-line message of about 1.5 KB sent through the
terminal **input line** can reach Claude Code without its first 1,022
characters (5/5 in the re-run; the first pass kept it whole). It
predates 0.4.8. Two fixes were built and rejected on runtime evidence:
bracketed paste over `pane.send_text` corrupts plain shells, and sending
through the attach stream lets a following Stop overtake the message. A
safe fix needs a program-aware paste RPC from herdr or a shared delivery
barrier. Direct typing, which phones start in from this release, and the
desktop terminal delivered the same messages whole. Details in #1174.
**Shipping with it is the maintainer's call at merge**: the QA plan
counted it as a blocker, and no safe fix exists in this release.

### Follow-ups filed
#1177 (Homebrew Bun hint), #1178 (three QA scripts outside CI drifted),
#1179 (small UI findings).

### Screenshots (from the re-run on this head)

**Reattach after a herdr restart (FIX1)**
<img
src="https://raw.githubusercontent.com/devswha/herdr-web-ui/9bc3d185945572068f3fe9746e646312a009618c/FIX1/restarted-1440-dark.png"
alt="pane re-attached after herdr restart, desktop" width="560"> <img
src="https://raw.githubusercontent.com/devswha/herdr-web-ui/9bc3d185945572068f3fe9746e646312a009618c/FIX1/restarted-390-light.png"
alt="phone" width="240">

**First run (L2)**
<img
src="https://raw.githubusercontent.com/devswha/herdr-web-ui/9bc3d185945572068f3fe9746e646312a009618c/L2/echo-terminal-1440-dark.png"
alt="first terminal" width="560"> <img
src="https://raw.githubusercontent.com/devswha/herdr-web-ui/9bc3d185945572068f3fe9746e646312a009618c/L2/echo-terminal-390-light.png"
alt="phone" width="240">

**Daily use (L3)**
<img
src="https://raw.githubusercontent.com/devswha/herdr-web-ui/9bc3d185945572068f3fe9746e646312a009618c/L3/split-03-typed-in-active-half-both-draw-1440-dark.png"
alt="split halves on one tab" width="560">
<img
src="https://raw.githubusercontent.com/devswha/herdr-web-ui/9bc3d185945572068f3fe9746e646312a009618c/L3/newfolder-03-created-directory-follows-1440-light.png"
alt="Browse, new folder" width="560">
<img
src="https://raw.githubusercontent.com/devswha/herdr-web-ui/9bc3d185945572068f3fe9746e646312a009618c/L3/pin-02-workspace-pinned-1440-dark.png"
alt="pinned workspace" width="560">
<img
src="https://raw.githubusercontent.com/devswha/herdr-web-ui/9bc3d185945572068f3fe9746e646312a009618c/L3/signout-02-confirm-dialog-1440-dark.png"
alt="sign out confirm" width="560"> <img
src="https://raw.githubusercontent.com/devswha/herdr-web-ui/9bc3d185945572068f3fe9746e646312a009618c/L3/phone-02-input-line-key-lit-390-dark.png"
alt="phone input line key lit" width="240">

**Errors (L4)**
<img
src="https://raw.githubusercontent.com/devswha/herdr-web-ui/9bc3d185945572068f3fe9746e646312a009618c/L4/herdr-stopped-disconnected-1440-dark.png"
alt="herdr stopped" width="560">
<img
src="https://raw.githubusercontent.com/devswha/herdr-web-ui/9bc3d185945572068f3fe9746e646312a009618c/L4/server-restarted-reconnected-draft-offered-1440-dark.png"
alt="draft offered after reconnect" width="560"> <img
src="https://raw.githubusercontent.com/devswha/herdr-web-ui/9bc3d185945572068f3fe9746e646312a009618c/L4/server-killed-typed-draft-kept-390-light.png"
alt="phone draft kept" width="240">
<img
src="https://raw.githubusercontent.com/devswha/herdr-web-ui/9bc3d185945572068f3fe9746e646312a009618c/L4/unauth-gate-signin-1440-dark.png"
alt="token gate" width="560">

**macOS (M1) and Windows (W1)**
<img
src="https://raw.githubusercontent.com/devswha/herdr-web-ui/9bc3d185945572068f3fe9746e646312a009618c/M1/06-echo-1440-dark.png"
alt="macOS" width="560">
<img
src="https://raw.githubusercontent.com/devswha/herdr-web-ui/9bc3d185945572068f3fe9746e646312a009618c/W1/echo-1440-dark.png"
alt="Windows mirrored pane" width="560"> <img
src="https://raw.githubusercontent.com/devswha/herdr-web-ui/9bc3d185945572068f3fe9746e646312a009618c/W1/echo-390-light.png"
alt="Windows, phone" width="240">

**Remote PC (R1), Claude long message on Linux (T1), update notes (L5)**
<img
src="https://raw.githubusercontent.com/devswha/herdr-web-ui/9bc3d185945572068f3fe9746e646312a009618c/R1/machines-1440-dark.png"
alt="remote PCs" width="560"> <img
src="https://raw.githubusercontent.com/devswha/herdr-web-ui/9bc3d185945572068f3fe9746e646312a009618c/R1/add-pc-390-light.png"
alt="add PC, phone" width="240"> <img
src="https://raw.githubusercontent.com/devswha/herdr-web-ui/9bc3d185945572068f3fe9746e646312a009618c/T1/claude-1484-390-dark.png"
alt="1,484 chars into Claude" width="240">
<img
src="https://raw.githubusercontent.com/devswha/herdr-web-ui/9bc3d185945572068f3fe9746e646312a009618c/L5/notes-desktop.png"
alt="release notes beside the update" width="560">

Screenshots live on the `qa/v0.4.9-evidence` branch (not for merging;
delete it after this release).

## Release

After this merges and its CI on `main` passes: push `remote-v28` on the
merged commit, wait for the Remote PC bundles workflow, then run the
Release workflow with `0.4.9`. None of that was done here.
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