* feat(app): preview HTML files in the file pane Renders .html/.htm files as a page in the file pane, with the same Preview/Source toggle Markdown already has. The main use is reading richer visual plans an agent wrote as a self-contained HTML page. A preview is a viewer, not a browser. Each document is prefixed with a doctype and a strict policy, so the policy is always the first element and always lands in the parser-created head: inline script and style run, and remote subresources, fetch, XHR, WebSocket, beacon, and form posts are refused. The frame has an opaque origin, so it cannot reach the app's DOM and storage and cookie APIs throw inside it. One gap is left on web and cannot be closed there: a sandboxed document may navigate itself, and no CSP directive in current browsers prevents it -- navigate-to was dropped from CSP3 and is unenforced, and meta refresh needs no script (both verified against the Chromium the app ships against). The opaque origin bounds it to the page's own contents, and SECURITY.md documents it. Native narrows it further by refusing navigation after the initial document, with the caveat that the decision runs in app JS. Self-contained pages only -- a page that pulls a CDN script renders unstyled, and Source still shows everything. * fix(app): harden HTML preview behavior and tests --------- Co-authored-by: Nicholas Salgueiro <nicholas.salgueiro@britecore.com> Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
41 lines
1.6 KiB
TypeScript
41 lines
1.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { filePreviewRenderKind, isRenderedMarkdownFile } from "@/components/file-pane-render-mode";
|
|
|
|
describe("isRenderedMarkdownFile", () => {
|
|
it("detects .md files", () => {
|
|
expect(isRenderedMarkdownFile("README.md")).toBe(true);
|
|
expect(isRenderedMarkdownFile("docs/guide.MD")).toBe(true);
|
|
});
|
|
|
|
it("detects .markdown files", () => {
|
|
expect(isRenderedMarkdownFile("notes.markdown")).toBe(true);
|
|
expect(isRenderedMarkdownFile("docs/CHANGELOG.MARKDOWN")).toBe(true);
|
|
});
|
|
|
|
it("does not treat .mdx files as rendered markdown", () => {
|
|
expect(isRenderedMarkdownFile("page.mdx")).toBe(false);
|
|
});
|
|
|
|
it("does not treat other text files as rendered markdown", () => {
|
|
expect(isRenderedMarkdownFile("src/index.ts")).toBe(false);
|
|
expect(isRenderedMarkdownFile("README.md.txt")).toBe(false);
|
|
expect(isRenderedMarkdownFile("plan.html")).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("filePreviewRenderKind", () => {
|
|
it("maps each renderable extension to its kind", () => {
|
|
expect(filePreviewRenderKind("README.md")).toBe("markdown");
|
|
expect(filePreviewRenderKind("notes.markdown")).toBe("markdown");
|
|
expect(filePreviewRenderKind("plan.html")).toBe("html");
|
|
expect(filePreviewRenderKind("docs/PLAN.HTML")).toBe("html");
|
|
expect(filePreviewRenderKind("plan.htm")).toBe("html");
|
|
});
|
|
|
|
it("returns null for files without a rendered preview", () => {
|
|
expect(filePreviewRenderKind("src/index.ts")).toBe(null);
|
|
expect(filePreviewRenderKind("page.mdx")).toBe(null);
|
|
expect(filePreviewRenderKind("index.html.erb")).toBe(null);
|
|
});
|
|
});
|