qwen-code/integration-tests
MikeWang0316tw 5b43edcaf6
fix(cli): bound the live streaming-table pending height (fix scroll-to-top lock, stall-then-dump, header flash) (#6421)
* fix(cli): charge a streaming table's wrapped height so it doesn't jump

The rendered-height estimator charged a table `2 * dataRows + 5`, assuming
one line per data row. When cells wrap (a wide table — many columns, long
content — on a bounded content width), each row renders taller, so the live
frame briefly exceeds the viewport and ink repaints from the top (a jump to
the top; #6170's clip/commit recover it, so it does not lock, but the jump
is visible).

Charge each row (header + data) by its wrapped height instead: approximate
the column width as an equal share of the content area (TableRenderer
shrinks columns proportionally to fit; an equal share never gives a wide
cell more room than TableRenderer would, so it is a safe upper bound) and
sum the tallest wrapped cell per row plus the inter-row separators and
chrome. For a table that fits, every row is one line and the formula
reduces exactly to the previous `2 * dataRows + 5`.

Only the height estimate changes (shared by the render-side clip and the
incremental scrollback commit); no rendering behaviour changes.

28 estimator tests pass; MarkdownDisplay clip tests unaffected.

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

* fix(cli): charge a wide-terminal table's vertical fallback height

The pending-height estimator mirrored only TableRenderer's WIDTH-based
vertical-fallback trigger, not the maxRowLines one. On a wide terminal a
multi-column table whose cells wrap past MAX_ROW_LINES is laid out
vertically (label: value — much taller), but the estimator charged the
shorter horizontal height, so the live frame overflowed and Ink fell into
its from-top full-redraw path (the scroll-to-top lock).

Model both triggers: compute the tallest wrapped cell (maxRowLines) in the
same pass as the horizontal height, and when it exceeds MAX_ROW_LINES
charge the vertical height instead. The vertical estimate now also wraps
each label:value at contentWidth so a long value that wraps is not
under-counted. maxRowLines uses an equal column share (never wider than
TableRenderer's column), so it is an upper bound and a real vertical
table is never missed.

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

* fix(cli): cap the live pending region at the viewport height (non-VP)

The estimator's source-line slice is the primary bound on the live
(non-<Static>) pending frame, but it is disabled whenever
availableTerminalHeight is undefined — which is exactly what happens when
constrainHeight is off (ctrl-s "show more lines"). A tall pending item, e.g.
a long vertical-fallback table, then renders past the viewport; Ink cannot
update incrementally, clears the terminal and redraws from the top on every
repaint — the scroll-to-top lock.

Wrap the non-VP pending region in an Ink maxHeight={availableTerminalHeight}
overflow="hidden" box as a hard backstop. availableTerminalHeight already
excludes the footer/controls, so the live frame can never exceed the
viewport and Ink never trips clearTerminal. While constrained the estimator
keeps content well under this, so the clamp is inert and only engages on
residual overflow. ShowMoreLines stays outside the clamp (it renders only
while constrained, so the clamp is inert then, and must not be clipped).

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

* fix(cli): anchor the estimator's vertical trigger to the first row

Mirror TableRenderer's change to decide the horizontal-vs-vertical format
from the header + first data row only (not every row), so the estimator and
the renderer still agree on which format a streaming table uses. Row heights
above continue to sum every row; only the maxRowLines trigger is anchored.

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

* fix(cli): commit a completed table that alone fills the pending budget

The rendered-height-aware incremental commit stalled when a single block
(a long-text table modelled tall / vertical) charged more than the commit
budget on its own. fitPendingSlice returns kept = the block's trailing
blank line, so the safe split boundary sits exactly at keptLines, but the
boundary search started at keptLines - 1 and missed it. The table has no
internal blank line and the blank before it was already committed, so the
search found nothing and broke the loop. Every later block then appended
past keptLines, so the search window never again contained a blank line —
nothing committed until the stream finalized and dumped all remaining
tables at once (the "stream a few tables, pause, then dump the rest" bug).

Start the boundary search at keptLines so a completed over-tall block's
trailing blank is found and the block commits to <Static>. Committing an
over-tall completed block is fine — only the live pending frame must stay
within the viewport.

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

* fix(cli): don't flash the header while its separator streams in

The forming-table hold-back released the header the moment its separator
line ended with `|` at a column count different from the header — meant to
let a genuinely mismatched separator render as plain text. But a separator
is typed one group at a time and momentarily ends with `|` at every
intermediate count (`| --- | --- |` on the way to seven columns), so the
header flashed as raw `| … |` text on every closed-group frame while the
separator streamed in — a visible strobe for wide (7-column) tables.

A streaming separator only ever gains columns, so treat a mismatch as final
only when it can no longer become valid: it overshot the header's column
count, or a further line has already committed it (it is not the trailing
line). Also hold the header while the separator is still a bare-pipe prefix
(`|`, `| `) before its first dash. The only remaining flash is the
unavoidable one-cell header window (`| Foo |`), indistinguishable from a
single-pipe line.

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

* test: add a terminal-capture ratchet for the streaming-table scroll-to-top lock

Drives ten wide 7-column tables with ~200-char wrapping cells through the
real TUI via a chunked fake OpenAI server and counts the full-screen clears
(`\x1b[2J\x1b[3J\x1b[H`) the app emits while they stream. Each such clear
resets the terminal scroll position, so it is exactly the "jump to top" a
user hits when scrolling up mid-stream. With the pending-height estimator
fix the count is 0; without it the under-charged frame overflows and the
count is ~300. Ratchet fails if it exceeds 20.

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

* fix(cli): tighten the streaming-table vertical height estimate (review)

Two accuracy fixes to fitPendingSlice from PR review, both reducing residual
under-charge of the vertical fallback:

- Charge each vertical data cell as its rendered `label: value` line (parsing
  the header labels once), not the value alone — TableRenderer's
  renderVerticalFormat prefixes the header label, so a long label pushed the
  wrapped line count higher than the estimator accounted for.
- Only model the vertical layout once a data row exists (`dataRows > 0`). With
  just a header + separator, TableRenderer keeps the horizontal header box, so
  the estimator no longer charges the shorter 2-row vertical stub for that
  transient state on a narrow terminal.

Adds a test where the `label: value` line itself wraps (covering the wrapped,
label-inclusive formula) and a zero-data-row narrow-terminal test.

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

* fix(cli): correct the clamp margin and share MAX_ROW_LINES (review round 2)

- Charge tableClampRows + 2 when the clamp engages: TableRenderer wraps the
  height-clamped <Text> in <Box marginY={1}>, so a clamped table renders two
  margin rows beyond maxHeight that the estimator was dropping.
- Make TABLE_MAX_ROW_LINES the single source of truth (pending-rendered-height)
  and import it into TableRenderer as MAX_ROW_LINES, so the renderer and the
  estimator can never disagree on the wrap-to-vertical threshold. Direction is
  util→renderer so the pure height module stays free of the React/ink graph.
- Correct the perColWidth comment: an equal column share is exact for uniform
  columns but can under-count a heterogeneous table (the renderer shrinks a
  narrow column below the share); the MainContent maxHeight backstop is the
  hard cap for that residual case.
- Add a horizontal-layout test whose cells wrap within MAX_ROW_LINES, covering
  the per-row wrapped contentRows sum (not just the old flat 2*dataRows).

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

* fix(cli): estimator cleanups and stronger tests (review round 3)

- Charge the clamped-table margin: covered by a new test asserting a clamped
  table costs tableClampRows + 2 (the marginY the earlier fix added).
- Strengthen the "no stall-then-dump" regression: require all three tables to
  have committed (>= 3 items, each table marker present), so a partial stall
  no longer passes.
- Reuse headerCells instead of re-parsing the header row inside the loop, drop
  the redundant .trim() (splitMarkdownTableRow already trims), and start the
  data-row loop at i + 2.
- Refresh the stale TABLE_CHROME_ROWS JSDoc (no longer 2*dataRows+5) and
  document the estimator's known under-charge gaps (proportional column widths,
  word-aware wrapping, the renderer's post-layout width fallback) that the
  MainContent maxHeight backstop is the hard cap for.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:33:55 +00:00
..
baselines test(perf): add daemon baseline harness (#4175 Wave 1 PR 1) (#4205) 2026-05-17 00:41:26 +08:00
cli docs: consolidate design docs and plans under docs/ (#6417) 2026-07-07 06:05:05 +00:00
concurrent-runner refactor(tools): rename TodoWrite tool display name to TodoList (#5319) 2026-06-19 08:32:10 +08:00
fixtures feat(daemon): merge daemon-mode feature batch into main (#4490) 2026-06-12 00:34:49 +08:00
hook-integration feat(hooks): Add HTTP Hook, Function Hook and Async Hook support (#2827) 2026-04-16 10:10:33 +08:00
interactive fix(cli): switch TUI prefix ✦→◆ to fix glyph overflow on some terminals (#5974) 2026-06-30 22:44:34 +00:00
sdk-typescript test(e2e): make fake OpenAI reachable from Docker sandbox (#6302) 2026-07-04 11:06:53 +00:00
terminal-bench Terminal Bench Integration Test (#521) 2025-09-05 17:02:03 +08:00
terminal-capture fix(cli): bound the live streaming-table pending height (fix scroll-to-top lock, stall-then-dump, header flash) (#6421) 2026-07-08 07:33:55 +00:00
channel-plugin.test.ts docs(channels): add plugin developer guide and rename mock to plugin-example 2026-03-27 03:19:34 +00:00
fake-openai-server.test.ts test(e2e): make fake OpenAI reachable from Docker sandbox (#6302) 2026-07-04 11:06:53 +00:00
fake-openai-server.ts test(e2e): make fake OpenAI reachable from Docker sandbox (#6302) 2026-07-04 11:06:53 +00:00
globalSetup.ts feat(core): support QWEN_HOME env var to customize config directory (#2953) 2026-05-09 15:51:52 +08:00
test-helper.ts test(integration): harden flaky sleep-interception e2e against skipped tool calls (#4936) 2026-06-11 00:08:26 +08:00
test-mcp-server.ts # 🚀 Sync Gemini CLI v0.2.1 - Major Feature Update (#483) 2025-09-01 14:48:55 +08:00
tsconfig.json refactor(serve): 1 daemon = 1 workspace (#3803 §02) (#4113) 2026-05-15 12:44:36 +08:00
vitest.config.ts feat(daemon): merge daemon-mode feature batch into main (#4490) 2026-06-12 00:34:49 +08:00
vitest.loadtest.config.ts feat(daemon): merge daemon-mode feature batch into main (#4490) 2026-06-12 00:34:49 +08:00
vitest.terminal-bench.config.ts Fix E2E caused by Terminal Bench test (#529) 2025-09-08 10:51:14 +08:00