From bdbf02a0c4ef3b9b9f9c1f335af9f2b2108f8e74 Mon Sep 17 00:00:00 2001 From: "Aryan Singh K." <70511529+aryansk@users.noreply.github.com> Date: Mon, 24 Aug 2026 22:52:12 +0530 Subject: [PATCH 1/2] perf: reduce live Markdown re-render churn while streaming Fixes #49 RichUI re-rendered streaming Markdown every 80 new characters under rich.live.Live, causing hundreds of full-screen re-renders for long responses (CPU-heavy, visible lag, brutal for vhs demo). Increase _TRUNCATE_STEP from 80 to 160 and throttle live updates to max ~10 fps (0.10s min interval via time.monotonic). Keeps partial output visible while streaming but halves re-renders and caps FPS. Validation: py_compile passes, git diff --check clean; manual check shows long response streams with ~50% fewer Live updates and no visual regression in partial-token display. --- gcode/ui.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/gcode/ui.py b/gcode/ui.py index ab3e783..6c970d7 100644 --- a/gcode/ui.py +++ b/gcode/ui.py @@ -5,6 +5,8 @@ display, a permission gate, and status/error output. """ +import time + import questionary from prompt_toolkit import PromptSession from prompt_toolkit.formatted_text import HTML @@ -16,7 +18,8 @@ from rich.spinner import Spinner from rich.text import Text -_TRUNCATE_STEP = 80 # re-render Markdown only after this many new characters +_TRUNCATE_STEP = 160 # re-render Markdown only after this many new characters +_TRUNCATE_MIN_INTERVAL = 0.10 # seconds between live updates (max ~10 fps) # Slash commands available in the interactive menu _SLASH_COMMANDS = [ @@ -148,6 +151,7 @@ def prompt(self) -> str: def assistant_start(self) -> None: self._buffer = "" self._last_len = 0 + self._last_update = 0.0 self._live = Live( Spinner("dots", text="Thinking…"), console=self.console, @@ -160,9 +164,11 @@ def token(self, text: str) -> None: if self._live is None: return self._buffer += text - if len(self._buffer) - self._last_len >= _TRUNCATE_STEP: + now = time.monotonic() + if len(self._buffer) - self._last_len >= _TRUNCATE_STEP and now - self._last_update >= _TRUNCATE_MIN_INTERVAL: self._live.update(Markdown(self._buffer)) self._last_len = len(self._buffer) + self._last_update = now def assistant_end(self) -> None: if self._live is None: From e652e7c44b22d811408ba46946df90433668853c Mon Sep 17 00:00:00 2001 From: shauryagangrade <288927048+shauryagangrade@users.noreply.github.com> Date: Sat, 29 Aug 2026 22:19:29 +0530 Subject: [PATCH 2/2] perf: reduce live Markdown re-render churn while streaming: format + make interval injectable for tests --- gcode/ui.py | 5 ++++- tests/test_ui.py | 30 ++++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/gcode/ui.py b/gcode/ui.py index 6c970d7..01d9c47 100644 --- a/gcode/ui.py +++ b/gcode/ui.py @@ -165,7 +165,10 @@ def token(self, text: str) -> None: return self._buffer += text now = time.monotonic() - if len(self._buffer) - self._last_len >= _TRUNCATE_STEP and now - self._last_update >= _TRUNCATE_MIN_INTERVAL: + if ( + len(self._buffer) - self._last_len >= _TRUNCATE_STEP + and now - self._last_update >= _TRUNCATE_MIN_INTERVAL + ): self._live.update(Markdown(self._buffer)) self._last_len = len(self._buffer) self._last_update = now diff --git a/tests/test_ui.py b/tests/test_ui.py index 46e3bfc..36ff5c7 100644 --- a/tests/test_ui.py +++ b/tests/test_ui.py @@ -133,16 +133,17 @@ def test_token_renders_markdown_after_threshold(monkeypatch): fake = _FakeLive() monkeypatch.setattr(ui_module, "Live", lambda *args, **kwargs: fake) + monkeypatch.setattr(ui_module, "_TRUNCATE_MIN_INTERVAL", 0.0) ui = RichUI() ui.assistant_start() - # Below the 80-char re-render threshold: no update yet. + # Below the 160-char re-render threshold: no update yet. ui.token("x" * 40) assert fake.updates == [] # Crossing the threshold triggers one Markdown update. - ui.token("y" * 50) # total 90 >= 80 + ui.token("y" * 150) # total 190 >= 160 assert len(fake.updates) == 1 # Ending the stream renders the final text and stops the live region. @@ -151,6 +152,31 @@ def test_token_renders_markdown_after_threshold(monkeypatch): assert len(fake.updates) == 2 +def test_token_throttled_by_time_interval(monkeypatch): + from gcode import ui as ui_module + + fake = _FakeLive() + monkeypatch.setattr(ui_module, "Live", lambda *args, **kwargs: fake) + monkeypatch.setattr(ui_module, "_TRUNCATE_STEP", 1) + monkeypatch.setattr(ui_module, "_TRUNCATE_MIN_INTERVAL", 60.0) + + clock = iter([10.0, 10.05, 70.0]) + monkeypatch.setattr(ui_module.time, "monotonic", lambda: next(clock)) + + ui = RichUI() + ui.assistant_start() + + ui.token("a") # 1 char but only 10s elapsed since 0.0 -> throttled + ui.token("b") # still < 60s elapsed -> throttled + assert len(fake.updates) == 0 + + ui.token("c") # 70s elapsed -> update fires + assert len(fake.updates) == 1 + + ui.assistant_end() + assert fake.stopped + + # -- tool display ----------------------------------------------------------