Skip to content

Fix image preview not showing in compose modal preview - #198

Open
PastaPastaPasta wants to merge 3 commits into
masterfrom
claude/fix-image-preview-Py7LR
Open

Fix image preview not showing in compose modal preview#198
PastaPastaPasta wants to merge 3 commits into
masterfrom
claude/fix-image-preview-Py7LR

Conversation

@PastaPastaPasta

@PastaPastaPasta PastaPastaPasta commented Jan 27, 2026

Copy link
Copy Markdown
Owner

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

    • Compose modal auto-detects the first direct image URL in post content and shows an inline image preview card with the source hostname footer.
    • The displayed post text removes the image URL while preserving remaining Markdown and styling.
  • Bug Fixes

    • Image preview hides gracefully and shows a fallback message when loading fails; preview state resets when content changes.

✏️ Tip: You can customize this high-level summary in your review settings.

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
@coderabbitai

coderabbitai Bot commented Jan 27, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Detects 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

Cohort / File(s) Summary
Compose Modal Image/URL Preview
components/compose/compose-modal.tsx
Added imports (extractFirstUrl, isDirectImageUrl, stripTrailingPunctuation, Image, LinkIcon → alias LinkIconSolid), introduced imageError state, detect/strip first image URL from post.content, render clickable image preview with hostname footer and onError handling, and keep remaining Markdown rendering intact.

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
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Poem

🐰 I found a link that gleams with light,
I pulled it out and showed its sight.
A tiny card with domain below,
If pixels fail, a message shows.
Hop — compose feels snug and bright!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the main change: adding image preview functionality to the compose modal preview to show inline images instead of just links.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

- 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
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jan 28, 2026

Copy link
Copy Markdown

Deploying yappr with  Cloudflare Pages  Cloudflare Pages

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

View logs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread components/compose/compose-modal.tsx Outdated
Comment thread components/compose/compose-modal.tsx
Comment thread components/compose/compose-modal.tsx Outdated
- 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
@thepastaclaw

thepastaclaw commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

🕓 Ready for review — 61 ahead in queue (commit fc582c9)
Queue position: 62/74 · 3 reviews active
ETA: start ~08:00 UTC · complete ~08:36 UTC (median 36m across 30 recent reviews; 3 slots)
Queued 40m ago · Last checked: 2026-07-21 19:20 UTC

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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`.

Comment on lines +452 to +470
{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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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']

Comment on lines +423 to +427
const urlPattern = new RegExp(
`(?:https?:\\/\\/|\\/)?\\/?(www\\.)?${escapedHost}${escapedPath}`,
'gi'
)
displayContent = post.content.replace(urlPattern, '').trim()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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']

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants