Skip to content
This repository was archived by the owner on Jun 26, 2026. It is now read-only.

Commit 77effbc

Browse files
dsimanskclaude
andcommitted
fix: handle clipboard copy failures gracefully
The copy buttons on the landing page silently failed when the Clipboard API was unavailable (non-HTTPS) or when writeText was rejected. Added a pre-check for navigator.clipboard and a .catch() handler, both showing an error state with an X icon and "Copy failed" label. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent a37f987 commit 77effbc

3 files changed

Lines changed: 44 additions & 0 deletions

File tree

docs/claude-progress.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
# Claude Progress Log
22
# Newest entries first. Agents: append your entry at the top after the header.
33

4+
---
5+
## 2026-06-12 | Session: Fix clipboard copy error handling
6+
Worked on: Add error state for copy-to-clipboard buttons on landing page
7+
Completed:
8+
- Added error handling for navigator.clipboard API unavailability (e.g. non-HTTPS contexts)
9+
- Added .catch() handler for clipboard.writeText promise rejection
10+
- Added error state styling (.copy-error class) with red color, X icon, and "Copy failed" hover label
11+
- Applied error handling to both install command and YAML copy buttons
12+
Left off: Branch pr/fix-catch ready for review.
13+
Blockers: None
14+
415
---
516
## 2026-05-21 | Session: Page co-location restructure (continued)
617
Worked on: Follow-up fixes from review, test cleanup, docs

docs/features.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,5 +180,16 @@
180180
"All tests pass, no broken imports"
181181
],
182182
"passes": true
183+
},
184+
{
185+
"category": "technical",
186+
"description": "Landing page: copy buttons handle clipboard failures gracefully (non-HTTPS, permission denied)",
187+
"steps": [
188+
"Pre-check navigator.clipboard availability before calling writeText",
189+
"Catch writeText rejections (e.g. permission denied, iframe sandbox)",
190+
"Error state shows X icon with 'Copy failed' label on hover, auto-clears after 1.5s",
191+
"Both copy buttons (install command and YAML) have error handling"
192+
],
193+
"passes": true
183194
}
184195
]

pages/index.html

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@
149149
position: absolute;
150150
top: 0.6rem;
151151
right: 0.6rem;
152+
display: inline-flex;
153+
align-items: center;
154+
gap: 0.3rem;
152155
background: transparent;
153156
border: none;
154157
color: #484f58;
@@ -160,10 +163,16 @@
160163
}
161164
.copy-btn:hover { color: #8b949e; background: rgba(255, 255, 255, 0.06); }
162165
.copy-btn.copied { color: var(--accent); }
166+
.copy-btn.copy-error { color: #f85149; }
163167
.copy-icon { width: 16px; height: 16px; }
164168
.check-icon { width: 16px; height: 16px; display: none; }
169+
.error-icon { width: 16px; height: 16px; display: none; }
170+
.error-label { display: none; font-family: 'JetBrains Mono', monospace; font-size: 0.65rem; }
165171
.copy-btn.copied .copy-icon { display: none; }
166172
.copy-btn.copied .check-icon { display: inline; }
173+
.copy-btn.copy-error .copy-icon { display: none; }
174+
.copy-btn.copy-error .error-icon { display: inline; }
175+
.copy-btn.copy-error:hover .error-label { display: inline; }
167176
.fade-in { animation: fadeIn 0.6s ease both; }
168177
.fade-in-delay-1 { animation-delay: 0.1s; }
169178
.fade-in-delay-2 { animation-delay: 0.2s; }
@@ -188,6 +197,8 @@ <h5 class="card-title">Install</h5>
188197
<button class="copy-btn" data-target="install-cmd" data-strip="$ ">
189198
<svg class="copy-icon" viewBox="0 0 16 16" fill="currentColor"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"/><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"/></svg>
190199
<svg class="check-icon" viewBox="0 0 16 16" fill="currentColor"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"/></svg>
200+
<svg class="error-icon" viewBox="0 0 16 16" fill="currentColor"><path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"/></svg>
201+
<span class="error-label">Copy failed</span>
191202
</button>
192203
<pre><code id="install-cmd"><span class="prompt">$</span> oc new-project console-functions-plugin
193204
<span class="prompt">$</span> oc apply -f https://functions-dev.github.io/ocp-console-plugin/plugin.yaml</code></pre>
@@ -209,6 +220,8 @@ <h5 class="card-title">plugin.yaml</h5>
209220
<button class="copy-btn" data-target="yaml-content">
210221
<svg class="copy-icon" viewBox="0 0 16 16" fill="currentColor"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"/><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"/></svg>
211222
<svg class="check-icon" viewBox="0 0 16 16" fill="currentColor"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"/></svg>
223+
<svg class="error-icon" viewBox="0 0 16 16" fill="currentColor"><path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"/></svg>
224+
<span class="error-label">Copy failed</span>
212225
</button>
213226
<div class="yaml-scroll">
214227
<pre><code id="yaml-content">Loading...</code></pre>
@@ -227,9 +240,18 @@ <h5 class="card-title">plugin.yaml</h5>
227240
if (strip) {
228241
text = text.split('\n').map(l => l.startsWith(strip) ? l.slice(strip.length) : l).join('\n');
229242
}
243+
// Clipboard API requires a secure context (HTTPS). On plain HTTP, navigator.clipboard might be undefined.
244+
if (typeof navigator.clipboard === 'undefined') {
245+
btn.classList.add('copy-error');
246+
setTimeout(() => { btn.classList.remove('copy-error'); }, 1500);
247+
return;
248+
}
230249
navigator.clipboard.writeText(text).then(() => {
231250
btn.classList.add('copied');
232251
setTimeout(() => { btn.classList.remove('copied'); }, 1500);
252+
}).catch(() => {
253+
btn.classList.add('copy-error');
254+
setTimeout(() => { btn.classList.remove('copy-error'); }, 1500);
233255
});
234256
});
235257
});

0 commit comments

Comments
 (0)