Conversation
Human messages carrying AO's staged-attachment suffix (as written by the desktop composer, spawn, legacy image prompts, and earlier mobile builds) showed the raw ".ao/attachments/..." path list on mobile and no image. Parse the suffix like desktop does, render staged images through the daemon's preview-files route with the connection's Bearer header, and fall back to a name chip for non-images or failed loads. Mobile now writes desktop's wording so its own attachments render on desktop too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The mobile Chat timeline now shows images the human attached. Before this change, a message sent with an image from the desktop composer appeared on mobile as a raw
.ao/attachments/...path list, with no image.Why
Fixes #5380.
How
lib/chat/messageAttachments.tsmirrors desktop'sstagedAttachmentParts. It removes the AO-owned suffix only when every listed path is AO-staged (.ao/attachments/(attachment|image)-*), so prose that happens to quote the same wording stays as written. It accepts every wording AO has shipped: desktop composer, spawn (… for context), legacyAttached images …, and the older mobileAttached files are available in the worktree:.ChatTimeline: human bubbles and steered bubbles show the body text plus the staged attachments. Images load fromGET /api/v1/sessions/{id}/preview/files/<path>through RN<Image source={{ uri, headers }}>with the connection's existing Bearer header (authHeaders(config), the same credential every mobile API call uses). Non-image files, and images that fail to load, show as a name chip.attachmentstore, and the LAN listener allows it behindauthMiddleware.Intentional omissions:
content[].type == "image") are not rendered. The daemon returns only a summary without bytes, and desktop's human message bubble also relies on the staged paths.markdownBlocks.tsonly accepts) are a separate issue and are out of scope here.Testing
TDD:
lib/chat/messageAttachments.test.tswas written first and failed because the module did not exist; after the change it passes. Added a marker-title case totimelineModel.test.ts.packages/mobile:npm ci,npm run typecheck(clean),npm test(80 files / 796 tests passing). These are theMobileworkflow's steps. Ran on Node 22.18.0 locally; CI uses Node 20. The local Node 20.17 is below rolldown's^20.19engine floor, so it could not run vitest.Not run locally:
gitleaks(Docker unavailable on this machine), so I'm relying on the CI run.Verified on iOS simulator (iPhone 17 Pro, iOS 26.3; local
expo run:iosbuild with Xcode 27 RC; paired to a running daemon over the Connect Mobile listener), using an existing chat message whose PNG was attached from the desktop composer:main(baseline): the bubble shows the rawAttached files (read these files in the workspace):line plus the.ao/attachments/attachment-….pngpath, and no image.preview/fileswith the Bearer header). Fetching the same URL over the LAN listener returns200 image/pngwith the header and401without it.Screenshots (iOS simulator; a neutral solid-color test PNG sent to an empty scratch chat in the desktop composer's format):
main(before)Checklist
main(or continuing an existing PR branch)go, frontend, etc.)🤖 Generated with Claude Code