Problem
User bubbles can depend on non-empty cleaned text, so attachment-only turns may lack a normal user-message container. Copy actions can receive raw persisted message text containing attachment or Canvas serialization rather than the human-visible content. Clipboard failure is swallowed, and image attachments are represented mainly by generic file metadata.
User impact
The conversation can lose visual authorship for attachment-only turns, Copy can put internal storage markup on the clipboard, and users receive no explanation when copying fails.
Relevant code
apps/desktop/src/ui/chat/FeedRow.tsx- User-message cleaning/attachment parsing
- Message copy action
- Attachment rendering and stored Canvas request format
Implementation outline
- Derive one semantic visible-message model for rendering, copy, accessibility, and export.
- Always render attachment-only turns inside a user-message container.
- Copy the human-visible message and useful attachment names, never raw persistence markup.
- Show safe image thumbnails/previews where supported.
- Surface clipboard failure through inline/toast feedback.
- Handle malformed or legacy stored payloads without leaking serialization.
- Keep the screen-reader author/message structure aligned with #225.
Acceptance criteria
- Text-only, attachment-only, mixed, and Canvas-request turns all have clear user authorship.
- Copy output matches what the user sees.
- Internal attachment/Canvas serialization is never copied unintentionally.
- Copy failure is visible and retryable.
- Supported images have useful previews without unsafe loading.
- Malformed legacy payloads degrade to a readable representation.
Required tests
- Text-only, attachment-only, mixed files, images, and Canvas requests.
- Raw/legacy/malformed stored payloads.
- Clipboard success/failure.
- Accessible message labeling.