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
+19
View File
@@ -690,6 +690,25 @@ describe('mobile IME commit and authoritative terminal output', () => {
expect(controller.noteAuthoritativeOutput).not.toHaveBeenCalled();
});
it('re-places an overlay that holds only a composition after output (hasPending is false there)', () => {
const { app, flush } = outputHarness();
const overlay = { hasPending: false, pendingText: '', composition: '今日', rerender: vi.fn() };
app._localEchoOverlay = overlay;
app.batchTerminalWrite('output that moves the prompt');
flush();
expect(overlay.rerender).toHaveBeenCalledOnce();
});
it('re-places it after a resize too: no rerender() site is gated on hasPending', () => {
// The resize observer is a closure inside initTerminal(), so it is pinned
// by source; the post-write follow runs against real xterm in
// test/mobile-ime-preview.browser.test.ts.
expect(terminalSource).toMatch(
/this\.updateConnectionLines\(\);\s*(?:\/\/[^\n]*\n\s*)*this\._localEchoOverlay\?\.rerender\(\);/
);
expect(terminalSource).not.toMatch(/hasPending\)\s*\{?\s*this\._localEchoOverlay\.rerender\(\)/);
});
it('notifies once per commit, never for later output', () => {
const { app, controller, flush, parseNext } = outputHarness();
app._consumeMobileImeTerminalData('你好');