Skip to content

Commit b8eec2c

Browse files
committed
test(tui): tighten markdown review guards
1 parent 70bf376 commit b8eec2c

5 files changed

Lines changed: 28 additions & 21 deletions

File tree

docs/superpowers/plans/2026-05-29-tui-markdown-report-contract-hardening.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
**Architecture:** Lead phase of `docs/superpowers/specs/2026-05-29-tui-renderer-contract-hardening-design.md`. We do **not** build a renderer. We add Tier-1 contract tests (capture + assertion) under `tests/ui_and_conv/`, characterize the existing regex repair pipeline (pin, don't refactor), ground report tests in the real 92-finding fixture, and apply exactly one source fix (AST-based report-fence extraction in `report.py`) gated by a failing test.
88

9-
**Tech Stack:** Python 3.12+, Rich 15, prompt_toolkit 3, markdown-it-py, pytest (`asyncio_mode = auto`), `uv`. Run tests with `uv run pytest …` (fallback: `.venv/bin/python -m pytest …`).
9+
**Tech Stack:** Python 3.12+, Rich 15, prompt_toolkit 3, markdown-it-py, pytest (`asyncio_mode = auto`), `uv`. Run tests with `uv run pytest …`.
1010

1111
**Spec reference:** `docs/superpowers/specs/2026-05-29-tui-renderer-contract-hardening-design.md` §6–§8.
1212

@@ -748,14 +748,16 @@ def render_agent_body(text: str, *, theme: ThemeName | None = None) -> Renderabl
748748
report block renders via :func:`render_report`; an invalid or nested block is
749749
left in place so the surrounding markdown shows it as an ordinary code block.
750750
"""
751-
lines = text.splitlines(keepends=True)
751+
# Split on "\n" only: markdown-it's token.map counts only "\n", so
752+
# splitlines() can desync fence delimiter indices on other line separators.
753+
lines = text.split("\n")
752754
segments: list[RenderableType] = []
753755
cursor = 0 # line index
754756
for start, end, payload in _iter_report_payloads(text):
755757
report = parse_report_block(payload)
756758
if report is None:
757759
continue # malformed — leave it for the markdown renderer
758-
before = "".join(lines[cursor:start]).strip("\n")
760+
before = "\n".join(lines[cursor:start]).strip("\n")
759761
if before:
760762
segments.append(pythinker_markdown(before))
761763
segments.append(render_report(report, theme=theme))
@@ -764,7 +766,7 @@ def render_agent_body(text: str, *, theme: ThemeName | None = None) -> Renderabl
764766
if not segments:
765767
return pythinker_markdown(text)
766768

767-
rest = "".join(lines[cursor:]).strip("\n")
769+
rest = "\n".join(lines[cursor:]).strip("\n")
768770
if rest:
769771
segments.append(pythinker_markdown(rest))
770772

tests/ui_and_conv/test_md_color_contract.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,14 @@ def test_code_block_border_does_not_use_inline_code_color():
3535
# The rounded frame characters must not carry the inline-code foreground.
3636
inline_fg = _sgr_fg(colors.inline_code)
3737
for frame_char in ("╭", "╰", "─"):
38-
idx = coloured.find(frame_char)
39-
if idx == -1:
40-
continue
41-
window = coloured[max(0, idx - 24) : idx]
42-
assert inline_fg not in window, "border frame inherited inline-code color"
38+
found_count = 0
39+
start = 0
40+
while True:
41+
idx = coloured.find(frame_char, start)
42+
if idx == -1:
43+
break
44+
found_count += 1
45+
window = coloured[max(0, idx - 24) : idx]
46+
assert inline_fg not in window, "border frame inherited inline-code color"
47+
start = idx + 1
48+
assert found_count > 0, f"missing expected frame glyph {frame_char!r}"

tests/ui_and_conv/test_md_render_authority.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ def test_no_direct_terminal_writes_in_renderer(filename):
2828
func = node.func
2929
if isinstance(func, ast.Name) and func.id == "print":
3030
offenders.append(f"print() at line {node.lineno}")
31-
if (
32-
isinstance(func, ast.Attribute)
33-
and func.attr == "write"
34-
and isinstance(func.value, ast.Attribute)
35-
and func.value.attr in {"stdout", "stderr"}
36-
):
37-
offenders.append(f"std*.write at line {node.lineno}")
31+
if isinstance(func, ast.Attribute) and func.attr == "write":
32+
target = func.value
33+
is_std_stream = (
34+
isinstance(target, ast.Attribute) and target.attr in {"stdout", "stderr"}
35+
) or (isinstance(target, ast.Name) and target.id in {"stdout", "stderr"})
36+
if is_std_stream:
37+
offenders.append(f"std*.write at line {node.lineno}")
3838
assert not offenders, f"{filename} bypasses the screen model: {offenders}"

tests/ui_and_conv/test_md_stream_idempotency.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,10 @@ def test_streaming_table_is_not_committed_mid_row():
3232
# stream character-by-character to maximize the chance of a mid-table commit
3333
committed = _drain(list(full))
3434
# No committed slice may end in the middle of the table (i.e. contain the
35-
# delimiter row but not the closing blank line + following block).
35+
# delimiter row but not the data row that completes this fixture's table).
3636
for slice_ in committed[:-1]:
3737
if "---" in slice_:
38-
assert slice_.rstrip().endswith("|") is False or "After" in "".join(committed), (
39-
"a partial table row was committed before the table closed"
40-
)
38+
assert "| 1 | 2 |" in slice_, "a header-only table was committed before data arrived"
4139
# Reassembled stream equals the original (no loss, no duplication).
4240
assert "".join(committed) == full
4341

tests/ui_and_conv/test_md_table_contract.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ def test_table_with_escaped_pipes_keeps_literal_pipe():
3737
"""Bug class: escaped pipe must render as a literal '|', not split a cell."""
3838
md = "| Col |\n| --- |\n| a \\| b |\n"
3939
out = render_plain(pythinker_markdown(md), width=80)
40-
assert "a | b" in out or "a \\| b" not in out # literal pipe preserved
40+
assert "a | b" in out # literal pipe preserved
41+
assert "a \\| b" not in out
4142
assert "Col" in out
4243

4344

0 commit comments

Comments
 (0)