diff --git a/CHANGELOG.md b/CHANGELOG.md
index 36806311..f03f19df 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
### Changed
- Migrated AI logic from numbered demo folders into `src/`; shared LLM client at `src/core/llm-client.ts`.
- Moved marketplace HTML to `docs/` with `css/` and `js/` assets; updated all script paths and README links.
+- Code snippet copy buttons now show a failure state if browser clipboard access is denied.
### Removed
- Deleted legacy folders `01-Self-Healing-Tests`, `02-Smart-Data-Gen`, `03-Automated-Bug-Report`, `04-AI-Agents-QA`, `05-Visual-Regression`, and `lib/`.
diff --git a/README.md b/README.md
index 656889c2..f424a8e2 100644
--- a/README.md
+++ b/README.md
@@ -102,7 +102,7 @@ Fill in a form at `/submit` — name, system prompt, category, run command — a
-One-click copy reference with **10 collapsible sections** — all your go-to patterns in one place:
+One-click copy reference with **10 collapsible sections** — all your go-to patterns in one place. Copy buttons show a failure state if browser clipboard access is denied.
| Section | Contents |
|---------|----------|
diff --git a/ui/src/components/CodeSnippet.tsx b/ui/src/components/CodeSnippet.tsx
index 1b33ecea..ee413ae5 100644
--- a/ui/src/components/CodeSnippet.tsx
+++ b/ui/src/components/CodeSnippet.tsx
@@ -1,22 +1,35 @@
-import { useState } from 'react'
-import { useTranslation } from 'react-i18next'
-import ContentCopyIcon from '@mui/icons-material/ContentCopy'
-import CheckIcon from '@mui/icons-material/Check'
+import { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import ContentCopyIcon from '@mui/icons-material/ContentCopy';
+import CheckIcon from '@mui/icons-material/Check';
+import ErrorOutlinedIcon from '@mui/icons-material/ErrorOutlined';
interface Props {
- code: string
- language?: string
+ code: string;
+ language?: string;
}
export default function CodeSnippet({ code, language }: Props) {
- const { t } = useTranslation()
- const [copied, setCopied] = useState(false)
+ const { t } = useTranslation();
+ const [copyStatus, setCopyStatus] = useState<'idle' | 'copied' | 'failed'>('idle');
const handleCopy = async () => {
- await navigator.clipboard.writeText(code)
- setCopied(true)
- setTimeout(() => setCopied(false), 2000)
- }
+ try {
+ await navigator.clipboard.writeText(code);
+ setCopyStatus('copied');
+ } catch {
+ setCopyStatus('failed');
+ }
+
+ setTimeout(() => setCopyStatus('idle'), 2000);
+ };
+
+ const copyLabel =
+ copyStatus === 'copied'
+ ? t('common.copied')
+ : copyStatus === 'failed'
+ ? t('common.copy_failed')
+ : t('common.copy');
return (
// dir="ltr" — code is always left-to-right regardless of document language
@@ -37,15 +50,26 @@ export default function CodeSnippet({ code, language }: Props) {
-
+
{code}
- )
+ );
}
diff --git a/ui/src/locales/en.json b/ui/src/locales/en.json
index 0ee05cdb..9754a8ce 100644
--- a/ui/src/locales/en.json
+++ b/ui/src/locales/en.json
@@ -16,6 +16,7 @@
"common": {
"copy": "Copy",
"copied": "Copied!",
+ "copy_failed": "Failed to copy",
"add_to_claude": "Add to Claude",
"active": "Active",
"planned": "Planned",
diff --git a/ui/src/locales/he.json b/ui/src/locales/he.json
index b5ff85b6..89d312ff 100644
--- a/ui/src/locales/he.json
+++ b/ui/src/locales/he.json
@@ -16,6 +16,7 @@
"common": {
"copy": "העתק",
"copied": "הועתק!",
+ "copy_failed": "ההעתקה נכשלה",
"add_to_claude": "הוסף לקלוד",
"active": "פעיל",
"planned": "מתוכנן",