Skip to content

fix: composer geometry and terminal-pane control (4 reported defects) - #66

Merged
Lexus2016 merged 3 commits into
mainfrom
fix/composer-terminal-controls
Aug 24, 2026
Merged

Lexus2016 merged 3 commits into
mainfrom
fix/composer-terminal-controls

Conversation

@Lexus2016

Copy link
Copy Markdown
Owner

Four defects reported against the shipped build. None was a regression — git log -S
on every identifier is empty, so each was work that never reached a commit.

1 & 2 — the composer

  • #interruptPill sat upstream of <textarea id="input"> in the .iw flex row.
    Every earlier sibling owns the text's left edge, so starting a turn shoved the
    placeholder ~70px right and narrowed the box enough to re-wrap the hint. One report,
    two symptoms. The pill now sits downstream; the fade-in slides from the other side.
  • The height recompute was correct but ran from one function (restoreComposerState).
    Boot (markup ships rows="1"), setLang() (a longer translation wraps differently),
    a sidebar toggle, a window resize, fonts landing after first paint and the pill
    entering the row all change the WRAP without changing the VALUE. autosizeInput()
    now owns the cap (COMPOSER_MAX_H) and is called from all seven paths. There were
    six open-coded copies of the formula, not five — the slash-command inserter was
    the one the first pass missed, and the test caught it.

3 & 4 — the terminal pane

  • Focus, not the socket. xterm routes keystrokes through a hidden textarea; a
    reconnect swaps entry.ws but leaves focus wherever it drifted, so the pane looks
    live and swallows every key. Switching tabs away and back worked only because
    showTerminalView() ends on term.focus(). ws.onopen and a mouseup on the host
    now do the same — the latter skipped while a selection is being dragged out. The
    v7.6.0 server heartbeat is the other half and only the other half: it reaps a dead
    TCP path within ~60s, which is a timeout, not an answer to "it is stuck now".
  • #termRefreshBtn + refreshTerminalSession() reconnects unconditionally. Order
    is the fix: clear the latched exited → null entry.ws → close the old socket →
    reconnect. onclose compares entry.ws === ws, so detaching first is what stops the
    dying socket racing a second reconnect against the new one.
  • .term-send — .center.term-mode > .ia is display: none, so a terminal tab had
    no input surface but the pane itself, which is exactly what a stale socket takes away.
    It writes the same {type:'input'} frame term.onData() writes, so it works on an
    agent tab and on the subscription engine's pane, where it is how a blocking permission
    prompt gets answered. \r not \n, text and CR in ONE frame.

Three new i18n keys in all five locales.

Tests

test/composer-terminal.test.js — 41 structure assertions on public/index.html,
wired into the npm test chain (66 files now).

Full suite on this branch: EXIT=0, 1993 ok, 0 failed.
Not covered: the live browser/WebSocket path — needs a manual click-through.

…resh and a send line

Four defects reported against v7.6.0/7.7.0 that were never actually in a commit.

1. The interrupt pill sat UPSTREAM of the textarea in the composer's flex row, so
   starting a turn shoved the placeholder ~70px right and re-wrapped the hint. It
   now lives downstream of the textarea; the fade-in slides from the other side.
2. The textarea's height was recomputed only from restoreComposerState(), so boot,
   setLang(), a panel toggle, a window resize and the pill entering the row all left
   the wrapped second line clipped. One autosizeInput() helper now owns it and is
   called from every path that can change the wrap.
3. A terminal whose socket died silently kept focus in xterm's hidden textarea and
   swallowed every key until you switched tabs and back (showTerminalView() ends on
   term.focus(), which is why that worked). Reconnect and a click on the pane now
   refocus it, and a new header button reconnects unconditionally.
4. .center.term-mode hides the composer, so a terminal tab had no way to send a line
   except typing into the pane -- exactly what a stale socket takes away. A send line
   under the pane writes the same {type:'input'} frame term.onData() writes, so it
   works on an agent tab and on the engine pane, where it answers a blocking prompt.
…t for

composer-terminal.test.js reads public/index.html and asserts the four invariants
behind the fixes in 417d31a: the pill's position in the flex row, that
inEl.scrollHeight is read in exactly ONE place, the refresh button's statement
order (clear exited -> detach entry.ws -> close -> reconnect), and the send line's
frame shape (CR, one frame, refuses a non-OPEN socket).

It caught two things while being written: a sixth open-coded height computation in
the slash-command inserter that the original patch missed, and the fact that the
composer really is hidden by a selector LIST, not a rule of its own.
…on, ignore a stale terminal close

An adversarial review of the diff (independent non-Claude advisor) found four ways the
result still fell short of the task:

- toggle() measured the composer at the START of the 250ms panel animation, i.e. the
  width the user was leaving. A ResizeObserver on the textarea covers the whole
  animation and a split-pane drag with it; it compares clientWidth first, because the
  callback sets the height and would otherwise re-enter forever.
- The staleness test in the terminal's onclose guarded only the reconnect, not the
  "disconnected" paint above it. refreshTerminalSession() detaches before it closes,
  so the old socket's close event lands after the new one is ready and marked a
  working pane dead. The guard is now the first line of the handler.
- The send line refused an empty string, which made a bare Return -- the one key you
  most need at a [Y/n] prompt -- the one key it could not send.
- That line is shared by every pane, so a draft typed for one terminal was sitting in
  it when another was opened. showTerminalView() clears it.

test/composer-terminal.test.js grows five assertions (46 total); each was mutation-
checked by reverting the fix and confirming the failure.
@Lexus2016
Lexus2016 merged commit 10b7954 into main Aug 24, 2026
2 checks passed
@Lexus2016
Lexus2016 deleted the fix/composer-terminal-controls branch August 24, 2026 12:56
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