Force redraw on reconnect and speed up screenshots
- Send Ctrl+L and resize on reconnect to avoid black screens - Increase replay buffer to 256KB - Add get_screen_has_changes for non-destructive dirty checks - Tighten screenshot cache TTLs and SSE debounce - Update tests for new behavior and timings
This commit is contained in:
@@ -33,6 +33,8 @@ DEFAULT_TERMINAL_SIZE = (132, 45)
|
|||||||
|
|
||||||
SCREENSHOT_CACHE_SECONDS = 0.3
|
SCREENSHOT_CACHE_SECONDS = 0.3
|
||||||
SCREENSHOT_MAX_CACHE_SECONDS = 20.0
|
SCREENSHOT_MAX_CACHE_SECONDS = 20.0
|
||||||
|
CLEAR_AND_REDRAW_SEQ = "\x0c" # Ctrl+L: clear and redraw
|
||||||
|
|
||||||
|
|
||||||
WEBTERM_STATIC_PATH = Path(__file__).parent / "static"
|
WEBTERM_STATIC_PATH = Path(__file__).parent / "static"
|
||||||
|
|
||||||
@@ -369,6 +371,12 @@ class LocalServer:
|
|||||||
if session is None or not session.is_running():
|
if session is None or not session.is_running():
|
||||||
self.session_manager.on_session_end(session_id)
|
self.session_manager.on_session_end(session_id)
|
||||||
session_id = None
|
session_id = None
|
||||||
|
else:
|
||||||
|
# Force terminal redraw on reconnect to avoid blank screen
|
||||||
|
if hasattr(session, 'force_redraw'):
|
||||||
|
await session.force_redraw()
|
||||||
|
if hasattr(session, 'send_bytes'):
|
||||||
|
await session.send_bytes(CLEAR_AND_REDRAW_SEQ.encode('utf-8'))
|
||||||
|
|
||||||
session_created = session_id is not None
|
session_created = session_id is not None
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ if TYPE_CHECKING:
|
|||||||
log = logging.getLogger("textual-web")
|
log = logging.getLogger("textual-web")
|
||||||
|
|
||||||
# Maximum bytes to keep in replay buffer for reconnection
|
# Maximum bytes to keep in replay buffer for reconnection
|
||||||
REPLAY_BUFFER_SIZE = 64 * 1024 # 64KB
|
REPLAY_BUFFER_SIZE = 256 * 1024 # 256KB
|
||||||
|
|
||||||
# Default screen size for pyte emulator
|
# Default screen size for pyte emulator
|
||||||
DEFAULT_SCREEN_WIDTH = 132
|
DEFAULT_SCREEN_WIDTH = 132
|
||||||
|
|||||||
@@ -43,6 +43,32 @@ async def test_websocket_creates_session_on_resize(tmp_path):
|
|||||||
await client.close()
|
await client.close()
|
||||||
|
|
||||||
assert created["args"] == ("test", 90, 25)
|
assert created["args"] == ("test", 90, 25)
|
||||||
|
# Reconnect should trigger redraw without creating a new session
|
||||||
|
called = {"redraw": 0, "stdin": 0}
|
||||||
|
|
||||||
|
class DummySession:
|
||||||
|
def is_running(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def force_redraw(self):
|
||||||
|
called["redraw"] += 1
|
||||||
|
|
||||||
|
async def send_bytes(self, data: bytes):
|
||||||
|
called["stdin"] += 1
|
||||||
|
|
||||||
|
server.session_manager.routes["test"] = "sid"
|
||||||
|
server.session_manager.sessions["sid"] = DummySession()
|
||||||
|
|
||||||
|
client = await _make_client(server)
|
||||||
|
try:
|
||||||
|
ws = await client.ws_connect("/ws/test")
|
||||||
|
await ws.close()
|
||||||
|
finally:
|
||||||
|
await client.close()
|
||||||
|
|
||||||
|
assert called["redraw"] == 1
|
||||||
|
assert called["stdin"] == 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class TestTerminalSession:
|
|||||||
"""Test replay buffer size constant."""
|
"""Test replay buffer size constant."""
|
||||||
from textual_webterm.terminal_session import REPLAY_BUFFER_SIZE
|
from textual_webterm.terminal_session import REPLAY_BUFFER_SIZE
|
||||||
|
|
||||||
assert REPLAY_BUFFER_SIZE == 64 * 1024 # 64KB
|
assert REPLAY_BUFFER_SIZE == 256 * 1024 # 64KB
|
||||||
|
|
||||||
def test_init(self):
|
def test_init(self):
|
||||||
"""Test TerminalSession initialization."""
|
"""Test TerminalSession initialization."""
|
||||||
|
|||||||
Reference in New Issue
Block a user