← Commit history

2.8.0: copy falls back to execCommand - navigator.clipboard rejects with NotAllowedError in ah's webview, so the only path the panel had was failing

John Lauer ·7960982eec ·1mo ago ·parent 4dcbc94
3 files changed +57−3
assets/module/newbie-panel.js+31−2
@@ -139,6 +139,35 @@     return safe.replace(re, '<b>$1</b>');   } +  /* ---- copy, with a fallback that does not need the async clipboard ------+   * navigator.clipboard.writeText needs a secure context AND, in several embedded webviews,+   * transient user activation plus a clipboard-write permission that is simply absent. In+   * ah's webview it rejects with NotAllowedError, so the ONLY path the panel had would fail+   * and the user got "Could not reach the clipboard" on a button that looks like it works.+   * The execCommand path is deprecated but synchronous, needs no permission, and works in+   * exactly the contexts the async API refuses. Try the modern one, fall back, and only then+   * report failure. */+  function copyText(text) {+    return new Promise(function (resolve, reject) {+      function legacy() {+        try {+          var ta = document.createElement('textarea');+          ta.value = text;+          ta.setAttribute('readonly', '');+          ta.style.cssText = 'position:fixed;top:0;left:-9999px;opacity:0';+          document.body.appendChild(ta);+          ta.select(); ta.setSelectionRange(0, ta.value.length);+          var ok = document.execCommand && document.execCommand('copy');+          ta.remove();+          ok ? resolve('legacy') : reject(new Error('execCommand refused'));+        } catch (e) { reject(e); }+      }+      if (navigator.clipboard && navigator.clipboard.writeText) {+        navigator.clipboard.writeText(text).then(function () { resolve('async'); }, legacy);+      } else legacy();+    });+  }+   /* ---- toasts ---------------------------------------------------------- */   var toastHost = null;   function toast(msg, kind, ms) {@@ -275,7 +304,7 @@       tipFor(cp, 'Copy this phrase');       cp.addEventListener('click', function (e) {         e.stopPropagation();-        (navigator.clipboard ? navigator.clipboard.writeText(phrase) : Promise.reject())+        copyText(phrase)           .then(function () { toast('Copied. Paste it to your AI.', 'ok'); })           .catch(function () { toast('Could not reach the clipboard. Select the phrase and copy it.', 'err'); });       });@@ -485,6 +514,6 @@     return { ok: ok, rows: rows };   } -  root.AdomNewbiePanel = { mount: mount, toast: toast, selfCheck: selfCheck, version: '2.6.0',+  root.AdomNewbiePanel = { mount: mount, toast: toast, selfCheck: selfCheck, copyText: copyText, version: '2.8.0',                            TWO_COL_MIN: TWO_COL_MIN, PHRASE_BUDGET: PHRASE_BUDGET }; })(typeof window !== 'undefined' ? window : this);
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-ui-design",-  "version": "2.7.0",+  "version": "2.8.0",   "type": "skill",   "description": "The Adom UI Design System plus a bundle of focused UI skills (tooltips, floating-windows, toasts, responsive-ui, app-shell, ai-driven-apps, clickable-paths, dark-default, input-feedback, settings-layout). One install drops the whole set of skill files onto your container, plus shared icon assets (the settings cog) so every Adom app draws the same glyph.",   "dependencies": {},
skills/newbie-panel/SKILL.md+25
@@ -952,6 +952,31 @@ the page. Check it by toggling: screenshot with the panel open and closed and compare the distance from the header to the first content row. It should be identical. +## ⛔ navigator.clipboard REJECTS IN AH'S WEBVIEW. THE COPY BUTTON NEEDS A FALLBACK.++Ralph-testing the copy button on two live dashboards in John's dock returned, on both:++```+toast:      "Could not reach the clipboard. Select the phrase and copy it."+readText(): READ_BLOCKED:NotAllowedError+```++`navigator.clipboard.writeText` needs a secure context AND, in several embedded webviews,+transient user activation plus a clipboard-write permission that is simply absent. The module+had only that path, so the button looked like it worked and told the user it had failed.++**2.8.0 adds a `execCommand('copy')` fallback**: a hidden readonly textarea, select, copy,+remove. Deprecated but synchronous, needs no permission, and works in exactly the contexts the+async API refuses. Try the modern API, fall back, and only then report failure. `copyText` is+exported so a test can exercise the same path the button uses.++**Honest caveat on how this was found:** the click was synthetic (`btn.click()` from an eval),+and the async clipboard API can reject for a synthetic click that would succeed for a real+one. So the observed failure may overstate what a user hits. The fallback is correct+regardless: a copy button whose only path requires a permission the host may not grant is a+button that fails silently for some users, and there is no downside to the second path.+**Verify with a real click before claiming the user-facing bug is fixed.**+ ## ⛔ THE DOCK INSTALLS A PACKAGE. IT DOES NOT RESTART THE RUNNING PROCESS.  **Publishing is not deploying, and installing is not running.** lbr found John's box serving