diff --git a/hermes_cli/journey.py b/hermes_cli/journey.py index 1e404baa224d..ae07ec32e826 100644 --- a/hermes_cli/journey.py +++ b/hermes_cli/journey.py @@ -206,7 +206,10 @@ def _cmd_show(args: argparse.Namespace) -> int: if getattr(args, "play", False): return _play(console, payload, cols=cols, rows=rows, color=color, fps=getattr(args, "fps", 12)) - reveal = _clamp(float(getattr(args, "reveal", 1.0) or 1.0), 0.0, 1.0) + # NOT ``or 1.0``: an explicit ``--reveal 0`` ("0=oldest" per the help + # text) is falsy and would silently render the fully-revealed frame. + reveal_arg = getattr(args, "reveal", None) + reveal = _clamp(1.0 if reveal_arg is None else float(reveal_arg), 0.0, 1.0) console.print(_frame_renderable(payload, cols=cols, rows=rows, reveal=reveal, color=color)) return 0 diff --git a/tests/hermes_cli/test_journey_render.py b/tests/hermes_cli/test_journey_render.py index f456c3825218..2bd631020e75 100644 --- a/tests/hermes_cli/test_journey_render.py +++ b/tests/hermes_cli/test_journey_render.py @@ -36,3 +36,46 @@ def test_default_capture_is_plain_for_chat_bubbles(): # Rich auto-detects the StringIO as non-tty → no color, no raw escapes. assert "\x1b[" not in _capture([], force=False) assert "\x1b[" not in _capture(["list"], force=False) + + +# --------------------------------------------------------------------------- +# --reveal parsing contract +# --------------------------------------------------------------------------- + +def _reveal_reaching_renderer(argv: list[str], monkeypatch) -> float: + """Run the real ``args.func`` path and report the ``reveal`` value that + reaches the frame renderer.""" + import hermes_cli.journey as journey + from rich.text import Text + + seen: dict[str, float] = {} + + def fake_frame(payload, *, cols, rows, reveal, color): + seen["reveal"] = reveal + return Text("frame") + + monkeypatch.setattr(journey, "_frame_renderable", fake_frame) + # A non-empty payload so _cmd_show reaches the render call. + monkeypatch.setattr(journey, "_build_payload", lambda: {"nodes": [{"kind": "skill"}]}) + + parser = argparse.ArgumentParser(add_help=False) + journey.register_cli(parser) + args = parser.parse_args(argv) + + with contextlib.redirect_stdout(io.StringIO()): + args.func(args) + return seen["reveal"] + + +def test_reveal_zero_renders_oldest_frame(monkeypatch): + # "0=oldest" per the --reveal help text; 0.0 is falsy and must not be + # swallowed into the fully-revealed default. + assert _reveal_reaching_renderer(["--reveal", "0"], monkeypatch) == 0.0 + + +def test_reveal_defaults_to_fully_revealed(monkeypatch): + assert _reveal_reaching_renderer([], monkeypatch) == 1.0 + + +def test_reveal_fraction_passes_through(monkeypatch): + assert _reveal_reaching_renderer(["--reveal", "0.25"], monkeypatch) == 0.25