Repository navigation
feat(web): preview opendocument, epub, rich text, and notebooks - #21469
maria-rcks wants to merge 4 commits into
Conversation
ApprovabilityVerdict: Needs Human Approval Macroscope's review found this PR requires human approval — This PR introduces substantial new production preview capability across six document formats, including new parsers, worker behavior, and renderers, while also changing the default RTF preview path. It adds a file-level lint suppression and has unresolved Medium/High correctness findings concerning notebook fallbacks, ODF namespaces, and RTF encoding. Needs human approval because:
Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more. |
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: unavailable · PR result: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
| if (data["text/html"]) | ||
| return { kind: "html" as const, text: notebookText(data["text/html"]) }; |
There was a problem hiding this comment.
🟡 Medium files/extendedDocumentPreview.ts:354
When data["text/html"] is an empty array or string and text/plain contains content, this branch returns an empty HTML output that line 361 filters out, so the plain-text fallback is silently lost. Check the decoded HTML for non-whitespace content before preferring it.
| if (data["text/html"]) | |
| return { kind: "html" as const, text: notebookText(data["text/html"]) }; | |
| const html = notebookText(data["text/html"]); | |
| if (html.trim()) return { kind: "html" as const, text: html }; |
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/files/extendedDocumentPreview.ts around lines 354-355:
When `data["text/html"]` is an empty array or string and `text/plain` contains content, this branch returns an empty HTML output that line 361 filters out, so the plain-text fallback is silently lost. Check the decoded HTML for non-whitespace content before preferring it.
| } | ||
| return { name: "root", attrs: {}, children: convert(ordered, 0), text: "" }; | ||
| } | ||
| function find(node: XmlNode, name: string): XmlNode[] { |
There was a problem hiding this comment.
🟠 High files/extendedDocumentPreview.ts:53
OpenDocument files that bind the standard ODF namespaces to prefixes such as t, o, or d are rejected as empty because find only matches the literal names table:table, office:text, and draw:page. XML element identity is determined by namespace URI and local name, not prefix; resolve namespace declarations and match expanded names instead.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/files/extendedDocumentPreview.ts around line 53:
OpenDocument files that bind the standard ODF namespaces to prefixes such as `t`, `o`, or `d` are rejected as empty because `find` only matches the literal names `table:table`, `office:text`, and `draw:page`. XML element identity is determined by namespace URI and local name, not prefix; resolve namespace declarations and match expanded names instead.
| export function openRtf(bytes: ArrayBuffer): OfficeWorkerResult { | ||
| if (bytes.byteLength > 8 * 1024 * 1024) | ||
| throw new Error("Rich text preview limited to files under 8 MB."); | ||
| const source = new TextDecoder("windows-1252").decode(bytes); |
There was a problem hiding this comment.
🟠 High files/extendedDocumentPreview.ts:371
Valid RTF files declaring a non-1252 code page render their text incorrectly; for example, \ansicpg1251 text containing byte 0xC0 is previewed as À instead of Cyrillic А. openRtf decodes both the entire stream and \'hh escapes as windows-1252 without reading \ansicpgN; use the declared code page for both decodings.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/files/extendedDocumentPreview.ts around line 371:
Valid RTF files declaring a non-1252 code page render their text incorrectly; for example, `\ansicpg1251` text containing byte `0xC0` is previewed as `À` instead of Cyrillic `А`. `openRtf` decodes both the entire stream and `\'hh` escapes as `windows-1252` without reading `\ansicpgN`; use the declared code page for both decodings.
OpenDocument files, EPUBs, rich text, and notebooks open from attachment chips and agent links in the existing viewer. EPUBs have chapter navigation; notebooks show markdown, highlighted code, and saved text, raster images, and sanitized HTML. ODS now uses the shared workbook protocol: the worker retains bounded sheet rows, returns rowCount/columns and the first 32 rows, and serves later sheet-page requests. Dedicated 500-row × 50-column ODS expansion bounds preserve the aggregate 8 MiB text guard and are shown in the truncation notice. The parent merges retain XLSX's 128 MiB expanded / 64 MiB XML limits and the Office archive's 8 MiB compression-ratio floor.
Parsing runs in the bounded Office worker and renderers load lazily. DOMPurify, script-blocking iframe sandboxes, and restrictive CSP prevent scripts and network content; notebook HTML permits parent measurement. Already-locked MIT fast-xml-parser 5.7.3 had no OSV advisories when checked in the original pass; its permissive transitive licenses are included in notices. This integration adds no dependencies. Legacy binary DOC/XLS/PPT retain their fallback. Mobile native UI is out of scope; desktop native UI, other providers, and relay/tunnel remain unverified. Verify those paths by opening the same attached and agent-linked documents on the corresponding client and connection.
The combined stack passed web/server/shared typechecks, scoped lint on 40 files (zero errors, one existing server-test warning), 285 unit tests in 8 files, 13 browser tests in 2 files, and a production web build through testbox. The unchanged repeated-cell ODS test passes. A real Codex turn received nine attachments and returned their source links; both paths rendered two-sheet ODS files in light/dark, side panel/fullscreen and at 390px. A 5,000-row-per-sheet ODS paged through the bounded row 500 on both sheets. Regressions checked: XLSX row 100,000, exact 50 MiB CSV through its partial final record, DOCX text/table/image, ZIP entry content, SQLite next/previous pages across 120 rows, EPUB chapter switching, and notebook markdown/code/text/HTML/image outputs. The uploaded 9.225-second recording was inspected, played, sought and played to completion. Production index changed from 7,681 bytes / 3,748 gzip at office base 6932387 to 6,664 / 3,275; main changed from 1,061,018 / 341,026 to 1,050,822 / 337,459 at 2e554e3, measured with the same tooling. These are chunk sizes, not runtime performance measurements; parsers and renderers remain separate lazy chunks.
stack: main ← #21355 ← #21363 ← #21362 ← #21466 ← #21467 ← #21469.
Built by gpt-6.1-sol in Codex, orchestrated from T3 Code.