#!/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"""
{esc(json.dumps(case.get("meta", {}), indent=2))}