Repository navigation
feat(app): send PDFs as their text and open attached files from the timeline - #479
Merged
Merged
Conversation
…imeline A PDF dropped, picked, or pasted into the composer is read with pdf.js in the app and sent as a TextFile, so it is stored and reaches every model exactly like a text file from #469: no protocol, storage, or migration change. Scanned PDFs (no text layer) and unreadable PDFs are refused. A sent file's timeline block now shows its line count and opens its text in the shared FileView in a dialog. Why: apps/app/src/attachments.ts#function text | pdf.js page text must be read through the stream's reader; WebKit's ReadableStream isn't async-iterable, so getTextContent throws in the desktop webview Why: apps/app/src/attachments.ts#function read | detect PDFs by MIME type only; a withdrawn PDF returns to the composer as its extracted text typed text/plain under its .pdf name Why: packages/ui/src/components/timeline/layout.ts | a file block's cache key includes its text, since the block opens that text and names can repeat across messages
This was referenced Oct 16, 2026
This was referenced Oct 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #26. Follows #469.
A PDF in the composer (dropped, picked, or pasted) is no longer refused. The app extracts its text with pdf.js and sends it as a
TextFile, so it reaches every model, and is stored, exactly like a text file. A sent file's timeline block now opens its contents.Design decision. I picked the simplest option that works the same for local, hosted, and peer channels, with no second store and no stored-format change:
UserMessagecontent is onlyTextContent | ImageContent, and its Anthropic/OpenAI conversions turn every non-text block into an image block, so a PDF can't reach a model natively without changing pi.{ name: "x.pdf", text }. The protocol,<attachment>storage, the 1.5 MB budget, and channel checks from feat: send text files from the composer to the agent #469 are unchanged, so there's no migration and older hosts and clients read it as text.Changes
apps/app/src/attachments.ts: if a file's type isapplication/pdf, the app lazy-loadspdfjs-dist(6.3.289, Apache-2.0, added to theuicatalog). Its worker is a separate ~1.3 MB asset, so the main bundle doesn't grow. Pages are separated by blank lines. A PDF with no text layer is refused with "x.pdf has no text to read; scanned PDFs can't be attached", and a corrupt one with "x.pdf couldn't be read as a PDF". Page text is read through the stream's reader, notgetTextContent, because that call async-iterates aReadableStream, which WebKit doesn't support and which threw in the desktop's engine (both the modern and the legacy pdf.js builds). PDFs are detected by MIME type only, so a withdrawn PDF that comes back to the composer as its text (text/plain) is read as text.packages/ui): the existing timeline file block now gets the file's text throughContent.File.text. It shows a line count and, when clicked, opens the existingFileView(components/file/file-view, with Shiki highlighting by file name) in the sharedDialog, the same way timeline images open. The block's cache key includes the text, since names can repeat across messages.docs/architecture.mdrecords the attachment design and its limits.Limits
Smoke-tested with a source host on an isolated
ACE_HOME/ACE_CONFIG_HOME(ace serve --loopback), the built app, and realanthropic/claude-haiku-4-5runs. The app was driven in headless Playwright WebKit and Chromium, attaching through the composer's file input:FileView..pdfwere refused with the messages above..txtattachment still works.bun typesandbun run cipass.TextFileas feat: send text files from the composer to the agent #469 and nothing on them changed.Built in Ace
Built with Claude Code outside Ace, in a session already running there; no Ace gap involved.