Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions apps/docs/src/components/page-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,36 @@ import { cva } from 'class-variance-authority';

const cache = new Map<string, string>();

function toIndexMarkdownUrl(markdownUrl: string): string | null {
if (!markdownUrl.endsWith('.mdx')) return `${markdownUrl.replace(/\/$/, '')}/index.mdx`;

const withoutExtension = markdownUrl.slice(0, -'.mdx'.length);
if (withoutExtension.endsWith('/index')) return null;

return `${withoutExtension}/index.mdx`;
}

async function fetchMarkdownWithFallback(markdownUrl: string): Promise<{ content: string; resolvedUrl: string }> {
const res = await fetch(markdownUrl);
if (res.ok) {
return { content: await res.text(), resolvedUrl: markdownUrl };
}

const fallbackUrl = toIndexMarkdownUrl(markdownUrl);
if (!fallbackUrl) {
throw new Error(`Failed to fetch markdown from ${markdownUrl} (${res.status})`);
}

const fallbackRes = await fetch(fallbackUrl);
if (fallbackRes.ok) {
return { content: await fallbackRes.text(), resolvedUrl: fallbackUrl };
}

throw new Error(
`Failed to fetch markdown from ${markdownUrl} (${res.status}) and ${fallbackUrl} (${fallbackRes.status})`,
);
}

export function LLMCopyButton({
/**
* A URL to fetch the raw Markdown/MDX content of page
Expand All @@ -29,19 +59,20 @@ export function LLMCopyButton({
}) {
const [isLoading, setLoading] = useState(false);
const [checked, onClick] = useCopyButton(async () => {
const cached = cache.get(markdownUrl);
const fallbackUrl = toIndexMarkdownUrl(markdownUrl);
const cached = cache.get(markdownUrl) ?? (fallbackUrl ? cache.get(fallbackUrl) : undefined);
if (cached) return navigator.clipboard.writeText(cached);

setLoading(true);

try {
await navigator.clipboard.write([
new ClipboardItem({
'text/plain': fetch(markdownUrl).then(async (res) => {
const content = await res.text();
'text/plain': fetchMarkdownWithFallback(markdownUrl).then(({ content, resolvedUrl }) => {
cache.set(markdownUrl, content);
cache.set(resolvedUrl, content);

return content;
return new Blob([content], { type: 'text/plain' });
}),
}),
]);
Comment on lines 61 to 78
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Smart dual-key caching strategy.

The cache lookup logic on lines 62-63 is clever—by pre-computing the potential fallback URL and checking both cache keys before fetching, you avoid unnecessary network requests when content was previously loaded via either path. The dual caching on lines 72-73 completes this nicely.

One observation: if fetchMarkdownWithFallback rejects (network failure or both URLs returning errors), the promise passed to ClipboardItem will reject. The finally block correctly resets the loading state, but the error itself isn't caught. The user would see the button stop loading with no indication of what went wrong. Consider whether a user-facing error state or toast notification would improve the experience.

💡 Optional: Add error feedback
+  const [error, setError] = useState<string | null>(null);
   const [checked, onClick] = useCopyButton(async () => {
+    setError(null);
     const fallbackUrl = toIndexMarkdownUrl(markdownUrl);
     const cached = cache.get(markdownUrl) ?? (fallbackUrl ? cache.get(fallbackUrl) : undefined);
     if (cached) return navigator.clipboard.writeText(cached);

     setLoading(true);

     try {
       await navigator.clipboard.write([
         new ClipboardItem({
           'text/plain': fetchMarkdownWithFallback(markdownUrl).then(({ content, resolvedUrl }) => {
             cache.set(markdownUrl, content);
             cache.set(resolvedUrl, content);

             return new Blob([content], { type: 'text/plain' });
           }),
         }),
       ]);
+    } catch (e) {
+      setError('Failed to copy markdown');
+      console.error(e);
     } finally {
       setLoading(false);
     }
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/docs/src/components/page-actions.tsx` around lines 61 - 78, The promise
passed into ClipboardItem via fetchMarkdownWithFallback can reject and that
error is currently uncaught, so update the onClick handler (the useCopyButton
callback) to wrap the navigator.clipboard.write call in try/catch around the
await and handle failures from fetchMarkdownWithFallback/ClipboardItem; on
error, ensure you still clear loading (setLoading(false) is in finally) and
surface user feedback (e.g., set an error state or call your app’s
toast/notification function) and optionally log the error and avoid caching on
failure—refer to fetchMarkdownWithFallback, ClipboardItem,
navigator.clipboard.write, setLoading, cache, and the useCopyButton callback to
locate the change.

Expand Down
Loading