feat: open artifacts in a browser view that renders Markdown cleanly (#22) - #42
Merged
Merged
Conversation
Pressing o showed the selected artifact as raw text in the terminal detail panel. Stage documents are Markdown and often long, so headings, lists, tables and code blocks were hard to read and wide lines were truncated. o now opens the artifact as a rendered page in the browser, served by the dashboard server that already exists: the same loopback-only listener, the same per-session token, the same token-to-cookie redirect the index uses. The page names the run, revision, stage and attempt the artifact came from, with its digest and size, and links the checkpoint's other artifacts so moving between plan, design and test results needs no terminal. The terminal preview stays as the fallback where no browser can be opened, and says why. Nothing is fetched from the network. Markdown is rendered in Go with goldmark, already a dependency, configured without raw HTML because artifacts are agent-written; dangerous link targets are dropped. The syntax highlighter is a small built-in lexer that escapes every token rather than a highlighter downloaded from a CDN, which the dashboard's script-src 'self' policy would refuse anyway. Screenshots are marked template.URL. html/template rejects a data: URI in a src attribute and substitutes #ZgotmplZ, which rendered every screenshot broken; the URIs are safe to mark because this package decodes the base64 itself and confirms the PNG magic number before building one. Refs #22 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
sam-bretz
marked this pull request as ready for review
September 16, 2026 15:29
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #22.
What changed
onow opens the selected artifact as a rendered page in the browser instead of raw text in the terminal panel.,/.still choose which artifact.envctl run artifact <digest> --openis the CLI equivalent.It reuses the existing dashboard server rather than standing up a second one: same loopback-only listener, same per-session token, same token→cookie→redirect the index already does (which the TUI needs, since it opens a fresh browser with
?token=).Acceptance criteria
oopens the selected artifact in the default browserscreenshot_pngas imagesrun artifactenvctl run artifact <digest> --openSecurity notes
Artifacts are agent-written, so this is the untrusted-input surface:
go.mod, no new dependency) configured withoutWithUnsafe, so raw HTML is escaped, not rendered; dangerous link schemes are dropped. Tested:<script>alert(1)</script>and[bad](javascript:alert(1))both neutralised whilehttps://links and tables survive.template.HTMLescape hatch is fed only by goldmark output — the text and JSON paths go through{{.Content.Text}}, whichhtml/templateescapes normally.script-src 'self'would have refused a CDN anyway.One bug I fixed on top of the worker's diff
html/templaterewrites adata:URI in asrcattribute to#ZgotmplZ, so every screenshot rendered broken — the exact placeholder behaviour this issue set out to remove. The worker's tests checked that screenshots were extracted, not that they survived templating, so it passed.Screenshots are now typed
template.URL, which is safe here because this package decodes the base64 itself and verifies the PNG magic number before building the URI. AddedTestAScreenshotReachesThePageAsAnImageNotAPlaceholder, which asserts on the rendered page; I confirmed it fails without the fix and passes with it.Testing
gofmt -l .silent,go vet ./...clean, fullgo test ./...passes (the worker's own run was blocked by its sandbox's port restrictions, not by the code — it passes unsandboxed).Not verified: I did not open the page in a real browser. Rendering is asserted at the HTML level, so visual layout and the actual
open/xdg-openhandoff are worth one manual check before merge.🤖 Generated with Claude Code