Skip to content

fix(site): report copy failures instead of claiming success - #5

Merged
w0rxbend merged 1 commit into
mainfrom
fix/site-copy-feedback
Sep 6, 2026
Merged

w0rxbend merged 1 commit into
mainfrom
fix/site-copy-feedback

Conversation

@w0rxbend

@w0rxbend w0rxbend commented Sep 6, 2026

Copy link
Copy Markdown
Member

document.execCommand('copy') signals failure by returning false, not by throwing. The legacy copy path called it inside a try with an empty catch and then ran done() unconditionally:

try { document.execCommand('copy'); done(); } catch (e) {}

So the button showed "copied ✓" whether or not anything reached the clipboard. The clipboard-API path was silent in the same way — its rejection handler was function () {}.

This is javascript:S2486 ("handle this exception, don't catch it at all, or explain why it is ignored"), and it is a genuine bug rather than a style finding: the one thing this button exists to tell the user is exactly what it got wrong.

Changes

  • A failure state. copy failed ✗ with a .copy.err colour, alongside the existing .copy.ok.
  • done and fail declared in the IIFE scope, not inside the click handler, so neither is redefined per click. This also clears javascript:S7721, and declaring both together avoids raising a fresh S7721 on the new function.
  • The outcome is written to aria-label, not just textContent. The button carries a static aria-label="Copy install command", which is what a screen reader announces — updating only the visible text would have left non-sighted users with precisely the silence this PR removes.
  • The reset timer is cancelled before a new one starts. Without that, a second click inherits the first click's pending timer and the button reverts early. The failure path adds a timer that did not previously exist, so this became reachable.
  • ta.remove() replaces removeChild (javascript:S7762) and sits outside the try, so the textarea is torn down on both paths.
  • overflow-wrap: anywhere replaces the deprecated word-break: break-word (css:S1874). Not break-all, which would break the curl command mid-character.

Four outcomes, all now distinct: secure-context success and failure, legacy success and failure.

Deliberately untouched

Finding Why
css:S7924 contrast at line 262 False positive. Real composited contrast is ~9.8:1 against a 4.5:1 requirement — the analyzer could not resolve var(--bg) and composited the 5% white overlay onto white. "Fixing" it means hardcoding an opaque background, which kills the gradient the card is built around.
Web:S6819 role="img" at line 416 Won't fix. It wraps a CSS-styled fake terminal with block-character progress bars — the WAI-ARIA-sanctioned technique. Removing the role makes screen readers read the ASCII art aloud; converting to a real <img> means rasterizing the transcript. Naive fixing is an accessibility regression.

Both should be resolved in the SonarCloud UI rather than in code.

Verification

site/index.html is static, with no build step and no HTML or JS linter in CI, so there is nothing to run — node is not available in this environment, so node --check was not run. The changed block was traced by hand through both copy paths and all four outcomes, and a bracket/string/comment-aware balance scan over the file passes. Worth a manual click on both paths before merge.

Closes 4 code smells.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AaPvsFt1xYoXErXaZ4Kk8A

document.execCommand('copy') signals failure by returning false, not by
throwing. The legacy copy path called it inside a try with an empty catch
and then ran done() unconditionally, so the button showed "copied ✓"
whether or not anything reached the clipboard — and the clipboard-API
rejection handler was an empty function, silent in the same way.

The button now has a failure state. done and fail are declared in the IIFE
scope rather than inside the click handler, so neither is redefined per
click. Both route through one settle() that also writes the outcome to
aria-label: the button's label is what a screen reader announces, so
updating only textContent would have left non-sighted users with exactly
the silence this commit is removing.

The reset timer is now cancelled before a new one starts. Without that, a
second click inherits the first click's pending timer and the button
reverts early.

ta.remove() replaces removeChild and sits outside the try, so the textarea
is torn down on both paths.

Also switches a deprecated `word-break: break-word` to
`overflow-wrap: anywhere` — not break-all, which would break the curl
command mid-character.

Leaves the contrast warning and the role="img" terminal alone: both are
false positives, and "fixing" the latter makes a screen reader read the
ASCII art aloud.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AaPvsFt1xYoXErXaZ4Kk8A
@coderabbitai

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: a731de9c-8e62-4e4d-aa9f-7bbe4397a09a


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.

@sonarqubecloud

sonarqubecloud Bot commented Sep 6, 2026

Copy link
Copy Markdown

@w0rxbend
w0rxbend merged commit 0f791df into main Sep 6, 2026
4 of 5 checks passed
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