Skip to content

fix(web): Word previews no longer cut ordinary documents short - #32040

Merged
juliusmarminge merged 6 commits into
pingdotgg:mainfrom
yaacovcorcos:fix/word-preview-engine-limits
Nov 11, 2026
Merged

juliusmarminge merged 6 commits into
pingdotgg:mainfrom
yaacovcorcos:fix/word-preview-engine-limits

Conversation

@yaacovcorcos

@yaacovcorcos yaacovcorcos commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Problem

A Word preview stops at the first long table, and long documents lose their style and list definitions. A report with a 300-row table previews as one page, with "Preview limited", and nothing from the table on. Fixes #32039.

Change

officeArchive.ts bounds a Word file before @betteroffice/docx opens it. The old bounds were sized for a renderer on the page's main thread, but the engine lays out in its own worker. The limits now sit in one WORD_LIMITS table, and each was measured against the engine (Apple silicon, in the worker):

Limit Was Now Why
One body element 2,000 tags or 128 KB 100,000 tags A table is laid out in one go: a 4,000-row table takes about 2 s.
One paragraph's text (none; the 128 KB element limit stood in) 32K characters Line breaking grows with the square of a paragraph's text: 16K 0.7 s, 32K 1.5 s, 128K 11.6 s. The old 128 KB limit mostly measured picture markup.
A header or footer (the expansion totals) 128 KB; a longer one alone is left out Its layout grows with its own length, not the page count: 128 KB takes about 1 s. The other headers, footers and notes stay, and the preview says it is limited only when the document uses the long one.
Body 3 MB, 5,000 elements, 100,000 tags 4 MB, 50,000, 1,000,000 4 MB is about 700 pages of prose; the engine opens it in under 1 s.
Styles, lists, notes, expansion totals 1,024 styles, 1 MB, 2,000 list tags, 3 MB see WORD_LIMITS Raised to where they no longer bind real documents; the engine opened 4,000 chained styles, 2,000 lists or 5,000 footnotes in under 0.75 s.

Archive, entry and nesting limits are unchanged. The existing limit cases in AttachmentFilePreview.browser.test.tsx now size their inputs from WORD_LIMITS, so each still crosses the limit it checks.

Scope and approval

Bug issue #32039. This changes only the preparation bounds for Word files; the viewer, the engine and the other formats are untouched.

Verification

  • New officeArchive.test.ts (8 cases, under 0.5 s): a table of 500 rows followed by a paragraph is kept whole; a document with 1,500 styles, 60 lists and 6,000 paragraphs keeps its styles and numbering; a 40,000-character paragraph still stops the preview, also when an empty paragraph in a text box comes first and when its text is CDATA; indentation between tags and field codes (w:instrText) do not count as a paragraph's text; a header over 128 KB is left out alone, keeping the footer and footnotes, and marks the preview limited only when the document uses it. The first four fail on main and pass with this change; the rest guard the new counts.
  • Review follow-up (second commit): the bot findings on the oversized header and on CDATA are fixed as above.
  • AttachmentFilePreview.browser.test.tsx: 132 passed. vp fmt --check, vp lint and the web typecheck pass.
  • The engine timings above come from documents generated for each kind of input and opened by office/document.worker.ts in Chromium on an M-series Mac. Six real documents (reports, a proposal, a 9 MB summary) all preview complete with this change; three were cut short or unstyled before.
  • Not checked: Windows and Linux. The bounds are platform-independent; a slower machine takes proportionally longer per limit.

Before (main): 1 page, "Preview limited", the table and everything after it missing.

Before: 1 page, Preview limited

After: 6 pages, the whole table and the section after it.

After: 6 pages, the table

After: the section after the table

Written with Claude Opus 5.5 in Claude Code; reviewed with GPT-6.1 Sol in Codex.

🤖 Generated with Claude Code

The Word preview bounds a document before the @betteroffice/docx engine
opens it, but the bounds were sized for a renderer on the page's main
thread. Ordinary documents lost parts: a table over about 2,000 tags or
128 KB ended the preview there, a body stopped at 5,000 paragraphs, and
styles past 1,024 definitions or lists past 2,000 tags were dropped,
which unstyled the whole document.

The engine lays out in its own worker, so the limits are now measured
against it and kept in one WORD_LIMITS table:

- a paragraph's text, 32K characters (line breaking grows with the
  square of one paragraph's text), replacing the 128 KB element limit
  that mostly measured picture markup;
- one body element, 100,000 tags (a table is laid out in one go; a
  4,000-row table takes about 2 s);
- a header or footer, 128 KB (its layout grows with its own length);
- body 4 MB, and styles, lists, notes and expansion totals raised to
  where they no longer bind real documents.

Archive, entry and nesting limits are unchanged. The existing limit
tests size their inputs from WORD_LIMITS so each still crosses the
limit it checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-06T14:29:28.389449Z 6349b59 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions github-actions Bot added the vouch:unvouched PR author is not yet trusted in the VOUCHED list. label Nov 6, 2026
@github-actions github-actions Bot added the size:L 100-499 changed lines (additions + deletions). label Nov 6, 2026
bytes > WORD_LIMITS.expandedBytes ||
markup > WORD_LIMITS.expandedMarkup ||
media > OFFICE_PREVIEW_MAX_BYTES;
const truncated = (used.size > 0 && overBudget) || bytes === Infinity || oversized;

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/officeArchive.ts:951

An oversized header or footer causes wordExpansion to remove every optional part, including unrelated referenced notes and headers, even when that oversized part is unused and the remaining expansion is within budget. oversized unconditionally sets truncated, and omitted then includes every optional path; track oversized paths separately and omit only those unless an actual expansion budget is exceeded.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/files/officeArchive.ts around line 951:

An oversized header or footer causes `wordExpansion` to remove every optional part, including unrelated referenced notes and headers, even when that oversized part is unused and the remaining expansion is within budget. `oversized` unconditionally sets `truncated`, and `omitted` then includes every optional path; track oversized paths separately and omit only those unless an actual expansion budget is exceeded.

@macroscopeapp

macroscopeapp Bot commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval — Word previews now retain substantially larger documents, with new paragraph and header/footer limits. Human review is required for the changed default preview budgets and their completeness-versus-resource-use tradeoff.

Needs human approval because:

  • 1 blocking correctness issue 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.

@coderabbitai

coderabbitai Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

Run configuration

Commits

Reviewing files that changed from the base of the PR and between d8cadaa and b2e5156.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review

No actionable comments were generated in the recent review.

Walkthrough

Word preview preparation now uses centralized limits for document bodies, styles, numbering, auxiliary parts, and expanded content. Regression tests cover content retention and truncation across these limits.

Changes: Word preview processing

Layer / File(s) Summary
Bound Word body parsing
apps/web/src/components/files/officeArchive.ts, apps/web/src/components/files/officeArchive.test.ts, apps/web/src/components/files/AttachmentFilePreview.browser.test.tsx
Body parsing applies configured byte, element, tag, and paragraph-text limits. Tests cover long tables and paragraphs, truncation, whitespace and instruction text, and body-size boundaries.
Bound Word parts and expansion
apps/web/src/components/files/officeArchive.ts, apps/web/src/components/files/officeArchive.test.ts, apps/web/src/components/files/AttachmentFilePreview.browser.test.tsx
Style, numbering, auxiliary-part, and expanded-content processing use configured limits. Tests cover styles, numbering, auxiliary markup, expanded content, and preservation of header, footer, footnotes, and body content.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Possibly related PRs

  • pingdotgg/t3code#25562: Tests Word omission behavior in prepareOffice, which depends on preparation limits changed here.
  • pingdotgg/t3code#25515: Changes an expansion-budget fixture for the Word preparation budget updated here.
  • pingdotgg/t3code#25664: Tests prepared Word output for retained content and omitted expanded parts, which these limits control.

Suggested reviewers: maria-rcks

Merge Risk: Minimal · previous review d8cad

No actionable Word-preview regression is established by the supplied evidence; the web-test failure still needs its cause identified through normal CI follow-up.

Pre-merge checks: Passed 4

Check name Status Explanation
Title check Passed The title clearly and concisely describes the primary change: Word previews no longer truncate ordinary documents prematurely.
Description check Passed The description includes the required Problem, Change, Scope and approval, and Verification sections. It explains the issue, implementation, scope, testing, observed results, limitations, screenshots,…
Linked Issues check Passed [#32039] The PR raises the Word body, element, tag, style, and numbering budgets through shared WORD_LIMITS. wordDocuments now keeps a 500-row table and the following paragraph in a regression tes…
Out of Scope Changes check Passed The changes stay within Word preview preparation. They modify officeArchive.ts and Word preview tests. The shared limits and boundary fixtures support the linked issue, including auxiliary-part and …
Full details: Description check

Explanation

The description includes the required Problem, Change, Scope and approval, and Verification sections. It explains the issue, implementation, scope, testing, observed results, limitations, screenshots, and agent details.

Full details: Linked Issues check

Explanation

[#32039] The PR raises the Word body, element, tag, style, and numbering budgets through shared WORD_LIMITS. wordDocuments now keeps a 500-row table and the following paragraph in a regression test. A 6,000-paragraph fixture retains 1,500 styles and 60 numbering definitions. The tests also cover truncation of an oversized paragraph. These changes address the linked issue's incomplete table, style, and list previews.

Full details: Out of Scope Changes check

Explanation

The changes stay within Word preview preparation. They modify officeArchive.ts and Word preview tests. The shared limits and boundary fixtures support the linked issue, including auxiliary-part and expansion safeguards. No viewer, rendering engine, non-Word format, or unrelated feature change is shown.

Finishing Touches

Generate unit tests (beta)

  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Recent review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 75c3835 and d8cadaa.

Files selected for processing: 2

  • apps/web/src/components/files/officeArchive.test.ts
  • apps/web/src/components/files/officeArchive.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

@coderabbitai coderabbitai Bot left a comment

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.

Additional comments: 1

apps/web/src/components/files/officeArchive.ts: 1

14-38: Trivial · Nitpick

Unverified finding
Verification ran but could not confirm this finding. It is shown for review, not as a verified issue.

Verify that the new limits fit the worker's memory and time budget.

expandedBytes is 64 MiB. auxiliaryPartBytes and styleBytes are 8 MiB and 16 MiB. OFFICE_PREVIEW_MAX_BYTES is 50 MiB. The streaming guard at lines 1038 and 1075 still caps total expanded archive bytes at 50 MiB. Per-part limits above that cap are only reachable as accumulated cost estimates, not as real sizes. This is fine. The estimated costs are not enforced against real heap use, so confirm the worker handles an input near the new caps without running out of memory.

Review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 6577c6b and 6349b59.

Files selected for processing: 3

  • apps/web/src/components/files/AttachmentFilePreview.browser.test.tsx
  • apps/web/src/components/files/officeArchive.test.ts
  • apps/web/src/components/files/officeArchive.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6349b59dba

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 951 to 952
const truncated = (used.size > 0 && overBudget) || bytes === Infinity || oversized;
const omitted = new Set([...optional].filter((path) => truncated || !used.has(path)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve unaffected parts when omitting an oversized header

For a DOCX with any header/footer over 128 KiB, oversized makes truncated true, and this filter consequently omits all optional parts—not just the oversized one. A file with a large, even unreferenced header plus a small active footer and referenced footnote therefore loses both useful parts despite staying within every expansion budget. Keep per-header size omissions separate from the global over-budget path so that unrelated headers, footers, and notes are retained.

Useful? React with 👍 / 👎.

Comment on lines +178 to +181
parser.on("text", (text) => {
if (!inText || paragraphText.length === 0) return;
const length = paragraphText.at(-1)! + text.length;
if (length > WORD_LIMITS.paragraphText) throw limit;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Count CDATA toward the paragraph text budget

For a w:t containing CDATA, saxes emits cdata rather than text, so this counter never sees the paragraph's characters. A 300,000-character CDATA paragraph is consequently retained with truncated: false, whereas the same ordinary text is bounded; removing the previous 128 KiB element-length guard now lets such paragraphs reach the layout worker and bypass the protection against quadratic line breaking. Reuse the counter for both events, as the worksheet text readers already do.

Useful? React with 👍 / 👎.

@juliusmarminge juliusmarminge added the bugfix Fix PR opened by an issue lead; enters the PR loop label Nov 6, 2026
Review follow-up. A header or footer over its size limit marked the
whole expansion truncated, which removed every other header, footer,
note and comment too. It is now left out alone, as an unused part is,
and the preview says it is limited only when the document uses it. The
paragraph limit also counts text written as CDATA, which saxes reports
apart from ordinary text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
yaacovcorcos added a commit to ScientFactory/scient-desktop that referenced this pull request Nov 7, 2026
A header or footer over its size limit marked the whole expansion
truncated, which removed every other header, footer, note and comment
too. It is now left out alone, as an unused part is, and the preview
says it is limited only when the document uses it. The paragraph limit
also counts text written as CDATA, which saxes reports apart from
ordinary text. Found by review on the T3 copy of these limits
(pingdotgg/t3code#32040).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juliusmarminge juliusmarminge added the pr:gate-failed A land gate failed label Nov 9, 2026
@maria-rcks

maria-rcks commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator

Note

Written by gpt-6.1-sol on behalf of Maria

Verified in a real dev environment

verified head 6458a463fb57cd2fc0a2de3083b9d8cbfa4d1b00 on this linux vm. no source changes were needed.

used the real dev web app with repository Playwright and /usr/bin/google-chrome. added a project pointing at a real git repository through the app, then submitted Codex gpt-6.1-sol turns through the composer. the provider created and committed the documents; previews opened from its reply links. no mocked provider or renderer was used.

  • main 6c9adfb273: the 300-row, five-column report rendered one page with "Preview limited". the table and closing section were absent.
  • pr, identical report: 14 pages without that notice. scrolling reached row 300 and the closing recommendation.
  • a second provider turn on the pr created a 6,000-paragraph document with 1,500 styles and 60 numbering definitions. all 126 pages rendered, including the bold blue title, numbered items and final recommendation.
  • nearby behavior: the small table and three-item numbered list rendered; a 40,000-character paragraph was limited while its opening paragraph remained; an oversized referenced header was omitted while its body, short footer and referenced footnote remained.

stopped main by its recorded pid before starting the pr server. remote blacksmith-check ran vp run check:changed 6577c6b6185167c0e502fa01f9e8e8f2dee6ff25: format, lint, both knip passes, affected typechecks, 140 focused tests and the react compiler check passed. installed the missing browser on the worker before rerunning. runtime command transcript.

main: one page, preview limited, table missing

pr: 14 pages with the table retained

pr: row 300 and the closing recommendation

pr: scrolling the real report to its closing section

pr: long document retains its blue title and numbering

pr: long document reaches its final recommendation

Not verified

native desktop, ios/android, macos/windows, other providers, relay/tunnel connections, and resource use near every raised cap were not exercised. verification used linux chrome and local dev servers. no cross-platform or performance claim is made.

The per-paragraph text limit stopped the preview at the first paragraph
over 32K characters, which main previewed in full. Raise it to main's
effective 128 KB per-element cap.

AI-assisted; verified with the tests and tools listed above.
A header or footer over 128 KB of raw XML was always left out and the
preview marked truncated, even when its layout was trivial. Drop that
cutoff and rely on the expansion budget, as main does.

AI-assisted; verified with the tests and tools listed above.
@github-actions github-actions Bot added size:M 30-99 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Nov 11, 2026
@juliusmarminge

Copy link
Copy Markdown
Member

Evidence

Chromium web, 1280×800, light theme, fresh synthetic state on each commit. Open the generated DOCX files through Files and maximize the preview. Scroll the 300-row, five-column table to its final page and closing section. Separately inspect the formatting and final page of the 6,000-paragraph document with 1,500 styles and 60 list definitions.

Base omits the entire table and closing section with Preview limited. The larger document loses its blue bold style and custom Item numbering and ends at paragraph 5,000. Head shows row 300 and the closing section, preserves the style and custom numbering, and reaches paragraph 6,000.

Before After
950c34226c b2e5156f3c
Before: table and closing section omitted After: row 300 and closing section visible
Before: styling and custom numbering lost After: blue bold style and custom Item numbering preserved
Before: document stops at paragraph 5000 After: paragraph 6000 visible
Before recording After recording

Recordings begin at the document preview; pauses on base include driver waits for missing content. Stills were accepted only after identical screenshots 500 ms apart.

Driver and fixture generator · 300-row DOCX · 6,000-paragraph DOCX · Sanitized run log

AI-assisted; verified with the tests and tools listed above.

Not shown:

  • Microsoft Word authoring/re-saving was not exercised; synthetic OOXML fixtures matching the requested structures were generated.
  • The attachment entry point was not exercised; both documents were opened through Files.
  • Pathological-document limits and worst-case performance were not exercised; evidence covers the two ordinary-document reproductions.

@juliusmarminge
juliusmarminge merged commit 787c59c into pingdotgg:main Nov 11, 2026
31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Fix PR opened by an issue lead; enters the PR loop pr:gate-failed A land gate failed size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Word previews stop at the first long table, and long documents lose their styles and lists

3 participants