From d55e5d1474b8dc8133667347b65192e401fcc03e Mon Sep 17 00:00:00 2001 From: Roland Tannous Date: Thu, 28 May 2026 15:08:19 +0400 Subject: [PATCH] Revert "Studio: inline DOCX preview via docx-preview + DOMPurify" This reverts commit ba78141ac5edf00c87e72909b0331288d6e9eecd. --- studio/frontend/package.json | 2 - .../src/__tests__/preview-panel.test.tsx | 4 +- .../src/__tests__/preview-store.test.ts | 22 ++----- .../rag/components/preview-docx-view.tsx | 63 ------------------- .../features/rag/components/preview-panel.tsx | 14 +---- .../src/features/rag/stores/preview-store.ts | 46 +------------- 6 files changed, 12 insertions(+), 139 deletions(-) delete mode 100644 studio/frontend/src/features/rag/components/preview-docx-view.tsx diff --git a/studio/frontend/package.json b/studio/frontend/package.json index b07be9a589..58bc2d8f09 100644 --- a/studio/frontend/package.json +++ b/studio/frontend/package.json @@ -57,8 +57,6 @@ "clsx": "^2.1.1", "cmdk": "^1.1.1", "dexie": "^4.3.0", - "docx-preview": "^0.3.6", - "dompurify": "^3.2.7", "event-source-polyfill": "1.0.31", "fflate": "0.8.3", "js-yaml": "^4.1.1", diff --git a/studio/frontend/src/__tests__/preview-panel.test.tsx b/studio/frontend/src/__tests__/preview-panel.test.tsx index ab9dd9e7fc..18d34454ae 100644 --- a/studio/frontend/src/__tests__/preview-panel.test.tsx +++ b/studio/frontend/src/__tests__/preview-panel.test.tsx @@ -4,9 +4,7 @@ * Acceptance criteria (contracts §5.4, PLAN.md T5, decisions Q7): * - mediaKind === "pdf" → react-pdf view is mounted (or loading indicator shown). * - mediaKind === "html" → text-view fallback shown, NO object/embed/iframe with blob URL. - * - mediaKind === "docx" → DOMPurify-sanitized docx-preview render when a - * blob is present; falls back to text-view when it isn't. Never uses - * object/embed/iframe and never creates a raw object URL. + * - mediaKind === "docx" → text-view fallback shown, NO inline rendering. * - mediaKind === "unknown" → unavailable/download state, NOT inline. * - mediaKind === "text" → text/snippet view shown. * - Panel without a target renders nothing or unavailable state. diff --git a/studio/frontend/src/__tests__/preview-store.test.ts b/studio/frontend/src/__tests__/preview-store.test.ts index 6d8ee50b58..fa68ba8bf9 100644 --- a/studio/frontend/src/__tests__/preview-store.test.ts +++ b/studio/frontend/src/__tests__/preview-store.test.ts @@ -7,9 +7,7 @@ * - Opening doc B while doc A is loaded revokes doc A's URL. * - PDFs use a signed range URL instead of a full blob download. * - Inline object URLs are created ONLY for safe non-PDF mediaKind (text/image). - * - For html/unknown blob fetch is skipped; previewBlobUrl = null. - * - docx fetches bytes (for the sanitized inline render) but still never - * creates an object URL (previewBlobUrl = null). + * - For unsafe mediaKind (html/docx/unknown) blob fetch is skipped; previewBlobUrl = null. * - isInlineBlobAllowed pure predicate matches contracts §5.4 allowlist. * - __previewStoreInternals() verifies module-scoped cleanup. */ @@ -227,28 +225,18 @@ describe("preview-store open/close lifecycle (contracts §5)", () => { expect(status).toBe("ready"); }); - it("docx mediaKind fetches bytes for sanitized render but never creates an object URL (Risk #3)", async () => { + it("docx mediaKind skips blob fetch and sets previewBlobUrl = null (Risk #3)", async () => { mockFetchPreviewTarget.mockResolvedValue( makeTarget({ mediaKind: "docx", filename: "report.docx" }), ); - mockFetchPreviewFileBlob.mockResolvedValue( - new Blob(["PK"], { - type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - }), - ); await usePreviewStore.getState().open({ documentId: "doc-docx" }); - // Bytes ARE fetched (the DOMPurify-sanitized docx-preview renderer - // needs them) but no object URL is created — the raw file is never - // exposed as an inline blob: URL. - expect(mockFetchPreviewFileBlob).toHaveBeenCalled(); + expect(mockFetchPreviewFileBlob).not.toHaveBeenCalled(); expect(mockFetchPreviewFileUrl).not.toHaveBeenCalled(); expect(URL.createObjectURL).not.toHaveBeenCalled(); - const { previewBlob, previewBlobUrl, status } = usePreviewStore.getState(); - expect(previewBlob).not.toBeNull(); - expect(previewBlobUrl).toBeNull(); - expect(status).toBe("ready"); + expect(usePreviewStore.getState().previewBlob).toBeNull(); + expect(usePreviewStore.getState().previewBlobUrl).toBeNull(); }); it("unknown mediaKind skips blob fetch and sets previewBlobUrl = null", async () => { diff --git a/studio/frontend/src/features/rag/components/preview-docx-view.tsx b/studio/frontend/src/features/rag/components/preview-docx-view.tsx deleted file mode 100644 index e039c90cf5..0000000000 --- a/studio/frontend/src/features/rag/components/preview-docx-view.tsx +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0 - -import DOMPurify from "dompurify"; -import { renderAsync } from "docx-preview"; -import { type FC, useEffect, useRef, useState } from "react"; -import type { PreviewTarget } from "../api/rag-api"; - -/** Inline DOCX preview. docx-preview renders the .docx bytes to HTML; the - * source document is user-supplied, so we sanitize that HTML with - * DOMPurify before injecting it (Risk #3 — a malicious .docx must not - * execute script in the app origin). The raw bytes are never exposed as - * an object URL — only the sanitized render and the Download action. */ -export const PreviewDocxView: FC<{ target: PreviewTarget; blob: Blob }> = ({ - blob, -}) => { - const ref = useRef(null); - const [error, setError] = useState(null); - - useEffect(() => { - let cancelled = false; - setError(null); - // Render off-screen first so we can sanitize before the markup ever - // touches the live DOM. - const offscreen = document.createElement("div"); - void renderAsync(blob, offscreen, undefined, { - inWrapper: true, - ignoreLastRenderedPageBreak: true, - }) - .then(() => { - if (cancelled || !ref.current) return; - // Keep