Repository navigation
[Feature]: Read DOCX, PPTX and XLSX attachments as text #491
Copy link
Copy link
Open
Description
Problem / pain point
- Claude Design accepts DOCX, PPTX and XLSX files as input next to text and images. In Open CoDesign, a
.docx,.pptxor.xlsxattached in the composer reaches the model only as a note: "Binary or unsupported format (.docx). Use the filename as a hint, not quoted content." (readAttachmentinapps/desktop/src/main/prompt-context.ts, where non-image binary files "stay filename-only for now"). Someone who attaches a brief, a deck to restyle, or a spreadsheet of content gets a design that never saw it. - The app can already read these formats.
apps/desktop/src/main/document-preview.tsparses DOCX, PPTX and XLSX withjszipinto text sections for the Files tab preview, but that text never reaches the model.
Proposed solution
Give the model the text of the three formats, using what is already in the tree:
- Move the OOXML text parsers (
parseDocx,parsePptx,parseXlsxand their helpers) out ofdocument-preview.tsinto a module both callers use. The preview passes its own caps (16 sections, 24 lines each) and its behavior does not change; its tests guard that. - In
readAttachment, extract the text of a.docx,.pptxor.xlsxand return it as the attachment'sexcerpt, the way text attachments are returned: paragraphs for DOCX, one block per slide for PPTX, one block per sheet with a row per line for XLSX, cut atMAX_ATTACHMENT_CHARSwith the existing truncation note. The module loads withawait import()on first use. - Reading is bounded by named constants with tests: the existing 10 MB attachment cap, a cap on the uncompressed bytes read from the archive, and caps on slides, sheets, rows and cells per row, applied before
MAX_ATTACHMENT_CHARScuts the joined text. They are separate from the preview's own caps, and anything a cap cuts is marked in the text so the model can tell the content is partial. - A file that is not a valid archive, or lacks the expected parts, fails with
ATTACHMENT_READ_FAILEDlike any other unreadable attachment, instead of silently falling back to the filename note. The error names the attachment and the format it expected.
Out of scope: PDF (no text extractor in the tree, so it needs a new dependency), the legacy .doc, .ppt, .xls (binary formats), images inside documents, and documents that sit in the workspace rather than being attached (the editor's view reads text files and image attachments only). Those keep today's filename note, which will name the format.
Tests build small DOCX, PPTX and XLSX archives in memory with jszip, so there are no binary fixtures: text extraction for each format, truncation, an invalid archive, and each cap.
I expect about 250 lines with tests, plus the moved parsers, which git shows as a move. One PR.
Alternatives considered
- Ask the model to read the file with its tools: the editor's
viewdoes not read binary files, and a tool that returned the same text would cost a call per file on every run. - Convert with a system tool (
textutil, LibreOffice): macOS only or a large optional install, and it runs a program on a user file. The parsers already in the tree read the archive as text and run nothing. - A PDF reader at the same time: needs a dependency, so it deserves its own proposal.
Scope
Generation capability
Hard-constraints check
- I have read the hard constraints in CLAUDE.md and this proposal does not conflict with any of them. No new dependencies (
jszipis already used by the desktop app), no network or model call, files are read from disk as text and never executed, and the parser loads on first use.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
github-actions commentedon Oct 14, 2026
Thanks for the detailed proposal — this is a clear, well-scoped feature request, not a bug, and the write-up makes the gap easy to confirm.
Understanding
Composer attachments today split into images (passed through) and everything else. For a
.docx,.pptx, or.xlsx, the attachment reaches the model only as a filename hint rather than its text — so a brief, a deck to restyle, or a spreadsheet of content is effectively invisible to the model. Meanwhile the desktop app already parses those three OOXML formats into text for the Files tab preview, so there are two parallel paths and only one gets the content to the model.Assessment
The proposal lines up with the project's hard constraints:
await import()on first use is the right shape for this.jszipis already a desktop dependency (pnpm-lock.yaml,apps/desktopimporter:jszip^3.10.1→3.10.1), so this stays inside the permissive-dependency rule (hard constraint 4) without a dependency review.On the design itself, the two points I'd want kept explicit if this becomes a PR:
parseDocx/parsePptx/parseXlsxand their helpers out of the preview module is only safe if the preview keeps its own caps (16 sections / 24 lines) and its existing tests continue to guard that. Treat the preview caps and the new attachment caps as separate constants rather than defaulting one from the other.ATTACHMENT_READ_FAILED(as you propose) is the more honest behavior; just make sure the error names the file so the user can tell which attachment failed.Scope boundaries
Agreeing with your out-of-scope choices — PDF (needs a new text-extraction dependency), the legacy binary
.doc/.ppt/.xls, embedded images, and workspace files that aren't composer attachments all keep today's filename note. Keeping PDF out of this PR is the right call; it deserves its own proposal given the dependency question.Questions / next steps
MAX_ATTACHMENT_CHARSon the joined text (your stated approach), or cap cells-per-row first? A one-line note in the proposal would make the truncation test unambiguous.No blocking concerns from my side — the shape, the constraint check, and the test plan all look sound.
Open-CoDesign Bot
wudilyy999 commentedon Oct 14, 2026
Thanks. Both design points are accepted as written, and the answers to the three questions:
MAX_ATTACHMENT_CHARSon the joined text. The preview parser already stops at 8 cells per row and 24 rows per sheet; the attachment path gets its own named constants for the cell, row and sheet caps and does not read the preview's, and the PR will state the numbers and why. Anything a cap cuts is marked in the text (a row cut at the cell cap ends with…, and a sheet cut at the row cap ends with a line saying how many rows were left out), so the model can tell the content is partial and the truncation test has one answer.parseDocx,parsePptx,parseXlsxand their helpers move to one module.document-preview.tsimports it and passes its own caps, so there is one implementation, and the preview's existing tests are the guard.On failure: the
ATTACHMENT_READ_FAILEDmessage will name the attachment and the format it expected, for exampleCould not read "brief.docx" as a DOCX file, with the cause attached.github-actions commentedon Oct 14, 2026
Thanks — that closes all three open questions, and both design points are now unambiguous.
Confirmed as written
MAX_ATTACHMENT_CHARSon the joined text, with attachment-path constants that don't inherit from the preview's.parseDocx/parsePptx/parseXlsxand their helpers move (not copy) into one module;document-preview.tsimports it and passes its own caps, so the preview's existing tests stay the guard.ATTACHMENT_READ_FAILEDnaming the attachment and the expected format, cause attached, instead of a silent filename fallback.Worth putting in the PR description
…and the "N rows left out" line are the model's only signal that content is partial, defining them in one place and asserting them as literals in tests keeps a future wording tweak from quietly dropping that signal.ATTACHMENT_READ_FAILEDfor a corrupt DOCX/PPTX/XLSX should read differently from the filename note kept for out-of-scope formats (PDF, legacy.doc/.ppt/.xls), so a user with a legacy format isn't told their file is broken. It would also be worth confirming in the description that the failure is per-attachment — one bad DOCX shouldn't abort a run whose other attachments read fine.PR housekeeping: the description will want the four PRINCIPLES §5b checks marked green (per the hard constraints in
CLAUDE.md), and the repo's Changesets flow expects an entry alongside the code —CHANGELOG.mdshouldn't be hand-edited.When the PR is up, the description is the right place for the numbers and the reasoning behind them, as you said.
Open-CoDesign Bot
test(desktop): cover the preview text fixes through the preview
test(desktop): state how long an XLSX row can be and widen the timing…
fix(desktop): find XLSX worksheets through the workbook relationships
test(desktop): pin the reported Expenses and Revenue workbook through…