feat(mobile): upload attachments while composing - #8978

Merged
juliusmarminge merged 3 commits into
mainfrom
feat/mobile-direct-uploads
Sep 1, 2026
Merged

feat(mobile): upload attachments while composing#8978
juliusmarminge merged 3 commits into
mainfrom
feat/mobile-direct-uploads

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 1, 2026

Copy link
Copy Markdown
Member

What Changed

Mobile uploads images and files over HTTP as soon as they are attached to a connected environment. Composer thumbnails show progress and retry, with at most three concurrent uploads. Send reuses completed uploads and refreshes expired ones from the local copy.

Local image data and owned files stay with drafts and queued messages. Cloud sign-out saves relay drafts and the outbox under their account, then restores them before that account reconnects. This uses the existing Expo file APIs and server endpoints; no new native dependency or rebuild is needed.

Why

Mobile previously waited until Send to upload files and sent images inline. Uploading while composing removes that wait without making drafts depend on connectivity. Local copies remain available offline and survive app restarts and sign-out. Older servers keep the inline-image fallback.

UI Changes

The demo opens a saved PDF after an offline app restart, then shows the composer after reconnecting. Both attachments uploaded before Send, and their server bytes matched the originals. The idle wait is omitted.

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/90e07f8e31875d08/offline-and-reconnected-demo.mp4

Before reconnectingAfter reconnecting, before Send
Offline composer retaining an image and PDFConnected composer with uploaded attachments still available locally
Local PDF preview after restarting offlineNative PDF preview opened from the local copy while offline

Validation

  • 96 focused tests pass for uploads, cancellation, retries, concurrency, draft persistence, outbox delivery, and account-specific restoration.
  • Mobile typecheck and lint of changed files pass.
  • iPhone 17 Pro Simulator, iOS 27: offline attachment creation, app restart, local PDF preview, and automatic upload on reconnect. Server files matched the original SHA-256 hashes.
  • Cloud sign-out retention was covered by storage tests; live cloud sign-out and Android were not exercised.

Checklist

  • Scope limited to mobile attachment uploads and local retention
  • Explained the change and why
  • Included screenshots of the offline and connected states
  • Included an interaction demo

Note

Medium Risk
Changes span cloud auth transitions, durable draft/outbox persistence, and send-time attachment preparation; regressions could lose drafts on sign-out or block sends incorrectly, but behavior is heavily covered by new tests.

Overview
Mobile composer attachments now upload in the background while you draft, instead of waiting until Send. A global worker (hosted next to the thread outbox drain) syncs draft attachments for connected environments into a bounded queue (max three concurrent transfers), persists upload IDs back onto drafts, and survives navigation away from the composer.

UI and send gating:ComposerAttachmentStrip shows per-thumbnail progress, percentage, and tap-to-retry on failure. Thread composer, new-task flow, and thread send paths block Send/Start when online until uploads finish or failures are retried/removed; offline queuing is unchanged.

Upload pipeline:prepareTurnAttachments gains abortable HTTP uploads with progress, image upload when the server supports attachmentUploads (temp cache file from dataUrl, with inline-image fallback on older servers), reuse/re-upload of expired pending IDs for both files and images, and stamped uploadedAttachmentId on image drafts.

Cloud account lifecycle: Sign-out runs removeCloudEnvironments, which archives relay-environment drafts and queued outbox messages under the owning account before removing environments; sign-in restores them and reconciles delivered messages against the archive. Attachment file/upload cleanup respects signed-out snapshots so local bytes are not deleted prematurely.

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

Note

Add background attachment uploads to mobile composer with cloud draft preservation

  • Introduces a bounded-concurrency upload queue (max 3) in composerAttachmentUploadQueue.ts that uploads images and files over HTTP when the server supports it, with per-attachment progress, abort, and retry
  • Adds a long-lived worker hook useComposerAttachmentUploadWorker in composer-attachment-uploads.ts that reconciles the queue against current drafts and connection state, persists uploaded references back into drafts, and keeps previews usable during transfers
  • Stamps uploadedAttachmentId/uploadEnvironmentId onto image drafts in attachmentUpload.ts and composer-image-schema.ts, enabling reuse of previously uploaded ids on the same environment and reupload when expired
  • Blocks sending in use-thread-composer-state.ts, ThreadComposer.tsx, and NewTaskDraftScreen.tsx while uploads are in-progress or failed; thumbnails in ComposerAttachmentStrip.tsx show progress overlays and retry controls
  • On cloud sign-out, archives drafts and queued messages per-account in use-composer-drafts.ts via archiveCloudComposerDrafts; on sign-in, restores them via restoreCloudComposerDrafts in CloudAuthProvider.tsx
  • Risk: prepareTurnAttachments in attachmentUpload.ts now validates image mime types against PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPES and throws on unsupported types; older servers without upload support continue to receive inline images, but any caller passing unsupported image types will now fail at upload time

Macroscope summarized 312dc46.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Sep 1, 2026
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ The exact PR base did not have a successful artifact. Baseline uses the latest successful main measurement shown below.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.3 KiB+13 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−1 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.4 KiB+14 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded55.6 KiB55.6 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages11110 (0.0%)21
ClaudeTotal thread wire13.3 KiB13.3 KiB+4 B (+0.0%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB+1 B (+0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.5 KiB6.5 KiB+3 B (+0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB56.4 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages11110 (0.0%)21

Baseline: 85b656f · PR result: 312dc46 · 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.

Comment threadapps/mobile/src/features/threads/ThreadComposer.tsx
Comment threadapps/mobile/src/state/use-thread-composer-state.ts
Comment threadapps/mobile/src/lib/composerAttachmentUploadQueue.ts Outdated
Comment threadapps/mobile/src/lib/composerAttachmentUploadQueue.ts

@cursorcursorBot 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 ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 934f66d. Configure here.

Comment threadapps/mobile/src/state/use-composer-drafts.ts
@macroscopeapp

macroscopeappBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a substantial mobile workflow for automatic background attachment uploads, offline persistence, queued-message handling, and account-specific cloud draft restoration. It also changes the default composing behavior and touches authentication lifecycle code, so the scope and side effects require human review.

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

@macroscopeappmacroscopeappBot 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.

</antml the="" parameter="">

Posted via Macroscope — Effect Service Conventions

Comment threadapps/mobile/src/features/cloud/cloud-drafts.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Sep 1, 2026
Comment threadapps/mobile/src/state/use-thread-outbox-drain.ts Outdated
@juliusmarminge
juliusmarminge merged commit 9bc7a56 into mainSep 1, 2026
27 checks passed
@juliusmarminge
juliusmarminge deleted the feat/mobile-direct-uploads branch September 1, 2026 02:31
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 1, 2026
## What's Changed
* feat(mobile): upload attachments while composing by @juliusmarminge in pingdotgg/t3code#8978
* fix(chat): keep agent activity visible between actions by @maria-rcks in pingdotgg/t3code#8984
* fix(server): isolate remote web session cookies by @Bil0000 in pingdotgg/t3code#8085
* feat(pull-requests): link GitHub references in markdown by @maria-rcks in pingdotgg/t3code#8812
* perf(server): reduce frequency of full tool call output being loaded into memory from db by @t3dotgg in pingdotgg/t3code#8988
**Full Changelog**: pingdotgg/t3code@v0.0.38-nightly.20260901.1242...v0.0.38-nightly.20260901.1243
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.38-nightly.20260901.1243
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 1, 2026
## What's Changed
* Add mobile composer attachment menu with video support by @juliusmarminge in pingdotgg/t3code#8843
* fix(web): restore unified activity logs and composer banners by @t3dotgg in pingdotgg/t3code#8734
* fix(web): address composer banner review follow-ups by @juliusmarminge in pingdotgg/t3code#8850
* fix(web): widen sync banners and simplify the working timer by @juliusmarminge in pingdotgg/t3code#8855
* fix(preview): improve browser recording quality by @maria-rcks in pingdotgg/t3code#8839
* fix(web): mark pull request links as external by @juliusmarminge in pingdotgg/t3code#8856
* fix(mobile): replace Callstack glass with Expo glass by @juliusmarminge in pingdotgg/t3code#8862
* fix(server): skip IDE detection in Claude probes by @yashranaway in pingdotgg/t3code#8634
* chore(macroscope): review diagnostic overrides by @t3-code[bot] in pingdotgg/t3code#8917
* fix(contracts): accept CLI event origins by @nateEc in pingdotgg/t3code#8905
* fix(web): hide invalid slash skill completions by @nateEc in pingdotgg/t3code#8904
* fix(mobile): defer draft navigation until submission completes by @juliusmarminge in pingdotgg/t3code#8914
* chore: disable CodeRabbit review status by @juliusmarminge in pingdotgg/t3code#8933
* Delete app.json by @juliusmarminge in pingdotgg/t3code#8934
* fix(web): show scrollbar for wide markdown tables by @UtkarshUsername in pingdotgg/t3code#8868
* fix(mobile): shimmer active tool rows by @juliusmarminge in pingdotgg/t3code#8932
* chore(deps): bump Electron to 43.4.1 by @StiensWout in pingdotgg/t3code#8626
* fix(chat): smooth worktree setup status by @juliusmarminge in pingdotgg/t3code#8922
* feat(mobile): add video playback with native iOS controls by @juliusmarminge in pingdotgg/t3code#8919
* fix(web): prevent chat metadata overlap by @MatthewFeroz in pingdotgg/t3code#8851
* fix(server): preserve usage cache outside walked roots by @Lucenx9 in pingdotgg/t3code#8540
* feat(mobile): add native image and PDF previews by @juliusmarminge in pingdotgg/t3code#8959
* fix(server): allow long thread IDs in HTTP routes by @nateEc in pingdotgg/t3code#8898
* fix(shared): preserve Windows shell PATH priority by @UtkarshUsername in pingdotgg/t3code#8748
* fix(web): make WSL settings searchable by @UtkarshUsername in pingdotgg/t3code#8881
* feat(web): add expand/collapse all control to the files surface by @UtkarshUsername in pingdotgg/t3code#8889
* feat(mobile): upload attachments while composing by @juliusmarminge in pingdotgg/t3code#8978
* fix(chat): keep agent activity visible between actions by @maria-rcks in pingdotgg/t3code#8984
* fix(server): isolate remote web session cookies by @Bil0000 in pingdotgg/t3code#8085
* feat(pull-requests): link GitHub references in markdown by @maria-rcks in pingdotgg/t3code#8812
* perf(server): reduce frequency of full tool call output being loaded into memory from db by @t3dotgg in pingdotgg/t3code#8988
* feat(web): add pull request list filters by @maria-rcks in pingdotgg/t3code#8809
* feat(web): search individual settings by detail by @maria-rcks in pingdotgg/t3code#8831
* feat(client): render viewed images in work logs by @maria-rcks in pingdotgg/t3code#8936
* fix(client): use package import for markdown image helpers by @juliusmarminge in pingdotgg/t3code#9010
* test: remove static presentation snapshots by @t3-code[bot] in pingdotgg/t3code#9008
* perf(server): bound snapshot activity payload memory by @t3dotgg in pingdotgg/t3code#9000
* perf(server): cut idle CPU use and stop provider event leaks by @t3dotgg in pingdotgg/t3code#8187
* perf(server): scan only appended transcript bytes for usage summaries by @StiensWout in pingdotgg/t3code#9024
* perf(server): cut chatty tool-update frames by 90% by @Adamulek123 in pingdotgg/t3code#8368
* fix(server): settle threads server-side by @t3dotgg in pingdotgg/t3code#8600
* fix(clients): dedupe skills in composer menus by @Adamulek123 in pingdotgg/t3code#8043
* fix(server): stop OpenCode child sessions by @t3dotgg in pingdotgg/t3code#9005
* perf(web): defer pull request line stats until visible by @Adamulek123 in pingdotgg/t3code#6471
* perf(server): skip full-message reads while streaming by @t3dotgg in pingdotgg/t3code#9032
* perf(client-runtime): halve server config bootstrap traffic by @Adamulek123 in pingdotgg/t3code#8367
* fix(web): align un-settle banner action by @StiensWout in pingdotgg/t3code#9033
* fix(web): block type-to-focus behind open dialogs by @Lucenx9 in pingdotgg/t3code#8139
* feat(shortcuts): copy active thread reference by @maria-rcks in pingdotgg/t3code#8994
* fix(mobile): keep thread scroll bounds current after animations by @juliusmarminge in pingdotgg/t3code#9013
* fix(server): cache project favicon resolution by @willsheldon in pingdotgg/t3code#9080
* feat(claude): add Claude Fable 5.1 model by @q1 in pingdotgg/t3code#9078
* fix(preview): restore recording and macOS rendering after Electron 43 by @StiensWout in pingdotgg/t3code#9001
* feat(desktop): add configurable quit shortcut confirmation by @juliusmarminge in pingdotgg/t3code#9076
* feat(web): open project settings from thread menus by @SunkenInTime in pingdotgg/t3code#8925
* fix(chat): reuse one row for live activity by @maria-rcks in pingdotgg/t3code#9062
* feat(models): discover Claude models from remote manifest by @juliusmarminge in pingdotgg/t3code#9084
* Revert "fix(chat): reuse one row for live activity" by @maria-rcks in pingdotgg/t3code#9096
* fix(web): sync sidebar PR state from open panel by @t3-code[bot] in pingdotgg/t3code#9092
* fix(web): changing projects no longer creates a draft by @extoci in pingdotgg/t3code#9097
* fix(web): keep theme placeholder text dimmer than entered text by @flamboh in pingdotgg/t3code#9104
* fix(web): keep the selected environment when changing projects by @t3dotgg in pingdotgg/t3code#9102
## New Contributors
* @willsheldon made their first contribution in pingdotgg/t3code#9080
* @q1 made their first contribution in pingdotgg/t3code#9078
**Full Changelog**: pingdotgg/t3code@v0.0.37...v0.0.38
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(mobile): upload attachments while composing - #8978

Merged
juliusmarminge merged 3 commits into
mainfrom
feat/mobile-direct-uploads
Sep 1, 2026
Merged

feat(mobile): upload attachments while composing#8978
juliusmarminge merged 3 commits into
mainfrom
feat/mobile-direct-uploads

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 1, 2026

Copy link
Copy Markdown
Member

What Changed

Mobile uploads images and files over HTTP as soon as they are attached to a connected environment. Composer thumbnails show progress and retry, with at most three concurrent uploads. Send reuses completed uploads and refreshes expired ones from the local copy.

Local image data and owned files stay with drafts and queued messages. Cloud sign-out saves relay drafts and the outbox under their account, then restores them before that account reconnects. This uses the existing Expo file APIs and server endpoints; no new native dependency or rebuild is needed.

Why

Mobile previously waited until Send to upload files and sent images inline. Uploading while composing removes that wait without making drafts depend on connectivity. Local copies remain available offline and survive app restarts and sign-out. Older servers keep the inline-image fallback.

UI Changes

The demo opens a saved PDF after an offline app restart, then shows the composer after reconnecting. Both attachments uploaded before Send, and their server bytes matched the originals. The idle wait is omitted.

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/90e07f8e31875d08/offline-and-reconnected-demo.mp4

Before reconnectingAfter reconnecting, before Send
Offline composer retaining an image and PDFConnected composer with uploaded attachments still available locally
Local PDF preview after restarting offlineNative PDF preview opened from the local copy while offline

Validation

  • 96 focused tests pass for uploads, cancellation, retries, concurrency, draft persistence, outbox delivery, and account-specific restoration.
  • Mobile typecheck and lint of changed files pass.
  • iPhone 17 Pro Simulator, iOS 27: offline attachment creation, app restart, local PDF preview, and automatic upload on reconnect. Server files matched the original SHA-256 hashes.
  • Cloud sign-out retention was covered by storage tests; live cloud sign-out and Android were not exercised.

Checklist

  • Scope limited to mobile attachment uploads and local retention
  • Explained the change and why
  • Included screenshots of the offline and connected states
  • Included an interaction demo

Note

Medium Risk
Changes span cloud auth transitions, durable draft/outbox persistence, and send-time attachment preparation; regressions could lose drafts on sign-out or block sends incorrectly, but behavior is heavily covered by new tests.

Overview
Mobile composer attachments now upload in the background while you draft, instead of waiting until Send. A global worker (hosted next to the thread outbox drain) syncs draft attachments for connected environments into a bounded queue (max three concurrent transfers), persists upload IDs back onto drafts, and survives navigation away from the composer.

UI and send gating:ComposerAttachmentStrip shows per-thumbnail progress, percentage, and tap-to-retry on failure. Thread composer, new-task flow, and thread send paths block Send/Start when online until uploads finish or failures are retried/removed; offline queuing is unchanged.

Upload pipeline:prepareTurnAttachments gains abortable HTTP uploads with progress, image upload when the server supports attachmentUploads (temp cache file from dataUrl, with inline-image fallback on older servers), reuse/re-upload of expired pending IDs for both files and images, and stamped uploadedAttachmentId on image drafts.

Cloud account lifecycle: Sign-out runs removeCloudEnvironments, which archives relay-environment drafts and queued outbox messages under the owning account before removing environments; sign-in restores them and reconciles delivered messages against the archive. Attachment file/upload cleanup respects signed-out snapshots so local bytes are not deleted prematurely.

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

Note

Add background attachment uploads to mobile composer with cloud draft preservation

  • Introduces a bounded-concurrency upload queue (max 3) in composerAttachmentUploadQueue.ts that uploads images and files over HTTP when the server supports it, with per-attachment progress, abort, and retry
  • Adds a long-lived worker hook useComposerAttachmentUploadWorker in composer-attachment-uploads.ts that reconciles the queue against current drafts and connection state, persists uploaded references back into drafts, and keeps previews usable during transfers
  • Stamps uploadedAttachmentId/uploadEnvironmentId onto image drafts in attachmentUpload.ts and composer-image-schema.ts, enabling reuse of previously uploaded ids on the same environment and reupload when expired
  • Blocks sending in use-thread-composer-state.ts, ThreadComposer.tsx, and NewTaskDraftScreen.tsx while uploads are in-progress or failed; thumbnails in ComposerAttachmentStrip.tsx show progress overlays and retry controls
  • On cloud sign-out, archives drafts and queued messages per-account in use-composer-drafts.ts via archiveCloudComposerDrafts; on sign-in, restores them via restoreCloudComposerDrafts in CloudAuthProvider.tsx
  • Risk: prepareTurnAttachments in attachmentUpload.ts now validates image mime types against PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPES and throws on unsupported types; older servers without upload support continue to receive inline images, but any caller passing unsupported image types will now fail at upload time

Macroscope summarized 312dc46.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Sep 1, 2026
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ The exact PR base did not have a successful artifact. Baseline uses the latest successful main measurement shown below.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.3 KiB+13 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−1 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.4 KiB+14 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded55.6 KiB55.6 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages11110 (0.0%)21
ClaudeTotal thread wire13.3 KiB13.3 KiB+4 B (+0.0%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB+1 B (+0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.5 KiB6.5 KiB+3 B (+0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB56.4 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages11110 (0.0%)21

Baseline: 85b656f · PR result: 312dc46 · 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.

Comment threadapps/mobile/src/features/threads/ThreadComposer.tsx
Comment threadapps/mobile/src/state/use-thread-composer-state.ts
Comment threadapps/mobile/src/lib/composerAttachmentUploadQueue.ts Outdated
Comment threadapps/mobile/src/lib/composerAttachmentUploadQueue.ts

@cursorcursorBot 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 ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 934f66d. Configure here.

Comment threadapps/mobile/src/state/use-composer-drafts.ts
@macroscopeapp

macroscopeappBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a substantial mobile workflow for automatic background attachment uploads, offline persistence, queued-message handling, and account-specific cloud draft restoration. It also changes the default composing behavior and touches authentication lifecycle code, so the scope and side effects require human review.

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

@macroscopeappmacroscopeappBot 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.

</antml the="" parameter="">

Posted via Macroscope — Effect Service Conventions

Comment threadapps/mobile/src/features/cloud/cloud-drafts.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Sep 1, 2026
Comment threadapps/mobile/src/state/use-thread-outbox-drain.ts Outdated
@juliusmarminge
juliusmarminge merged commit 9bc7a56 into mainSep 1, 2026
27 checks passed
@juliusmarminge
juliusmarminge deleted the feat/mobile-direct-uploads branch September 1, 2026 02:31
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 1, 2026
## What's Changed
* feat(mobile): upload attachments while composing by @juliusmarminge in pingdotgg/t3code#8978
* fix(chat): keep agent activity visible between actions by @maria-rcks in pingdotgg/t3code#8984
* fix(server): isolate remote web session cookies by @Bil0000 in pingdotgg/t3code#8085
* feat(pull-requests): link GitHub references in markdown by @maria-rcks in pingdotgg/t3code#8812
* perf(server): reduce frequency of full tool call output being loaded into memory from db by @t3dotgg in pingdotgg/t3code#8988
**Full Changelog**: pingdotgg/t3code@v0.0.38-nightly.20260901.1242...v0.0.38-nightly.20260901.1243
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.38-nightly.20260901.1243
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 1, 2026
## What's Changed
* Add mobile composer attachment menu with video support by @juliusmarminge in pingdotgg/t3code#8843
* fix(web): restore unified activity logs and composer banners by @t3dotgg in pingdotgg/t3code#8734
* fix(web): address composer banner review follow-ups by @juliusmarminge in pingdotgg/t3code#8850
* fix(web): widen sync banners and simplify the working timer by @juliusmarminge in pingdotgg/t3code#8855
* fix(preview): improve browser recording quality by @maria-rcks in pingdotgg/t3code#8839
* fix(web): mark pull request links as external by @juliusmarminge in pingdotgg/t3code#8856
* fix(mobile): replace Callstack glass with Expo glass by @juliusmarminge in pingdotgg/t3code#8862
* fix(server): skip IDE detection in Claude probes by @yashranaway in pingdotgg/t3code#8634
* chore(macroscope): review diagnostic overrides by @t3-code[bot] in pingdotgg/t3code#8917
* fix(contracts): accept CLI event origins by @nateEc in pingdotgg/t3code#8905
* fix(web): hide invalid slash skill completions by @nateEc in pingdotgg/t3code#8904
* fix(mobile): defer draft navigation until submission completes by @juliusmarminge in pingdotgg/t3code#8914
* chore: disable CodeRabbit review status by @juliusmarminge in pingdotgg/t3code#8933
* Delete app.json by @juliusmarminge in pingdotgg/t3code#8934
* fix(web): show scrollbar for wide markdown tables by @UtkarshUsername in pingdotgg/t3code#8868
* fix(mobile): shimmer active tool rows by @juliusmarminge in pingdotgg/t3code#8932
* chore(deps): bump Electron to 43.4.1 by @StiensWout in pingdotgg/t3code#8626
* fix(chat): smooth worktree setup status by @juliusmarminge in pingdotgg/t3code#8922
* feat(mobile): add video playback with native iOS controls by @juliusmarminge in pingdotgg/t3code#8919
* fix(web): prevent chat metadata overlap by @MatthewFeroz in pingdotgg/t3code#8851
* fix(server): preserve usage cache outside walked roots by @Lucenx9 in pingdotgg/t3code#8540
* feat(mobile): add native image and PDF previews by @juliusmarminge in pingdotgg/t3code#8959
* fix(server): allow long thread IDs in HTTP routes by @nateEc in pingdotgg/t3code#8898
* fix(shared): preserve Windows shell PATH priority by @UtkarshUsername in pingdotgg/t3code#8748
* fix(web): make WSL settings searchable by @UtkarshUsername in pingdotgg/t3code#8881
* feat(web): add expand/collapse all control to the files surface by @UtkarshUsername in pingdotgg/t3code#8889
* feat(mobile): upload attachments while composing by @juliusmarminge in pingdotgg/t3code#8978
* fix(chat): keep agent activity visible between actions by @maria-rcks in pingdotgg/t3code#8984
* fix(server): isolate remote web session cookies by @Bil0000 in pingdotgg/t3code#8085
* feat(pull-requests): link GitHub references in markdown by @maria-rcks in pingdotgg/t3code#8812
* perf(server): reduce frequency of full tool call output being loaded into memory from db by @t3dotgg in pingdotgg/t3code#8988
* feat(web): add pull request list filters by @maria-rcks in pingdotgg/t3code#8809
* feat(web): search individual settings by detail by @maria-rcks in pingdotgg/t3code#8831
* feat(client): render viewed images in work logs by @maria-rcks in pingdotgg/t3code#8936
* fix(client): use package import for markdown image helpers by @juliusmarminge in pingdotgg/t3code#9010
* test: remove static presentation snapshots by @t3-code[bot] in pingdotgg/t3code#9008
* perf(server): bound snapshot activity payload memory by @t3dotgg in pingdotgg/t3code#9000
* perf(server): cut idle CPU use and stop provider event leaks by @t3dotgg in pingdotgg/t3code#8187
* perf(server): scan only appended transcript bytes for usage summaries by @StiensWout in pingdotgg/t3code#9024
* perf(server): cut chatty tool-update frames by 90% by @Adamulek123 in pingdotgg/t3code#8368
* fix(server): settle threads server-side by @t3dotgg in pingdotgg/t3code#8600
* fix(clients): dedupe skills in composer menus by @Adamulek123 in pingdotgg/t3code#8043
* fix(server): stop OpenCode child sessions by @t3dotgg in pingdotgg/t3code#9005
* perf(web): defer pull request line stats until visible by @Adamulek123 in pingdotgg/t3code#6471
* perf(server): skip full-message reads while streaming by @t3dotgg in pingdotgg/t3code#9032
* perf(client-runtime): halve server config bootstrap traffic by @Adamulek123 in pingdotgg/t3code#8367
* fix(web): align un-settle banner action by @StiensWout in pingdotgg/t3code#9033
* fix(web): block type-to-focus behind open dialogs by @Lucenx9 in pingdotgg/t3code#8139
* feat(shortcuts): copy active thread reference by @maria-rcks in pingdotgg/t3code#8994
* fix(mobile): keep thread scroll bounds current after animations by @juliusmarminge in pingdotgg/t3code#9013
* fix(server): cache project favicon resolution by @willsheldon in pingdotgg/t3code#9080
* feat(claude): add Claude Fable 5.1 model by @q1 in pingdotgg/t3code#9078
* fix(preview): restore recording and macOS rendering after Electron 43 by @StiensWout in pingdotgg/t3code#9001
* feat(desktop): add configurable quit shortcut confirmation by @juliusmarminge in pingdotgg/t3code#9076
* feat(web): open project settings from thread menus by @SunkenInTime in pingdotgg/t3code#8925
* fix(chat): reuse one row for live activity by @maria-rcks in pingdotgg/t3code#9062
* feat(models): discover Claude models from remote manifest by @juliusmarminge in pingdotgg/t3code#9084
* Revert "fix(chat): reuse one row for live activity" by @maria-rcks in pingdotgg/t3code#9096
* fix(web): sync sidebar PR state from open panel by @t3-code[bot] in pingdotgg/t3code#9092
* fix(web): changing projects no longer creates a draft by @extoci in pingdotgg/t3code#9097
* fix(web): keep theme placeholder text dimmer than entered text by @flamboh in pingdotgg/t3code#9104
* fix(web): keep the selected environment when changing projects by @t3dotgg in pingdotgg/t3code#9102
## New Contributors
* @willsheldon made their first contribution in pingdotgg/t3code#9080
* @q1 made their first contribution in pingdotgg/t3code#9078
**Full Changelog**: pingdotgg/t3code@v0.0.37...v0.0.38
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(mobile): upload attachments while composing - #8978

Merged
juliusmarminge merged 3 commits into
mainfrom
feat/mobile-direct-uploads
Sep 1, 2026
Merged

feat(mobile): upload attachments while composing#8978
juliusmarminge merged 3 commits into
mainfrom
feat/mobile-direct-uploads

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 1, 2026

Copy link
Copy Markdown
Member

What Changed

Mobile uploads images and files over HTTP as soon as they are attached to a connected environment. Composer thumbnails show progress and retry, with at most three concurrent uploads. Send reuses completed uploads and refreshes expired ones from the local copy.

Local image data and owned files stay with drafts and queued messages. Cloud sign-out saves relay drafts and the outbox under their account, then restores them before that account reconnects. This uses the existing Expo file APIs and server endpoints; no new native dependency or rebuild is needed.

Why

Mobile previously waited until Send to upload files and sent images inline. Uploading while composing removes that wait without making drafts depend on connectivity. Local copies remain available offline and survive app restarts and sign-out. Older servers keep the inline-image fallback.

UI Changes

The demo opens a saved PDF after an offline app restart, then shows the composer after reconnecting. Both attachments uploaded before Send, and their server bytes matched the originals. The idle wait is omitted.

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/90e07f8e31875d08/offline-and-reconnected-demo.mp4

Before reconnectingAfter reconnecting, before Send
Offline composer retaining an image and PDFConnected composer with uploaded attachments still available locally
Local PDF preview after restarting offlineNative PDF preview opened from the local copy while offline

Validation

  • 96 focused tests pass for uploads, cancellation, retries, concurrency, draft persistence, outbox delivery, and account-specific restoration.
  • Mobile typecheck and lint of changed files pass.
  • iPhone 17 Pro Simulator, iOS 27: offline attachment creation, app restart, local PDF preview, and automatic upload on reconnect. Server files matched the original SHA-256 hashes.
  • Cloud sign-out retention was covered by storage tests; live cloud sign-out and Android were not exercised.

Checklist

  • Scope limited to mobile attachment uploads and local retention
  • Explained the change and why
  • Included screenshots of the offline and connected states
  • Included an interaction demo

Note

Medium Risk
Changes span cloud auth transitions, durable draft/outbox persistence, and send-time attachment preparation; regressions could lose drafts on sign-out or block sends incorrectly, but behavior is heavily covered by new tests.

Overview
Mobile composer attachments now upload in the background while you draft, instead of waiting until Send. A global worker (hosted next to the thread outbox drain) syncs draft attachments for connected environments into a bounded queue (max three concurrent transfers), persists upload IDs back onto drafts, and survives navigation away from the composer.

UI and send gating:ComposerAttachmentStrip shows per-thumbnail progress, percentage, and tap-to-retry on failure. Thread composer, new-task flow, and thread send paths block Send/Start when online until uploads finish or failures are retried/removed; offline queuing is unchanged.

Upload pipeline:prepareTurnAttachments gains abortable HTTP uploads with progress, image upload when the server supports attachmentUploads (temp cache file from dataUrl, with inline-image fallback on older servers), reuse/re-upload of expired pending IDs for both files and images, and stamped uploadedAttachmentId on image drafts.

Cloud account lifecycle: Sign-out runs removeCloudEnvironments, which archives relay-environment drafts and queued outbox messages under the owning account before removing environments; sign-in restores them and reconciles delivered messages against the archive. Attachment file/upload cleanup respects signed-out snapshots so local bytes are not deleted prematurely.

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

Note

Add background attachment uploads to mobile composer with cloud draft preservation

  • Introduces a bounded-concurrency upload queue (max 3) in composerAttachmentUploadQueue.ts that uploads images and files over HTTP when the server supports it, with per-attachment progress, abort, and retry
  • Adds a long-lived worker hook useComposerAttachmentUploadWorker in composer-attachment-uploads.ts that reconciles the queue against current drafts and connection state, persists uploaded references back into drafts, and keeps previews usable during transfers
  • Stamps uploadedAttachmentId/uploadEnvironmentId onto image drafts in attachmentUpload.ts and composer-image-schema.ts, enabling reuse of previously uploaded ids on the same environment and reupload when expired
  • Blocks sending in use-thread-composer-state.ts, ThreadComposer.tsx, and NewTaskDraftScreen.tsx while uploads are in-progress or failed; thumbnails in ComposerAttachmentStrip.tsx show progress overlays and retry controls
  • On cloud sign-out, archives drafts and queued messages per-account in use-composer-drafts.ts via archiveCloudComposerDrafts; on sign-in, restores them via restoreCloudComposerDrafts in CloudAuthProvider.tsx
  • Risk: prepareTurnAttachments in attachmentUpload.ts now validates image mime types against PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPES and throws on unsupported types; older servers without upload support continue to receive inline images, but any caller passing unsupported image types will now fail at upload time

Macroscope summarized 312dc46.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Sep 1, 2026
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ The exact PR base did not have a successful artifact. Baseline uses the latest successful main measurement shown below.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.3 KiB+13 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−1 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.4 KiB+14 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded55.6 KiB55.6 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages11110 (0.0%)21
ClaudeTotal thread wire13.3 KiB13.3 KiB+4 B (+0.0%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB+1 B (+0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.5 KiB6.5 KiB+3 B (+0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB56.4 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages11110 (0.0%)21

Baseline: 85b656f · PR result: 312dc46 · 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.

Comment threadapps/mobile/src/features/threads/ThreadComposer.tsx
Comment threadapps/mobile/src/state/use-thread-composer-state.ts
Comment threadapps/mobile/src/lib/composerAttachmentUploadQueue.ts Outdated
Comment threadapps/mobile/src/lib/composerAttachmentUploadQueue.ts

@cursorcursorBot 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 ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 934f66d. Configure here.

Comment threadapps/mobile/src/state/use-composer-drafts.ts
@macroscopeapp

macroscopeappBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a substantial mobile workflow for automatic background attachment uploads, offline persistence, queued-message handling, and account-specific cloud draft restoration. It also changes the default composing behavior and touches authentication lifecycle code, so the scope and side effects require human review.

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

@macroscopeappmacroscopeappBot 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.

</antml the="" parameter="">

Posted via Macroscope — Effect Service Conventions

Comment threadapps/mobile/src/features/cloud/cloud-drafts.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Sep 1, 2026
Comment threadapps/mobile/src/state/use-thread-outbox-drain.ts Outdated
@juliusmarminge
juliusmarminge merged commit 9bc7a56 into mainSep 1, 2026
27 checks passed
@juliusmarminge
juliusmarminge deleted the feat/mobile-direct-uploads branch September 1, 2026 02:31
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 1, 2026
## What's Changed
* feat(mobile): upload attachments while composing by @juliusmarminge in pingdotgg/t3code#8978
* fix(chat): keep agent activity visible between actions by @maria-rcks in pingdotgg/t3code#8984
* fix(server): isolate remote web session cookies by @Bil0000 in pingdotgg/t3code#8085
* feat(pull-requests): link GitHub references in markdown by @maria-rcks in pingdotgg/t3code#8812
* perf(server): reduce frequency of full tool call output being loaded into memory from db by @t3dotgg in pingdotgg/t3code#8988
**Full Changelog**: pingdotgg/t3code@v0.0.38-nightly.20260901.1242...v0.0.38-nightly.20260901.1243
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.38-nightly.20260901.1243
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 1, 2026
## What's Changed
* Add mobile composer attachment menu with video support by @juliusmarminge in pingdotgg/t3code#8843
* fix(web): restore unified activity logs and composer banners by @t3dotgg in pingdotgg/t3code#8734
* fix(web): address composer banner review follow-ups by @juliusmarminge in pingdotgg/t3code#8850
* fix(web): widen sync banners and simplify the working timer by @juliusmarminge in pingdotgg/t3code#8855
* fix(preview): improve browser recording quality by @maria-rcks in pingdotgg/t3code#8839
* fix(web): mark pull request links as external by @juliusmarminge in pingdotgg/t3code#8856
* fix(mobile): replace Callstack glass with Expo glass by @juliusmarminge in pingdotgg/t3code#8862
* fix(server): skip IDE detection in Claude probes by @yashranaway in pingdotgg/t3code#8634
* chore(macroscope): review diagnostic overrides by @t3-code[bot] in pingdotgg/t3code#8917
* fix(contracts): accept CLI event origins by @nateEc in pingdotgg/t3code#8905
* fix(web): hide invalid slash skill completions by @nateEc in pingdotgg/t3code#8904
* fix(mobile): defer draft navigation until submission completes by @juliusmarminge in pingdotgg/t3code#8914
* chore: disable CodeRabbit review status by @juliusmarminge in pingdotgg/t3code#8933
* Delete app.json by @juliusmarminge in pingdotgg/t3code#8934
* fix(web): show scrollbar for wide markdown tables by @UtkarshUsername in pingdotgg/t3code#8868
* fix(mobile): shimmer active tool rows by @juliusmarminge in pingdotgg/t3code#8932
* chore(deps): bump Electron to 43.4.1 by @StiensWout in pingdotgg/t3code#8626
* fix(chat): smooth worktree setup status by @juliusmarminge in pingdotgg/t3code#8922
* feat(mobile): add video playback with native iOS controls by @juliusmarminge in pingdotgg/t3code#8919
* fix(web): prevent chat metadata overlap by @MatthewFeroz in pingdotgg/t3code#8851
* fix(server): preserve usage cache outside walked roots by @Lucenx9 in pingdotgg/t3code#8540
* feat(mobile): add native image and PDF previews by @juliusmarminge in pingdotgg/t3code#8959
* fix(server): allow long thread IDs in HTTP routes by @nateEc in pingdotgg/t3code#8898
* fix(shared): preserve Windows shell PATH priority by @UtkarshUsername in pingdotgg/t3code#8748
* fix(web): make WSL settings searchable by @UtkarshUsername in pingdotgg/t3code#8881
* feat(web): add expand/collapse all control to the files surface by @UtkarshUsername in pingdotgg/t3code#8889
* feat(mobile): upload attachments while composing by @juliusmarminge in pingdotgg/t3code#8978
* fix(chat): keep agent activity visible between actions by @maria-rcks in pingdotgg/t3code#8984
* fix(server): isolate remote web session cookies by @Bil0000 in pingdotgg/t3code#8085
* feat(pull-requests): link GitHub references in markdown by @maria-rcks in pingdotgg/t3code#8812
* perf(server): reduce frequency of full tool call output being loaded into memory from db by @t3dotgg in pingdotgg/t3code#8988
* feat(web): add pull request list filters by @maria-rcks in pingdotgg/t3code#8809
* feat(web): search individual settings by detail by @maria-rcks in pingdotgg/t3code#8831
* feat(client): render viewed images in work logs by @maria-rcks in pingdotgg/t3code#8936
* fix(client): use package import for markdown image helpers by @juliusmarminge in pingdotgg/t3code#9010
* test: remove static presentation snapshots by @t3-code[bot] in pingdotgg/t3code#9008
* perf(server): bound snapshot activity payload memory by @t3dotgg in pingdotgg/t3code#9000
* perf(server): cut idle CPU use and stop provider event leaks by @t3dotgg in pingdotgg/t3code#8187
* perf(server): scan only appended transcript bytes for usage summaries by @StiensWout in pingdotgg/t3code#9024
* perf(server): cut chatty tool-update frames by 90% by @Adamulek123 in pingdotgg/t3code#8368
* fix(server): settle threads server-side by @t3dotgg in pingdotgg/t3code#8600
* fix(clients): dedupe skills in composer menus by @Adamulek123 in pingdotgg/t3code#8043
* fix(server): stop OpenCode child sessions by @t3dotgg in pingdotgg/t3code#9005
* perf(web): defer pull request line stats until visible by @Adamulek123 in pingdotgg/t3code#6471
* perf(server): skip full-message reads while streaming by @t3dotgg in pingdotgg/t3code#9032
* perf(client-runtime): halve server config bootstrap traffic by @Adamulek123 in pingdotgg/t3code#8367
* fix(web): align un-settle banner action by @StiensWout in pingdotgg/t3code#9033
* fix(web): block type-to-focus behind open dialogs by @Lucenx9 in pingdotgg/t3code#8139
* feat(shortcuts): copy active thread reference by @maria-rcks in pingdotgg/t3code#8994
* fix(mobile): keep thread scroll bounds current after animations by @juliusmarminge in pingdotgg/t3code#9013
* fix(server): cache project favicon resolution by @willsheldon in pingdotgg/t3code#9080
* feat(claude): add Claude Fable 5.1 model by @q1 in pingdotgg/t3code#9078
* fix(preview): restore recording and macOS rendering after Electron 43 by @StiensWout in pingdotgg/t3code#9001
* feat(desktop): add configurable quit shortcut confirmation by @juliusmarminge in pingdotgg/t3code#9076
* feat(web): open project settings from thread menus by @SunkenInTime in pingdotgg/t3code#8925
* fix(chat): reuse one row for live activity by @maria-rcks in pingdotgg/t3code#9062
* feat(models): discover Claude models from remote manifest by @juliusmarminge in pingdotgg/t3code#9084
* Revert "fix(chat): reuse one row for live activity" by @maria-rcks in pingdotgg/t3code#9096
* fix(web): sync sidebar PR state from open panel by @t3-code[bot] in pingdotgg/t3code#9092
* fix(web): changing projects no longer creates a draft by @extoci in pingdotgg/t3code#9097
* fix(web): keep theme placeholder text dimmer than entered text by @flamboh in pingdotgg/t3code#9104
* fix(web): keep the selected environment when changing projects by @t3dotgg in pingdotgg/t3code#9102
## New Contributors
* @willsheldon made their first contribution in pingdotgg/t3code#9080
* @q1 made their first contribution in pingdotgg/t3code#9078
**Full Changelog**: pingdotgg/t3code@v0.0.37...v0.0.38
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(mobile): upload attachments while composing - #8978

Merged
juliusmarminge merged 3 commits into
mainfrom
feat/mobile-direct-uploads
Sep 1, 2026
Merged

feat(mobile): upload attachments while composing#8978
juliusmarminge merged 3 commits into
mainfrom
feat/mobile-direct-uploads

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 1, 2026

Copy link
Copy Markdown
Member

What Changed

Mobile uploads images and files over HTTP as soon as they are attached to a connected environment. Composer thumbnails show progress and retry, with at most three concurrent uploads. Send reuses completed uploads and refreshes expired ones from the local copy.

Local image data and owned files stay with drafts and queued messages. Cloud sign-out saves relay drafts and the outbox under their account, then restores them before that account reconnects. This uses the existing Expo file APIs and server endpoints; no new native dependency or rebuild is needed.

Why

Mobile previously waited until Send to upload files and sent images inline. Uploading while composing removes that wait without making drafts depend on connectivity. Local copies remain available offline and survive app restarts and sign-out. Older servers keep the inline-image fallback.

UI Changes

The demo opens a saved PDF after an offline app restart, then shows the composer after reconnecting. Both attachments uploaded before Send, and their server bytes matched the originals. The idle wait is omitted.

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/90e07f8e31875d08/offline-and-reconnected-demo.mp4

Before reconnectingAfter reconnecting, before Send
Offline composer retaining an image and PDFConnected composer with uploaded attachments still available locally
Local PDF preview after restarting offlineNative PDF preview opened from the local copy while offline

Validation

  • 96 focused tests pass for uploads, cancellation, retries, concurrency, draft persistence, outbox delivery, and account-specific restoration.
  • Mobile typecheck and lint of changed files pass.
  • iPhone 17 Pro Simulator, iOS 27: offline attachment creation, app restart, local PDF preview, and automatic upload on reconnect. Server files matched the original SHA-256 hashes.
  • Cloud sign-out retention was covered by storage tests; live cloud sign-out and Android were not exercised.

Checklist

  • Scope limited to mobile attachment uploads and local retention
  • Explained the change and why
  • Included screenshots of the offline and connected states
  • Included an interaction demo

Note

Medium Risk
Changes span cloud auth transitions, durable draft/outbox persistence, and send-time attachment preparation; regressions could lose drafts on sign-out or block sends incorrectly, but behavior is heavily covered by new tests.

Overview
Mobile composer attachments now upload in the background while you draft, instead of waiting until Send. A global worker (hosted next to the thread outbox drain) syncs draft attachments for connected environments into a bounded queue (max three concurrent transfers), persists upload IDs back onto drafts, and survives navigation away from the composer.

UI and send gating:ComposerAttachmentStrip shows per-thumbnail progress, percentage, and tap-to-retry on failure. Thread composer, new-task flow, and thread send paths block Send/Start when online until uploads finish or failures are retried/removed; offline queuing is unchanged.

Upload pipeline:prepareTurnAttachments gains abortable HTTP uploads with progress, image upload when the server supports attachmentUploads (temp cache file from dataUrl, with inline-image fallback on older servers), reuse/re-upload of expired pending IDs for both files and images, and stamped uploadedAttachmentId on image drafts.

Cloud account lifecycle: Sign-out runs removeCloudEnvironments, which archives relay-environment drafts and queued outbox messages under the owning account before removing environments; sign-in restores them and reconciles delivered messages against the archive. Attachment file/upload cleanup respects signed-out snapshots so local bytes are not deleted prematurely.

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

Note

Add background attachment uploads to mobile composer with cloud draft preservation

  • Introduces a bounded-concurrency upload queue (max 3) in composerAttachmentUploadQueue.ts that uploads images and files over HTTP when the server supports it, with per-attachment progress, abort, and retry
  • Adds a long-lived worker hook useComposerAttachmentUploadWorker in composer-attachment-uploads.ts that reconciles the queue against current drafts and connection state, persists uploaded references back into drafts, and keeps previews usable during transfers
  • Stamps uploadedAttachmentId/uploadEnvironmentId onto image drafts in attachmentUpload.ts and composer-image-schema.ts, enabling reuse of previously uploaded ids on the same environment and reupload when expired
  • Blocks sending in use-thread-composer-state.ts, ThreadComposer.tsx, and NewTaskDraftScreen.tsx while uploads are in-progress or failed; thumbnails in ComposerAttachmentStrip.tsx show progress overlays and retry controls
  • On cloud sign-out, archives drafts and queued messages per-account in use-composer-drafts.ts via archiveCloudComposerDrafts; on sign-in, restores them via restoreCloudComposerDrafts in CloudAuthProvider.tsx
  • Risk: prepareTurnAttachments in attachmentUpload.ts now validates image mime types against PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPES and throws on unsupported types; older servers without upload support continue to receive inline images, but any caller passing unsupported image types will now fail at upload time

Macroscope summarized 312dc46.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Sep 1, 2026
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ The exact PR base did not have a successful artifact. Baseline uses the latest successful main measurement shown below.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.3 KiB+13 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−1 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.4 KiB+14 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded55.6 KiB55.6 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages11110 (0.0%)21
ClaudeTotal thread wire13.3 KiB13.3 KiB+4 B (+0.0%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB+1 B (+0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.5 KiB6.5 KiB+3 B (+0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB56.4 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages11110 (0.0%)21

Baseline: 85b656f · PR result: 312dc46 · 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.

Comment threadapps/mobile/src/features/threads/ThreadComposer.tsx
Comment threadapps/mobile/src/state/use-thread-composer-state.ts
Comment threadapps/mobile/src/lib/composerAttachmentUploadQueue.ts Outdated
Comment threadapps/mobile/src/lib/composerAttachmentUploadQueue.ts

@cursorcursorBot 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 ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 934f66d. Configure here.

Comment threadapps/mobile/src/state/use-composer-drafts.ts
@macroscopeapp

macroscopeappBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a substantial mobile workflow for automatic background attachment uploads, offline persistence, queued-message handling, and account-specific cloud draft restoration. It also changes the default composing behavior and touches authentication lifecycle code, so the scope and side effects require human review.

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

@macroscopeappmacroscopeappBot 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.

</antml the="" parameter="">

Posted via Macroscope — Effect Service Conventions

Comment threadapps/mobile/src/features/cloud/cloud-drafts.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Sep 1, 2026
Comment threadapps/mobile/src/state/use-thread-outbox-drain.ts Outdated
@juliusmarminge
juliusmarminge merged commit 9bc7a56 into mainSep 1, 2026
27 checks passed
@juliusmarminge
juliusmarminge deleted the feat/mobile-direct-uploads branch September 1, 2026 02:31
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 1, 2026
## What's Changed
* feat(mobile): upload attachments while composing by @juliusmarminge in pingdotgg/t3code#8978
* fix(chat): keep agent activity visible between actions by @maria-rcks in pingdotgg/t3code#8984
* fix(server): isolate remote web session cookies by @Bil0000 in pingdotgg/t3code#8085
* feat(pull-requests): link GitHub references in markdown by @maria-rcks in pingdotgg/t3code#8812
* perf(server): reduce frequency of full tool call output being loaded into memory from db by @t3dotgg in pingdotgg/t3code#8988
**Full Changelog**: pingdotgg/t3code@v0.0.38-nightly.20260901.1242...v0.0.38-nightly.20260901.1243
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.38-nightly.20260901.1243
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 1, 2026
## What's Changed
* Add mobile composer attachment menu with video support by @juliusmarminge in pingdotgg/t3code#8843
* fix(web): restore unified activity logs and composer banners by @t3dotgg in pingdotgg/t3code#8734
* fix(web): address composer banner review follow-ups by @juliusmarminge in pingdotgg/t3code#8850
* fix(web): widen sync banners and simplify the working timer by @juliusmarminge in pingdotgg/t3code#8855
* fix(preview): improve browser recording quality by @maria-rcks in pingdotgg/t3code#8839
* fix(web): mark pull request links as external by @juliusmarminge in pingdotgg/t3code#8856
* fix(mobile): replace Callstack glass with Expo glass by @juliusmarminge in pingdotgg/t3code#8862
* fix(server): skip IDE detection in Claude probes by @yashranaway in pingdotgg/t3code#8634
* chore(macroscope): review diagnostic overrides by @t3-code[bot] in pingdotgg/t3code#8917
* fix(contracts): accept CLI event origins by @nateEc in pingdotgg/t3code#8905
* fix(web): hide invalid slash skill completions by @nateEc in pingdotgg/t3code#8904
* fix(mobile): defer draft navigation until submission completes by @juliusmarminge in pingdotgg/t3code#8914
* chore: disable CodeRabbit review status by @juliusmarminge in pingdotgg/t3code#8933
* Delete app.json by @juliusmarminge in pingdotgg/t3code#8934
* fix(web): show scrollbar for wide markdown tables by @UtkarshUsername in pingdotgg/t3code#8868
* fix(mobile): shimmer active tool rows by @juliusmarminge in pingdotgg/t3code#8932
* chore(deps): bump Electron to 43.4.1 by @StiensWout in pingdotgg/t3code#8626
* fix(chat): smooth worktree setup status by @juliusmarminge in pingdotgg/t3code#8922
* feat(mobile): add video playback with native iOS controls by @juliusmarminge in pingdotgg/t3code#8919
* fix(web): prevent chat metadata overlap by @MatthewFeroz in pingdotgg/t3code#8851
* fix(server): preserve usage cache outside walked roots by @Lucenx9 in pingdotgg/t3code#8540
* feat(mobile): add native image and PDF previews by @juliusmarminge in pingdotgg/t3code#8959
* fix(server): allow long thread IDs in HTTP routes by @nateEc in pingdotgg/t3code#8898
* fix(shared): preserve Windows shell PATH priority by @UtkarshUsername in pingdotgg/t3code#8748
* fix(web): make WSL settings searchable by @UtkarshUsername in pingdotgg/t3code#8881
* feat(web): add expand/collapse all control to the files surface by @UtkarshUsername in pingdotgg/t3code#8889
* feat(mobile): upload attachments while composing by @juliusmarminge in pingdotgg/t3code#8978
* fix(chat): keep agent activity visible between actions by @maria-rcks in pingdotgg/t3code#8984
* fix(server): isolate remote web session cookies by @Bil0000 in pingdotgg/t3code#8085
* feat(pull-requests): link GitHub references in markdown by @maria-rcks in pingdotgg/t3code#8812
* perf(server): reduce frequency of full tool call output being loaded into memory from db by @t3dotgg in pingdotgg/t3code#8988
* feat(web): add pull request list filters by @maria-rcks in pingdotgg/t3code#8809
* feat(web): search individual settings by detail by @maria-rcks in pingdotgg/t3code#8831
* feat(client): render viewed images in work logs by @maria-rcks in pingdotgg/t3code#8936
* fix(client): use package import for markdown image helpers by @juliusmarminge in pingdotgg/t3code#9010
* test: remove static presentation snapshots by @t3-code[bot] in pingdotgg/t3code#9008
* perf(server): bound snapshot activity payload memory by @t3dotgg in pingdotgg/t3code#9000
* perf(server): cut idle CPU use and stop provider event leaks by @t3dotgg in pingdotgg/t3code#8187
* perf(server): scan only appended transcript bytes for usage summaries by @StiensWout in pingdotgg/t3code#9024
* perf(server): cut chatty tool-update frames by 90% by @Adamulek123 in pingdotgg/t3code#8368
* fix(server): settle threads server-side by @t3dotgg in pingdotgg/t3code#8600
* fix(clients): dedupe skills in composer menus by @Adamulek123 in pingdotgg/t3code#8043
* fix(server): stop OpenCode child sessions by @t3dotgg in pingdotgg/t3code#9005
* perf(web): defer pull request line stats until visible by @Adamulek123 in pingdotgg/t3code#6471
* perf(server): skip full-message reads while streaming by @t3dotgg in pingdotgg/t3code#9032
* perf(client-runtime): halve server config bootstrap traffic by @Adamulek123 in pingdotgg/t3code#8367
* fix(web): align un-settle banner action by @StiensWout in pingdotgg/t3code#9033
* fix(web): block type-to-focus behind open dialogs by @Lucenx9 in pingdotgg/t3code#8139
* feat(shortcuts): copy active thread reference by @maria-rcks in pingdotgg/t3code#8994
* fix(mobile): keep thread scroll bounds current after animations by @juliusmarminge in pingdotgg/t3code#9013
* fix(server): cache project favicon resolution by @willsheldon in pingdotgg/t3code#9080
* feat(claude): add Claude Fable 5.1 model by @q1 in pingdotgg/t3code#9078
* fix(preview): restore recording and macOS rendering after Electron 43 by @StiensWout in pingdotgg/t3code#9001
* feat(desktop): add configurable quit shortcut confirmation by @juliusmarminge in pingdotgg/t3code#9076
* feat(web): open project settings from thread menus by @SunkenInTime in pingdotgg/t3code#8925
* fix(chat): reuse one row for live activity by @maria-rcks in pingdotgg/t3code#9062
* feat(models): discover Claude models from remote manifest by @juliusmarminge in pingdotgg/t3code#9084
* Revert "fix(chat): reuse one row for live activity" by @maria-rcks in pingdotgg/t3code#9096
* fix(web): sync sidebar PR state from open panel by @t3-code[bot] in pingdotgg/t3code#9092
* fix(web): changing projects no longer creates a draft by @extoci in pingdotgg/t3code#9097
* fix(web): keep theme placeholder text dimmer than entered text by @flamboh in pingdotgg/t3code#9104
* fix(web): keep the selected environment when changing projects by @t3dotgg in pingdotgg/t3code#9102
## New Contributors
* @willsheldon made their first contribution in pingdotgg/t3code#9080
* @q1 made their first contribution in pingdotgg/t3code#9078
**Full Changelog**: pingdotgg/t3code@v0.0.37...v0.0.38
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(mobile): upload attachments while composing - #8978

Merged
juliusmarminge merged 3 commits into
mainfrom
feat/mobile-direct-uploads
Sep 1, 2026
Merged

feat(mobile): upload attachments while composing#8978
juliusmarminge merged 3 commits into
mainfrom
feat/mobile-direct-uploads

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 1, 2026

Copy link
Copy Markdown
Member

What Changed

Mobile uploads images and files over HTTP as soon as they are attached to a connected environment. Composer thumbnails show progress and retry, with at most three concurrent uploads. Send reuses completed uploads and refreshes expired ones from the local copy.

Local image data and owned files stay with drafts and queued messages. Cloud sign-out saves relay drafts and the outbox under their account, then restores them before that account reconnects. This uses the existing Expo file APIs and server endpoints; no new native dependency or rebuild is needed.

Why

Mobile previously waited until Send to upload files and sent images inline. Uploading while composing removes that wait without making drafts depend on connectivity. Local copies remain available offline and survive app restarts and sign-out. Older servers keep the inline-image fallback.

UI Changes

The demo opens a saved PDF after an offline app restart, then shows the composer after reconnecting. Both attachments uploaded before Send, and their server bytes matched the originals. The idle wait is omitted.

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/90e07f8e31875d08/offline-and-reconnected-demo.mp4

Before reconnectingAfter reconnecting, before Send
Offline composer retaining an image and PDFConnected composer with uploaded attachments still available locally
Local PDF preview after restarting offlineNative PDF preview opened from the local copy while offline

Validation

  • 96 focused tests pass for uploads, cancellation, retries, concurrency, draft persistence, outbox delivery, and account-specific restoration.
  • Mobile typecheck and lint of changed files pass.
  • iPhone 17 Pro Simulator, iOS 27: offline attachment creation, app restart, local PDF preview, and automatic upload on reconnect. Server files matched the original SHA-256 hashes.
  • Cloud sign-out retention was covered by storage tests; live cloud sign-out and Android were not exercised.

Checklist

  • Scope limited to mobile attachment uploads and local retention
  • Explained the change and why
  • Included screenshots of the offline and connected states
  • Included an interaction demo

Note

Medium Risk
Changes span cloud auth transitions, durable draft/outbox persistence, and send-time attachment preparation; regressions could lose drafts on sign-out or block sends incorrectly, but behavior is heavily covered by new tests.

Overview
Mobile composer attachments now upload in the background while you draft, instead of waiting until Send. A global worker (hosted next to the thread outbox drain) syncs draft attachments for connected environments into a bounded queue (max three concurrent transfers), persists upload IDs back onto drafts, and survives navigation away from the composer.

UI and send gating:ComposerAttachmentStrip shows per-thumbnail progress, percentage, and tap-to-retry on failure. Thread composer, new-task flow, and thread send paths block Send/Start when online until uploads finish or failures are retried/removed; offline queuing is unchanged.

Upload pipeline:prepareTurnAttachments gains abortable HTTP uploads with progress, image upload when the server supports attachmentUploads (temp cache file from dataUrl, with inline-image fallback on older servers), reuse/re-upload of expired pending IDs for both files and images, and stamped uploadedAttachmentId on image drafts.

Cloud account lifecycle: Sign-out runs removeCloudEnvironments, which archives relay-environment drafts and queued outbox messages under the owning account before removing environments; sign-in restores them and reconciles delivered messages against the archive. Attachment file/upload cleanup respects signed-out snapshots so local bytes are not deleted prematurely.

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

Note

Add background attachment uploads to mobile composer with cloud draft preservation

  • Introduces a bounded-concurrency upload queue (max 3) in composerAttachmentUploadQueue.ts that uploads images and files over HTTP when the server supports it, with per-attachment progress, abort, and retry
  • Adds a long-lived worker hook useComposerAttachmentUploadWorker in composer-attachment-uploads.ts that reconciles the queue against current drafts and connection state, persists uploaded references back into drafts, and keeps previews usable during transfers
  • Stamps uploadedAttachmentId/uploadEnvironmentId onto image drafts in attachmentUpload.ts and composer-image-schema.ts, enabling reuse of previously uploaded ids on the same environment and reupload when expired
  • Blocks sending in use-thread-composer-state.ts, ThreadComposer.tsx, and NewTaskDraftScreen.tsx while uploads are in-progress or failed; thumbnails in ComposerAttachmentStrip.tsx show progress overlays and retry controls
  • On cloud sign-out, archives drafts and queued messages per-account in use-composer-drafts.ts via archiveCloudComposerDrafts; on sign-in, restores them via restoreCloudComposerDrafts in CloudAuthProvider.tsx
  • Risk: prepareTurnAttachments in attachmentUpload.ts now validates image mime types against PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPES and throws on unsupported types; older servers without upload support continue to receive inline images, but any caller passing unsupported image types will now fail at upload time

Macroscope summarized 312dc46.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Sep 1, 2026
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ The exact PR base did not have a successful artifact. Baseline uses the latest successful main measurement shown below.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.3 KiB+13 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−1 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.4 KiB+14 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded55.6 KiB55.6 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages11110 (0.0%)21
ClaudeTotal thread wire13.3 KiB13.3 KiB+4 B (+0.0%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB+1 B (+0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.5 KiB6.5 KiB+3 B (+0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB56.4 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages11110 (0.0%)21

Baseline: 85b656f · PR result: 312dc46 · 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.

Comment threadapps/mobile/src/features/threads/ThreadComposer.tsx
Comment threadapps/mobile/src/state/use-thread-composer-state.ts
Comment threadapps/mobile/src/lib/composerAttachmentUploadQueue.ts Outdated
Comment threadapps/mobile/src/lib/composerAttachmentUploadQueue.ts

@cursorcursorBot 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 ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 934f66d. Configure here.

Comment threadapps/mobile/src/state/use-composer-drafts.ts
@macroscopeapp

macroscopeappBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a substantial mobile workflow for automatic background attachment uploads, offline persistence, queued-message handling, and account-specific cloud draft restoration. It also changes the default composing behavior and touches authentication lifecycle code, so the scope and side effects require human review.

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

@macroscopeappmacroscopeappBot 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.

</antml the="" parameter="">

Posted via Macroscope — Effect Service Conventions

Comment threadapps/mobile/src/features/cloud/cloud-drafts.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Sep 1, 2026
Comment threadapps/mobile/src/state/use-thread-outbox-drain.ts Outdated
@juliusmarminge
juliusmarminge merged commit 9bc7a56 into mainSep 1, 2026
27 checks passed
@juliusmarminge
juliusmarminge deleted the feat/mobile-direct-uploads branch September 1, 2026 02:31
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 1, 2026
## What's Changed
* feat(mobile): upload attachments while composing by @juliusmarminge in pingdotgg/t3code#8978
* fix(chat): keep agent activity visible between actions by @maria-rcks in pingdotgg/t3code#8984
* fix(server): isolate remote web session cookies by @Bil0000 in pingdotgg/t3code#8085
* feat(pull-requests): link GitHub references in markdown by @maria-rcks in pingdotgg/t3code#8812
* perf(server): reduce frequency of full tool call output being loaded into memory from db by @t3dotgg in pingdotgg/t3code#8988
**Full Changelog**: pingdotgg/t3code@v0.0.38-nightly.20260901.1242...v0.0.38-nightly.20260901.1243
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.38-nightly.20260901.1243
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 1, 2026
## What's Changed
* Add mobile composer attachment menu with video support by @juliusmarminge in pingdotgg/t3code#8843
* fix(web): restore unified activity logs and composer banners by @t3dotgg in pingdotgg/t3code#8734
* fix(web): address composer banner review follow-ups by @juliusmarminge in pingdotgg/t3code#8850
* fix(web): widen sync banners and simplify the working timer by @juliusmarminge in pingdotgg/t3code#8855
* fix(preview): improve browser recording quality by @maria-rcks in pingdotgg/t3code#8839
* fix(web): mark pull request links as external by @juliusmarminge in pingdotgg/t3code#8856
* fix(mobile): replace Callstack glass with Expo glass by @juliusmarminge in pingdotgg/t3code#8862
* fix(server): skip IDE detection in Claude probes by @yashranaway in pingdotgg/t3code#8634
* chore(macroscope): review diagnostic overrides by @t3-code[bot] in pingdotgg/t3code#8917
* fix(contracts): accept CLI event origins by @nateEc in pingdotgg/t3code#8905
* fix(web): hide invalid slash skill completions by @nateEc in pingdotgg/t3code#8904
* fix(mobile): defer draft navigation until submission completes by @juliusmarminge in pingdotgg/t3code#8914
* chore: disable CodeRabbit review status by @juliusmarminge in pingdotgg/t3code#8933
* Delete app.json by @juliusmarminge in pingdotgg/t3code#8934
* fix(web): show scrollbar for wide markdown tables by @UtkarshUsername in pingdotgg/t3code#8868
* fix(mobile): shimmer active tool rows by @juliusmarminge in pingdotgg/t3code#8932
* chore(deps): bump Electron to 43.4.1 by @StiensWout in pingdotgg/t3code#8626
* fix(chat): smooth worktree setup status by @juliusmarminge in pingdotgg/t3code#8922
* feat(mobile): add video playback with native iOS controls by @juliusmarminge in pingdotgg/t3code#8919
* fix(web): prevent chat metadata overlap by @MatthewFeroz in pingdotgg/t3code#8851
* fix(server): preserve usage cache outside walked roots by @Lucenx9 in pingdotgg/t3code#8540
* feat(mobile): add native image and PDF previews by @juliusmarminge in pingdotgg/t3code#8959
* fix(server): allow long thread IDs in HTTP routes by @nateEc in pingdotgg/t3code#8898
* fix(shared): preserve Windows shell PATH priority by @UtkarshUsername in pingdotgg/t3code#8748
* fix(web): make WSL settings searchable by @UtkarshUsername in pingdotgg/t3code#8881
* feat(web): add expand/collapse all control to the files surface by @UtkarshUsername in pingdotgg/t3code#8889
* feat(mobile): upload attachments while composing by @juliusmarminge in pingdotgg/t3code#8978
* fix(chat): keep agent activity visible between actions by @maria-rcks in pingdotgg/t3code#8984
* fix(server): isolate remote web session cookies by @Bil0000 in pingdotgg/t3code#8085
* feat(pull-requests): link GitHub references in markdown by @maria-rcks in pingdotgg/t3code#8812
* perf(server): reduce frequency of full tool call output being loaded into memory from db by @t3dotgg in pingdotgg/t3code#8988
* feat(web): add pull request list filters by @maria-rcks in pingdotgg/t3code#8809
* feat(web): search individual settings by detail by @maria-rcks in pingdotgg/t3code#8831
* feat(client): render viewed images in work logs by @maria-rcks in pingdotgg/t3code#8936
* fix(client): use package import for markdown image helpers by @juliusmarminge in pingdotgg/t3code#9010
* test: remove static presentation snapshots by @t3-code[bot] in pingdotgg/t3code#9008
* perf(server): bound snapshot activity payload memory by @t3dotgg in pingdotgg/t3code#9000
* perf(server): cut idle CPU use and stop provider event leaks by @t3dotgg in pingdotgg/t3code#8187
* perf(server): scan only appended transcript bytes for usage summaries by @StiensWout in pingdotgg/t3code#9024
* perf(server): cut chatty tool-update frames by 90% by @Adamulek123 in pingdotgg/t3code#8368
* fix(server): settle threads server-side by @t3dotgg in pingdotgg/t3code#8600
* fix(clients): dedupe skills in composer menus by @Adamulek123 in pingdotgg/t3code#8043
* fix(server): stop OpenCode child sessions by @t3dotgg in pingdotgg/t3code#9005
* perf(web): defer pull request line stats until visible by @Adamulek123 in pingdotgg/t3code#6471
* perf(server): skip full-message reads while streaming by @t3dotgg in pingdotgg/t3code#9032
* perf(client-runtime): halve server config bootstrap traffic by @Adamulek123 in pingdotgg/t3code#8367
* fix(web): align un-settle banner action by @StiensWout in pingdotgg/t3code#9033
* fix(web): block type-to-focus behind open dialogs by @Lucenx9 in pingdotgg/t3code#8139
* feat(shortcuts): copy active thread reference by @maria-rcks in pingdotgg/t3code#8994
* fix(mobile): keep thread scroll bounds current after animations by @juliusmarminge in pingdotgg/t3code#9013
* fix(server): cache project favicon resolution by @willsheldon in pingdotgg/t3code#9080
* feat(claude): add Claude Fable 5.1 model by @q1 in pingdotgg/t3code#9078
* fix(preview): restore recording and macOS rendering after Electron 43 by @StiensWout in pingdotgg/t3code#9001
* feat(desktop): add configurable quit shortcut confirmation by @juliusmarminge in pingdotgg/t3code#9076
* feat(web): open project settings from thread menus by @SunkenInTime in pingdotgg/t3code#8925
* fix(chat): reuse one row for live activity by @maria-rcks in pingdotgg/t3code#9062
* feat(models): discover Claude models from remote manifest by @juliusmarminge in pingdotgg/t3code#9084
* Revert "fix(chat): reuse one row for live activity" by @maria-rcks in pingdotgg/t3code#9096
* fix(web): sync sidebar PR state from open panel by @t3-code[bot] in pingdotgg/t3code#9092
* fix(web): changing projects no longer creates a draft by @extoci in pingdotgg/t3code#9097
* fix(web): keep theme placeholder text dimmer than entered text by @flamboh in pingdotgg/t3code#9104
* fix(web): keep the selected environment when changing projects by @t3dotgg in pingdotgg/t3code#9102
## New Contributors
* @willsheldon made their first contribution in pingdotgg/t3code#9080
* @q1 made their first contribution in pingdotgg/t3code#9078
**Full Changelog**: pingdotgg/t3code@v0.0.37...v0.0.38
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(mobile): upload attachments while composing - #8978

Merged
juliusmarminge merged 3 commits into
mainfrom
feat/mobile-direct-uploads
Sep 1, 2026
Merged

feat(mobile): upload attachments while composing#8978
juliusmarminge merged 3 commits into
mainfrom
feat/mobile-direct-uploads

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 1, 2026

Copy link
Copy Markdown
Member

What Changed

Mobile uploads images and files over HTTP as soon as they are attached to a connected environment. Composer thumbnails show progress and retry, with at most three concurrent uploads. Send reuses completed uploads and refreshes expired ones from the local copy.

Local image data and owned files stay with drafts and queued messages. Cloud sign-out saves relay drafts and the outbox under their account, then restores them before that account reconnects. This uses the existing Expo file APIs and server endpoints; no new native dependency or rebuild is needed.

Why

Mobile previously waited until Send to upload files and sent images inline. Uploading while composing removes that wait without making drafts depend on connectivity. Local copies remain available offline and survive app restarts and sign-out. Older servers keep the inline-image fallback.

UI Changes

The demo opens a saved PDF after an offline app restart, then shows the composer after reconnecting. Both attachments uploaded before Send, and their server bytes matched the originals. The idle wait is omitted.

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/90e07f8e31875d08/offline-and-reconnected-demo.mp4

Before reconnectingAfter reconnecting, before Send
Offline composer retaining an image and PDFConnected composer with uploaded attachments still available locally
Local PDF preview after restarting offlineNative PDF preview opened from the local copy while offline

Validation

  • 96 focused tests pass for uploads, cancellation, retries, concurrency, draft persistence, outbox delivery, and account-specific restoration.
  • Mobile typecheck and lint of changed files pass.
  • iPhone 17 Pro Simulator, iOS 27: offline attachment creation, app restart, local PDF preview, and automatic upload on reconnect. Server files matched the original SHA-256 hashes.
  • Cloud sign-out retention was covered by storage tests; live cloud sign-out and Android were not exercised.

Checklist

  • Scope limited to mobile attachment uploads and local retention
  • Explained the change and why
  • Included screenshots of the offline and connected states
  • Included an interaction demo

Note

Medium Risk
Changes span cloud auth transitions, durable draft/outbox persistence, and send-time attachment preparation; regressions could lose drafts on sign-out or block sends incorrectly, but behavior is heavily covered by new tests.

Overview
Mobile composer attachments now upload in the background while you draft, instead of waiting until Send. A global worker (hosted next to the thread outbox drain) syncs draft attachments for connected environments into a bounded queue (max three concurrent transfers), persists upload IDs back onto drafts, and survives navigation away from the composer.

UI and send gating:ComposerAttachmentStrip shows per-thumbnail progress, percentage, and tap-to-retry on failure. Thread composer, new-task flow, and thread send paths block Send/Start when online until uploads finish or failures are retried/removed; offline queuing is unchanged.

Upload pipeline:prepareTurnAttachments gains abortable HTTP uploads with progress, image upload when the server supports attachmentUploads (temp cache file from dataUrl, with inline-image fallback on older servers), reuse/re-upload of expired pending IDs for both files and images, and stamped uploadedAttachmentId on image drafts.

Cloud account lifecycle: Sign-out runs removeCloudEnvironments, which archives relay-environment drafts and queued outbox messages under the owning account before removing environments; sign-in restores them and reconciles delivered messages against the archive. Attachment file/upload cleanup respects signed-out snapshots so local bytes are not deleted prematurely.

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

Note

Add background attachment uploads to mobile composer with cloud draft preservation

  • Introduces a bounded-concurrency upload queue (max 3) in composerAttachmentUploadQueue.ts that uploads images and files over HTTP when the server supports it, with per-attachment progress, abort, and retry
  • Adds a long-lived worker hook useComposerAttachmentUploadWorker in composer-attachment-uploads.ts that reconciles the queue against current drafts and connection state, persists uploaded references back into drafts, and keeps previews usable during transfers
  • Stamps uploadedAttachmentId/uploadEnvironmentId onto image drafts in attachmentUpload.ts and composer-image-schema.ts, enabling reuse of previously uploaded ids on the same environment and reupload when expired
  • Blocks sending in use-thread-composer-state.ts, ThreadComposer.tsx, and NewTaskDraftScreen.tsx while uploads are in-progress or failed; thumbnails in ComposerAttachmentStrip.tsx show progress overlays and retry controls
  • On cloud sign-out, archives drafts and queued messages per-account in use-composer-drafts.ts via archiveCloudComposerDrafts; on sign-in, restores them via restoreCloudComposerDrafts in CloudAuthProvider.tsx
  • Risk: prepareTurnAttachments in attachmentUpload.ts now validates image mime types against PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPES and throws on unsupported types; older servers without upload support continue to receive inline images, but any caller passing unsupported image types will now fail at upload time

Macroscope summarized 312dc46.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Sep 1, 2026
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ The exact PR base did not have a successful artifact. Baseline uses the latest successful main measurement shown below.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.3 KiB+13 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−1 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.4 KiB+14 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded55.6 KiB55.6 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages11110 (0.0%)21
ClaudeTotal thread wire13.3 KiB13.3 KiB+4 B (+0.0%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB+1 B (+0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.5 KiB6.5 KiB+3 B (+0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB56.4 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages11110 (0.0%)21

Baseline: 85b656f · PR result: 312dc46 · 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.

Comment threadapps/mobile/src/features/threads/ThreadComposer.tsx
Comment threadapps/mobile/src/state/use-thread-composer-state.ts
Comment threadapps/mobile/src/lib/composerAttachmentUploadQueue.ts Outdated
Comment threadapps/mobile/src/lib/composerAttachmentUploadQueue.ts

@cursorcursorBot 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 ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 934f66d. Configure here.

Comment threadapps/mobile/src/state/use-composer-drafts.ts
@macroscopeapp

macroscopeappBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a substantial mobile workflow for automatic background attachment uploads, offline persistence, queued-message handling, and account-specific cloud draft restoration. It also changes the default composing behavior and touches authentication lifecycle code, so the scope and side effects require human review.

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

@macroscopeappmacroscopeappBot 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.

</antml the="" parameter="">

Posted via Macroscope — Effect Service Conventions

Comment threadapps/mobile/src/features/cloud/cloud-drafts.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Sep 1, 2026
Comment threadapps/mobile/src/state/use-thread-outbox-drain.ts Outdated
@juliusmarminge
juliusmarminge merged commit 9bc7a56 into mainSep 1, 2026
27 checks passed
@juliusmarminge
juliusmarminge deleted the feat/mobile-direct-uploads branch September 1, 2026 02:31
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 1, 2026
## What's Changed
* feat(mobile): upload attachments while composing by @juliusmarminge in pingdotgg/t3code#8978
* fix(chat): keep agent activity visible between actions by @maria-rcks in pingdotgg/t3code#8984
* fix(server): isolate remote web session cookies by @Bil0000 in pingdotgg/t3code#8085
* feat(pull-requests): link GitHub references in markdown by @maria-rcks in pingdotgg/t3code#8812
* perf(server): reduce frequency of full tool call output being loaded into memory from db by @t3dotgg in pingdotgg/t3code#8988
**Full Changelog**: pingdotgg/t3code@v0.0.38-nightly.20260901.1242...v0.0.38-nightly.20260901.1243
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.38-nightly.20260901.1243
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 1, 2026
## What's Changed
* Add mobile composer attachment menu with video support by @juliusmarminge in pingdotgg/t3code#8843
* fix(web): restore unified activity logs and composer banners by @t3dotgg in pingdotgg/t3code#8734
* fix(web): address composer banner review follow-ups by @juliusmarminge in pingdotgg/t3code#8850
* fix(web): widen sync banners and simplify the working timer by @juliusmarminge in pingdotgg/t3code#8855
* fix(preview): improve browser recording quality by @maria-rcks in pingdotgg/t3code#8839
* fix(web): mark pull request links as external by @juliusmarminge in pingdotgg/t3code#8856
* fix(mobile): replace Callstack glass with Expo glass by @juliusmarminge in pingdotgg/t3code#8862
* fix(server): skip IDE detection in Claude probes by @yashranaway in pingdotgg/t3code#8634
* chore(macroscope): review diagnostic overrides by @t3-code[bot] in pingdotgg/t3code#8917
* fix(contracts): accept CLI event origins by @nateEc in pingdotgg/t3code#8905
* fix(web): hide invalid slash skill completions by @nateEc in pingdotgg/t3code#8904
* fix(mobile): defer draft navigation until submission completes by @juliusmarminge in pingdotgg/t3code#8914
* chore: disable CodeRabbit review status by @juliusmarminge in pingdotgg/t3code#8933
* Delete app.json by @juliusmarminge in pingdotgg/t3code#8934
* fix(web): show scrollbar for wide markdown tables by @UtkarshUsername in pingdotgg/t3code#8868
* fix(mobile): shimmer active tool rows by @juliusmarminge in pingdotgg/t3code#8932
* chore(deps): bump Electron to 43.4.1 by @StiensWout in pingdotgg/t3code#8626
* fix(chat): smooth worktree setup status by @juliusmarminge in pingdotgg/t3code#8922
* feat(mobile): add video playback with native iOS controls by @juliusmarminge in pingdotgg/t3code#8919
* fix(web): prevent chat metadata overlap by @MatthewFeroz in pingdotgg/t3code#8851
* fix(server): preserve usage cache outside walked roots by @Lucenx9 in pingdotgg/t3code#8540
* feat(mobile): add native image and PDF previews by @juliusmarminge in pingdotgg/t3code#8959
* fix(server): allow long thread IDs in HTTP routes by @nateEc in pingdotgg/t3code#8898
* fix(shared): preserve Windows shell PATH priority by @UtkarshUsername in pingdotgg/t3code#8748
* fix(web): make WSL settings searchable by @UtkarshUsername in pingdotgg/t3code#8881
* feat(web): add expand/collapse all control to the files surface by @UtkarshUsername in pingdotgg/t3code#8889
* feat(mobile): upload attachments while composing by @juliusmarminge in pingdotgg/t3code#8978
* fix(chat): keep agent activity visible between actions by @maria-rcks in pingdotgg/t3code#8984
* fix(server): isolate remote web session cookies by @Bil0000 in pingdotgg/t3code#8085
* feat(pull-requests): link GitHub references in markdown by @maria-rcks in pingdotgg/t3code#8812
* perf(server): reduce frequency of full tool call output being loaded into memory from db by @t3dotgg in pingdotgg/t3code#8988
* feat(web): add pull request list filters by @maria-rcks in pingdotgg/t3code#8809
* feat(web): search individual settings by detail by @maria-rcks in pingdotgg/t3code#8831
* feat(client): render viewed images in work logs by @maria-rcks in pingdotgg/t3code#8936
* fix(client): use package import for markdown image helpers by @juliusmarminge in pingdotgg/t3code#9010
* test: remove static presentation snapshots by @t3-code[bot] in pingdotgg/t3code#9008
* perf(server): bound snapshot activity payload memory by @t3dotgg in pingdotgg/t3code#9000
* perf(server): cut idle CPU use and stop provider event leaks by @t3dotgg in pingdotgg/t3code#8187
* perf(server): scan only appended transcript bytes for usage summaries by @StiensWout in pingdotgg/t3code#9024
* perf(server): cut chatty tool-update frames by 90% by @Adamulek123 in pingdotgg/t3code#8368
* fix(server): settle threads server-side by @t3dotgg in pingdotgg/t3code#8600
* fix(clients): dedupe skills in composer menus by @Adamulek123 in pingdotgg/t3code#8043
* fix(server): stop OpenCode child sessions by @t3dotgg in pingdotgg/t3code#9005
* perf(web): defer pull request line stats until visible by @Adamulek123 in pingdotgg/t3code#6471
* perf(server): skip full-message reads while streaming by @t3dotgg in pingdotgg/t3code#9032
* perf(client-runtime): halve server config bootstrap traffic by @Adamulek123 in pingdotgg/t3code#8367
* fix(web): align un-settle banner action by @StiensWout in pingdotgg/t3code#9033
* fix(web): block type-to-focus behind open dialogs by @Lucenx9 in pingdotgg/t3code#8139
* feat(shortcuts): copy active thread reference by @maria-rcks in pingdotgg/t3code#8994
* fix(mobile): keep thread scroll bounds current after animations by @juliusmarminge in pingdotgg/t3code#9013
* fix(server): cache project favicon resolution by @willsheldon in pingdotgg/t3code#9080
* feat(claude): add Claude Fable 5.1 model by @q1 in pingdotgg/t3code#9078
* fix(preview): restore recording and macOS rendering after Electron 43 by @StiensWout in pingdotgg/t3code#9001
* feat(desktop): add configurable quit shortcut confirmation by @juliusmarminge in pingdotgg/t3code#9076
* feat(web): open project settings from thread menus by @SunkenInTime in pingdotgg/t3code#8925
* fix(chat): reuse one row for live activity by @maria-rcks in pingdotgg/t3code#9062
* feat(models): discover Claude models from remote manifest by @juliusmarminge in pingdotgg/t3code#9084
* Revert "fix(chat): reuse one row for live activity" by @maria-rcks in pingdotgg/t3code#9096
* fix(web): sync sidebar PR state from open panel by @t3-code[bot] in pingdotgg/t3code#9092
* fix(web): changing projects no longer creates a draft by @extoci in pingdotgg/t3code#9097
* fix(web): keep theme placeholder text dimmer than entered text by @flamboh in pingdotgg/t3code#9104
* fix(web): keep the selected environment when changing projects by @t3dotgg in pingdotgg/t3code#9102
## New Contributors
* @willsheldon made their first contribution in pingdotgg/t3code#9080
* @q1 made their first contribution in pingdotgg/t3code#9078
**Full Changelog**: pingdotgg/t3code@v0.0.37...v0.0.38
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(mobile): upload attachments while composing - #8978

Merged
juliusmarminge merged 3 commits into
mainfrom
feat/mobile-direct-uploads
Sep 1, 2026
Merged

feat(mobile): upload attachments while composing#8978
juliusmarminge merged 3 commits into
mainfrom
feat/mobile-direct-uploads

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 1, 2026

Copy link
Copy Markdown
Member

What Changed

Mobile uploads images and files over HTTP as soon as they are attached to a connected environment. Composer thumbnails show progress and retry, with at most three concurrent uploads. Send reuses completed uploads and refreshes expired ones from the local copy.

Local image data and owned files stay with drafts and queued messages. Cloud sign-out saves relay drafts and the outbox under their account, then restores them before that account reconnects. This uses the existing Expo file APIs and server endpoints; no new native dependency or rebuild is needed.

Why

Mobile previously waited until Send to upload files and sent images inline. Uploading while composing removes that wait without making drafts depend on connectivity. Local copies remain available offline and survive app restarts and sign-out. Older servers keep the inline-image fallback.

UI Changes

The demo opens a saved PDF after an offline app restart, then shows the composer after reconnecting. Both attachments uploaded before Send, and their server bytes matched the originals. The idle wait is omitted.

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/90e07f8e31875d08/offline-and-reconnected-demo.mp4

Before reconnectingAfter reconnecting, before Send
Offline composer retaining an image and PDFConnected composer with uploaded attachments still available locally
Local PDF preview after restarting offlineNative PDF preview opened from the local copy while offline

Validation

  • 96 focused tests pass for uploads, cancellation, retries, concurrency, draft persistence, outbox delivery, and account-specific restoration.
  • Mobile typecheck and lint of changed files pass.
  • iPhone 17 Pro Simulator, iOS 27: offline attachment creation, app restart, local PDF preview, and automatic upload on reconnect. Server files matched the original SHA-256 hashes.
  • Cloud sign-out retention was covered by storage tests; live cloud sign-out and Android were not exercised.

Checklist

  • Scope limited to mobile attachment uploads and local retention
  • Explained the change and why
  • Included screenshots of the offline and connected states
  • Included an interaction demo

Note

Medium Risk
Changes span cloud auth transitions, durable draft/outbox persistence, and send-time attachment preparation; regressions could lose drafts on sign-out or block sends incorrectly, but behavior is heavily covered by new tests.

Overview
Mobile composer attachments now upload in the background while you draft, instead of waiting until Send. A global worker (hosted next to the thread outbox drain) syncs draft attachments for connected environments into a bounded queue (max three concurrent transfers), persists upload IDs back onto drafts, and survives navigation away from the composer.

UI and send gating:ComposerAttachmentStrip shows per-thumbnail progress, percentage, and tap-to-retry on failure. Thread composer, new-task flow, and thread send paths block Send/Start when online until uploads finish or failures are retried/removed; offline queuing is unchanged.

Upload pipeline:prepareTurnAttachments gains abortable HTTP uploads with progress, image upload when the server supports attachmentUploads (temp cache file from dataUrl, with inline-image fallback on older servers), reuse/re-upload of expired pending IDs for both files and images, and stamped uploadedAttachmentId on image drafts.

Cloud account lifecycle: Sign-out runs removeCloudEnvironments, which archives relay-environment drafts and queued outbox messages under the owning account before removing environments; sign-in restores them and reconciles delivered messages against the archive. Attachment file/upload cleanup respects signed-out snapshots so local bytes are not deleted prematurely.

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

Note

Add background attachment uploads to mobile composer with cloud draft preservation

  • Introduces a bounded-concurrency upload queue (max 3) in composerAttachmentUploadQueue.ts that uploads images and files over HTTP when the server supports it, with per-attachment progress, abort, and retry
  • Adds a long-lived worker hook useComposerAttachmentUploadWorker in composer-attachment-uploads.ts that reconciles the queue against current drafts and connection state, persists uploaded references back into drafts, and keeps previews usable during transfers
  • Stamps uploadedAttachmentId/uploadEnvironmentId onto image drafts in attachmentUpload.ts and composer-image-schema.ts, enabling reuse of previously uploaded ids on the same environment and reupload when expired
  • Blocks sending in use-thread-composer-state.ts, ThreadComposer.tsx, and NewTaskDraftScreen.tsx while uploads are in-progress or failed; thumbnails in ComposerAttachmentStrip.tsx show progress overlays and retry controls
  • On cloud sign-out, archives drafts and queued messages per-account in use-composer-drafts.ts via archiveCloudComposerDrafts; on sign-in, restores them via restoreCloudComposerDrafts in CloudAuthProvider.tsx
  • Risk: prepareTurnAttachments in attachmentUpload.ts now validates image mime types against PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPES and throws on unsupported types; older servers without upload support continue to receive inline images, but any caller passing unsupported image types will now fail at upload time

Macroscope summarized 312dc46.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Sep 1, 2026
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ The exact PR base did not have a successful artifact. Baseline uses the latest successful main measurement shown below.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.3 KiB+13 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−1 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.4 KiB+14 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded55.6 KiB55.6 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages11110 (0.0%)21
ClaudeTotal thread wire13.3 KiB13.3 KiB+4 B (+0.0%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB+1 B (+0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.5 KiB6.5 KiB+3 B (+0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB56.4 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages11110 (0.0%)21

Baseline: 85b656f · PR result: 312dc46 · 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.

Comment threadapps/mobile/src/features/threads/ThreadComposer.tsx
Comment threadapps/mobile/src/state/use-thread-composer-state.ts
Comment threadapps/mobile/src/lib/composerAttachmentUploadQueue.ts Outdated
Comment threadapps/mobile/src/lib/composerAttachmentUploadQueue.ts

@cursorcursorBot 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 ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 934f66d. Configure here.

Comment threadapps/mobile/src/state/use-composer-drafts.ts
@macroscopeapp

macroscopeappBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a substantial mobile workflow for automatic background attachment uploads, offline persistence, queued-message handling, and account-specific cloud draft restoration. It also changes the default composing behavior and touches authentication lifecycle code, so the scope and side effects require human review.

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

@macroscopeappmacroscopeappBot 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.

</antml the="" parameter="">

Posted via Macroscope — Effect Service Conventions

Comment threadapps/mobile/src/features/cloud/cloud-drafts.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Sep 1, 2026
Comment threadapps/mobile/src/state/use-thread-outbox-drain.ts Outdated
@juliusmarminge
juliusmarminge merged commit 9bc7a56 into mainSep 1, 2026
27 checks passed
@juliusmarminge
juliusmarminge deleted the feat/mobile-direct-uploads branch September 1, 2026 02:31
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 1, 2026
## What's Changed
* feat(mobile): upload attachments while composing by @juliusmarminge in pingdotgg/t3code#8978
* fix(chat): keep agent activity visible between actions by @maria-rcks in pingdotgg/t3code#8984
* fix(server): isolate remote web session cookies by @Bil0000 in pingdotgg/t3code#8085
* feat(pull-requests): link GitHub references in markdown by @maria-rcks in pingdotgg/t3code#8812
* perf(server): reduce frequency of full tool call output being loaded into memory from db by @t3dotgg in pingdotgg/t3code#8988
**Full Changelog**: pingdotgg/t3code@v0.0.38-nightly.20260901.1242...v0.0.38-nightly.20260901.1243
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.38-nightly.20260901.1243
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 1, 2026
## What's Changed
* Add mobile composer attachment menu with video support by @juliusmarminge in pingdotgg/t3code#8843
* fix(web): restore unified activity logs and composer banners by @t3dotgg in pingdotgg/t3code#8734
* fix(web): address composer banner review follow-ups by @juliusmarminge in pingdotgg/t3code#8850
* fix(web): widen sync banners and simplify the working timer by @juliusmarminge in pingdotgg/t3code#8855
* fix(preview): improve browser recording quality by @maria-rcks in pingdotgg/t3code#8839
* fix(web): mark pull request links as external by @juliusmarminge in pingdotgg/t3code#8856
* fix(mobile): replace Callstack glass with Expo glass by @juliusmarminge in pingdotgg/t3code#8862
* fix(server): skip IDE detection in Claude probes by @yashranaway in pingdotgg/t3code#8634
* chore(macroscope): review diagnostic overrides by @t3-code[bot] in pingdotgg/t3code#8917
* fix(contracts): accept CLI event origins by @nateEc in pingdotgg/t3code#8905
* fix(web): hide invalid slash skill completions by @nateEc in pingdotgg/t3code#8904
* fix(mobile): defer draft navigation until submission completes by @juliusmarminge in pingdotgg/t3code#8914
* chore: disable CodeRabbit review status by @juliusmarminge in pingdotgg/t3code#8933
* Delete app.json by @juliusmarminge in pingdotgg/t3code#8934
* fix(web): show scrollbar for wide markdown tables by @UtkarshUsername in pingdotgg/t3code#8868
* fix(mobile): shimmer active tool rows by @juliusmarminge in pingdotgg/t3code#8932
* chore(deps): bump Electron to 43.4.1 by @StiensWout in pingdotgg/t3code#8626
* fix(chat): smooth worktree setup status by @juliusmarminge in pingdotgg/t3code#8922
* feat(mobile): add video playback with native iOS controls by @juliusmarminge in pingdotgg/t3code#8919
* fix(web): prevent chat metadata overlap by @MatthewFeroz in pingdotgg/t3code#8851
* fix(server): preserve usage cache outside walked roots by @Lucenx9 in pingdotgg/t3code#8540
* feat(mobile): add native image and PDF previews by @juliusmarminge in pingdotgg/t3code#8959
* fix(server): allow long thread IDs in HTTP routes by @nateEc in pingdotgg/t3code#8898
* fix(shared): preserve Windows shell PATH priority by @UtkarshUsername in pingdotgg/t3code#8748
* fix(web): make WSL settings searchable by @UtkarshUsername in pingdotgg/t3code#8881
* feat(web): add expand/collapse all control to the files surface by @UtkarshUsername in pingdotgg/t3code#8889
* feat(mobile): upload attachments while composing by @juliusmarminge in pingdotgg/t3code#8978
* fix(chat): keep agent activity visible between actions by @maria-rcks in pingdotgg/t3code#8984
* fix(server): isolate remote web session cookies by @Bil0000 in pingdotgg/t3code#8085
* feat(pull-requests): link GitHub references in markdown by @maria-rcks in pingdotgg/t3code#8812
* perf(server): reduce frequency of full tool call output being loaded into memory from db by @t3dotgg in pingdotgg/t3code#8988
* feat(web): add pull request list filters by @maria-rcks in pingdotgg/t3code#8809
* feat(web): search individual settings by detail by @maria-rcks in pingdotgg/t3code#8831
* feat(client): render viewed images in work logs by @maria-rcks in pingdotgg/t3code#8936
* fix(client): use package import for markdown image helpers by @juliusmarminge in pingdotgg/t3code#9010
* test: remove static presentation snapshots by @t3-code[bot] in pingdotgg/t3code#9008
* perf(server): bound snapshot activity payload memory by @t3dotgg in pingdotgg/t3code#9000
* perf(server): cut idle CPU use and stop provider event leaks by @t3dotgg in pingdotgg/t3code#8187
* perf(server): scan only appended transcript bytes for usage summaries by @StiensWout in pingdotgg/t3code#9024
* perf(server): cut chatty tool-update frames by 90% by @Adamulek123 in pingdotgg/t3code#8368
* fix(server): settle threads server-side by @t3dotgg in pingdotgg/t3code#8600
* fix(clients): dedupe skills in composer menus by @Adamulek123 in pingdotgg/t3code#8043
* fix(server): stop OpenCode child sessions by @t3dotgg in pingdotgg/t3code#9005
* perf(web): defer pull request line stats until visible by @Adamulek123 in pingdotgg/t3code#6471
* perf(server): skip full-message reads while streaming by @t3dotgg in pingdotgg/t3code#9032
* perf(client-runtime): halve server config bootstrap traffic by @Adamulek123 in pingdotgg/t3code#8367
* fix(web): align un-settle banner action by @StiensWout in pingdotgg/t3code#9033
* fix(web): block type-to-focus behind open dialogs by @Lucenx9 in pingdotgg/t3code#8139
* feat(shortcuts): copy active thread reference by @maria-rcks in pingdotgg/t3code#8994
* fix(mobile): keep thread scroll bounds current after animations by @juliusmarminge in pingdotgg/t3code#9013
* fix(server): cache project favicon resolution by @willsheldon in pingdotgg/t3code#9080
* feat(claude): add Claude Fable 5.1 model by @q1 in pingdotgg/t3code#9078
* fix(preview): restore recording and macOS rendering after Electron 43 by @StiensWout in pingdotgg/t3code#9001
* feat(desktop): add configurable quit shortcut confirmation by @juliusmarminge in pingdotgg/t3code#9076
* feat(web): open project settings from thread menus by @SunkenInTime in pingdotgg/t3code#8925
* fix(chat): reuse one row for live activity by @maria-rcks in pingdotgg/t3code#9062
* feat(models): discover Claude models from remote manifest by @juliusmarminge in pingdotgg/t3code#9084
* Revert "fix(chat): reuse one row for live activity" by @maria-rcks in pingdotgg/t3code#9096
* fix(web): sync sidebar PR state from open panel by @t3-code[bot] in pingdotgg/t3code#9092
* fix(web): changing projects no longer creates a draft by @extoci in pingdotgg/t3code#9097
* fix(web): keep theme placeholder text dimmer than entered text by @flamboh in pingdotgg/t3code#9104
* fix(web): keep the selected environment when changing projects by @t3dotgg in pingdotgg/t3code#9102
## New Contributors
* @willsheldon made their first contribution in pingdotgg/t3code#9080
* @q1 made their first contribution in pingdotgg/t3code#9078
**Full Changelog**: pingdotgg/t3code@v0.0.37...v0.0.38
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(mobile): upload attachments while composing - #8978

Merged
juliusmarminge merged 3 commits into
mainfrom
feat/mobile-direct-uploads
Sep 1, 2026
Merged

feat(mobile): upload attachments while composing#8978
juliusmarminge merged 3 commits into
mainfrom
feat/mobile-direct-uploads

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 1, 2026

Copy link
Copy Markdown
Member

What Changed

Mobile uploads images and files over HTTP as soon as they are attached to a connected environment. Composer thumbnails show progress and retry, with at most three concurrent uploads. Send reuses completed uploads and refreshes expired ones from the local copy.

Local image data and owned files stay with drafts and queued messages. Cloud sign-out saves relay drafts and the outbox under their account, then restores them before that account reconnects. This uses the existing Expo file APIs and server endpoints; no new native dependency or rebuild is needed.

Why

Mobile previously waited until Send to upload files and sent images inline. Uploading while composing removes that wait without making drafts depend on connectivity. Local copies remain available offline and survive app restarts and sign-out. Older servers keep the inline-image fallback.

UI Changes

The demo opens a saved PDF after an offline app restart, then shows the composer after reconnecting. Both attachments uploaded before Send, and their server bytes matched the originals. The idle wait is omitted.

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/90e07f8e31875d08/offline-and-reconnected-demo.mp4

Before reconnectingAfter reconnecting, before Send
Offline composer retaining an image and PDFConnected composer with uploaded attachments still available locally
Local PDF preview after restarting offlineNative PDF preview opened from the local copy while offline

Validation

  • 96 focused tests pass for uploads, cancellation, retries, concurrency, draft persistence, outbox delivery, and account-specific restoration.
  • Mobile typecheck and lint of changed files pass.
  • iPhone 17 Pro Simulator, iOS 27: offline attachment creation, app restart, local PDF preview, and automatic upload on reconnect. Server files matched the original SHA-256 hashes.
  • Cloud sign-out retention was covered by storage tests; live cloud sign-out and Android were not exercised.

Checklist

  • Scope limited to mobile attachment uploads and local retention
  • Explained the change and why
  • Included screenshots of the offline and connected states
  • Included an interaction demo

Note

Medium Risk
Changes span cloud auth transitions, durable draft/outbox persistence, and send-time attachment preparation; regressions could lose drafts on sign-out or block sends incorrectly, but behavior is heavily covered by new tests.

Overview
Mobile composer attachments now upload in the background while you draft, instead of waiting until Send. A global worker (hosted next to the thread outbox drain) syncs draft attachments for connected environments into a bounded queue (max three concurrent transfers), persists upload IDs back onto drafts, and survives navigation away from the composer.

UI and send gating:ComposerAttachmentStrip shows per-thumbnail progress, percentage, and tap-to-retry on failure. Thread composer, new-task flow, and thread send paths block Send/Start when online until uploads finish or failures are retried/removed; offline queuing is unchanged.

Upload pipeline:prepareTurnAttachments gains abortable HTTP uploads with progress, image upload when the server supports attachmentUploads (temp cache file from dataUrl, with inline-image fallback on older servers), reuse/re-upload of expired pending IDs for both files and images, and stamped uploadedAttachmentId on image drafts.

Cloud account lifecycle: Sign-out runs removeCloudEnvironments, which archives relay-environment drafts and queued outbox messages under the owning account before removing environments; sign-in restores them and reconciles delivered messages against the archive. Attachment file/upload cleanup respects signed-out snapshots so local bytes are not deleted prematurely.

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

Note

Add background attachment uploads to mobile composer with cloud draft preservation

  • Introduces a bounded-concurrency upload queue (max 3) in composerAttachmentUploadQueue.ts that uploads images and files over HTTP when the server supports it, with per-attachment progress, abort, and retry
  • Adds a long-lived worker hook useComposerAttachmentUploadWorker in composer-attachment-uploads.ts that reconciles the queue against current drafts and connection state, persists uploaded references back into drafts, and keeps previews usable during transfers
  • Stamps uploadedAttachmentId/uploadEnvironmentId onto image drafts in attachmentUpload.ts and composer-image-schema.ts, enabling reuse of previously uploaded ids on the same environment and reupload when expired
  • Blocks sending in use-thread-composer-state.ts, ThreadComposer.tsx, and NewTaskDraftScreen.tsx while uploads are in-progress or failed; thumbnails in ComposerAttachmentStrip.tsx show progress overlays and retry controls
  • On cloud sign-out, archives drafts and queued messages per-account in use-composer-drafts.ts via archiveCloudComposerDrafts; on sign-in, restores them via restoreCloudComposerDrafts in CloudAuthProvider.tsx
  • Risk: prepareTurnAttachments in attachmentUpload.ts now validates image mime types against PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPES and throws on unsupported types; older servers without upload support continue to receive inline images, but any caller passing unsupported image types will now fail at upload time

Macroscope summarized 312dc46.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Sep 1, 2026
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ The exact PR base did not have a successful artifact. Baseline uses the latest successful main measurement shown below.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.3 KiB+13 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−1 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.4 KiB+14 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded55.6 KiB55.6 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages11110 (0.0%)21
ClaudeTotal thread wire13.3 KiB13.3 KiB+4 B (+0.0%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB+1 B (+0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.5 KiB6.5 KiB+3 B (+0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB56.4 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages11110 (0.0%)21

Baseline: 85b656f · PR result: 312dc46 · 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.

Comment threadapps/mobile/src/features/threads/ThreadComposer.tsx
Comment threadapps/mobile/src/state/use-thread-composer-state.ts
Comment threadapps/mobile/src/lib/composerAttachmentUploadQueue.ts Outdated
Comment threadapps/mobile/src/lib/composerAttachmentUploadQueue.ts

@cursorcursorBot 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 ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 934f66d. Configure here.

Comment threadapps/mobile/src/state/use-composer-drafts.ts
@macroscopeapp

macroscopeappBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a substantial mobile workflow for automatic background attachment uploads, offline persistence, queued-message handling, and account-specific cloud draft restoration. It also changes the default composing behavior and touches authentication lifecycle code, so the scope and side effects require human review.

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

@macroscopeappmacroscopeappBot 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.

</antml the="" parameter="">

Posted via Macroscope — Effect Service Conventions

Comment threadapps/mobile/src/features/cloud/cloud-drafts.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Sep 1, 2026
Comment threadapps/mobile/src/state/use-thread-outbox-drain.ts Outdated
@juliusmarminge
juliusmarminge merged commit 9bc7a56 into mainSep 1, 2026
27 checks passed
@juliusmarminge
juliusmarminge deleted the feat/mobile-direct-uploads branch September 1, 2026 02:31
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 1, 2026
## What's Changed
* feat(mobile): upload attachments while composing by @juliusmarminge in pingdotgg/t3code#8978
* fix(chat): keep agent activity visible between actions by @maria-rcks in pingdotgg/t3code#8984
* fix(server): isolate remote web session cookies by @Bil0000 in pingdotgg/t3code#8085
* feat(pull-requests): link GitHub references in markdown by @maria-rcks in pingdotgg/t3code#8812
* perf(server): reduce frequency of full tool call output being loaded into memory from db by @t3dotgg in pingdotgg/t3code#8988
**Full Changelog**: pingdotgg/t3code@v0.0.38-nightly.20260901.1242...v0.0.38-nightly.20260901.1243
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.38-nightly.20260901.1243
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 1, 2026
## What's Changed
* Add mobile composer attachment menu with video support by @juliusmarminge in pingdotgg/t3code#8843
* fix(web): restore unified activity logs and composer banners by @t3dotgg in pingdotgg/t3code#8734
* fix(web): address composer banner review follow-ups by @juliusmarminge in pingdotgg/t3code#8850
* fix(web): widen sync banners and simplify the working timer by @juliusmarminge in pingdotgg/t3code#8855
* fix(preview): improve browser recording quality by @maria-rcks in pingdotgg/t3code#8839
* fix(web): mark pull request links as external by @juliusmarminge in pingdotgg/t3code#8856
* fix(mobile): replace Callstack glass with Expo glass by @juliusmarminge in pingdotgg/t3code#8862
* fix(server): skip IDE detection in Claude probes by @yashranaway in pingdotgg/t3code#8634
* chore(macroscope): review diagnostic overrides by @t3-code[bot] in pingdotgg/t3code#8917
* fix(contracts): accept CLI event origins by @nateEc in pingdotgg/t3code#8905
* fix(web): hide invalid slash skill completions by @nateEc in pingdotgg/t3code#8904
* fix(mobile): defer draft navigation until submission completes by @juliusmarminge in pingdotgg/t3code#8914
* chore: disable CodeRabbit review status by @juliusmarminge in pingdotgg/t3code#8933
* Delete app.json by @juliusmarminge in pingdotgg/t3code#8934
* fix(web): show scrollbar for wide markdown tables by @UtkarshUsername in pingdotgg/t3code#8868
* fix(mobile): shimmer active tool rows by @juliusmarminge in pingdotgg/t3code#8932
* chore(deps): bump Electron to 43.4.1 by @StiensWout in pingdotgg/t3code#8626
* fix(chat): smooth worktree setup status by @juliusmarminge in pingdotgg/t3code#8922
* feat(mobile): add video playback with native iOS controls by @juliusmarminge in pingdotgg/t3code#8919
* fix(web): prevent chat metadata overlap by @MatthewFeroz in pingdotgg/t3code#8851
* fix(server): preserve usage cache outside walked roots by @Lucenx9 in pingdotgg/t3code#8540
* feat(mobile): add native image and PDF previews by @juliusmarminge in pingdotgg/t3code#8959
* fix(server): allow long thread IDs in HTTP routes by @nateEc in pingdotgg/t3code#8898
* fix(shared): preserve Windows shell PATH priority by @UtkarshUsername in pingdotgg/t3code#8748
* fix(web): make WSL settings searchable by @UtkarshUsername in pingdotgg/t3code#8881
* feat(web): add expand/collapse all control to the files surface by @UtkarshUsername in pingdotgg/t3code#8889
* feat(mobile): upload attachments while composing by @juliusmarminge in pingdotgg/t3code#8978
* fix(chat): keep agent activity visible between actions by @maria-rcks in pingdotgg/t3code#8984
* fix(server): isolate remote web session cookies by @Bil0000 in pingdotgg/t3code#8085
* feat(pull-requests): link GitHub references in markdown by @maria-rcks in pingdotgg/t3code#8812
* perf(server): reduce frequency of full tool call output being loaded into memory from db by @t3dotgg in pingdotgg/t3code#8988
* feat(web): add pull request list filters by @maria-rcks in pingdotgg/t3code#8809
* feat(web): search individual settings by detail by @maria-rcks in pingdotgg/t3code#8831
* feat(client): render viewed images in work logs by @maria-rcks in pingdotgg/t3code#8936
* fix(client): use package import for markdown image helpers by @juliusmarminge in pingdotgg/t3code#9010
* test: remove static presentation snapshots by @t3-code[bot] in pingdotgg/t3code#9008
* perf(server): bound snapshot activity payload memory by @t3dotgg in pingdotgg/t3code#9000
* perf(server): cut idle CPU use and stop provider event leaks by @t3dotgg in pingdotgg/t3code#8187
* perf(server): scan only appended transcript bytes for usage summaries by @StiensWout in pingdotgg/t3code#9024
* perf(server): cut chatty tool-update frames by 90% by @Adamulek123 in pingdotgg/t3code#8368
* fix(server): settle threads server-side by @t3dotgg in pingdotgg/t3code#8600
* fix(clients): dedupe skills in composer menus by @Adamulek123 in pingdotgg/t3code#8043
* fix(server): stop OpenCode child sessions by @t3dotgg in pingdotgg/t3code#9005
* perf(web): defer pull request line stats until visible by @Adamulek123 in pingdotgg/t3code#6471
* perf(server): skip full-message reads while streaming by @t3dotgg in pingdotgg/t3code#9032
* perf(client-runtime): halve server config bootstrap traffic by @Adamulek123 in pingdotgg/t3code#8367
* fix(web): align un-settle banner action by @StiensWout in pingdotgg/t3code#9033
* fix(web): block type-to-focus behind open dialogs by @Lucenx9 in pingdotgg/t3code#8139
* feat(shortcuts): copy active thread reference by @maria-rcks in pingdotgg/t3code#8994
* fix(mobile): keep thread scroll bounds current after animations by @juliusmarminge in pingdotgg/t3code#9013
* fix(server): cache project favicon resolution by @willsheldon in pingdotgg/t3code#9080
* feat(claude): add Claude Fable 5.1 model by @q1 in pingdotgg/t3code#9078
* fix(preview): restore recording and macOS rendering after Electron 43 by @StiensWout in pingdotgg/t3code#9001
* feat(desktop): add configurable quit shortcut confirmation by @juliusmarminge in pingdotgg/t3code#9076
* feat(web): open project settings from thread menus by @SunkenInTime in pingdotgg/t3code#8925
* fix(chat): reuse one row for live activity by @maria-rcks in pingdotgg/t3code#9062
* feat(models): discover Claude models from remote manifest by @juliusmarminge in pingdotgg/t3code#9084
* Revert "fix(chat): reuse one row for live activity" by @maria-rcks in pingdotgg/t3code#9096
* fix(web): sync sidebar PR state from open panel by @t3-code[bot] in pingdotgg/t3code#9092
* fix(web): changing projects no longer creates a draft by @extoci in pingdotgg/t3code#9097
* fix(web): keep theme placeholder text dimmer than entered text by @flamboh in pingdotgg/t3code#9104
* fix(web): keep the selected environment when changing projects by @t3dotgg in pingdotgg/t3code#9102
## New Contributors
* @willsheldon made their first contribution in pingdotgg/t3code#9080
* @q1 made their first contribution in pingdotgg/t3code#9078
**Full Changelog**: pingdotgg/t3code@v0.0.37...v0.0.38
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

@juliusmarminge