#!/usr/bin/env python3 """ Manual visual report: terminal caret blink + single-character typing visibility. This generates a self-contained HTML report (base64-embedded PNGs) so you can open it locally and visually confirm: 1) The caret is blinking (or not). 2) A single typed character appears immediately (before Enter / focus toggle). Usage: python3 tests/test_terminal_input_render_report.py # Then open: tests/terminal_input_report.html Environment: CMUX_SOCKET or CMUX_SOCKET_PATH can override the socket path. """ import base64 import json import os import sys import time from dataclasses import dataclass from datetime import datetime from pathlib import Path from typing import Optional sys.path.insert(0, str(Path(__file__).parent)) from cmux import cmux, cmuxError SOCKET_PATH = os.environ.get("CMUX_SOCKET") or os.environ.get("CMUX_SOCKET_PATH") or "/tmp/cmux-debug.sock" HTML_REPORT = Path(__file__).parent / "terminal_input_report.html" @dataclass class Shot: path: Path label: str changed_pixels: int def to_base64(self) -> str: return base64.b64encode(self.path.read_bytes()).decode("utf-8") def _wait_for(pred, timeout_s: float, step_s: float = 0.05) -> None: start = time.time() while time.time() - start < timeout_s: if pred(): return time.sleep(step_s) raise cmuxError("Timed out waiting for condition") def _focused_panel_id(c: cmux) -> str: surfaces = c.list_surfaces() if not surfaces: raise cmuxError("Expected at least 1 surface") return next((sid for _i, sid, focused in surfaces if focused), surfaces[0][1]) def _snap_panel(c: cmux, panel_id: str, label: str) -> Shot: info = c.panel_snapshot(panel_id, label) return Shot( path=Path(info["path"]), label=label, changed_pixels=int(info["changed_pixels"]), ) def _panel_sequence_blink_and_type(c: cmux, panel_id: str, prefix: str, typed_char: str = "x") -> tuple[list[Shot], dict]: shots: list[Shot] = [] # Keep the app key/active while we probe focus + rendering; on a host machine the # terminal running this script can steal focus mid-sequence. c.activate_app() time.sleep(0.15) _wait_for(lambda: c.is_terminal_focused(panel_id), timeout_s=3.0) stats0 = c.render_stats(panel_id) # Blink probe: capture a few frames over ~1.3s c.panel_snapshot_reset(panel_id) shots.append(_snap_panel(c, panel_id, f"{prefix}_blink_0")) time.sleep(0.65) shots.append(_snap_panel(c, panel_id, f"{prefix}_blink_1")) time.sleep(0.65) shots.append(_snap_panel(c, panel_id, f"{prefix}_blink_2")) # Type probe: before, after typing a single char, after Enter. c.panel_snapshot_reset(panel_id) shots.append(_snap_panel(c, panel_id, f"{prefix}_type_before")) # Use keyDown path (not insertText) to match real typing. c.simulate_shortcut(typed_char) time.sleep(0.2) shots.append(_snap_panel(c, panel_id, f"{prefix}_type_after_char_{ord(typed_char)}")) c.simulate_shortcut("enter") time.sleep(0.35) shots.append(_snap_panel(c, panel_id, f"{prefix}_type_after_enter")) # Grab stats after, for debugging. stats1 = c.render_stats(panel_id) meta = { "panel_id": panel_id, "render_stats_before": stats0, "render_stats_after": stats1, } return shots, meta def _write_report(cases: list[dict]) -> None: generated = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC") def esc(s: str) -> str: return ( s.replace("&", "&") .replace("<", "<") .replace(">", ">") .replace('"', """) ) html = f""" cmux terminal input render report

cmux terminal input render report

generated: {esc(generated)} | socket: {esc(SOCKET_PATH)}
""" for case in cases: html += f"""

{esc(case["name"])}

{esc(case["description"])}
""" for shot in case["shots"]: label = f'{shot.label} | changed_pixels={shot.changed_pixels}' html += f"""
{esc(label)}
{esc(shot.label)}
""" html += f"""
{esc(json.dumps(case.get("meta", {}), indent=2))}
""" html += """ """ HTML_REPORT.write_text(html) def main() -> int: cases: list[dict] = [] with cmux(SOCKET_PATH) as c: c.activate_app() time.sleep(0.25) # Case 1: fresh workspace, initial terminal ws_id = c.new_workspace() c.select_workspace(ws_id) time.sleep(0.35) panel0 = _focused_panel_id(c) shots0, meta0 = _panel_sequence_blink_and_type(c, panel0, "initial", typed_char="a") cases.append( { "name": "Initial Terminal (Fresh Workspace)", "description": "Caret blink probe + type a single character, then Enter.", "shots": shots0, "meta": meta0, } ) # Case 2: after split churn + new surface in a split for _ in range(4): c.new_split("right") time.sleep(0.7) new_id = c.new_surface(panel_type="terminal") time.sleep(0.5) # new_surface doesn't always steal focus (depends on split state); ensure we test the right panel. c.focus_surface(new_id) time.sleep(0.25) shots1, meta1 = _panel_sequence_blink_and_type(c, new_id, "after_splits", typed_char="b") cases.append( { "name": "After 4 Right Splits + New Surface", "description": "Repro-oriented: split churn then create a new terminal surface; verify caret + typing.", "shots": shots1, "meta": meta1, } ) _write_report(cases) print(f"Wrote report: {HTML_REPORT}") return 0 if __name__ == "__main__": raise SystemExit(main())