feat(files): an HTML file opens as the page — Files panel, Chat preview, File Explorer

Clicking an .html/.htm file now renders it instead of showing its source or
downloading it:
- Changes/Files panel: a whole HTML file opens on Preview (Raw = the editor).
- Chat file preview: a workspace page brings its relative files along.
- File Explorer: double-click / Enter opens a viewer; Download stays on its
  header and right-click menu.

The page runs in the chat artifact's sandbox (iframe srcdoc, scripts, never
allow-same-origin). Its relative stylesheets (and their url()s), scripts,
images, media and icons are read by the dashboard through the existing
download routes and inlined as <style>/data: URLs (lib/htmlInline.ts: never
above the folder's root, ≤ 60 files / 30 MB) — the login cookie is
SameSite=Strict, so a frame could not fetch them itself, and no new route
serves files without it.
This commit is contained in:
2026-10-28 16:45:57 +07:00
parent ca55ff8ec5
commit 7b5abc6ad9
11 changed files with 633 additions and 25 deletions
+12 -2
View File
@@ -677,8 +677,18 @@ reads the workspace's git surface in five modes:
line-comment buttons — Review keeps them for every file. In a read-only side-by-side diff, every row is at
least one line tall (`MIN_ROW`), so an empty filler row keeps the two
columns aligned. An image file (png, jpg, gif, webp, avif, bmp, ico, svg) is
shown as the picture (`/git/raw`), and a conflicted file opens in the
conflict view. **Cancel** reverts unsaved edits. **Save** (or
shown as the picture (`/git/raw`), an HTML file (`.html`/`.htm`) opens
as the page itself — **Raw** shows its source in the editor, **Preview**
brings the page back — and a conflicted file opens in the
conflict view. The page (here, in a Chat file preview, and on a
double-click in the File Explorer, whose right-click menu keeps
**Download**) runs in the chat artifact's sandbox (`HtmlFrame.tsx`:
`<iframe srcdoc>`, scripts but never `allow-same-origin`); its relative
stylesheets (and their `url()`s), scripts, images, media and icons are
read by the dashboard through the same download route and written into
it as `<style>`/`data:` URLs (`client/src/lib/htmlInline.ts`: never above
the folder's root, at most 60 files / 30 MB a page). The page's own
`fetch()`, ES module imports and links to other pages do not resolve. **Cancel** reverts unsaved edits. **Save** (or
Ctrl/Cmd+S) is `PUT /api/lanes/:id/git/file`, which writes only over the
exact version that was opened — a file the agent changed meanwhile answers
`409 ESTALE` and the draft stays for a retry — and keeps the file's CRLF