Render HTML wiki artifacts in an iframe - #87
Conversation
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>
|
This head is reviewer-green and ready to read. An independent review by State: open draft at Waiting on you: nothing except the read-and-decide. It is still a draft and I will not undraft or merge it — those are yours. I keep watching for new commits, checks, and comments. |
Why
The agent wiki explorer lists every file it finds in the
streamlit/streamlit.wikiclone —fetch_wiki_documents()walks the repo withrglob("*")and filters nothing by extension. So self-contained HTML artifacts, like the visual PR reports theexplaining-pull-requestsskill generates, have always shown up in the file picker. They just fell through to the generic code branch and rendered as syntax-highlighted source. A report meant to be read as a page arrived as a wall of markup, and anything it scripted — an interactive quiz, tab switching — was inert. Linking someone to a report was pointless: they got the source of the report instead of the report.What / How
Three code files, +49/−1 across 6 changed files (the other three are PNGs, see below).
app/utils/agent_wiki.py(+9) adds the classification, following the shapeis_markdown/is_imagealready use: anHTML_EXTENSIONS = {".html", ".htm"}set, anis_html_path()predicate keyed off the existing lowercased-extension helper, and anis_htmlfield on theWikiDocumentTypedDict populated inbuild_wiki_documents(). Nothing else consumes the new field, andis_supported_document_path()is untouched — this PR does not change which files get listed, only how one kind of them renders.app/agent_wiki_explorer.py(+23/−1) does the rendering..htmlis removed fromCODE_LANGUAGE_BY_EXTENSION, and a new_render_html_asset()branch is inserted into the dispatch chain before the code branch — order matters, because leaving.htmlin that map would have kept the old behaviour winning. The renderer fetches the artifact through the samefetch_wiki_document_text()path the code branch uses, so error and empty-body handling stay consistent with its neighbours, then hands the text tostreamlit.components.v1.html(...)at a fixedHTML_ASSET_IFRAME_HEIGHT = 900.The choice worth understanding is
components.htmloverst.htmlorst.markdown(unsafe_allow_html=True): it renders into a sandboxed iframe. That buys two things at once — the artifact's own stylesheet cannot leak out into the app chrome, and its<script>tags still execute, which is what makes an embedded quiz work at all. The cost is a fixed-height viewport, since an iframe cannot size itself to its content from the outside; 900px is a deliberate pick so a report's first screen is visible, withscrolling=Truehandling the rest. A "View source" expander below the frame keeps the old syntax-highlighted view one click away rather than removing it.tests/test_agent_wiki.py(+17) covers the classification as a pure function: areport.htmlpath is flaggedis_htmland not markdown or image, and a siblingnotes.txtin the same folder is not flagged — the assertion that guards against the new branch swallowing plain-text artifacts. The rendering itself is not unit-tested;components.htmlneeds a running script context, so that behaviour is covered by the manual pass below instead.Also on the branch: three PNGs the author committed under
.github/pr-assets/, showing the same artifact before and after. They are linked below as raw URLs against thefeature/render-html-wiki-artifactsbranch ref, so those links will break once the branch is deleted after merge.main)Verified
I booted this branch's code and drove it. Preview at
http://localhost:4310/, all observations ate795ac5:/agent_wiki_explorer?file=pull-requests/16385/report.html, the one real.htmlfile currently in the live wiki (1 of 129 documents). One<iframe title="st.iframe">on the page with a 35,751-charactersrcdocandoffsetHeightexactly 900. Inside it:document.title= "PR 16385 — text_input types", an<h1>, and 2 scripts. The report renders on its own white card while the surrounding app is in dark mode — visible proof the sandbox is holding its styling in and the app's out.qa1–qa4) and clicked Submit inside the frame. It graded live:.scoreread 3 / 4, the stamp flipped to "Reread and retry", and 5 option rows picked up correctness classes with per-answer explanations rendered. This is the behaviour the old source-rendering made impossible.st.codeblock beginning<!DOCTYPE html> <html lang="en">, syntax-highlighted./agent_wiki_explorer?file=issues/12065/repro_app.py: zero iframes on the page, still rendered as code.Separately, the branch author reports rendering the report at 380px as well as 900px and taking the quiz to a full CLEARED, and reports the same end-to-end pass through a local explorer. I confirmed the 900px case and the grading mechanism; I did not exercise a narrow viewport, and the 380px claim is theirs, not mine.
Deviations and open risks
The author's notes for this branch are mostly about the sibling change that produces these reports — a proto-field-vs-enum-value detection widening, an
e2e_appsregex exclusion,finalize.pyword budgets, three helper scripts. None of that appears in this diff, so I dropped all of it rather than repeat it here under the author's authority. What survived reconciliation is the dependency direction: this PR is the consumer, and it is what makes a report link in the sibling PR resolve to a rendered report instead of source.Two things genuinely left open, both visible in the diff:
.github/pr-assets/screenshot links are branch-relative raw URLs and will 404 after the branch is deleted.Testing
make check— clean: ruff lint passed,ruff format --checkreported 65 files already formatted,typassed, mypy found no issues in 57 source files.uv run pytest— 26 passed, including the 8 intests/test_agent_wiki.py.checkjob — SUCCESS one795ac5; PR isMERGEABLE/CLEANagainstmain..htmextension specifically (the classification set covers it, but no.htmfile exists in the live wiki to open).