Skip to content

feat(links): add Open Graph metadata support for shared links#3971

Open
crbon wants to merge 9 commits intoumami-software:masterfrom
crbon:beta-links
Open

feat(links): add Open Graph metadata support for shared links#3971
crbon wants to merge 9 commits intoumami-software:masterfrom
crbon:beta-links

Conversation

@crbon
Copy link

@crbon crbon commented Jan 21, 2026

Add customisable Open Graph fields (title, description, image URL) to shared links, allowing improved social media previews when links are shared on platforms such as Facebook, Twitter, LinkedIn, Slack, etc. Users can also customise or specify the slug.

Changes:

  • Add ogTitle, ogDescription, and ogImageUrl fields to Link model with migration
  • Extend link create/update API endpoints to accept OG metadata
  • Serve custom HTML with OG meta tags to social media crawlers/bots
  • Add collapsible "Advanced" section in LinkEditForm with OG fields and slug editor
  • Allow users to customise or specify the slug
  • Reduce minimum slug length from 8 to 4 characters

crbon added 3 commits January 8, 2026 14:43
- Add custom title field for user-defined link names
- Implement custom OG image URL input for social media previews
- Enable Open Graph metadata customization (description, type, etc.)
- Add link slug/path customization for personalized URLs
- Update link creation form with new metadata fields
- Extend database schema to store custom link properties
- Add validation for OG image URLs and metadata inputs

This allows users to fully customize their shared analytics links with
custom titles, Open Graph images, and other metadata for better social
media presentation and link management.
- Introduced ogTitle, ogDescription, and ogImageUrl fields in the Link model for improved social media previews.
- Updated the database schema to accommodate new Open Graph fields.
- Modified link creation and editing forms to include inputs for Open Graph metadata.
- Enhanced the GET route to serve Open Graph metadata for bots.

This update allows for better customization of shared links, improving their presentation on social media platforms.
- Added a toggle for advanced settings in the LinkEditForm to show/hide Open Graph fields (ogTitle, ogDescription, ogImageUrl).
- Updated the form to initialize these fields with default values if available.
- Introduced a new label for the advanced section in the messages file.

This enhancement improves user experience by allowing users to manage Open Graph metadata more efficiently.
@vercel
Copy link

vercel bot commented Jan 21, 2026

@crbon is attempting to deploy a commit to the umami-software Team on Vercel.

A member of the Team first needs to authorize it.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 21, 2026

Greptile Overview

Greptile Summary

This PR adds Open Graph metadata support to shared links, enabling better social media previews. The implementation includes database schema changes, API endpoint updates, bot detection for serving OG meta tags, and a collapsible UI for editing OG fields.

Key Changes:

  • Added three nullable OG fields (og_title, og_description, og_image_url) to the link table via migration
  • Updated link creation/update APIs to accept and validate OG metadata with proper URL validation
  • Implemented bot detection using isbot library to serve HTML with OG meta tags to social media crawlers
  • Added collapsible "Advanced" section in LinkEditForm for OG fields and slug customization
  • Reduced minimum slug length from 8 to 4 characters
  • Modified ExternalLink component behavior (breaking UX change)

Issues Found:

  • Slug comparison logic in form submission has a flaw that may cause unnecessary updates when editing links
  • ExternalLink component introduces breaking UX change where clicking link text copies instead of navigating
  • Missing error handling for clipboard operations in ExternalLink
  • OG metadata validation handles empty strings but the form always sends empty strings for optional fields

Confidence Score: 3/5

  • This PR has logical issues and a breaking UX change that should be addressed before merging
  • The core Open Graph functionality is well-implemented with proper validation and bot detection. However, there are three notable issues: (1) flawed slug comparison logic that could cause unintended database updates, (2) a breaking UX change in ExternalLink where clicking text copies instead of navigating, and (3) missing error handling for clipboard operations. The database schema changes and API validations are solid.
  • Pay close attention to src/app/(main)/links/LinkEditForm.tsx (slug submission logic) and src/components/common/ExternalLink.tsx (breaking UX change)

Important Files Changed

Filename Overview
src/app/(collect)/q/[slug]/route.ts Implements bot detection and serves HTML with Open Graph meta tags for social media crawlers. Previous issues around empty OG tags and hardcoded values were addressed.
src/app/(main)/links/LinkEditForm.tsx Adds collapsible Advanced section with Open Graph fields and slug editor. Form state management was refactored but still has issues with slug submission logic.
src/app/api/links/route.ts Extends POST endpoint to accept and store Open Graph metadata fields with validation. Reduces minimum slug length from 8 to 4.
src/components/common/ExternalLink.tsx Changed link behavior from direct navigation to copy-on-click for the text, with separate icon for external navigation. Breaking UX change.

Sequence Diagram

sequenceDiagram
    participant User
    participant LinkEditForm
    participant API
    participant Database
    participant Bot as Social Media Bot
    participant RouteHandler as /q/[slug] Route

    Note over User,Database: Creating/Updating Link with OG Metadata

    User->>LinkEditForm: Fill in link details
    User->>LinkEditForm: Expand Advanced section
    User->>LinkEditForm: Enter OG title, description, image URL
    User->>LinkEditForm: Submit form
    
    LinkEditForm->>API: POST/PATCH /api/links with OG fields
    API->>API: Validate slug (min 4 chars)
    API->>API: Validate ogImageUrl format
    API->>Database: Store link with OG metadata
    Database-->>API: Return created/updated link
    API-->>LinkEditForm: Success response
    LinkEditForm-->>User: Show success toast

    Note over Bot,RouteHandler: Social Media Crawler Access

    Bot->>RouteHandler: GET /q/[slug]
    RouteHandler->>Database: Find link by slug (with Redis cache)
    Database-->>RouteHandler: Return link with OG fields
    RouteHandler->>RouteHandler: Detect bot via isbot(userAgent)
    RouteHandler->>RouteHandler: Generate HTML with OG meta tags
    RouteHandler->>RouteHandler: Use ogTitle || name for title
    RouteHandler->>RouteHandler: Escape HTML content
    RouteHandler-->>Bot: Return HTML with OG metadata
    Bot->>Bot: Parse OG tags for preview

    Note over User,RouteHandler: Regular User Access

    User->>RouteHandler: GET /q/[slug]
    RouteHandler->>Database: Find link by slug
    Database-->>RouteHandler: Return link
    RouteHandler->>RouteHandler: Detect non-bot user agent
    RouteHandler->>RouteHandler: Track link click event
    RouteHandler-->>User: Redirect to destination URL
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

7 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines 72 to 73
<meta property="og:description" content="${ogDescription}">
<meta property="og:image" content="${ogImageUrl}">
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Empty og:description and og:image meta tags may cause validation issues on some social platforms. Should empty OG fields be omitted entirely rather than included as empty attributes?

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/(collect)/q/[slug]/route.ts
Line: 72:73

Comment:
**style:** Empty og:description and og:image meta tags may cause validation issues on some social platforms. Should empty OG fields be omitted entirely rather than included as empty attributes?

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

crbon added 3 commits January 22, 2026 00:37
…onality

- Fix inconsistent icon sizes in ExternalLink component by removing hardcoded props.
- Standardize icon alignment using new CSS class.
- Add copy-to-clipboard functionality when clicking the link text.
- Refactored the GET route to dynamically generate Open Graph and Twitter meta tags based on available link metadata.
- Added support for conditional rendering of meta tags, improving SEO and social media sharing capabilities.
- Ensured that the response includes appropriate meta tags for both Open Graph and Twitter cards, enhancing link previews.
- Introduced a new metaTag function to simplify the creation of meta tags for Open Graph and Twitter.
- Replaced regex-based bot detection with the isbot library for improved accuracy.
- Enhanced the GET route to conditionally render meta tags based on available link metadata, improving SEO and social media sharing capabilities.
@crbon
Copy link
Author

crbon commented Jan 22, 2026

@greptileai

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines 148 to 158
<FormField label="Title" name="ogTitle">
<TextField autoComplete="off" />
</FormField>

<FormField label="Description" name="ogDescription">
<TextField autoComplete="off" />
</FormField>

<FormField label="Image URL" name="ogImageUrl">
<TextField autoComplete="off" />
</FormField>
Copy link
Contributor

Choose a reason for hiding this comment

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

Hardcoded English labels "Title", "Description", "Image URL" inconsistent with the rest of the form which uses formatMessage(labels.*) for internationalization.

Suggested change
<FormField label="Title" name="ogTitle">
<TextField autoComplete="off" />
</FormField>
<FormField label="Description" name="ogDescription">
<TextField autoComplete="off" />
</FormField>
<FormField label="Image URL" name="ogImageUrl">
<TextField autoComplete="off" />
</FormField>
<FormField label={formatMessage(labels.title)} name="ogTitle">
<TextField autoComplete="off" />
</FormField>
<FormField label={formatMessage(labels.description)} name="ogDescription">
<TextField autoComplete="off" />
</FormField>
<FormField label={formatMessage(labels.imageUrl)} name="ogImageUrl">
<TextField autoComplete="off" />
</FormField>
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/(main)/links/LinkEditForm.tsx
Line: 148:158

Comment:
Hardcoded English labels "Title", "Description", "Image URL" inconsistent with the rest of the form which uses `formatMessage(labels.*)` for internationalization.

```suggestion
                <FormField label={formatMessage(labels.title)} name="ogTitle">
                  <TextField autoComplete="off" />
                </FormField>

                <FormField label={formatMessage(labels.description)} name="ogDescription">
                  <TextField autoComplete="off" />
                </FormField>

                <FormField label={formatMessage(labels.imageUrl)} name="ogImageUrl">
                  <TextField autoComplete="off" />
                </FormField>
```

How can I resolve this? If you propose a fix, please make it concise.

…h fields

- Replaced static labels in LinkEditForm with localized messages for ogTitle, ogDescription, and ogImageUrl.
- Added a new label for imageUrl in the messages file to support localization.
@crbon
Copy link
Author

crbon commented Jan 22, 2026

@greptileai

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

3 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

slug: z.string().min(4).max(100),
ogTitle: z.string().max(500).optional(),
ogDescription: z.string().max(500).optional(),
ogImageUrl: z.url().max(500).optional().or(z.literal('')),
Copy link
Contributor

Choose a reason for hiding this comment

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

validation will reject empty strings before checking .or(z.literal('')) - the schema should be .optional().or(z.literal('')) without .url() first, or use .url().optional().nullable() and convert empty strings to null.

Suggested change
ogImageUrl: z.url().max(500).optional().or(z.literal('')),
ogImageUrl: z.string().url().optional().or(z.literal('')),
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/api/links/route.ts
Line: 35:35

Comment:
validation will reject empty strings before checking `.or(z.literal(''))` - the schema should be `.optional().or(z.literal(''))` without `.url()` first, or use `.url().optional().nullable()` and convert empty strings to null.

```suggestion
    ogImageUrl: z.string().url().optional().or(z.literal('')),
```

How can I resolve this? If you propose a fix, please make it concise.

slug: z.string().min(4).optional(),
ogTitle: z.string().max(500).optional(),
ogDescription: z.string().max(500).optional(),
ogImageUrl: z.url().max(500).optional().or(z.literal('')),
Copy link
Contributor

Choose a reason for hiding this comment

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

validation will reject empty strings before checking .or(z.literal('')) - the schema should be .optional().or(z.literal('')) without .url() first, or use .url().optional().nullable() and convert empty strings to null.

Suggested change
ogImageUrl: z.url().max(500).optional().or(z.literal('')),
ogImageUrl: z.string().url().optional().or(z.literal('')),
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/api/links/[linkId]/route.ts
Line: 32:32

Comment:
validation will reject empty strings before checking `.or(z.literal(''))` - the schema should be `.optional().or(z.literal(''))` without `.url()` first, or use `.url().optional().nullable()` and convert empty strings to null.

```suggestion
    ogImageUrl: z.string().url().optional().or(z.literal('')),
```

How can I resolve this? If you propose a fix, please make it concise.

<title>${escapeHtml(ogTitle)}</title>
${metaTag('title', ogTitle, true)}
${metaTag('description', ogDescription, true)}
${metaTag('og:type', 'website')}
Copy link
Contributor

Choose a reason for hiding this comment

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

hardcoded site name 'Umami' - consider using a configuration value or environment variable for customization.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/(collect)/q/[slug]/route.ts
Line: 78:78

Comment:
hardcoded site name 'Umami' - consider using a configuration value or environment variable for customization.

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

- Update schema from z.url().max() to use .pipe() with length check.
- Properly validates URL format while preventing oversized input.
@crbon
Copy link
Author

crbon commented Jan 22, 2026

@greptileai

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

3 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Comment on lines 160 to 170
<Column>
<Label>{formatMessage(labels.path)}</Label>
<TextField
value={slug}
onChange={(value: string) => {
setSlug(value);
setValue('slug', value, { shouldDirty: true });
}}
autoComplete="off"
/>
</Column>
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing validation on the slug field allows users to submit empty or invalid slugs. The API expects min(4) but there's no client-side validation, potentially causing backend errors.

Suggested change
<Column>
<Label>{formatMessage(labels.path)}</Label>
<TextField
value={slug}
onChange={(value: string) => {
setSlug(value);
setValue('slug', value, { shouldDirty: true });
}}
autoComplete="off"
/>
</Column>
<Column>
<Label>{formatMessage(labels.path)}</Label>
<TextField
value={slug}
onChange={(value: string) => {
setSlug(value);
setValue('slug', value, { shouldDirty: true, shouldValidate: true });
}}
autoComplete="off"
minLength={4}
maxLength={100}
/>
</Column>
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/(main)/links/LinkEditForm.tsx
Line: 160:170

Comment:
Missing validation on the slug field allows users to submit empty or invalid slugs. The API expects `min(4)` but there's no client-side validation, potentially causing backend errors.

```suggestion
                <Column>
                  <Label>{formatMessage(labels.path)}</Label>
                  <TextField
                    value={slug}
                    onChange={(value: string) => {
                      setSlug(value);
                      setValue('slug', value, { shouldDirty: true, shouldValidate: true });
                    }}
                    autoComplete="off"
                    minLength={4}
                    maxLength={100}
                  />
                </Column>
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 85 to 94
<Form
onSubmit={handleSubmit}
error={getErrorMessage(error)}
defaultValues={{
slug,
...data,
ogTitle: data?.ogTitle || '',
ogDescription: data?.ogDescription || '',
ogImageUrl: data?.ogImageUrl || '',
}}
Copy link
Contributor

Choose a reason for hiding this comment

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

The slug field is now always included in form submission even when editing an existing link and the user hasn't modified it. This differs from the previous behavior where the slug was hidden and only set during creation. When updating a link, if the user doesn't change the slug, it shouldn't be included in the update payload to avoid unnecessary database writes.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/(main)/links/LinkEditForm.tsx
Line: 85:94

Comment:
The `slug` field is now always included in form submission even when editing an existing link and the user hasn't modified it. This differs from the previous behavior where the slug was hidden and only set during creation. When updating a link, if the user doesn't change the slug, it shouldn't be included in the update payload to avoid unnecessary database writes.

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +69 to +93
return new Response(
`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>${escapeHtml(ogTitle)}</title>
${metaTag('title', ogTitle, true)}
${metaTag('description', ogDescription, true)}
${metaTag('og:type', 'website')}
${metaTag('og:site_name', 'Umami')}
${metaTag('og:title', ogTitle)}
${metaTag('og:url', request.url)}
${metaTag('og:description', ogDescription)}
${metaTag('og:image', ogImageUrl)}
<meta name="twitter:card" content="${twitterCard}">
${metaTag('twitter:title', ogTitle, true)}
${metaTag('twitter:description', ogDescription, true)}
${metaTag('twitter:image', ogImageUrl, true)}
</head>
<body>
<p>Redirecting to ${escapeHtml(link.url)}...</p>
</body>
</html>
`,
Copy link
Contributor

Choose a reason for hiding this comment

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

Indentation in the HTML string includes leading whitespace on every line. Consider using template literal without indentation or a template engine for cleaner HTML output.

Suggested change
return new Response(
`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>${escapeHtml(ogTitle)}</title>
${metaTag('title', ogTitle, true)}
${metaTag('description', ogDescription, true)}
${metaTag('og:type', 'website')}
${metaTag('og:site_name', 'Umami')}
${metaTag('og:title', ogTitle)}
${metaTag('og:url', request.url)}
${metaTag('og:description', ogDescription)}
${metaTag('og:image', ogImageUrl)}
<meta name="twitter:card" content="${twitterCard}">
${metaTag('twitter:title', ogTitle, true)}
${metaTag('twitter:description', ogDescription, true)}
${metaTag('twitter:image', ogImageUrl, true)}
</head>
<body>
<p>Redirecting to ${escapeHtml(link.url)}...</p>
</body>
</html>
`,
return new Response(
`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>${escapeHtml(ogTitle)}</title>
${metaTag('title', ogTitle, true)}
${metaTag('description', ogDescription, true)}
${metaTag('og:type', 'website')}
${metaTag('og:site_name', 'Umami')}
${metaTag('og:title', ogTitle)}
${metaTag('og:url', request.url)}
${metaTag('og:description', ogDescription)}
${metaTag('og:image', ogImageUrl)}
<meta name="twitter:card" content="${twitterCard}">
${metaTag('twitter:title', ogTitle, true)}
${metaTag('twitter:description', ogDescription, true)}
${metaTag('twitter:image', ogImageUrl, true)}
</head>
<body>
<p>Redirecting to ${escapeHtml(link.url)}...</p>
</body>
</html>`,

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/(collect)/q/[slug]/route.ts
Line: 69:93

Comment:
Indentation in the HTML string includes leading whitespace on every line. Consider using template literal without indentation or a template engine for cleaner HTML output.

```suggestion
    return new Response(
      `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>${escapeHtml(ogTitle)}</title>
  ${metaTag('title', ogTitle, true)}
  ${metaTag('description', ogDescription, true)}
  ${metaTag('og:type', 'website')}
  ${metaTag('og:site_name', 'Umami')}
  ${metaTag('og:title', ogTitle)}
  ${metaTag('og:url', request.url)}
  ${metaTag('og:description', ogDescription)}
  ${metaTag('og:image', ogImageUrl)}
  <meta name="twitter:card" content="${twitterCard}">
  ${metaTag('twitter:title', ogTitle, true)}
  ${metaTag('twitter:description', ogDescription, true)}
  ${metaTag('twitter:image', ogImageUrl, true)}
</head>
<body>
  <p>Redirecting to ${escapeHtml(link.url)}...</p>
</body>
</html>`,
```

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

…ug handling

- Removed unnecessary useEffect for slug initialization and replaced it with a more efficient state setup.
- Updated handleSubmit to conditionally include the slug in the payload based on link creation or modification.
- Enhanced form handling by utilizing watch for dynamic slug updates and improved validation messages for slug input.
@crbon
Copy link
Author

crbon commented Jan 22, 2026

@greptileai

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

3 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +48 to +51
const handleSubmit = async (formData: any) => {
const { slug: formSlug, ...rest } = formData;
// Only include slug if creating new link or if it was modified
const payload = !linkId || formSlug !== data?.slug ? formData : rest;
Copy link
Contributor

Choose a reason for hiding this comment

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

The slug comparison logic is flawed when editing links. The watch('slug') may return undefined initially (before form hydration), so the condition formSlug !== data?.slug could incorrectly evaluate to true even when the slug hasn't changed, causing unnecessary slug updates.

Suggested change
const handleSubmit = async (formData: any) => {
const { slug: formSlug, ...rest } = formData;
// Only include slug if creating new link or if it was modified
const payload = !linkId || formSlug !== data?.slug ? formData : rest;
const handleSubmit = async (formData: any) => {
const { slug: formSlug, ...rest } = formData;
// Only include slug if creating new link or if it was modified
const payload = !linkId || (formSlug && formSlug !== data?.slug) ? formData : rest;
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/(main)/links/LinkEditForm.tsx
Line: 48:51

Comment:
The slug comparison logic is flawed when editing links. The `watch('slug')` may return `undefined` initially (before form hydration), so the condition `formSlug !== data?.slug` could incorrectly evaluate to true even when the slug hasn't changed, causing unnecessary slug updates.

```suggestion
  const handleSubmit = async (formData: any) => {
    const { slug: formSlug, ...rest } = formData;
    // Only include slug if creating new link or if it was modified
    const payload = !linkId || (formSlug && formSlug !== data?.slug) ? formData : rest;
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 24 to +27
<Text title={href} truncate>
<Link {...props} href={href} target="_blank">
<span onClick={handleCopy} className={styles.link}>
{children}
</Link>
</span>
Copy link
Contributor

Choose a reason for hiding this comment

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

Breaking UX change: clicking the link text now copies to clipboard instead of navigating. This violates user expectations where clicking text that looks like a link should navigate to that link. Users must now click the small icon to navigate.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/common/ExternalLink.tsx
Line: 24:27

Comment:
Breaking UX change: clicking the link text now copies to clipboard instead of navigating. This violates user expectations where clicking text that looks like a link should navigate to that link. Users must now click the small icon to navigate.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +17 to +19
const handleCopy = () => {
navigator.clipboard.writeText(href);
toast(formatMessage(labels.copied));
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing error handling for clipboard write operation. navigator.clipboard.writeText returns a Promise that can fail (e.g., in insecure contexts or if permissions are denied).

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/common/ExternalLink.tsx
Line: 17:19

Comment:
Missing error handling for clipboard write operation. `navigator.clipboard.writeText` returns a Promise that can fail (e.g., in insecure contexts or if permissions are denied).

How can I resolve this? If you propose a fix, please make it concise.

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