diff --git a/docs/superpowers/specs/2026-08-08-html-preview-toggle-design.md b/docs/superpowers/specs/2026-08-08-html-preview-toggle-design.md new file mode 100644 index 0000000..38cdae1 --- /dev/null +++ b/docs/superpowers/specs/2026-08-08-html-preview-toggle-design.md @@ -0,0 +1,67 @@ +# HTML 文件 Preview/Source 切换 — 设计 + +日期:2026-08-08 + +## 背景 + +Workspace 文件对话框(`WorkspaceFileDialog.tsx`)打开 `.html` 文件时只显示源码(SyntaxHighlighter)。服务端有意以 `text/plain` + `Content-Security-Policy: sandbox` + `nosniff` 返回 HTML(`src/app/api/workspace/file/route.ts:53`),防止直接打开时渲染执行。 + +## 需求 + +1. HTML 文件增加 **Preview | Source** 切换,默认 Preview。 +2. Preview 渲染页面(允许 JavaScript,plotly/echarts 交互图表可用)。 +3. Source 为现有源码高亮视图;现有 Edit(textarea 编辑 + Save)保持不变。 +4. 同目录相对资源(CSS/JS/图片)在 Preview 中可解析。 + +## 设计 + +### 1. 服务端:`?render=1` 渲染模式 + +修改 `src/app/api/workspace/file/route.ts` 的 GET: + +- 新参数 `render=1`,仅当解析出的扩展名为 `html` 时生效;其他扩展名忽略该参数。 +- render 模式下: + - `Content-Type: text/html; charset=utf-8` + - `Content-Security-Policy: sandbox allow-scripts`(允许脚本;不授予 `allow-same-origin`,脚本运行在 opaque origin,无法读取应用 cookie/DOM,其 fetch 会被 API 的跨源与会话检查拒绝) + - `Content-Disposition: inline` + - 保留 `Cache-Control: no-store`;不加 `X-Content-Type-Options: nosniff` 以外的变化 +- `?download=1` 优先于 `render=1`(仍为 attachment、text/plain)。 +- 非 render 模式行为完全不变(HTML 仍 text/plain + `sandbox`)。 + +**相对资源 MIME 修正**:同目录 `.css`/`.js` 当前以 `text/plain` + `nosniff` 返回,浏览器在渲染页面中会拒绝应用/执行。`CONTENT_TYPES` 中: + +- `css`: `text/plain` → `text/css; charset=utf-8` +- `js`: `text/plain` → `text/javascript; charset=utf-8` + +这两个扩展仍受 CSP `sandbox`(直接打开时脚本不执行)与既有同源检查保护;图片扩展(png/jpg/…)已是正确 MIME,无需改动。 + +### 2. 对话框 UI(`src/app/components/WorkspaceFileDialog.tsx`) + +- 仅对 `ext === "html"` 且 `kind === "text"` 且未超大小限制的文件启用。 +- 新 state:`htmlView: "preview" | "source"`,默认 `"preview"`;`useEffect` 在 `path`/`threadId` 变化时重置为 `"preview"`。 +- 头部工具栏(现有 Edit/Download/Delete 之前)增加分段切换:Preview | Source 两个 ghost Button,激活态用 `bg-accent` 类区分;进入编辑模式时隐藏该切换(编辑已有 Cancel/Save)。 +- **Preview 视图**:`