Skip to content

Commit 8bfd1c5

Browse files
Render HTML wiki artifacts in an iframe
Self-contained HTML artifacts (e.g. visual PR reports) were listed by the agent wiki explorer but fell through to the code branch, so they rendered as syntax-highlighted source with any scripts inert. Add `is_html` alongside the existing `is_markdown` / `is_image` flags and dispatch it to `components.v1.html`, which renders in a sandboxed iframe: the artifact's own stylesheet cannot leak into the app chrome, and its scripts still run. A "View source" expander keeps the old behaviour available. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent c432d4b commit 8bfd1c5

3 files changed

Lines changed: 49 additions & 1 deletion

File tree

app/agent_wiki_explorer.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import streamlit as st
4+
import streamlit.components.v1 as components
45

56
from app.utils.agent_wiki import (
67
WikiDocument,
@@ -17,6 +18,10 @@
1718

1819
st.set_page_config(page_title="Agent wiki explorer", page_icon="📚")
1920

21+
# Viewport height for embedded HTML artifacts. Tall enough that a report's first
22+
# screen is visible without scrolling the iframe; the iframe scrolls beyond that.
23+
HTML_ASSET_IFRAME_HEIGHT = 900
24+
2025
# Map extensions of non-markdown text documents to a syntax-highlighting language
2126
# so artifacts like `repro_app.py` render inline as code.
2227
CODE_LANGUAGE_BY_EXTENSION = {
@@ -30,7 +35,6 @@
3035
".js": "javascript",
3136
".ts": "typescript",
3237
".css": "css",
33-
".html": "html",
3438
".sql": "sql",
3539
}
3640

@@ -80,6 +84,22 @@ def _render_code_asset(document: WikiDocument, language: str) -> None:
8084
st.code(document_text, language=language)
8185

8286

87+
def _render_html_asset(document: WikiDocument) -> None:
88+
document_text, document_error = fetch_wiki_document_text(document["path"])
89+
if document_error:
90+
st.error(document_error)
91+
return
92+
if document_text is None:
93+
st.warning("The selected asset could not be loaded.")
94+
return
95+
st.caption(document["path"])
96+
# A sandboxed iframe, so the artifact's own stylesheet cannot leak into the
97+
# app chrome and its scripts (e.g. an interactive quiz) still run.
98+
components.html(document_text, height=HTML_ASSET_IFRAME_HEIGHT, scrolling=True)
99+
with st.expander("View source"):
100+
st.code(document_text, language="html")
101+
102+
83103
def _render_other_asset() -> None:
84104
st.info("This asset is not rendered inline.")
85105

@@ -132,6 +152,8 @@ def _render_other_asset() -> None:
132152
_render_markdown_document(selected_document, document_text)
133153
elif selected_document["is_image"]:
134154
_render_image_asset(selected_document)
155+
elif selected_document["is_html"]:
156+
_render_html_asset(selected_document)
135157
elif selected_document["extension"] in CODE_LANGUAGE_BY_EXTENSION:
136158
_render_code_asset(selected_document, CODE_LANGUAGE_BY_EXTENSION[selected_document["extension"]])
137159
else:

app/utils/agent_wiki.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
TEXT_DOCUMENT_EXTENSIONS = {".md", ".markdown", ".mdx", ".txt"}
3232
MARKDOWN_EXTENSIONS = {".md", ".markdown", ".mdx"}
3333
IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"}
34+
# Self-contained HTML artifacts (e.g. visual PR reports). Rendered in a sandboxed
35+
# iframe rather than as source, so their own styling and scripts stay intact.
36+
HTML_EXTENSIONS = {".html", ".htm"}
3437

3538
ISSUES_SECTION = "issues"
3639
# Canonical filenames the agent wiki uses for runnable issue reproductions.
@@ -53,6 +56,7 @@ class WikiDocument(TypedDict):
5356
source_url: str
5457
is_markdown: bool
5558
is_image: bool
59+
is_html: bool
5660

5761

5862
class WikiIssueRepro(TypedDict):
@@ -149,6 +153,10 @@ def is_image_path(path: str) -> bool:
149153
return _get_extension(path) in IMAGE_EXTENSIONS
150154

151155

156+
def is_html_path(path: str) -> bool:
157+
return _get_extension(path) in HTML_EXTENSIONS
158+
159+
152160
def build_wiki_raw_url(path: str) -> str:
153161
return f"{WIKI_RAW_URL_PREFIX}/{quote(path, safe='/')}"
154162

@@ -204,6 +212,7 @@ def build_wiki_documents(paths: list[str]) -> list[WikiDocument]:
204212
"source_url": build_wiki_source_url(path),
205213
"is_markdown": is_markdown_path(path),
206214
"is_image": is_image_path(path),
215+
"is_html": is_html_path(path),
207216
}
208217
)
209218

tests/test_agent_wiki.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,23 @@ def test_build_wiki_documents_includes_issue_artifacts() -> None:
4848
assert documents[1]["folder"] == "issues/12345"
4949

5050

51+
def test_build_wiki_documents_flags_html_artifacts_as_html() -> None:
52+
documents = build_wiki_documents(
53+
[
54+
"pull-requests/12345/report.html",
55+
"pull-requests/12345/notes.txt",
56+
]
57+
)
58+
59+
report = documents[1]
60+
assert report["path"] == "pull-requests/12345/report.html"
61+
assert report["is_html"] is True
62+
assert report["is_markdown"] is False
63+
assert report["is_image"] is False
64+
# Plain text artifacts keep rendering as code, not in an iframe.
65+
assert documents[0]["is_html"] is False
66+
67+
5168
def test_get_wiki_folder_groups_issue_artifacts_by_issue_number() -> None:
5269
assert get_wiki_folder("issues/12345/repro_app.py") == "issues/12345"
5370
assert get_wiki_folder("issues/12345/nested/notes.md") == "issues/12345"

0 commit comments

Comments
 (0)