"""Turn untrusted API JSON (via view_model) into RTL HTML. Escape first; only our tags are added.
Layout follows the 7881 debug playground: status, answer, claims table (with a verified
column), episode triage table, warnings. Styling follows the 2026-09-28 Space redesign.
"""
from __future__ import annotations
import html
import re
from urllib.parse import urlsplit
from textproc import DisplayOpts, highlight_terms, strip_fffd, trim_at_word_boundary
from view_model import EpisodeView, ViewData
_BULLET = re.compile(r"^\s*[-*\u2022]\s+(.*)$")
_NUMBERED = re.compile(r"^\s*\d+\.\s+(.*)$")
_BOLD = re.compile(r"\*\*(.+?)\*\*|__(.+?)__")
_ITALIC = re.compile(
r"(? str:
return html.escape(str(text or ""), quote=True)
def render_notice(message: str, css_class: str = "notice") -> str:
return f'
',
f'
'
f'{_e(view["label_status"])} {_e(status_text)}
']
answer_html = markdown_to_safe_html(data.answer_markdown or "") or f"
{_e(view['msg_refusal'])}
"
parts.append(f'
{_e(view["label_answer"])}
'
f'{answer_html}
')
if data.claims:
h = view["claims_headers"]
rows = []
for c in data.claims:
quote = trim_at_word_boundary(strip_fffd(c.quote), opts.max_quote_chars, opts.ellipsis)
quote_html = f'
{highlight_terms(quote, question, opts)}
'
if c.verified:
check = f'
✓ {_e(view["verified_badge"])}'
else:
check = f'
{_e(view["unverified_badge"])}'
rows.append("
"
+ _cell(h[0], _e(c.text), "claim-text")
+ _cell(h[1], quote_html, "claim-quote")
+ _cell(h[2], check, "claim-verified")
+ _cell(h[3], episode_link(c.episode, view), "col-episode")
+ _cell(h[4], _date(c.episode), "col-date")
+ _cell(h[5], f'{_e(c.timestamp)}', "col-time")
+ "
")
parts.append(_table("claims", view["label_claims"], h, rows))
if data.triage:
h = view["triage_headers"]
rows = []
for t in data.triage:
label = view["triage_labels"].get(t.label, t.label)
rows.append("
" + _cell(h[0], episode_link(t.episode, view), "col-episode") + _cell(h[1], _date(t.episode), "col-date")
+ _cell(h[2], f'{_e(label)}', "col-label")
+ _cell(h[3], _e(t.reason), "triage-reason") + "
")
parts.append(_table("triage", view["label_triage"], h, rows))
if data.warnings:
items = "".join(f'
{_e(w)}' for w in data.warnings)
parts.append(f'
{_e(view["label_warnings"])}
'
f'{_e(view["warnings_note"])}
')
parts.append("
")
return "".join(parts)