Skip to content

feat: open artifacts in a browser view that renders Markdown cleanly (#22) - #42

Merged
sam-bretz merged 1 commit into
mainfrom
issue-22-artifact-browser
Sep 16, 2026
Merged

sam-bretz merged 1 commit into
mainfrom
issue-22-artifact-browser

Conversation

@sam-bretz

Copy link
Copy Markdown
Owner

Closes #22.

What changed

o now 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> --open is 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

  • o opens the selected artifact in the default browser
  • Markdown (headings, lists, tables, links, fenced code + highlighting), JSON pretty-printed, plain text monospace, base64 screenshot_png as images
  • Page shows name, run, revision, stage, attempt, digest, size
  • Links the checkpoint's other artifacts
  • Raw HTML escaped; nothing loaded from the network; 127.0.0.1 + per-session token
  • Checksum-verified retained bytes, same as run artifact
  • Light/dark system preference
  • Fallback to the terminal preview with a reason
  • envctl run artifact <digest> --open
  • Docs — first-workflow key table and CLI reference

Security notes

Artifacts are agent-written, so this is the untrusted-input surface:

  • goldmark (already in go.mod, no new dependency) configured without WithUnsafe, so raw HTML is escaped, not rendered; dangerous link schemes are dropped. Tested: <script>alert(1)</script> and [bad](javascript:alert(1)) both neutralised while https:// links and tables survive.
  • The template.HTML escape hatch is fed only by goldmark output — the text and JSON paths go through {{.Content.Text}}, which html/template escapes normally.
  • No CDN, no web font. Markdown renders in Go; the highlighter is a small built-in lexer that escapes every token. The dashboard's script-src 'self' would have refused a CDN anyway.

One bug I fixed on top of the worker's diff

html/template rewrites a data: URI in a src attribute 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. Added TestAScreenshotReachesThePageAsAnImageNotAPlaceholder, which asserts on the rendered page; I confirmed it fails without the fix and passes with it.

Testing

gofmt -l . silent, go vet ./... clean, full go 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-open handoff are worth one manual check before merge.

🤖 Generated with Claude Code

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Open artifacts in a browser view that renders Markdown cleanly

1 participant