Skip to content

Office files read like PDFs, and the PDF reader draws and reloads less - #783

Merged
yaacovcorcos merged 18 commits into
mainfrom
scient/office-viewers-20261105
Nov 9, 2026
Merged

yaacovcorcos merged 18 commits into
mainfrom
scient/office-viewers-20261105

Conversation

@yaacovcorcos

@yaacovcorcos yaacovcorcos commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Word previews lost parts of real documents, Office files had no zoom, search or page controls and reloaded after every agent command, and the PDF reader redrew and reloaded more than it needed to, which hurts most on slow Windows laptops. This PR makes Office files read like PDFs and takes over the PDF findings from the 2026-11-05 review.

Platforms: tested on macOS (Apple silicon) only, in a dev app and browser tests. Windows and Linux were not run. Nothing here is platform-specific. GPU drawing (enableHWA) falls back to software where Chromium has no GPU. The file revision uses the inode, which Node reports on Windows too. The pinch zoom reads Control+wheel, which Windows and Linux touchpads also send. Still to do: a before/after on a slow Windows laptop (the same long PDF at fit width and at 150%), since the browser CPU throttle does not slow the engines' workers.

Office

  • Word previews are complete (fix(web): size the Word preview limits…). T3's preparation step was sized for a renderer on the main thread. A table of a few hundred rows cut the document off there, a body stopped at 5,000 paragraphs, and long style or list definitions were dropped. Three of six real documents previewed wrong; one showed 6 of 21 pages. The new WORD_LIMITS are measured against the @betteroffice engine, which runs in a worker:

    • a paragraph's text is limited, because line breaking grows with its square;
    • so is one element's markup, because a table is laid out in one go;
    • so is a header's length: a longer header is left out alone, and the others, the footers and the notes stay. Text in CDATA counts toward a paragraph's limit (both found by review on the T3 copy).

    All six documents now open whole, with the first page in 0.3–0.7 s as before. A document past 1,000 pages shows its first 1,000 with "Preview limited" instead of an error.

  • The PDF reader's controls on Word and PowerPoint: page number and steps, zoom (buttons, keys, pinch, fit width, actual size), a sidebar of pages, and search with every match marked. Each engine searches in its own worker. Word searches its document model, so no page is built to search it. Slides gain a selectable text layer, word by word from their laid-out glyphs. Search: 95 matches in 30 ms on a 21-page report, 63 ms on a 69-slide deck, 2.2 s on a 385-page document (mostly waiting for its layout).

  • An open file stays put while the agent works. The file panel remounted every document preview (Word, PowerPoint, archives, fonts, binaries) after each command, edit, tool call or subagent in the thread, so an open report reloaded and went back to page 1 every few seconds. The preview is now keyed by its file; each possible change checks the file's revision (which the server now reports for every exact workspace file, not only PDFs), and only a real change reloads it. A reload you ask for (Reload file from disk, or refresh in Files) still always reloads it, so a failed preview can be retried. Offered to T3 as fix(web): keep an open document preview while other files change pingdotgg/t3code#32670 (issue #32669).

  • A Word file or slide deck reopens where it was left: page, point on the page and zoom, for the window's session, as PDFs already do. That also covers a file that changed and reloaded. A jump to a page past those mounted so far (a quote far into a long document) used to stop short; it now arrives.

  • Ask in chat on a Word page or slide. The new OfficeCitation quote kind sits beside the file, PDF and Compute kinds. It keeps the file, the page or slide number and the text. The agent gets the path, page and text with a note that the page number is the reader's. The chip reopens the page with the quote marked, and an exported conversation keeps it as a page quote.

PDF reader

  • Pages draw on the GPU (enableHWA). The experimental quarter-resolution mode and the halved canvas limit are gone: at 300% the base canvas is twice as sharp (0.14 → 0.27 of screen resolution).
  • Opening a PDF no longer extracts every page's text: 70 calls → 1 on a 69-page PDF. Search still prepares when it opens.
  • A renewed signed URL no longer reloads the PDF. The server now reports a PDF's file revision (device, inode, size, mtime), and the reader keeps the loaded document while that revision is unchanged.
  • Data loss fix: Save a copy kept none of the values typed into a form, yet reported success. It now saves them, and page edits start from them, while saves are still checked against the file on disk.
  • Thumbnails take their turn in pdf.js's rendering queue, after the pages in view, and copy pages already drawn. While a PDF opens, only the pages in view are checked.

Upstream seams

Scient code is in apps/web/src/scient/office, packages/shared/src/officeCitations.ts and the existing Scient quote modules. T3 files change only in marked SCIENT-FORK seams:

  • officeArchive.ts (limits), OfficePages, WordDocument, Presentation, both workers and their protocols, and word-text;
  • AssetAccess, FilePreviewPanel and AssistantCitationChip.

Presentation drops T3's previous/next slide bar, since the reader's page controls replace it. T3's limit tests in AttachmentFilePreview.browser.test.tsx now size their inputs from WORD_LIMITS. UPSTREAM.md records both ("Office preview limits", "Office reader"). The limits are offered to T3 as pingdotgg/t3code#32040, so this copy can go if it merges.

Verification

  • Codex reviews: one of the six PDF commits, one of the three Office commits, a re-check of those fixes, one of the Word limits as offered to T3, a design review of the reload and reopen work, a review of it and a re-check. T3's own review bots found the header and CDATA problems in #32040. The earlier rounds found eight real problems, all fixed here: form edits saved against the wrong revision, a thumbnail copy ignoring CSS rotation, find activating a header match (twice), thumbnail paints retaining built Word pages, and in the Word paragraph count, an empty text-box paragraph that turned the count off and indentation or field codes counted as text. The last two rounds found four more, also fixed: a document that failed to open did not retry once its file appeared; a remembered page past the end of a now shorter file was never reached; an older page jump could scroll back over a newer one; and a test that depended on timing. T3's review bot on #32670 found that a manual refresh no longer retried a failed preview, and the live check found a reload returning one page short; both fixed.

  • New tests:

    • officeArchive.scient.test.ts: each limit path.
    • officeFindPlacement.test.ts: Word match placement and slide words over glyphs.
    • ScientOfficeReader.browser.test.tsx: find, zoom, sidebar, Ask in chat and reveal; reopening at the left place, far into a long document, in a shorter file, and a newer jump over a waiting one.
    • ScientFileRevisionBoundary.browser.test.tsx: an unchanged file stays mounted across checks; a changed one reloads; a failed open retries.
    • AssetAccess.test.ts: a workspace document reports its revision, and a changed one a new revision.
    • officeCitations.test.ts: link format and what the agent gets.
    • URL renewal: in useScientPdfReader.browser.test.tsx and AssetAccess.test.ts.
    • Filled-form copy, with real pdf.js: pdfRendering.scient.browser.test.tsx.
    • Edit revision: pdfEditSession.browser.test.ts.

    Each regression test fails without its fix.

  • Gate: vp fmt --check, vp lint --report-unused-disable-directives, pnpm run typecheck, pnpm run build, pnpm run test:desktop-smoke and git diff --check pass. pnpm run test: everything passes except two suites that fail the same way on main on this machine: provider-fx skills, which reads ~/.fx, and scripts release-build. A notebook and an audio browser test flaked once under load and pass alone. After the reload and reopen work and the merge of main (481 commits, no conflicts): pnpm run typecheck, vp fmt --check and vp lint pass, and the file, Office, PDF, asset and file-surface browser tests (744) and the server asset tests (71) pass; the full suite runs in CI.

  • Dev app: Scient (Dev) · officeviewers, with a fresh profile, real Word, PowerPoint and PDF files and screenshots. It found two layout bugs, fixed here: a 3 px horizontal overflow, and fit width not following a resize. Not run there: Save a copy's native dialog, the 30-minute URL renewal, and sending to a provider. Each is covered by the tests above. Second session (2026-11-07), same app: a 21-page Word report open at page 8 stayed mounted at page 8, at the same scroll position, while a Codex agent in the thread ran four commands; switching to a slide deck and back returned to page 8; widening the panel from 277 to 533 px kept page 8; touching the file on disk reloaded it at page 8. That session found the long-document jump that stopped short, fixed here.

Not in this PR

  • Word equations (OMML) still render as flat text, and numbers beside Hebrew in PowerPoint land on the wrong side. Both are in the engine.
  • Excel keeps T3's viewer.
  • pdf.js still keeps about ten drawn pages. Raising that costs memory and needs the Windows measurement first.

Documentation impact: Updated — docs/user/file-previews.md, docs/user/composer.md, UPSTREAM.md.

Model and harness: Claude Opus 5.5 in Claude Code (Scient); reviews by Codex gpt-6.1-sol.

🤖 Generated with Claude Code

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL labels Nov 5, 2026
@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 5.5 KiB 5.5 KiB 0 B (0.0%) 6.8 KiB ✅
Codex Thread snapshot wire 4.2 KiB 4.2 KiB 0 B (0.0%) 4.9 KiB ✅
Codex Live turn WebSocket wire 1.3 KiB 1.3 KiB 0 B (0.0%) 2.0 KiB ✅
Codex Live turn WebSocket decoded 22.3 KiB 22.3 KiB 0 B (0.0%) 24.4 KiB ✅
Codex Live turn messages 1 1 0 (0.0%) 8 ✅
Claude Total thread wire 5.5 KiB 5.5 KiB 0 B (0.0%) 6.8 KiB ✅
Claude Thread snapshot wire 4.2 KiB 4.2 KiB 0 B (0.0%) 4.9 KiB ✅
Claude Live turn WebSocket wire 1.3 KiB 1.3 KiB 0 B (0.0%) 2.0 KiB ✅
Claude Live turn WebSocket decoded 22.7 KiB 22.7 KiB 0 B (0.0%) 24.4 KiB ✅
Claude Live turn messages 1 1 0 (0.0%) 8 ✅

Baseline: c098727 · PR result: 36d2907 · 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.
Bounded compact HTTP snapshot with live cursor.

  • Codex decoded thread snapshot: 88.2 KiB
  • Claude decoded thread snapshot: 88.4 KiB

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

yaacovcorcos and others added 6 commits November 6, 2026 16:10
Without `enableHWA`, pdf.js opens every page canvas with
`willReadFrequently`, which Chromium keeps in software, so pages drew on
the CPU. pdf.js's own viewer turns it on.

`enableOptimizedPartialRendering` (off by default in pdf.js) dropped a page
over the canvas limit to a quarter of its resolution and redrew a sharp
patch on every scroll, and the halved `maxCanvasPixels` made pages cross
that limit sooner: at any zoom above fit width, and on small or low
resolution screens. Both now use pdf.js's defaults.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opening a PDF dispatched an empty find within 1.5 s, and pdf.js then
extracted the text of every page on the worker that also draws the pages.
On a long PDF that slowed page drawing for minutes, whether or not the
person ever searched. Opening search, or typing a query, still prepares it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Asset URLs are re-signed every 30 minutes (and on a stale remount), and
the reader treated each new URL as a new document: it downloaded, parsed
and drew the whole PDF again in a hidden layer, then swapped it in.

The server now reports the revision a workspace or environment PDF's URL
serves (device, inode, size and modification time), as it already did
for media files. The reader knows a PDF by that revision, by a generated
PDF's revision id, or as an immutable attachment, and keeps the loaded
document while a renewed URL serves the same bytes; actions such as Save
a copy move to the renewed URL. A changed file, an edited copy, or a
Try again still loads afresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reader lets people fill in a PDF's form, but Save a copy downloaded
the served file again, so the copy lacked every value typed into it while
the success message still appeared. Page edits started from the same
original bytes. Both now take the document from pdf.js with its form
values (`saveDocument`) whenever a field has been filled in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each thumbnail in view rendered at once on the worker that draws the
pages, with no limit, and released the page's resources when it scrolled
away, even while the main view still used them.

Thumbnails now take their turn in pdf.js's own rendering queue, as in
pdf.js's sidebar: one at a time, once the pages in view are drawn. A page
the reader has already drawn is copied rather than drawn again, and page
resources the main view holds stay loaded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Until a newly opened PDF was drawn, every screen frame queried every page
element and read its position to find the visible ones. pdf.js finds them
with a search over its page positions; the fence now asks it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@yaacovcorcos
yaacovcorcos force-pushed the scient/office-viewers-20261105 branch 3 times, most recently from b281e5f to f08aa73 Compare November 6, 2026 14:20
yaacovcorcos and others added 3 commits November 6, 2026 16:20
T3's preparation step bounded Word files for a renderer on the main
thread, and real documents lost parts: a table of a few hundred rows cut
the document off there, a body stopped at 5,000 paragraphs, and long
style or list definitions were dropped, unstyling the whole document.
Of six real documents, three previewed wrong; one showed 6 of 21 pages.

The limits are now measured against the @betteroffice engine, which runs
in its own worker: a paragraph's text (line breaking grows with its
square), one element's tags (a table is laid out in one go) and a
header's length, with the other totals raised to where they no longer
bind real documents. A document past the viewer's 1,000 pages shows the
first 1,000 with the Preview limited notice instead of an error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Office files had no zoom, no find and no way to move by page; the PDF
reader has all three. Word and PowerPoint now open in a reader with the
same controls: page number and steps, zoom (buttons, keys, pinch, fit
width, actual size), a sidebar of pages and find with every match marked.

T3's viewer still draws the pages. Find runs in each engine's worker: the
Word engine searches its document model, so no page is built to search
it, and places matches by document position; a slide's text comes from
its laid-out glyphs. Slides gain a text layer for that, so their text can
now be selected and copied too. Thumbnails paint without releasing the
pages built around the view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PDFs, Markdown and code can be quoted into the chat; Office files could
not. Selected text on a Word page or PowerPoint slide now offers Ask in
chat (and Ask in side chat), as a PDF passage does.

An Office quote is its own kind, beside the file, PDF and Compute ones:
the workspace file, the page or slide the reader showed it on, and the
text with what surrounds it there. The agent gets the file path, page
and text with a note that the page is the reader's. The chip reopens the
file at that page with the quote marked, and an exported conversation
keeps it as a quote from the document's page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@yaacovcorcos
yaacovcorcos force-pushed the scient/office-viewers-20261105 branch from b281e5f to ebaded4 Compare November 6, 2026 14:21
yaacovcorcos and others added 8 commits November 7, 2026 23:08
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>
The file panel keyed a document preview (Word, PowerPoint, archives,
fonts, other binary files) by the thread's workspace mutation id, which
moves after every command, file edit, tool call or subagent anywhere in
the project. Each one remounted the preview: an open Word file was
fetched, prepared and laid out again, and went back to its first page,
every few seconds while an agent worked.

The preview is now keyed by its file. Each possible change asks the
server for the file's revision (device, inode, size and modification
time), which it now reports for every exact workspace file rather than
only PDFs, and only a new revision remounts the preview. A server that
reports no revision remounts it on every change, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Leaving a Word or PowerPoint file and coming back, or a file that
changed and reloaded, started at its first page and the default zoom,
while a PDF returns to its place. The Office reader now remembers, for
the window's session, the page at the top of the view, the point on it
and the zoom, and returns there once that page is laid out. Until the
reader moves, the place holds as the panel settles its width or Word
lays out more pages. A quote opened from the chat still opens at its
own page, and paging or scrolling first keeps where the reader went.

A jump to a page past those mounted so far (a remembered place, or a
quote far into a long document) mounted pages only up to it, so the
view could not scroll it to the top and stopped short. It now scrolls
again as the following pages mount.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-up (T3's review bot on pingdotgg/t3code#32670). With the
refresh counters moved into the revision check, Reload file from disk
and the Files pane's refresh no longer remounted an unchanged document,
so a preview whose fetch or worker failed after its URL was issued
could not be retried. A reload the reader asks for now has its own
counter, kept in the preview's key with the Files pane's; the watcher
and workspace mutations still go through the revision check. The
boundary test resets its shared mock through a setup helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reloading a Word file that was open at page 5 came back at page 4: the
page was mounted, but few pages below it were yet, so the view could
not scroll it to the top and stopped short. Any jump that stops at the
bottom while pages are still mounting now scrolls again as more mount,
unless a newer jump comes or the reader moves first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pages take their size from a width a resize observer reports after layout. On a loaded
CI runner the jump ran while every page was still zero high, so it stayed at the top.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@yaacovcorcos
yaacovcorcos added this pull request to the merge queue Nov 9, 2026
Merged via the queue into main with commit f335484 Nov 9, 2026
56 checks passed
@yaacovcorcos
yaacovcorcos deleted the scient/office-viewers-20261105 branch November 9, 2026 11:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 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.

1 participant