fix(web): separate image attachments from message text - #5177
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| @@ -0,0 +1,20 @@ | |||
| export const IMAGE_ONLY_BOOTSTRAP_PROMPT = | |||
There was a problem hiding this comment.
🟡 Medium lib/userMessage.ts:1
resolveUserMessageDisplayText hides a user's message by returning "" whenever the trimmed text exactly equals IMAGE_ONLY_BOOTSTRAP_PROMPT (or its Ultrathink: variant). A user who intentionally types this sentence alongside an image attachment will have their message silently replaced with an empty display string, losing their genuine caption. This happens because the function uses a user-readable prompt string as the sole sentinel to distinguish generated fallback text from user-authored text. Consider using a collision-resistant internal marker or persisted metadata instead, so genuine user input is never mistaken for a synthetic bootstrap prompt.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/lib/userMessage.ts around line 1:
`resolveUserMessageDisplayText` hides a user's message by returning `""` whenever the trimmed text exactly equals `IMAGE_ONLY_BOOTSTRAP_PROMPT` (or its `Ultrathink:` variant). A user who intentionally types this sentence alongside an image attachment will have their message silently replaced with an empty display string, losing their genuine caption. This happens because the function uses a user-readable prompt string as the sole sentinel to distinguish generated fallback text from user-authored text. Consider using a collision-resistant internal marker or persisted metadata instead, so genuine user input is never mistaken for a synthetic bootstrap prompt.
| data-user-message-attachments="true" | ||
| data-user-message-attachments-collapsed={collapsedImageCount > 0 ? "true" : "false"} | ||
| > | ||
| {visibleImages.map((image, index) => { |
There was a problem hiding this comment.
🟠 High chat/UserMessageAttachments.tsx:38
With more than four attachments, if the fourth item has no previewUrl, the +N collapsed-tile overlay and click target are not rendered — they exist only inside the image.previewUrl branch. Previewable attachments after index 3 are hidden by slice(0, MAX_VISIBLE_IMAGES), so with four non-previewable files followed by a previewable image, the image becomes completely inaccessible. Render the collapsed overlay and expand action independently of whether the fourth item itself has a preview URL (for example, by selecting a previewable image for that tile).
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/chat/UserMessageAttachments.tsx around line 38:
With more than four attachments, if the fourth item has no `previewUrl`, the `+N` collapsed-tile overlay and click target are not rendered — they exist only inside the `image.previewUrl` branch. Previewable attachments after index 3 are hidden by `slice(0, MAX_VISIBLE_IMAGES)`, so with four non-previewable files followed by a previewable image, the image becomes completely inaccessible. Render the collapsed overlay and expand action independently of whether the fourth item itself has a preview URL (for example, by selecting a previewable image for that tile).
| : `Preview ${image.name}` | ||
| } | ||
| onClick={() => { | ||
| const preview = buildExpandedImagePreview(props.images, image.id); |
There was a problem hiding this comment.
🟠 High chat/UserMessageAttachments.tsx:58
Overflow attachments without a previewUrl are permanently inaccessible. When there are more than four images, the last visible tile shows +N, but buildExpandedImagePreview filters out any attachment lacking a previewUrl. If an overflowed attachment has no preview URL, it never appears in the expanded gallery, so its fallback name is never shown and there is no way to see it. Consider passing the full props.images array (including non-previewable items) to the expanded preview, or surfacing non-previewable overflow attachments by name outside the image gallery.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/chat/UserMessageAttachments.tsx around line 58:
Overflow attachments without a `previewUrl` are permanently inaccessible. When there are more than four images, the last visible tile shows `+N`, but `buildExpandedImagePreview` filters out any attachment lacking a `previewUrl`. If an overflowed attachment has no preview URL, it never appears in the expanded gallery, so its fallback name is never shown and there is no way to see it. Consider passing the full `props.images` array (including non-previewable items) to the expanded preview, or surfacing non-previewable overflow attachments by name outside the image gallery.
What changed
Why
Images were rendered inside the same padded message box as text, and image-only turns exposed an internal provider instruction as user-authored content. Larger attachment sets also consumed unnecessary vertical space and flattened every preview into the same shallow proportion.
Impact
Images and captions now have clear visual separation, image-only turns show only their attachments, and larger sets remain compact without losing access to any image. The gallery changes with image count while keeping square crops and a balanced outer shape. This applies to the web client and the desktop app that wraps it; mobile does not use this web timeline.
Screenshots
Validation
vp test run apps/web/src/components/chat/MessagesTimeline.test.tsx— 18 tests passedvp run typecheckfromapps/webvp linton the changed filesGenerated with GPT-5.6 Sol via the Codex harness.