Fix image preview not showing in compose modal preview - #198
Fix image preview not showing in compose modal preview#198PastaPastaPasta wants to merge 3 commits into
Conversation
When composing a post with a direct image URL (e.g., https://example.com/image.jpg), the preview mode now shows the image inline, matching the behavior after posting. Previously, the compose preview only showed image URLs as clickable links while the posted version showed them as inline images via LinkPreview. https://claude.ai/code/session_01QS6hsm9agZ7diaU7Tp8waK
📝 WalkthroughWalkthroughDetects the first URL in compose content; if it's a direct image URL, removes it from the rendered Markdown preview and renders a clickable image preview card with domain footer and load-error handling; also adds imageError state and swaps to a solid link icon variant. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant User as "User (types content)"
participant Compose as "ComposeModal / ThreadPostEditor"
participant Utils as "use-link-preview (extractFirstUrl/isDirectImageUrl)"
participant Markdown as "Markdown Renderer"
participant ImageComp as "next/image (preview loader)"
User->>Compose: enters post.content
Compose->>Utils: extractFirstUrl(post.content)
Utils-->>Compose: firstUrl (or null)
alt firstUrl is direct image
Compose->>Utils: stripTrailingPunctuation(firstUrl)
Compose->>Markdown: render(post.content without firstUrl)
Compose->>ImageComp: render image preview (clickable)
ImageComp-->>Compose: load success
ImageComp-->>Compose: onError -> set imageError
Compose->>Compose: show/hide fallback based on imageError
else no image URL
Compose->>Markdown: render(full post.content)
end
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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 |
- Strip the image URL from displayed text (matching PostContent behavior) - Use exact LinkPreview styling: border, rounded corners, domain footer with LinkIcon - Match max height (400px) and object-contain behavior - Show hostname in footer like the actual post display https://claude.ai/code/session_01QS6hsm9agZ7diaU7Tp8waK
Deploying yappr with
|
| Latest commit: |
fc582c9
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://59330dfb.yappr.pages.dev |
| Branch Preview URL: | https://claude-fix-image-preview-py7.yappr.pages.dev |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@components/compose/compose-modal.tsx`:
- Around line 24-25: Duplicate imports of LinkIcon (outline and solid) cause a
TS2300 error; alias the outline import (e.g., import { LinkIcon as
LinkOutlineIcon } from '@heroicons/react/24/outline') and update the preview
footer to use that alias instead of the solid LinkIcon, and apply the same
alias/fix for the other duplicate occurrences around the preview footer area
(the spots corresponding to the other duplicate imports at 452-454).
- Around line 399-409: The preview stripping fails for protocol-less or http
variants because extractFirstUrl returns a normalized https:// URL (used as
cleanUrl) but post.content may contain "www.example.com" or "http://…" so
replace(cleanUrl, '') doesn't match; update the removal logic in the compose
modal (around extractFirstUrl, isDirectImageUrl, stripTrailingPunctuation and
displayContent) to remove all variants of the found URL by constructing a match
that accepts optional protocols and optional "www." (e.g., create an escaped URL
token from stripTrailingPunctuation(firstUrl) and remove occurrences matching
optional "http(s)://", optional "//", or protocol-less "www." forms,
case-insensitive and global) so the original content’s link is reliably stripped
for preview.
- Around line 438-449: Remove the eslint-disable comment and stop using direct
DOM innerHTML manipulation for image errors; instead implement state-based error
handling like the LinkPreview pattern: add a local imageError state (e.g., via
useState), replace the raw <img> with either a conditional render that shows the
preview when !imageError and a fallback div when imageError, and set the
element's onError to call setImageError(true); alternatively, import NextImage
from 'next/image' and render <Image src={firstUrl} ... unoptimized /> with
onError toggling the same imageError state and render the same fallback when the
state is true—ensure you remove the innerHTML logic and the eslint-disable line
and reference firstUrl, imageError, setImageError (or the chosen state names)
and LinkPreview as the implementation guide.
- Fix duplicate LinkIcon imports by aliasing solid variant to LinkIconSolid - Fix URL stripping to handle all variants (http://, https://, //, www.) using regex that matches optional protocols and www prefix - Replace DOM innerHTML manipulation with React state-based error handling - Use Next.js Image component with unoptimized for external URLs - Reset imageError state when post content changes https://claude.ai/code/session_01QS6hsm9agZ7diaU7Tp8waK
|
🕓 Ready for review — 61 ahead in queue (commit fc582c9) |
thepastaclaw
left a comment
There was a problem hiding this comment.
Preliminary review — Codex only
The PR adds direct-image cards to compose preview, but the new path still does not behave consistently with posted content. It bypasses the user's disabled link-preview privacy setting and can corrupt displayed preview text while removing image URLs, so changes are required.
Validated blockers were found in the Codex precheck. Sonnet is deferred until a fresh Codex revalidation clears the blocker gate.
Review provenance
- Codex reviewers:
gpt-5.6-sol— general (failed),gpt-5.6-sol— general (failed),gpt-5.6-sol— general (failed),gpt-5.6-sol— general (failed),gpt-5.6-sol— general (completed) - Verifier:
gpt-5.6-sol— verifier - Sonnet: not run (deferred by blocker gate)
🔴 2 blocking
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `components/compose/compose-modal.tsx`:
- [BLOCKING] components/compose/compose-modal.tsx:452-470: Respect the link-preview privacy setting before loading images
The compose preview renders the remote image whenever the first URL has an image extension, regardless of `useSettingsStore(...linkPreviews)`. Posted content disables `useLinkPreview` and `LinkPreview` when that privacy setting is false, leaving the URL visible instead of contacting its host. Opening compose preview therefore leaks a request to the remote image host despite the user's opt-out and produces a different preview from the posted content. Gate both the image rendering and URL removal on the setting, or reuse the shared posted-content rendering path.
- [BLOCKING] components/compose/compose-modal.tsx:423-427: Remove only the exact detected URL token
This global regex has no token boundaries, so it removes the detected image URL wherever its hostname and path appear as a substring. For example, detecting `https://cdn.example.com/a.jpg` changes neighboring text containing `https://evilcdn.example.com/a.jpg` to `https://evil` and `https://cdn.example.com/a.jpg2` to `2`. It also reconstructs the URL with `hostname`, excluding an explicit port and potentially failing to remove the actual token. The compose preview can therefore show corrupted text that the posted-content parser will preserve. Remove only the exact URL occurrence using the same token semantics as `PostContent`.
| {isImageUrl && firstUrl && ( | ||
| <div className="mt-3"> | ||
| <a | ||
| href={firstUrl} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| onClick={(e) => e.stopPropagation()} | ||
| className="block border border-neutral-200 dark:border-neutral-700 rounded-xl overflow-hidden hover:bg-neutral-50 dark:hover:bg-neutral-800/50 transition-colors" | ||
| > | ||
| {!imageError ? ( | ||
| <div className="relative bg-neutral-100 dark:bg-neutral-800"> | ||
| <Image | ||
| src={firstUrl} | ||
| alt="Image preview" | ||
| width={600} | ||
| height={400} | ||
| className="w-full max-h-[400px] object-contain" | ||
| onError={() => setImageError(true)} | ||
| unoptimized |
There was a problem hiding this comment.
🔴 Blocking: Respect the link-preview privacy setting before loading images
The compose preview renders the remote image whenever the first URL has an image extension, regardless of useSettingsStore(...linkPreviews). Posted content disables useLinkPreview and LinkPreview when that privacy setting is false, leaving the URL visible instead of contacting its host. Opening compose preview therefore leaks a request to the remote image host despite the user's opt-out and produces a different preview from the posted content. Gate both the image rendering and URL removal on the setting, or reuse the shared posted-content rendering path.
source: ['codex']
| const urlPattern = new RegExp( | ||
| `(?:https?:\\/\\/|\\/)?\\/?(www\\.)?${escapedHost}${escapedPath}`, | ||
| 'gi' | ||
| ) | ||
| displayContent = post.content.replace(urlPattern, '').trim() |
There was a problem hiding this comment.
🔴 Blocking: Remove only the exact detected URL token
This global regex has no token boundaries, so it removes the detected image URL wherever its hostname and path appear as a substring. For example, detecting https://cdn.example.com/a.jpg changes neighboring text containing https://evilcdn.example.com/a.jpg to https://evil and https://cdn.example.com/a.jpg2 to 2. It also reconstructs the URL with hostname, excluding an explicit port and potentially failing to remove the actual token. The compose preview can therefore show corrupted text that the posted-content parser will preserve. Remove only the exact URL occurrence using the same token semantics as PostContent.
source: ['codex']
When composing a post with a direct image URL (e.g., https://example.com/image.jpg),
the preview mode now shows the image inline, matching the behavior after posting.
Previously, the compose preview only showed image URLs as clickable links while
the posted version showed them as inline images via LinkPreview.
https://claude.ai/code/session_01QS6hsm9agZ7diaU7Tp8waK
Summary by CodeRabbit
New Features
Bug Fixes
✏️ Tip: You can customize this high-level summary in your review settings.