Copy share link silently fails on IPv6 / non-secure HTTP origins #99

Open
opened 2026-04-29 08:06:26 +00:00 by AhmedHanafy725 · 3 comments
Member

Bug

Clicking Copy on either share link in the share modal does nothing when the whiteboard is opened over an IPv6 address (e.g. http://[4c0:b29a:2940:5d2:1::1]:9988/...). No clipboard write, no feedback toast, no error visible to the user.

Root cause

crates/hero_whiteboard_ui/templates/web/home.html:782-790 calls navigator.clipboard.writeText(url) and chains only a .then() — no .catch().

The Clipboard API is only exposed in secure contexts (HTTPS, plus the loopback exceptions localhost, 127.0.0.1, and [::1]). Any other IPv6 address served over plain HTTP is not a secure context, so navigator.clipboard is undefined and .writeText(...) throws synchronously. With no .catch() the failure is silent — the user clicks Copy and nothing happens.

Expected

The Copy button works on any reachable origin (HTTP or HTTPS, IPv4 or IPv6) or, at minimum, surfaces a visible error so the user knows to copy manually.

Fix

In home.html::copyLink(url):

  1. Detect when navigator.clipboard and writeText are unavailable, and fall back to a document.execCommand("copy") flow via a temporary <textarea> (selected + copied + removed).
  2. Add a .catch() to the clipboard promise that, on rejection, also runs the fallback.
  3. If both paths fail, show a non-success toast (red) saying "Copy failed — select the link and copy manually."

The input fields in the share modal are already readonly inputs, so users can also select/copy them directly — but the Copy button should still work.

Acceptance

  • Copy button works on IPv6 HTTP origins (verified by clicking and pasting).
  • Copy button still works on localhost/IPv4/HTTPS (regression check).
  • On any failure path the user sees a visible toast (success or failure).

Notes

UI-only change. No server, SDK, or DB changes. The same fallback should be considered anywhere else navigator.clipboard is used (search for usages before fixing).

## Bug Clicking **Copy** on either share link in the share modal does nothing when the whiteboard is opened over an IPv6 address (e.g. `http://[4c0:b29a:2940:5d2:1::1]:9988/...`). No clipboard write, no feedback toast, no error visible to the user. ## Root cause `crates/hero_whiteboard_ui/templates/web/home.html:782-790` calls `navigator.clipboard.writeText(url)` and chains only a `.then()` — no `.catch()`. The Clipboard API is only exposed in **secure contexts** (HTTPS, plus the loopback exceptions `localhost`, `127.0.0.1`, and `[::1]`). Any other IPv6 address served over plain HTTP is **not** a secure context, so `navigator.clipboard` is `undefined` and `.writeText(...)` throws synchronously. With no `.catch()` the failure is silent — the user clicks Copy and nothing happens. ## Expected The Copy button works on any reachable origin (HTTP or HTTPS, IPv4 or IPv6) or, at minimum, surfaces a visible error so the user knows to copy manually. ## Fix In `home.html::copyLink(url)`: 1. Detect when `navigator.clipboard` and `writeText` are unavailable, and fall back to a `document.execCommand("copy")` flow via a temporary `<textarea>` (selected + copied + removed). 2. Add a `.catch()` to the clipboard promise that, on rejection, also runs the fallback. 3. If both paths fail, show a non-success toast (red) saying "Copy failed — select the link and copy manually." The input fields in the share modal are already `readonly` inputs, so users can also select/copy them directly — but the Copy button should still work. ## Acceptance - [ ] Copy button works on IPv6 HTTP origins (verified by clicking and pasting). - [ ] Copy button still works on `localhost`/IPv4/HTTPS (regression check). - [ ] On any failure path the user sees a visible toast (success or failure). ## Notes UI-only change. No server, SDK, or DB changes. The same fallback should be considered anywhere else `navigator.clipboard` is used (search for usages before fixing).
Author
Member

Implementation Spec for Issue #99

Objective

Make every "Copy link" / "Copy" affordance in the Hero Whiteboard UI work on any reachable origin, including plain-HTTP IPv6 (and any other non-secure context where navigator.clipboard is undefined). When the modern Clipboard API is missing or rejects, fall back to a document.execCommand('copy') flow over a temporary <textarea>. If both paths fail, surface a distinct error toast so the user knows to copy the link manually instead of silently doing nothing.

Requirements

  • Detect missing navigator.clipboard / navigator.clipboard.writeText and route to a synchronous document.execCommand('copy') fallback that creates an off-screen <textarea>, selects it, copies, and removes it.
  • Wrap navigator.clipboard.writeText(...) in a .then(success).catch(...) chain; on rejection, run the same execCommand fallback.
  • If execCommand('copy') returns false (or throws), show an error toast in the existing error style (red #dc3545, distinct from green #22c55e "Copied!") with text "Copy failed — select the link and copy manually."
  • Apply the same logic to every existing navigator.clipboard usage in the repo, not just the share modal in home.html.
  • No behavioral change on secure contexts beyond keeping the existing green success toast.

Files to Modify

  • crates/hero_whiteboard_ui/templates/web/home.html — rewrite copyLink(url) (around line 782) to use a unified copy helper with fallback + error toast.
  • crates/hero_whiteboard_ui/static/web/js/whiteboard/app.js — add a new exported copyToClipboard(text, successMsg) on WhiteboardApp that performs Clipboard API → execCommand fallback → red error toast (using the existing showToast(msg, isError)).
  • crates/hero_whiteboard_ui/static/web/js/whiteboard/ui-helpers.js — replace the two inline onclick="navigator.clipboard.writeText(...);WhiteboardApp.showToast('Copied!')" strings with calls to WhiteboardApp.copyToClipboard(...).
  • crates/hero_whiteboard_ui/static/web/js/whiteboard/properties.js — replace the bare navigator.clipboard.writeText(url); WhiteboardApp.showToast('Link copied!'); with WhiteboardApp.copyToClipboard(url, 'Link copied!').
  • crates/hero_whiteboard_ui/templates/web/board.html — replace the two inline onclick="navigator.clipboard.writeText(...);WhiteboardApp.showToast('Copied!')" strings with calls to WhiteboardApp.copyToClipboard(...).

Implementation Plan

Step 1: Add a centralized clipboard helper to WhiteboardApp

Files: crates/hero_whiteboard_ui/static/web/js/whiteboard/app.js

  • In the same scope that already exposes showToast, add function copyToClipboard(text, successMsg) (default successMsg = 'Copied!').
  • Inside it:
    1. Define function fallbackCopy(text) that creates a <textarea>, sets value, applies styles to keep it off-screen and non-interactive (position:fixed;top:-9999px;left:-9999px;opacity:0;), sets readonly/aria-hidden, appends to document.body, calls .focus() + .select() + setSelectionRange(0, text.length), wraps document.execCommand('copy') in try/catch, removes the textarea, returns the boolean result (false on throw or false return).
    2. Define inner onSuccess() (→ showToast(successMsg, false)) and onFailure() (→ showToast('Copy failed — select the link and copy manually.', true)).
    3. If navigator.clipboard && typeof navigator.clipboard.writeText === 'function', call navigator.clipboard.writeText(text).then(onSuccess).catch(function() { if (fallbackCopy(text)) onSuccess(); else onFailure(); });.
    4. Else run fallbackCopy(text) synchronously and toast accordingly.
  • Export copyToClipboard on the same returned object that already exposes showToast.
  • Dependencies: none.

Step 2: Wire all module-level call sites to the new helper

Files: crates/hero_whiteboard_ui/static/web/js/whiteboard/ui-helpers.js, crates/hero_whiteboard_ui/static/web/js/whiteboard/properties.js, crates/hero_whiteboard_ui/templates/web/board.html

  • ui-helpers.js line 39 and line 44: replace onclick="navigator.clipboard.writeText(document.getElementById('share-view-url').value);WhiteboardApp.showToast('Copied!')" (and the share-edit-url variant) with onclick="WhiteboardApp.copyToClipboard(document.getElementById('share-view-url').value)".
  • board.html lines 391 and 396: same two replacements (these strings are duplicated from ui-helpers.js).
  • properties.js line 992: replace navigator.clipboard.writeText(url); WhiteboardApp.showToast('Link copied!'); with WhiteboardApp.copyToClipboard(url, 'Link copied!'); (remove the now-redundant explicit toast call).
  • Dependencies: Step 1 (helper must exist on WhiteboardApp).

Files: crates/hero_whiteboard_ui/templates/web/home.html

  • The home page does not load WhiteboardApp. Inline equivalent logic locally.
  • Replace the existing copyLink(url) (lines 782–791) so it:
    1. Defines a local showCopyToast(message, isError) modeled on the current inline toast but parameterized: background = isError ? '#dc3545' : '#22c55e'. Keeps the existing positioning, animation, and 1500 ms timeout.
    2. Defines fallbackCopy(text) identical to Step 1's (off-screen textarea + execCommand('copy') returning a boolean).
    3. If navigator.clipboard?.writeText exists, call it and chain .then(→ success toast).catch(→ fallback then success or error toast).
    4. Else, run fallbackCopy(url) synchronously and toast accordingly.
  • The two call sites at lines 737 and 745 already pass the URL through escapeAttr and call copyLink('...'), so no changes are needed there — copyLink keeps the same signature.
  • Dependencies: none (this file is independent of WhiteboardApp).

Acceptance Criteria

  • On a non-secure origin (e.g. http://[2001:db8::1]/...), clicking any "Copy" / "Copy link" button copies the link via the textarea fallback and shows the green "Copied!" toast.
  • On a secure context (HTTPS, localhost, 127.0.0.1, [::1]), the existing Clipboard API path still runs and shows the green "Copied!" toast — no regression.
  • If both Clipboard API and execCommand('copy') fail, a red error toast appears with text "Copy failed — select the link and copy manually." (distinct from the green success toast).
  • No navigator.clipboard reference is left without a .catch() and execCommand fallback. Confirmed by grep -rn "navigator.clipboard" crates/hero_whiteboard_ui/.
  • All five call sites are converted: home.html:783, properties.js:992, board.html:391, board.html:396, ui-helpers.js:39, ui-helpers.js:44.
  • copyLink(url) in home.html keeps its public signature so the existing onclick handlers continue to work without template changes.

Notes

  • UI-only change. No server, SDK, OpenRPC, schema, database, or build-config changes. No new dependencies — document.execCommand('copy') is a built-in browser API and remains supported by every browser that runs the whiteboard UI today; it is deprecated but remains the canonical fallback for non-secure contexts.
  • The escapeAttr helper used to embed URLs into onclick="copyLink('...')" only escapes ' and ". The new copyLink body does not introduce any additional injection risk because the URL value is only forwarded to clipboard APIs and to textarea.value, never re-injected as HTML.
  • The fallback textarea must be appended to document.body (not the share modal) so it is not removed by modal close handlers mid-copy. It should set readonly and aria-hidden="true" to avoid accidental keyboard focus.
  • Mobile Safari needs setSelectionRange(0, text.length) after .select() for the textarea fallback to work — included in the helper.
## Implementation Spec for Issue #99 ### Objective Make every "Copy link" / "Copy" affordance in the Hero Whiteboard UI work on any reachable origin, including plain-HTTP IPv6 (and any other non-secure context where `navigator.clipboard` is `undefined`). When the modern Clipboard API is missing or rejects, fall back to a `document.execCommand('copy')` flow over a temporary `<textarea>`. If both paths fail, surface a distinct error toast so the user knows to copy the link manually instead of silently doing nothing. ### Requirements - Detect missing `navigator.clipboard` / `navigator.clipboard.writeText` and route to a synchronous `document.execCommand('copy')` fallback that creates an off-screen `<textarea>`, selects it, copies, and removes it. - Wrap `navigator.clipboard.writeText(...)` in a `.then(success).catch(...)` chain; on rejection, run the same `execCommand` fallback. - If `execCommand('copy')` returns `false` (or throws), show an error toast in the existing error style (red `#dc3545`, distinct from green `#22c55e` "Copied!") with text "Copy failed — select the link and copy manually." - Apply the same logic to every existing `navigator.clipboard` usage in the repo, not just the share modal in `home.html`. - No behavioral change on secure contexts beyond keeping the existing green success toast. ### Files to Modify - `crates/hero_whiteboard_ui/templates/web/home.html` — rewrite `copyLink(url)` (around line 782) to use a unified copy helper with fallback + error toast. - `crates/hero_whiteboard_ui/static/web/js/whiteboard/app.js` — add a new exported `copyToClipboard(text, successMsg)` on `WhiteboardApp` that performs Clipboard API → `execCommand` fallback → red error toast (using the existing `showToast(msg, isError)`). - `crates/hero_whiteboard_ui/static/web/js/whiteboard/ui-helpers.js` — replace the two inline `onclick="navigator.clipboard.writeText(...);WhiteboardApp.showToast('Copied!')"` strings with calls to `WhiteboardApp.copyToClipboard(...)`. - `crates/hero_whiteboard_ui/static/web/js/whiteboard/properties.js` — replace the bare `navigator.clipboard.writeText(url); WhiteboardApp.showToast('Link copied!');` with `WhiteboardApp.copyToClipboard(url, 'Link copied!')`. - `crates/hero_whiteboard_ui/templates/web/board.html` — replace the two inline `onclick="navigator.clipboard.writeText(...);WhiteboardApp.showToast('Copied!')"` strings with calls to `WhiteboardApp.copyToClipboard(...)`. ### Implementation Plan #### Step 1: Add a centralized clipboard helper to `WhiteboardApp` Files: `crates/hero_whiteboard_ui/static/web/js/whiteboard/app.js` - In the same scope that already exposes `showToast`, add `function copyToClipboard(text, successMsg)` (default `successMsg = 'Copied!'`). - Inside it: 1. Define `function fallbackCopy(text)` that creates a `<textarea>`, sets `value`, applies styles to keep it off-screen and non-interactive (`position:fixed;top:-9999px;left:-9999px;opacity:0;`), sets `readonly`/`aria-hidden`, appends to `document.body`, calls `.focus()` + `.select()` + `setSelectionRange(0, text.length)`, wraps `document.execCommand('copy')` in `try/catch`, removes the textarea, returns the boolean result (false on throw or false return). 2. Define inner `onSuccess()` (→ `showToast(successMsg, false)`) and `onFailure()` (→ `showToast('Copy failed — select the link and copy manually.', true)`). 3. If `navigator.clipboard && typeof navigator.clipboard.writeText === 'function'`, call `navigator.clipboard.writeText(text).then(onSuccess).catch(function() { if (fallbackCopy(text)) onSuccess(); else onFailure(); });`. 4. Else run `fallbackCopy(text)` synchronously and toast accordingly. - Export `copyToClipboard` on the same returned object that already exposes `showToast`. - Dependencies: none. #### Step 2: Wire all module-level call sites to the new helper Files: `crates/hero_whiteboard_ui/static/web/js/whiteboard/ui-helpers.js`, `crates/hero_whiteboard_ui/static/web/js/whiteboard/properties.js`, `crates/hero_whiteboard_ui/templates/web/board.html` - `ui-helpers.js` line 39 and line 44: replace `onclick="navigator.clipboard.writeText(document.getElementById('share-view-url').value);WhiteboardApp.showToast('Copied!')"` (and the `share-edit-url` variant) with `onclick="WhiteboardApp.copyToClipboard(document.getElementById('share-view-url').value)"`. - `board.html` lines 391 and 396: same two replacements (these strings are duplicated from ui-helpers.js). - `properties.js` line 992: replace `navigator.clipboard.writeText(url); WhiteboardApp.showToast('Link copied!');` with `WhiteboardApp.copyToClipboard(url, 'Link copied!');` (remove the now-redundant explicit toast call). - Dependencies: Step 1 (helper must exist on `WhiteboardApp`). #### Step 3: Replace `copyLink` in the home page template Files: `crates/hero_whiteboard_ui/templates/web/home.html` - The home page does not load `WhiteboardApp`. Inline equivalent logic locally. - Replace the existing `copyLink(url)` (lines 782–791) so it: 1. Defines a local `showCopyToast(message, isError)` modeled on the current inline toast but parameterized: `background = isError ? '#dc3545' : '#22c55e'`. Keeps the existing positioning, animation, and 1500 ms timeout. 2. Defines `fallbackCopy(text)` identical to Step 1's (off-screen textarea + `execCommand('copy')` returning a boolean). 3. If `navigator.clipboard?.writeText` exists, call it and chain `.then(→ success toast).catch(→ fallback then success or error toast)`. 4. Else, run `fallbackCopy(url)` synchronously and toast accordingly. - The two call sites at lines 737 and 745 already pass the URL through `escapeAttr` and call `copyLink('...')`, so no changes are needed there — `copyLink` keeps the same signature. - Dependencies: none (this file is independent of `WhiteboardApp`). ### Acceptance Criteria - [ ] On a non-secure origin (e.g. `http://[2001:db8::1]/...`), clicking any "Copy" / "Copy link" button copies the link via the textarea fallback and shows the green "Copied!" toast. - [ ] On a secure context (HTTPS, `localhost`, `127.0.0.1`, `[::1]`), the existing Clipboard API path still runs and shows the green "Copied!" toast — no regression. - [ ] If both Clipboard API and `execCommand('copy')` fail, a red error toast appears with text "Copy failed — select the link and copy manually." (distinct from the green success toast). - [ ] No `navigator.clipboard` reference is left without a `.catch()` and `execCommand` fallback. Confirmed by `grep -rn "navigator.clipboard" crates/hero_whiteboard_ui/`. - [ ] All five call sites are converted: `home.html:783`, `properties.js:992`, `board.html:391`, `board.html:396`, `ui-helpers.js:39`, `ui-helpers.js:44`. - [ ] `copyLink(url)` in `home.html` keeps its public signature so the existing `onclick` handlers continue to work without template changes. ### Notes - UI-only change. No server, SDK, OpenRPC, schema, database, or build-config changes. No new dependencies — `document.execCommand('copy')` is a built-in browser API and remains supported by every browser that runs the whiteboard UI today; it is deprecated but remains the canonical fallback for non-secure contexts. - The `escapeAttr` helper used to embed URLs into `onclick="copyLink('...')"` only escapes `'` and `"`. The new `copyLink` body does not introduce any additional injection risk because the URL value is only forwarded to clipboard APIs and to `textarea.value`, never re-injected as HTML. - The fallback textarea must be appended to `document.body` (not the share modal) so it is not removed by modal close handlers mid-copy. It should set `readonly` and `aria-hidden="true"` to avoid accidental keyboard focus. - Mobile Safari needs `setSelectionRange(0, text.length)` after `.select()` for the textarea fallback to work — included in the helper.
Author
Member

Test Results

  • cargo fmt --all -- --check: clean
  • cargo check --workspace: clean
  • cargo clippy --workspace -- -D warnings: clean
  • cargo test -p hero_whiteboard_server: 3 passed
  • node --check on app.js, ui-helpers.js, properties.js: parses cleanly
  • grep navigator.clipboard: only app.js::copyToClipboard and home.html::copyLink remain — both with .catch() and execCommand fallback

This is a UI-only behavior change; manual verification recommended:

  1. Open the whiteboard via plain-HTTP IPv6 (e.g. http://[2001:db8::1]:9988/...).
  2. Click any "Copy" button on the share modal — link should land in the clipboard, green "Copied!" toast appears.
  3. Force a failure by denying the page clipboard permission in browser settings — confirm a red "Copy failed" toast.
  4. Repeat on localhost / HTTPS — confirm no regression.
## Test Results - `cargo fmt --all -- --check`: clean - `cargo check --workspace`: clean - `cargo clippy --workspace -- -D warnings`: clean - `cargo test -p hero_whiteboard_server`: 3 passed - `node --check` on app.js, ui-helpers.js, properties.js: parses cleanly - `grep navigator.clipboard`: only `app.js::copyToClipboard` and `home.html::copyLink` remain — both with `.catch()` and `execCommand` fallback This is a UI-only behavior change; manual verification recommended: 1. Open the whiteboard via plain-HTTP IPv6 (e.g. `http://[2001:db8::1]:9988/...`). 2. Click any "Copy" button on the share modal — link should land in the clipboard, green "Copied!" toast appears. 3. Force a failure by denying the page clipboard permission in browser settings — confirm a red "Copy failed" toast. 4. Repeat on `localhost` / HTTPS — confirm no regression.
Author
Member

Implementation Summary

Four files changed, +63 / -11.

app.js (new helper)

  • Added WhiteboardApp.copyToClipboard(text, successMsg) (default successMsg = 'Copied!').
  • Inner fallbackCopy(text) builds an off-screen <textarea> with readonly + aria-hidden, applies setSelectionRange(0, text.length) (Safari iOS), wraps document.execCommand('copy') in try/catch, and removes the node.
  • When navigator.clipboard.writeText is available, the helper uses it and falls back to fallbackCopy on rejection. Otherwise it runs fallbackCopy synchronously.
  • On total failure it calls showToast('Copy failed — select the link and copy manually.', true) (red toast).
  • Exported on the public API alongside showToast.
  • Replaced the existing copyLink(url) with the same Clipboard API → execCommand → red error toast pattern, inlined locally because the home page does not load WhiteboardApp.
  • The function signature is unchanged, so the onclick="copyLink('...')" call sites at lines 737 / 745 are untouched.

ui-helpers.js

  • Lines 39 and 44: replaced onclick="navigator.clipboard.writeText(document.getElementById('share-view-url').value);WhiteboardApp.showToast('Copied!')" (and the share-edit-url variant) with onclick="WhiteboardApp.copyToClipboard(document.getElementById('share-view-url').value)". The helper handles its own toast.

board.html

  • Lines 391 and 396: same two replacements as in ui-helpers.js.

properties.js

  • Line 992: replaced the two-statement navigator.clipboard.writeText(url); WhiteboardApp.showToast('Link copied!'); with a single WhiteboardApp.copyToClipboard(url, 'Link copied!');.

Verification

  • cargo fmt --all -- --check: clean.
  • cargo check --workspace: clean.
  • cargo clippy --workspace -- -D warnings: clean.
  • cargo test -p hero_whiteboard_server: 3 passed.
  • node --check on every modified JS module: clean.
  • grep navigator.clipboard: only app.js::copyToClipboard and home.html::copyLink remain, both behind a .catch() + execCommand fallback.

Notes / caveats

  • UI-only; no server / SDK / openrpc / DB / build changes.
  • No new dependencies. document.execCommand('copy') is deprecated but still supported by every browser that runs the whiteboard UI today, and remains the canonical fallback for non-secure contexts.
  • The fallback textarea is appended to document.body (not the share modal) so a modal-close handler firing mid-copy cannot tear it out.
  • Manual verification: load the whiteboard over plain-HTTP IPv6, click any Copy button, paste somewhere — expect the green "Copied!" toast and the link in the clipboard. Deny clipboard permission in the browser to confirm the red "Copy failed" toast appears.
## Implementation Summary Four files changed, +63 / -11. ### `app.js` (new helper) - Added `WhiteboardApp.copyToClipboard(text, successMsg)` (default `successMsg = 'Copied!'`). - Inner `fallbackCopy(text)` builds an off-screen `<textarea>` with `readonly` + `aria-hidden`, applies `setSelectionRange(0, text.length)` (Safari iOS), wraps `document.execCommand('copy')` in `try/catch`, and removes the node. - When `navigator.clipboard.writeText` is available, the helper uses it and falls back to `fallbackCopy` on rejection. Otherwise it runs `fallbackCopy` synchronously. - On total failure it calls `showToast('Copy failed — select the link and copy manually.', true)` (red toast). - Exported on the public API alongside `showToast`. ### `home.html` (`copyLink`) - Replaced the existing `copyLink(url)` with the same Clipboard API → `execCommand` → red error toast pattern, inlined locally because the home page does not load `WhiteboardApp`. - The function signature is unchanged, so the `onclick="copyLink('...')"` call sites at lines 737 / 745 are untouched. ### `ui-helpers.js` - Lines 39 and 44: replaced `onclick="navigator.clipboard.writeText(document.getElementById('share-view-url').value);WhiteboardApp.showToast('Copied!')"` (and the `share-edit-url` variant) with `onclick="WhiteboardApp.copyToClipboard(document.getElementById('share-view-url').value)"`. The helper handles its own toast. ### `board.html` - Lines 391 and 396: same two replacements as in `ui-helpers.js`. ### `properties.js` - Line 992: replaced the two-statement `navigator.clipboard.writeText(url); WhiteboardApp.showToast('Link copied!');` with a single `WhiteboardApp.copyToClipboard(url, 'Link copied!');`. ### Verification - `cargo fmt --all -- --check`: clean. - `cargo check --workspace`: clean. - `cargo clippy --workspace -- -D warnings`: clean. - `cargo test -p hero_whiteboard_server`: 3 passed. - `node --check` on every modified JS module: clean. - `grep navigator.clipboard`: only `app.js::copyToClipboard` and `home.html::copyLink` remain, both behind a `.catch()` + `execCommand` fallback. ### Notes / caveats - UI-only; no server / SDK / openrpc / DB / build changes. - No new dependencies. `document.execCommand('copy')` is deprecated but still supported by every browser that runs the whiteboard UI today, and remains the canonical fallback for non-secure contexts. - The fallback textarea is appended to `document.body` (not the share modal) so a modal-close handler firing mid-copy cannot tear it out. - Manual verification: load the whiteboard over plain-HTTP IPv6, click any Copy button, paste somewhere — expect the green "Copied!" toast and the link in the clipboard. Deny clipboard permission in the browser to confirm the red "Copy failed" toast appears.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_whiteboard#99
No description provided.