Repository navigation
feat(web): upload-first attachments, files, video, and document preview - #257
Merged
Merged
Conversation
Add ChatUnknownAttachment to the ChatAttachment union so attachment types from newer builds decode instead of failing the whole message, and switch the chat view, timeline, and offline outbox to isImageAttachment / isDocumentAttachment / isFileAttachment guards. The server rejects unknown types on a turn. Ported from pingdotgg#8235 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When the server advertises attachmentUploads, composer images stream to a pending upload as soon as they are attached, with progress and retry on each tile, and the send references the pending id instead of an inline data URL. Failed uploads retry on their own after a reconnect. Offline sends and servers without the capability keep the inline payload. Ported from pingdotgg#8048 Ported from pingdotgg#10338 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When the server advertises fileAttachments, anything that is not an image or an extractable document attaches as a generic file up to the advertised limit, from drag and drop, paste, or a new paperclip picker. Files show as rows with size, upload progress, retry, and remove, and send upload-first. Documents keep the fork's local text extraction and also upload their original bytes, so the server can keep the real file beside the text. Drafts persist a file's finished upload id (never its bytes) and verify it after a reload; an interrupted or expired upload becomes an "Attach again" row. Sent files render in the timeline as download rows that save under their real name, and retry or fork edit re-uploads them. Ported from pingdotgg#8236 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Video files (by mime, or by extension when the browser hands over a generic type) mint inline asset URLs with their video Content-Type, and the server serves them without a download disposition. Sent videos render as play tiles in the timeline and composer video rows get a play button; both open the expanded preview with native controls, reading the bytes into a blob URL because the desktop CSP now allows media only from the app scheme and blob:. Formats the player cannot decode offer a download. Ported from pingdotgg#8688 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A sent PDF or HTML attachment now opens as an attachment tab in the right panel, rendered by the file viewer's HTML/PDF frame (moved to a shared BrowserDocumentFrame), with a download button beside it in the timeline and the panel header. Fork documents whose PDF original the server kept open the same way from their chip. AssetResource attachments gain an optional disposition. The server serves an inline request only for pdf/html extensions it assigned itself, with a sandbox CSP for HTML; everything else still downloads, and only generic files carry a caller-named Content-Type. Ported from pingdotgg#9292 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…-web Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
TheDarkPhantom
enabled auto-merge
September 30, 2026 13:02
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.
PR #253 landed the server and contracts half of upstream's file attachments. The composer still sent images as inline data URLs, documents as text only, and could not attach anything else. This is the web half, one commit per upstream item.
What landed
ChatUnknownAttachmentjoins theChatAttachmentunion, so a type from a newer build decodes instead of failing the whole message. The chat view, timeline, and offline outbox read attachments throughisImageAttachment/isDocumentAttachment/isFileAttachment. The Normalizer rejects unknown types on a turn.attachmentUploads, images upload as soon as they are attached, with progress, retry, and automatic retry after a reconnect. The send references the pending id. Offline sends and servers without the capability keep the inline payload. Held and queued turns keep their pending ids. One shared helper (lib/composerTurnAttachments.ts) builds the payload for normal send, plan follow-up, and fork edit.fileAttachmentsis advertised, anything that is not an image or an extractable document attaches as a file up to the limit, by drop, paste, or a new paperclip picker. Files show as rows with size, progress, retry, and remove. Documents keep the fork's local text extraction and also upload their original astype: "file", then send{ type: "document", id, extractedText }. A document whose text cannot be read falls back to a plain file attachment. Drafts persist a file's finished upload id (never its bytes) and verify it after a reload. An interrupted or expired upload becomes an "Attach again" row. In the timeline, sent files are download rows that save under their real name through a same-origin blob. Retry and fork edit re-upload them.media-src 'self' <scheme>: blob:. Timeline play tiles and composer play buttons open the expanded preview with native controls. Formats the player cannot decode offer a download instead.BrowserDocumentFrame. Fork documents whose PDF original the server kept open the same way.AssetResourcegainsdisposition. The server honors inline only for pdf/html extensions it assigned, with a sandbox CSP for HTML.Left out (recorded in FUTURE_ENHANCEMENTS): stashing files (they stay in the draft when you stash), moving files between projects, composer video thumbnails, and previews for TXT/MD/CSV/DOCX documents.
Verification. Focused tests cover the upload queue (reconnect retry, persisted verification, needs-reattach), upload state, turn payload building, draft file persistence, type guards, attachment file classification, the timeline and dialog rendering, right-panel attachment surfaces, and the server inline video and document headers. Typecheck is clean for web, server, desktop, contracts, and client-runtime.
check:avicodeand the changelog parser pass. An integrated desktop pass is still needed (see the checklist in the report).Model: Claude Opus 5.5 (1M context), harness: Claude Code
🤖 Generated with Claude Code