Use case
Working in a project that generates standalone HTML reports — backtest summaries, playwright reports, jupyter exports, diagram outputs — the only way to view them is to download via the right-click menu and open from local fs, or rely on a cd && python -m http.server running on the side. Both flow-break the editor.
Would be much nicer to single-click an .html in the Files tree and have it open in a new browser tab, rendered, with its relative CSS/JS/img assets resolving correctly.
Proposed design (already prototyped locally)
Backend: new route GET /api/projects/:projectId/preview/*
- Auth: existing
authenticateToken (already supports ?token= query for SSE), plus an expandCookieToQuery middleware that reads a scoped HttpOnly cookie so subsequent relative-asset fetches don't need the token in every URL
- HTML files: inject
<base href="./"> after <head> so relative refs resolve back to /preview/; serve as text/html
- Non-HTML files (CSS / JS / images): stream raw bytes with
mime.lookup()-detected Content-Type
- Path safety: same
startsWith(projectRoot) guard as /files/content
Verified working request matrix in my prototype:
| # |
Scenario |
Result |
| 1 |
?token= query → HTML |
200, <base href="./"> injected, Set-Cookie issued |
| 2 |
Cookie-only HTML (page navigation back) |
200 |
| 3 |
Cookie-only CSS / JS (relative asset fetch) |
200 with correct mime |
| 4 |
No auth |
401 |
| 5 |
../../../etc/passwd traversal |
403 |
Frontend: capture-phase click listener
Walks React Fiber tree (not DOM-exposed __reactProps$, those don't carry the file object — it's closure-captured in onClick). At each fiber, checks memoizedProps for {file: {path, projectId}}. If found and path matches \.html?$, preventDefault + stopPropagation + window.open('/api/projects/{id}/preview/{path}?token=...').
This shape was discovered empirically — the row component renders <div onClick={() => openFile(file)}>...</div> and the file prop never lands on the rendered DOM node, only on the fiber.
// extractor — works against current 1.32.0 bundle
const cands = [p.file, p.item, p.node, p.entry, p.data, p.row, p.record];
Why this needs to be upstream
I'm running this as a patched-on-startup hotfix locally (sed-injection into dist/index.html + new payload file in dist/), but it'd be 10× cleaner as a first-class feature, especially since:
- The right-click context menu already has "Download" — "Open in browser" is the obvious sibling
- The
/files/content route already does 80% of the work (mime + auth + path guard); preview just adds <base> injection + cookie path
- Doesn't affect non-HTML default editor behavior at all
Happy to send a PR if there's interest. Full local implementation is ~200 lines (route handler + middleware + frontend hook).
Environment
- Package:
@cloudcli-ai/cloudcli@1.32.0 (downstream rebrand)
- Browser: Firefox 151.0.1, Chromium 130 — both work
- OS: Linux
Use case
Working in a project that generates standalone HTML reports — backtest summaries, playwright reports, jupyter exports, diagram outputs — the only way to view them is to download via the right-click menu and open from local fs, or rely on a
cd && python -m http.serverrunning on the side. Both flow-break the editor.Would be much nicer to single-click an
.htmlin the Files tree and have it open in a new browser tab, rendered, with its relative CSS/JS/img assets resolving correctly.Proposed design (already prototyped locally)
Backend: new route
GET /api/projects/:projectId/preview/*authenticateToken(already supports?token=query for SSE), plus anexpandCookieToQuerymiddleware that reads a scoped HttpOnly cookie so subsequent relative-asset fetches don't need the token in every URL<base href="./">after<head>so relative refs resolve back to/preview/; serve astext/htmlmime.lookup()-detected Content-TypestartsWith(projectRoot)guard as/files/contentVerified working request matrix in my prototype:
?token=query → HTML<base href="./">injected,Set-Cookieissued../../../etc/passwdtraversalFrontend: capture-phase click listener
Walks React Fiber tree (not DOM-exposed
__reactProps$, those don't carry thefileobject — it's closure-captured inonClick). At each fiber, checksmemoizedPropsfor{file: {path, projectId}}. If found and path matches\.html?$,preventDefault + stopPropagation + window.open('/api/projects/{id}/preview/{path}?token=...').This shape was discovered empirically — the row component renders
<div onClick={() => openFile(file)}>...</div>and thefileprop never lands on the rendered DOM node, only on the fiber.Why this needs to be upstream
I'm running this as a patched-on-startup hotfix locally (sed-injection into
dist/index.html+ new payload file indist/), but it'd be 10× cleaner as a first-class feature, especially since:/files/contentroute already does 80% of the work (mime + auth + path guard); preview just adds<base>injection + cookie pathHappy to send a PR if there's interest. Full local implementation is ~200 lines (route handler + middleware + frontend hook).
Environment
@cloudcli-ai/cloudcli@1.32.0(downstream rebrand)