Skip to content

feat(web): preview opendocument, epub, rich text, and notebooks - #21469

Open
maria-rcks wants to merge 4 commits into
t3/binary-file-previewsfrom
t3/more-document-previews
Open

maria-rcks wants to merge 4 commits into
t3/binary-file-previewsfrom
t3/more-document-previews

Conversation

@maria-rcks

@maria-rcks maria-rcks commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

before integration: ods attachment opens a blank light grid

after integration: the same ods budget cells render from the attachment

before integration: ods agent link opens a blank dark grid

after integration: ods agent link renders the budget cells in dark fullscreen

scrolling beyond the first ods page and switching sheets

second ods sheet reaches row 500 and displays the preview bounds

ods timeline sheet at 390px in dark mode

ods budget sheet at 390px in light mode

before odt document viewer

after odt document viewer

before ods document viewer

after ods document viewer

before odp document viewer

after odp document viewer

before rtf document viewer

after rtf document viewer

before epub document viewer

after epub document viewer

before ipynb document viewer

before notebook spacing correction

after notebook spacing correction in light mode

after notebook spacing correction in dark mode

opening an epub, navigating chapters, and viewing saved notebook outputs

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.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Oct 19, 2026
@juliusmarminge juliusmarminge added the macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews label Oct 19, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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:

  • 3 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire — 5.0 KiB — 6.8 KiB ✅
Codex Thread snapshot wire — 3.8 KiB — 4.9 KiB ✅
Codex Live turn WebSocket wire — 1.2 KiB — 2.0 KiB ✅
Codex Live turn WebSocket decoded — 20.9 KiB — 29.3 KiB ✅
Codex Live turn messages — 2 — 8 ✅
Claude Total thread wire — 5.0 KiB — 6.8 KiB ✅
Claude Thread snapshot wire — 3.8 KiB — 4.9 KiB ✅
Claude Live turn WebSocket wire — 1.2 KiB — 2.0 KiB ✅
Claude Live turn WebSocket decoded — 21.2 KiB — 29.3 KiB ✅
Claude Live turn messages — 2 — 8 ✅

Baseline: unavailable · PR result: 2e554e3 · Source CI: success

Scenario and decoded snapshot size

10 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.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Comment on lines +354 to +355
if (data["text/html"])
return { kind: "html" as const, text: notebookText(data["text/html"]) };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.

Suggested change
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.

@maria-rcks
maria-rcks changed the base branch from t3/office-file-previews to t3/binary-file-previews October 19, 2026 05:43
@github-actions github-actions Bot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Oct 19, 2026
}
return { name: "root", attrs: {}, children: convert(ordered, 0), text: "" };
}
function find(node: XmlNode, name: string): XmlNode[] {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 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.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants