Skip to content

Comments

🎨 Palette: Add Copy to Clipboard button for generated lyrics#54

Open
Krosebrook wants to merge 1 commit intomainfrom
palette-copy-lyrics-button-13816031286280836629
Open

🎨 Palette: Add Copy to Clipboard button for generated lyrics#54
Krosebrook wants to merge 1 commit intomainfrom
palette-copy-lyrics-button-13816031286280836629

Conversation

@Krosebrook
Copy link
Owner

@Krosebrook Krosebrook commented Feb 15, 2026

Added a "Copy to Clipboard" button to the lyrics generation page to improve user workflow. This allows users to quickly copy generated lyrics without saving them first. The implementation includes a robust fallback for environments where the Clipboard API is restricted and provides immediate visual feedback.


PR created automatically by Jules for task 13816031286280836629 started by @Krosebrook


Summary by cubic

Added a Copy to Clipboard button on the lyrics generation page so users can quickly copy generated lyrics without saving. Includes a safe fallback and instant visual feedback.

  • New Features
    • Icon-only Copy button next to Save with aria-label/title and data-testid.
    • Success feedback: toast and a temporary check icon; error toast on failure.
    • Clipboard logic uses navigator.clipboard with document.execCommand fallback; documented in Palette journal.

Written for commit 80804de. Summary will update on new commits.

- Adds a "Copy to Clipboard" button next to "Save to Library" in Generate.tsx
- Implements `navigator.clipboard` with `document.execCommand` fallback for robust support
- Provides visual feedback (Check icon) and toast notification
- Updates Palette journal with clipboard pattern learning

Co-authored-by: Krosebrook <214532761+Krosebrook@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 15, 2026 05:52
@google-labs-jules
Copy link
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @Krosebrook, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the user experience on the lyrics generation page by introducing a direct way to copy generated content. The implementation prioritizes reliability and user feedback, ensuring that the copy feature works across various browser environments and clearly communicates its status to the user. This change streamlines the workflow for users who wish to quickly utilize generated lyrics without additional steps.

Highlights

  • Copy to Clipboard Functionality: A 'Copy to Clipboard' button has been added to the lyrics generation page, allowing users to quickly copy generated lyrics without needing to save them first.
  • Robust Clipboard Implementation: The copy functionality includes a robust fallback mechanism, utilizing navigator.clipboard.writeText for modern browsers and document.execCommand('copy') with a temporary textarea for older environments or restricted contexts, ensuring broad compatibility.
  • User Feedback: Immediate visual feedback is provided to the user, with a checkmark icon appearing briefly after a successful copy and toast notifications for both success and failure.
  • Documentation Update: A new learning entry has been added to the .jules/palette.md file, documenting the robust clipboard pattern implemented in this pull request.
Changelog
  • .jules/palette.md
    • Added a new learning entry detailing the robust clipboard pattern, including the navigator.clipboard.writeText and document.execCommand('copy') fallback.
  • client/src/pages/Generate.tsx
    • Imported Copy and Check icons from lucide-react for the new clipboard functionality.
    • Implemented handleCopy function to copy generated lyrics to the clipboard, including a fallback mechanism for older browsers or restricted environments.
    • Added isCopied state to manage visual feedback after copying.
    • Integrated a 'Copy to Clipboard' button next to the 'Save to Library' button, displaying a checkmark upon successful copy.
    • Added toast notifications for copy success and failure.
Activity
  • PR created automatically by Jules for task 13816031286280836629, initiated by @Krosebrook.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link

coderabbitai bot commented Feb 15, 2026

Warning

Rate limit exceeded

@Krosebrook has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 18 minutes and 16 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch palette-copy-lyrics-button-13816031286280836629

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.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 2 files

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a useful 'Copy to Clipboard' feature for the generated lyrics, improving the user experience. The implementation is robust, including a fallback for older browsers, which is a great touch. I've left one comment with a suggestion to refactor the handleCopy function to improve its maintainability and prevent potential UI glitches with the fallback mechanism.

Comment on lines +31 to +53
const handleCopy = async () => {
if (!generatedContent) return;
const text = generatedContent;
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(text);
setIsCopied(true);
toast({ title: "Copied!", description: "Lyrics copied to clipboard" });
} else {
const textArea = document.createElement("textarea");
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
setIsCopied(true);
toast({ title: "Copied!", description: "Lyrics copied to clipboard" });
}
setTimeout(() => setIsCopied(false), 2000);
} catch (err) {
toast({ variant: "destructive", title: "Failed to copy", description: "Please try again." });
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The handleCopy function is well-structured with a fallback mechanism. However, it can be improved in a few ways:

  1. Reduce Code Duplication: The success handling logic (setIsCopied(true) and the success toast) is repeated in both the if and else blocks. This can be moved outside the conditional to follow the DRY (Don't Repeat Yourself) principle.
  2. Improve Fallback Robustness: The temporary textarea in the fallback is appended to the DOM without styling. This can cause a screen flicker or an unexpected page scroll. It's better to position it off-screen.
  3. Error Logging: It's helpful to log the error in the catch block for easier debugging.

Here's a suggested refactoring that addresses these points:

  const handleCopy = async () => {
    if (!generatedContent) return;
    const text = generatedContent;
    try {
      if (navigator.clipboard && navigator.clipboard.writeText) {
        await navigator.clipboard.writeText(text);
      } else {
        const textArea = document.createElement("textarea");
        textArea.value = text;

        // Prevent screen flicker and scrolling.
        textArea.style.position = "fixed";
        textArea.style.top = "-9999px";
        textArea.style.left = "-9999px";

        document.body.appendChild(textArea);
        textArea.select();
        document.execCommand("copy");
        document.body.removeChild(textArea);
      }

      setIsCopied(true);
      toast({ title: "Copied!", description: "Lyrics copied to clipboard" });
      setTimeout(() => setIsCopied(false), 2000);
    } catch (err) {
      console.error("Failed to copy text:", err);
      toast({ variant: "destructive", title: "Failed to copy", description: "Please try again." });
    }
  };

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 80804de1eb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +35 to +36
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(text);

Choose a reason for hiding this comment

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

P2 Badge Use fallback when Clipboard API write is denied

If navigator.clipboard.writeText exists but rejects (common in embedded webviews, denied permissions, or non-user-gesture contexts), the code jumps to the catch path and never tries the execCommand('copy') fallback. That makes copy fail in environments this change is explicitly trying to support, even though the fallback could still succeed.

Useful? React with 👍 / 👎.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an in-page “Copy to Clipboard” action on the lyrics generation workflow so users can copy generated lyrics without saving.

Changes:

  • Added copy button (icon-only) alongside “Save to Library” on the Generate page with visual “copied” feedback.
  • Implemented clipboard copy logic with a legacy document.execCommand("copy") fallback and toast notifications.
  • Documented the clipboard fallback pattern in .jules/palette.md.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
client/src/pages/Generate.tsx Adds copy-to-clipboard UI + handler for generated lyrics (with feedback).
.jules/palette.md Documents the intended “robust clipboard” implementation pattern.

Comment on lines +33 to +51
const text = generatedContent;
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(text);
setIsCopied(true);
toast({ title: "Copied!", description: "Lyrics copied to clipboard" });
} else {
const textArea = document.createElement("textarea");
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
setIsCopied(true);
toast({ title: "Copied!", description: "Lyrics copied to clipboard" });
}
setTimeout(() => setIsCopied(false), 2000);
} catch (err) {
toast({ variant: "destructive", title: "Failed to copy", description: "Please try again." });
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

The copy-to-clipboard implementation here duplicates the same Clipboard API + textarea fallback logic already present in client/src/pages/SongDetails.tsx (copyToClipboard). Consider extracting a shared helper/hook (e.g., client/src/lib/clipboard.ts) so behavior, toasts, and future fixes stay consistent across the app.

Suggested change
const text = generatedContent;
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(text);
setIsCopied(true);
toast({ title: "Copied!", description: "Lyrics copied to clipboard" });
} else {
const textArea = document.createElement("textarea");
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
setIsCopied(true);
toast({ title: "Copied!", description: "Lyrics copied to clipboard" });
}
setTimeout(() => setIsCopied(false), 2000);
} catch (err) {
toast({ variant: "destructive", title: "Failed to copy", description: "Please try again." });
if (!navigator.clipboard || !navigator.clipboard.writeText) {
toast({
variant: "destructive",
title: "Copy not supported",
description: "Your browser does not support copying to the clipboard.",
});
return;
}
try {
await navigator.clipboard.writeText(generatedContent);
setIsCopied(true);
toast({ title: "Copied!", description: "Lyrics copied to clipboard" });
setTimeout(() => setIsCopied(false), 2000);
} catch (err) {
toast({
variant: "destructive",
title: "Failed to copy",
description: "Please try again.",
});

Copilot uses AI. Check for mistakes.
Comment on lines +34 to +51
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(text);
setIsCopied(true);
toast({ title: "Copied!", description: "Lyrics copied to clipboard" });
} else {
const textArea = document.createElement("textarea");
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
setIsCopied(true);
toast({ title: "Copied!", description: "Lyrics copied to clipboard" });
}
setTimeout(() => setIsCopied(false), 2000);
} catch (err) {
toast({ variant: "destructive", title: "Failed to copy", description: "Please try again." });
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

In the legacy fallback branch, document.execCommand("copy") returns a boolean indicating success, but the result is ignored and the UI always shows “Copied!”. Please check the return value and surface a failure (e.g., throw / show the destructive toast) when it returns false, and ensure the temporary textarea is removed in a finally so it can’t be leaked if an error occurs mid-flow.

Suggested change
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(text);
setIsCopied(true);
toast({ title: "Copied!", description: "Lyrics copied to clipboard" });
} else {
const textArea = document.createElement("textarea");
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
setIsCopied(true);
toast({ title: "Copied!", description: "Lyrics copied to clipboard" });
}
setTimeout(() => setIsCopied(false), 2000);
} catch (err) {
toast({ variant: "destructive", title: "Failed to copy", description: "Please try again." });
let textArea: HTMLTextAreaElement | null = null;
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(text);
setIsCopied(true);
toast({ title: "Copied!", description: "Lyrics copied to clipboard" });
} else {
textArea = document.createElement("textarea");
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
const successful = document.execCommand("copy");
if (!successful) {
throw new Error("Legacy copy command failed");
}
setIsCopied(true);
toast({ title: "Copied!", description: "Lyrics copied to clipboard" });
}
setTimeout(() => setIsCopied(false), 2000);
} catch (err) {
toast({ variant: "destructive", title: "Failed to copy", description: "Please try again." });
} finally {
if (textArea && textArea.parentNode) {
textArea.parentNode.removeChild(textArea);
}

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +52
setTimeout(() => setIsCopied(false), 2000);
} catch (err) {
toast({ variant: "destructive", title: "Failed to copy", description: "Please try again." });
}
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

setTimeout(() => setIsCopied(false), 2000) isn’t cleaned up. If the user navigates away quickly or clicks copy repeatedly, this can leave multiple pending timers and can trigger state updates after unmount. Store the timeout id (e.g., in a ref), clear any existing timer before starting a new one, and clear it in an effect cleanup.

Copilot uses AI. Check for mistakes.
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