Skip to content

feat: inline article preview embed#5854

Open
tomeredlich wants to merge 8 commits intomainfrom
url-preview-on-posts
Open

feat: inline article preview embed#5854
tomeredlich wants to merge 8 commits intomainfrom
url-preview-on-posts

Conversation

@tomeredlich
Copy link
Copy Markdown
Contributor

Summary

Adds an inline article preview feature that allows users to read articles directly inside daily.dev using the browser extension.

  • Responsive Layout:
    • Laptop+: Opens an inline column next to the post content. If the viewport gets too narrow (<360px), it automatically transitions to a floating right-anchored panel.
    • Tablet: Opens an inline column next to the post content.
    • Mobile: Opens a bottom drawer that covers 88% of the viewport.
  • Webapp Fallback: If the extension is not installed, the preview area shows a prompt asking the user to install the Chrome or Edge extension to activate the feature.
  • Animations: Smooth slide and fade transitions for the floating panel and mobile drawer.
  • State Management: Handles resize events with hysteresis to prevent flickering, and properly resets state when navigating between posts.

Test plan

  • Verify the "Preview article" button appears on post modals/pages.
  • On laptop, click the button and verify the preview opens inline.
  • Shrink the window width and verify the preview transitions to a floating panel on the right.
  • On mobile, verify the preview opens in a bottom drawer.
  • Without the extension installed, verify the prompt asks to install the extension and shows the correct browser icon (Chrome/Edge).

Made with Cursor

Add a three-column post modal with an inline article preview that supports extension embedding and webapp fallback states, including dismiss/restore and unavailable UX. Expose the extension id to webapp runtime so preview initialization can run consistently across app surfaces.

Made-with: Cursor
- Add tablet two-pane and laptop three-pane layout for inline article
  preview using CSS Grid with smooth grid-template-columns animation
- Isolate widgets column outside the animated grid so it stays fixed
  during expand/collapse transitions
- Stabilize embed callbacks with useCallback and ref-based state to
  fix infinite render loops in ExtensionSiteEmbed
- Remove debug fetch calls to 127.0.0.1:7456
- Add ChunkLoadError recovery for sidebar dynamic imports
- Clean up EmbeddedBrowsingWebPrompt copy

Made-with: Cursor
- Use overflow-x: clip instead of overflow-hidden on the preview grid
  cell so it doesn't create a scroll context that traps sticky
- Center the prompt at 50vh with translateY(-50%) so it stays at the
  screen midpoint as the page scrolls
- Stretch .root to full height for proper layout propagation

Made-with: Cursor
- Show site domain name instead of redirect URL in preview header
- Align toggle button with PostSourceInfo on tablet screens
- Add mobile-only preview button next to source info
- Open article preview in a bottom drawer (88vh) on mobile
- Use existing Drawer animation for slide-up behavior
- Remove background/border from browsing prompt on mobile
- Center browsing prompt vertically in mobile drawer

Made-with: Cursor
Prevent inline-to-floating flicker by coordinating preview width checks with toggle animations and add explicit enter/exit animation state for the floating right panel. Also simplify the embedded browsing prompt container styling by removing tablet-only surface framing.

Made-with: Cursor
- Remove direct iframe fallback from webapp preview
- Update prompt copy to explicitly ask users to install the extension
- Add browser-specific (Chrome/Edge) icons to the install button

Made-with: Cursor
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
daily-webapp Error Error Apr 13, 2026 0:45am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
storybook Ignored Ignored Apr 13, 2026 0:45am

Request Review

@tomeredlich
Copy link
Copy Markdown
Contributor Author

@claude check pr

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 13, 2026

Claude finished @tomeredlich's task in 3m 29s —— View job


PR Review: feat: inline article preview embed

  • Gather context and understand the request
  • Read changed files
  • Analyze code for bugs, performance, and best practices
  • Post review feedback

Overall the feature is well-structured and the responsive layout approach is solid. A few bugs and design issues worth addressing before merging:


Bug: onDismiss declared but never used in EmbeddedBrowsingWebPrompt

EmbeddedBrowsingWebPrompt.tsx:18-34onDismiss is part of the props type but is never destructured in the function signature, so callers that pass it can never dismiss the prompt. There's no close/dismiss button in the rendered JSX either. This means users cannot dismiss the "install extension" prompt even though the caller wires it up.

// Props type declares onDismiss...
export type EmbeddedBrowsingWebPromptProps = {
  onDismiss?: () => void;  // ← declared
  ...
};

// ...but the function never destructures or uses it
export function EmbeddedBrowsingWebPrompt({
  onEnablePreview,           // ← onDismiss is missing here
  isPreviewUnavailable = false,
  unavailablePreviewUrl,
}: EmbeddedBrowsingWebPromptProps): ReactElement {

The prop is passed through the chain (PostContentPostArticlePreviewEmbedEmbeddedBrowsingWebPrompt) but silently dropped. Either remove the prop from the type or implement a dismiss button. Fix this →


Bug: Floating preview animation state not reset on post navigation

PostContent.tsx:176-181 — When navigating to a different post, only these states reset:

useEffect(() => {
  setArticlePreviewDismissed(false);
  setArticlePreviewUnavailable(false);
  setMobilePreviewOpen(false);
  setIsPreviewNarrow(false);
}, [post.id]);

But isFloatingPreviewVisible, isFloatingPreviewClosing, isFloatingPreviewActive, and isTabletPreviewToggling are not reset. If a floating panel is open/mid-animation when the user navigates to a new post, these states carry over and can leave the floating preview panel rendered or stuck in an animation state. Fix this →


Design: ArticleLink component defined inside render

PostContent.tsx:339-352ArticleLink is declared as a React component function inside PostContentRaw. React sees a new component type every render and will unmount + remount its children (including <LazyImage>, <h1> text, etc.) on every parent re-render. This can cause focus loss, image reloads, and other subtle bugs:

// Inside PostContentRaw — this is recreated on every render
const ArticleLink = ({ children, ...props }: ComponentProps<'a'>) => {
  return <a href={post.permalink} ... {...combinedClicks(onReadArticle)} {...props}>{children}</a>;
};

Move it outside the component (accepting href and onClick as arguments) or use a useCallback-based render prop. Fix this →

Note: ArticleLink may be pre-existing — just flagging if it's new to this branch.


Design: Undocumented magic numbers in layout constant

PostContent.tsx:42-47:

const PREVIEW_LAYOUT_MIN_WIDTH =
  (22 + 21.25) * REM_IN_PX + PREVIEW_MIN_WIDTH;

22 and 21.25 are opaque. They appear to be the post content column width (22rem) and widgets column width (21.25rem) respectively, but this is impossible to verify without reading the grid class strings. These should either be named constants with comments, or at a minimum have an inline comment: Fix this →

// Post content fixed column (matches grid-cols-[22rem_...] on laptop)
const POST_COLUMN_REM = 22;
// Widgets sidebar width (matches w-[21.25rem] on laptop)
const WIDGETS_COLUMN_REM = 21.25;
const PREVIEW_LAYOUT_MIN_WIDTH =
  (POST_COLUMN_REM + WIDGETS_COLUMN_REM) * REM_IN_PX + PREVIEW_MIN_WIDTH;

Minor: useMemo overkill for stable value

PostArticlePreviewEmbed.tsx:85getBrowserExtensionInstallId() reads from window.location.origin and process.env — both stable for the lifetime of the page. Using useMemo with [] works but useState with an initializer is semantically more accurate for a value that's computed once and never changes:

// Current
const extensionId = useMemo(() => getBrowserExtensionInstallId(), []);

// Clearer
const [extensionId] = useState(() => getBrowserExtensionInstallId());

Minor: next.config.ts env var fallback can silently pick wrong extension ID

next.config.ts:112-117:

NEXT_PUBLIC_DAILY_EXTENSION_ID:
  process.env.NEXT_PUBLIC_DAILY_EXTENSION_ID ||
  process.env.EXTENSION_ID_CHROME ||
  process.env.EXTENSION_ID_EDGE ||
  '',

If both EXTENSION_ID_CHROME and EXTENSION_ID_EDGE are set in the environment (e.g., a shared CI that tests both), Chrome silently wins. Since the ID is baked into the build, and getBrowserExtensionInstallId can't distinguish which browser the user has, this is probably acceptable—but worth a comment explaining the precedence intent.


Vercel build failure

The Vercel deployment shows an error. Worth checking whether it's related to this PR or unrelated infra.


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.

1 participant