Skip to content

feat(web): upload-first attachments, files, video, and document preview - #257

Merged
TheDarkPhantom merged 7 commits into
mainfrom
feat/file-attachments-web
Sep 30, 2026
Merged

TheDarkPhantom merged 7 commits into
mainfrom
feat/file-attachments-web

Conversation

@TheDarkPhantom

Copy link
Copy Markdown
Owner

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

  1. Type guards (feat(server): accept PDF, ZIP, and other file uploads up to 50MB pingdotgg/t3code#8235 web part). ChatUnknownAttachment joins the ChatAttachment union, so a type from a newer build decodes instead of failing the whole message. The chat view, timeline, and offline outbox read attachments through isImageAttachment / isDocumentAttachment / isFileAttachment. The Normalizer rejects unknown types on a turn.
  2. Upload-first images (feat(web): upload image attachments before sending pingdotgg/t3code#8048, with fix(web): retry failed attachment uploads after reconnect pingdotgg/t3code#10338). When the server advertises 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.
  3. Files (feat(web): attach PDFs, ZIPs, and other files to a turn pingdotgg/t3code#8236). When fileAttachments is 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 as type: "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.
  4. Video (feat(web): play video attachments in chat pingdotgg/t3code#8688). Videos mint inline asset URLs with their video type, and the server serves them without a download disposition. The desktop CSP gains 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.
  5. Document preview (feat(web): preview document attachments in the file viewer pingdotgg/t3code#9292). Sent PDF and HTML attachments open as attachment tabs in the right panel, next to a download button. This reuses the file viewer's frame, moved to a shared BrowserDocumentFrame. Fork documents whose PDF original the server kept open the same way. AssetResource gains disposition. 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:avicode and 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

TheDarkPhantom and others added 7 commits September 30, 2026 20:19
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>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL labels Sep 30, 2026
@TheDarkPhantom
TheDarkPhantom merged commit 612f32b into main Sep 30, 2026
12 checks passed
@TheDarkPhantom
TheDarkPhantom deleted the feat/file-attachments-web branch September 30, 2026 13:56
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