Skip to content

feat(web): upload image attachments before sending - #8048

Merged
t3dotgg merged 5 commits into
mainfrom
t3code/review-attachment-upload-overhaul-1
Aug 24, 2026
Merged

feat(web): upload image attachments before sending#8048
t3dotgg merged 5 commits into
mainfrom
t3code/review-attachment-upload-overhaul-1

Conversation

@t3dotgg

@t3dotgg t3dotgg commented Aug 24, 2026

Copy link
Copy Markdown
Member

Image attachments currently travel inside turn-start WebSocket messages as large base64 strings. The earlier direct-upload proposal would also break released mobile apps and older remote servers.

Images now upload over signed HTTP URLs as soon as they are attached. Existing mobile clients keep their current inline attachment format, and newer web clients fall back to inline uploads when an older server does not support direct uploads. Existing drafts and stashed prompts are preserved.

Based on #6276.

Verified with focused contract, server, web, mobile, and client-runtime tests, all 129 server router tests, the production web build, and targeted type checks.

Built by GPT-5.6 Sol in the Codex harness.


Note

High Risk
Adds a signed-token HTTP upload path and filesystem persistence for pending attachments, plus claim/copy logic during turn dispatch. Token validation, size checks, and pending-only deletes are security-sensitive.

Overview
Composer images now upload immediately over signed HTTP URLs instead of riding as base64 in the turn-start WebSocket payload. Newer web clients wait for uploads (with progress, retry, and send blocking) and send attachment ids; older servers and mobile clients keep the inline dataUrl path via an optional attachmentUploads capability.

The server issues HMAC-signed upload URLs, accepts POST bytes at /api/attachments/upload/*, stores them as pending-* files, and claims them on dispatch by copying to a new thread-scoped id so failed bootstraps can retry. Stale pending (24h) and .part (1h) files are swept on startup and periodically; delete RPCs only remove pending uploads.

Thread ids that sanitize to pending are remapped to _pending so they cannot collide with the reserved pending segment.

Reviewed by Cursor Bugbot for commit 2718e15. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Upload image attachments before sending messages via signed URLs

  • Adds server-side attachment upload flow: signed URL issuance via WS RPC, token-validated HTTP POST endpoint at /api/attachments/upload, atomic .part-based file storage, and stale upload sweeping (1h for partials, 24h for pending) at startup and URL issuance
  • Introduces client upload queue in attachmentUploadQueue.ts with per-environment concurrency limit of 3, progress reporting, retry/cancel/release lifecycle, and send-gating until all images reach ready or failed state
  • Extends Normalizer.normalizeDispatchCommand to claim pending uploads into thread-owned final paths with size/type validation, rollback of claimed copies on failure, and cleanup of claimed attachments on dispatch errors
  • Updates ChatComposer and ChatView to start uploads on image add, display per-image progress and retry UI, use uploaded attachment metadata instead of inline data URLs, and release uploads after successful turn start
  • Risk: toSafeThreadAttachmentSegment now maps sanitized thread IDs equal to pending to _pending; any existing thread whose ID sanitizes to pending will have its attachment directory path change. ClientThreadTurnStartCommand schema now accepts a union of UploadChatAttachment and ChatAttachment for message attachments.

Macroscope summarized 2718e15.

@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 42c6cd66-fa51-47cc-8b9f-55d51ea76b40

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 24, 2026
@github-actions

github-actions Bot commented Aug 24, 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 13.4 KiB 13.4 KiB −6 B (−0.0%) 15.1 KiB
Codex Thread snapshot wire 6.9 KiB 6.9 KiB +2 B (+0.0%) 7.3 KiB
Codex Live turn WebSocket wire 6.5 KiB 6.5 KiB −8 B (−0.1%) 7.8 KiB
Codex Live turn WebSocket decoded 55.0 KiB 55.0 KiB 0 B (0.0%) 66.4 KiB
Codex Live turn messages 16 16 0 (0.0%) 21
Claude Total thread wire 13.4 KiB 13.4 KiB +4 B (+0.0%) 15.1 KiB
Claude Thread snapshot wire 6.9 KiB 6.9 KiB −7 B (−0.1%) 7.3 KiB
Claude Live turn WebSocket wire 6.6 KiB 6.6 KiB +11 B (+0.2%) 7.8 KiB
Claude Live turn WebSocket decoded 55.8 KiB 55.8 KiB 0 B (0.0%) 66.4 KiB
Claude Live turn messages 16 16 0 (0.0%) 21

Baseline: b4be33f · PR result: 2718e15 · 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.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

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

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI consistency review: one finding on the new upload-progress overlay in the composer attachment thumbnails.

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/components/chat/ChatComposer.tsx Outdated
Comment thread apps/web/src/components/ChatView.tsx
Comment thread apps/server/src/attachmentStore.ts Outdated
Comment thread apps/server/src/orchestration/Normalizer.ts Outdated
Comment thread apps/web/src/components/chat/ChatComposer.tsx Outdated
Comment thread apps/server/src/orchestration/Normalizer.ts Outdated
Comment thread apps/server/src/ws.ts

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two new retry actions convey the upload failure reason through the native title attribute instead of the shared Tooltip primitive. The repo enforces t3code/no-native-title-tooltip ("Use Tooltip + TooltipTrigger + TooltipPopup from components/ui/tooltip") — the rule only skips these because it ignores capitalized components, and Button forwards title straight onto the native <button>. Since upload.reason is the only place the specific failure ("Unsupported image type", "Upload rejected (500)", "Not connected") is surfaced, a native tooltip makes it unreachable on touch and keyboard focus, and it looks unlike the adjacent Tooltip in the same composer block.

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/components/chat/ChatComposer.tsx
Comment thread apps/web/src/components/chat/ComposerPreviewAnnotationCards.tsx Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a604df5. Configure here.

Comment thread apps/server/src/orchestration/Normalizer.ts Outdated
Comment thread apps/server/src/attachmentStore.ts Outdated
@macroscopeapp

macroscopeapp Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a full signed image-upload workflow with new HTTP and WebSocket APIs, authorization changes, filesystem persistence, cleanup, and client-side upload state management. Its broad cross-layer runtime impact and security-sensitive upload path warrant human review.

You can add or adjust custom eligibility rules. Learn more.

@t3dotgg
t3dotgg merged commit e9f50c3 into main Aug 24, 2026
25 checks passed
@t3dotgg
t3dotgg deleted the t3code/review-attachment-upload-overhaul-1 branch August 24, 2026 08:44
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Aug 24, 2026
## What's Changed
* fix(web): clarify terminal sidebar grouping by @StiensWout in pingdotgg/t3code#7967
* fix(codex): show app access approval prompts by @t3dotgg in pingdotgg/t3code#8058
* feat(web): upload image attachments before sending by @t3dotgg in pingdotgg/t3code#8048


**Full Changelog**: pingdotgg/t3code@v0.0.34-nightly.20260824.1174...v0.0.34-nightly.20260824.1175

Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.34-nightly.20260824.1175
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). 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