# feat\(web\): render HTML and PDF files in the file viewer · PR \#9143 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/9143)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: merged

Source repository: pingdotgg/t3code

Source branch: t3code/html-preview-in-panel

Target branch: t3code/open-outside-workspace-files

Head commit: 4f990b7ec27919888b1d159f46aaba7f7a8fc23e

[Discussion and reviews](https://git.cafe/pingdotgg/t3code/pull/9143)

[Commits](https://git.cafe/pingdotgg/t3code/pull/9143/commits?format=markdown)

[Files changed](https://git.cafe/pingdotgg/t3code/pull/9143/diffs?format=markdown)

## Description

---

Stacked on #9140.

On web there was no way to see an HTML file as a page: the integrated browser is desktop-only, so the file panel always showed markup. Mobile already renders browser files in a WebView; this brings web (and the desktop panel) in line.

## How

`FilePreviewPanel` gains a `WorkspaceBrowserPreview` that renders HTML and PDF from the signed asset URL in an `<iframe>`:

- HTML defaults to the rendered page with a source toggle in the panel header, using the same control as the rendered-Markdown toggle (persisted separately, and a line reveal still forces source). PDF is always rendered and skips the text read, like images do.
- HTML frames use `sandbox="allow-scripts allow-forms allow-popups allow-modals"` (no `allow-same-origin`), so the page gets an opaque origin and cannot reach the app's storage or bearer session. #9140 now also sends the same policy as a `Content-Security-Policy: sandbox` response header, so the frame attribute is defence in depth rather than the only barrier. PDFs load unsandboxed because the built-in viewer needs it and a PDF runs no script.
- Files inside the workspace use the directory-scoped `workspace-file` URL so sibling CSS/images load; host files outside it use the exact-file `media-file` URL from #9140.
- Workspace mutations bump the frame's `workspace-revision` query, matching the image preview.

## Demo

A `/tmp` HTML chip opening as a rendered page, toggling to source and back, then a workspace `report.html` from the explorer that pulls in its sibling `report.css` and `badge.svg`:

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/c36b5d260d5fa6ec/html-preview-in-panel.webm

## Verification

- Web typecheck and lint on the changed file; flow exercised end to end in the recording above (headless Chromium against `vp run dev`).
- Not verified: PDF rendering. Headless Chromium has no PDF viewer, so the PDF branch needs a look in a real browser.

Written by Claude Fable 5 in Claude Code.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> HTML previews run user-controlled scripts inside a sandboxed iframe (no app session access), but popups/modals are allowed; PDFs use an unsandboxed iframe by design.
> 
> **Overview**
> **The file viewer can show HTML and PDF as rendered pages on web and desktop**, not only raw markup—closing the gap where the integrated browser was desktop-only.
> 
> `FilePreviewPanel` adds **`WorkspaceBrowserPreview`**, which loads signed asset URLs in an iframe and appends **`workspace-revision`** on workspace mutations (same pattern as images). Workspace HTML uses **`workspace-file`** URLs so sibling assets resolve; host paths outside the workspace use **`media-file`**. HTML defaults to rendered view with a **persisted header toggle** (shared UX with rendered Markdown); **line-reveal links still force source**. PDFs are **always rendered** and skip the text file query, truncation banner, and workspace text refresh—like other binary previews.
> 
> HTML iframes use **`sandbox="allow-scripts allow-forms allow-popups allow-modals"`** (no same-origin); PDFs load **without sandbox** for the built-in viewer. User docs in **`composer.md`** describe the toggle, isolation, and desktop “open in browser” behavior.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 4f990b7ec27919888b1d159f46aaba7f7a8fc23e. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

<!-- Macroscope's pull request summary starts here -->
<!-- Macroscope will only edit the content between these invisible markers, and the markers themselves will not be visible in the GitHub rendered markdown. -->
<!-- If you delete either of the start / end markers from your PR's description, Macroscope will append its summary at the bottom of the description. -->
> [!NOTE]
> ### Add HTML and PDF rendering to web file viewer
> - Renders HTML and PDF files in-place using `WorkspaceBrowserPreview` in [FilePreviewPanel.tsx](https://github.com/pingdotgg/t3code/pull/9143/files#diff-776fa2f4352bbbf118206cfd39f4cf95c38e47b1629aa20a6c8c028cadf458ea).
> - HTML files load in a sandboxed iframe and can toggle between source and rendered page using a persisted local-storage preference. PDFs load in an unsandboxed iframe and always show the rendered page without a source toggle.
> - Line links force HTML and Markdown files into source mode until the reveal is handled. Truncation warnings are hidden for browser-rendered content.
> - Risk: `WorkspaceBrowserPreview` renders PDFs in an unsandboxed iframe, exposing the viewer to potential untrusted PDF execution.
>
> <!-- Macroscope's review summary starts here -->
>
> <sup><a href="https://app.macroscope.com">Macroscope</a> summarized 4f990b7.</sup>
> <!-- Macroscope's review summary ends here -->
>
<!-- Macroscope's pull request summary ends here -->
