"""Streaming display force-flush: long partial lines must paint before the first newline arrives (TTFT-perception fix, July 2026). Previously ``_emit_stream_text`` only emitted on ``"\\n"``, so a response opening with a long paragraph stayed invisible until the model produced a newline — seconds of blank box on slow models. Now partial lines are force-flushed at terminal width (mirroring the reasoning box's 80-char rule). """ import os import re import sys import pytest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..")) def _strip_ansi(s: str) -> str: return re.sub(r"\x1b\[[0-9;]*m", "", s) @pytest.fixture def cli_stub(monkeypatch): from cli import HermesCLI import cli as climod cli = HermesCLI.__new__(HermesCLI) cli.show_reasoning = False cli.final_response_markdown = "raw" cli.show_timestamps = False cli._reset_stream_state() emitted = [] monkeypatch.setattr(climod, "_cprint", lambda s: emitted.append(s)) # Deterministic width regardless of the test runner's terminal monkeypatch.setattr(climod, "_terminal_width_for_streaming", lambda: 74) return cli, emitted class TestPartialLineForceFlush: def test_long_paragraph_paints_before_first_newline(self, cli_stub): cli, emitted = cli_stub text = ( "This is a long opening paragraph that would previously sit " "invisible in the buffer until the model finally produced a " "newline character, which on a slow model could take seconds. " ) * 3 for i in range(0, len(text), 12): cli._stream_delta(text[i : i + 12]) # Box header + several wrapped lines painted with NO newline seen yet assert len(emitted) > 3 def test_no_content_lost_across_wraps(self, cli_stub): cli, emitted = cli_stub words = [f"word{i}" for i in range(120)] text = " ".join(words) for i in range(0, len(text), 7): cli._stream_delta(text[i : i + 7]) cli._flush_stream() plain = " ".join(_strip_ansi("\n".join(emitted)).split()) for w in words: assert w in plain, f"lost {w} at a wrap boundary" def test_short_partial_stays_buffered(self, cli_stub): cli, emitted = cli_stub cli._stream_delta("short line, no newline") # Under wrap width: the box header may open, but the text itself # stays buffered until a newline or the width threshold. plain = _strip_ansi("\n".join(emitted)) assert "short line" not in plain assert cli._stream_buf == "short line, no newline" def test_table_rows_not_force_flushed(self, cli_stub): cli, emitted = cli_stub # A long partial table row must stay buffered for block realignment row = "| " + " | ".join(f"cell{i}" for i in range(20)) + " |" cli._stream_delta(row) # no newline plain = _strip_ansi("\n".join(emitted)) assert "cell19" not in plain def test_newline_lines_still_emit_normally(self, cli_stub): cli, emitted = cli_stub cli._stream_delta("line one\nline two\n") plain = _strip_ansi("\n".join(emitted)) assert "line one" in plain assert "line two" in plain def test_unbreakable_run_hard_wraps(self, cli_stub): cli, emitted = cli_stub blob = "x" * 300 # no spaces cli._stream_delta(blob) cli._flush_stream() plain = _strip_ansi("\n".join(emitted)) assert plain.count("x") == 300