fix(terminal): let a composition-only overlay follow the prompt, repaint it on removeChar, document the API (#499 review)

Merge-time fixes for the three findings of the third review round of #499.

- minor: a composition on an empty prompt did not follow the prompt after
  output or a resize. The post-write re-place in flushPendingWrites and the
  resize observer both ran rerender() only when hasPending was true, and
  hasPending deliberately excludes the composition, so the first word of a
  prompt (an overlay holding only a composition) stayed on the old row over
  whatever output moved there. Both sites now call rerender() unconditionally;
  it already returns early when there is nothing to draw, so nothing changes
  without a composition. New browser case drives the real
  batchTerminalWrite/flushPendingWrites path against real xterm 6 and the
  overlay built from source, moves the prompt from row 0 to row 3 and checks
  the overlay follows (it fails on the old guard, overlay left on row 0), with
  a parity case for pending text. The structure test pins the post-write site
  through vm and the resize site, which is a closure inside initTerminal(), by
  source.
- nit: removeChar() dropped the composition but did not repaint on its false
  path, leaving a composition-only overlay on screen showing text the addon no
  longer held. It now hides the overlay there when a composition was dropped.
  Package tests cover that path and the flushed path repainting without the
  tail.
- nit: the package README did not document setComposition() or the
  composition getter and described hasPending as "any content". Added both to
  the API tables plus a short IME composition section, reworded hasPending
  (pending or flushed text, excludes the composition), and made the quick
  start re-render unconditionally instead of teaching the hasPending guard.
  The hasPending JSDoc says the same.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-10-04 23:52:41 +02:00
parent 06c4c7da16
commit 470cf79776
6 changed files with 185 additions and 16 deletions
@@ -169,6 +169,26 @@ describe('setComposition', () => {
expect(lineText(lineDivs(overlay)[0])).toBe('ab');
});
it('removeChar() with nothing to remove still takes a composition-only overlay off screen', () => {
const { addon, overlay } = setup();
addon.setComposition('ka');
expect(compositionText(overlay)).toBe('ka');
expect(addon.removeChar()).toBe(false);
expect(addon.composition).toBe('');
expect(compositionText(overlay)).toBe('');
expect(overlay.style.display).toBe('none');
expect(addon.state.visible).toBe(false);
});
it('removeChar() repaints flushed text without the dropped composition', () => {
const { addon, overlay } = setup();
addon.setFlushed(3, 'abc');
addon.setComposition('xy');
expect(addon.removeChar()).toBe('flushed');
expect(compositionText(overlay)).toBe('');
expect(lineText(lineDivs(overlay)[0])).toBe('ab');
});
it('text appended while composing lands before the tail', () => {
const { addon, overlay } = setup();
addon.appendText('ab');