From ca07b7546ca5503a2572d31a741f4b40a9fa0480 Mon Sep 17 00:00:00 2001 From: simien Date: Fri, 2 Oct 2026 15:18:26 -0400 Subject: [PATCH 1/2] fix: only hand the link to the OS share sheet on a touch device Desktop Chromium exposes navigator.share, so Brave and Chrome on macOS relabelled the button Share and opened the macOS sheet. That sheet offers Mail, Messages, Notes and Reminders and no clipboard entry, which left no way to get the link out of a button that used to say Copy Link. Safari on macOS took the same wrong branch. Gate the sheet on (pointer: coarse) rather than on the API existing. A phone still gets the sheet; every desktop browser goes back to copying the link and showing the toast. Also let a failed share fall through to the clipboard. The old catch swallowed every error, so anything but a cancelled sheet left the button doing nothing at all; now only AbortError returns early. Co-Authored-By: Claude Opus 5 --- index.html | 44 +++++++++++++++++++++++++++++--------------- sitemap.xml | 2 +- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/index.html b/index.html index b016667..ab341a2 100644 --- a/index.html +++ b/index.html @@ -4164,26 +4164,23 @@

Flield: seamless animated b const copyLinkBtn = document.getElementById("copyLinkBtn"); - // Wherever the OS share sheet exists (Messages, AirDrop, most mobile - // and some desktop browsers), the button hands the link straight to - // it instead of the clipboard; the label and icon change to match so - // it's never a hidden feature riding along under an unrelated label. - const canShare = !!navigator.share; + // On a touch device the OS share sheet is the better hand-off + // (Messages, AirDrop, whatever else is installed), so the button + // gives it the link instead of the clipboard, and the label and icon + // change to match rather than hiding the swap under Copy Link. + // + // Gated on the pointer, not on the API: desktop Chromium (Brave and + // Chrome on macOS) exposes navigator.share too, and the sheet it + // opens there offers Mail, Messages, Notes and Reminders but no + // clipboard, which is the one thing someone pressing a button + // labelled Copy Link was after. + const canShare = !!navigator.share && window.matchMedia("(pointer: coarse)").matches; if (canShare) { copyLinkBtn.innerHTML = iconTag("share-network") + " Share"; copyLinkBtn.title = "Share this exact composition, locks included"; } - copyLinkBtn.addEventListener("click", async () => { - const { url, autoplay } = buildShareLink(); - if (canShare) { - try { - await navigator.share({ title: "Flield", text: "A composition I made with Flield:", url }); - } catch (err) { - // AbortError on a cancelled share sheet is normal, not a failure. - } - return; - } + async function copyShareLink(url, autoplay) { try { await navigator.clipboard.writeText(url); showToast(autoplay ? "Link copied, plays on open" : "Link copied"); @@ -4194,6 +4191,23 @@

Flield: seamless animated b // Dialogs are unavailable in some environments; nothing more to do. } } + } + + copyLinkBtn.addEventListener("click", async () => { + const { url, autoplay } = buildShareLink(); + if (canShare) { + try { + await navigator.share({ title: "Flield", text: "A composition I made with Flield:", url }); + return; + } catch (err) { + // A cancelled sheet is normal and wants nothing after it. Any + // other failure (a browser that refuses the call, a sheet that + // never opens) would leave the button doing nothing at all, so + // the clipboard below still gets the link. + if (err && err.name === "AbortError") return; + } + } + copyShareLink(url, autoplay); }); if (!loadStateFromURL()) { diff --git a/sitemap.xml b/sitemap.xml index 5889e1e..894a63e 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -2,7 +2,7 @@ https://flield.com/ - 2026-09-29 + 2026-10-02 monthly 1.0 From 54910cb4bb6c48f9170875632dbe1c861212e3c7 Mon Sep 17 00:00:00 2001 From: simien Date: Fri, 2 Oct 2026 15:23:06 -0400 Subject: [PATCH 2/2] docs: record the share gate as a trap and fix the stale markup comment The comment above the button still said the label swaps wherever the OS share sheet is available, which is the rule that caused the bug. AGENTS.md gains the trap itself: the API exists on desktop, the sheet it opens there has no clipboard entry, and the pane cannot verify the copy path because its document is never focused. Co-Authored-By: Claude Opus 5 --- AGENTS.md | 22 ++++++++++++++++++++++ index.html | 9 +++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 23b264a..3a531de 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -91,6 +91,28 @@ unguarded hover style sits latched on a phone until the next tap elsewhere. Any hover rule that changes a control's fill belongs inside `@media (hover: hover)`. +## Sharing is gated on the pointer, not on the API + +`navigator.share` exists on desktop Chromium and desktop Safari, so a +feature-detect alone sends Brave, Chrome and Safari on macOS into the OS +share sheet. That sheet offers Mail, Messages, Notes and Reminders and no +clipboard entry, which leaves no way to get the link out of a button that +otherwise says Copy Link. The test is +`navigator.share && matchMedia("(pointer: coarse)")`, and the markup +comment beside the button says so too, because the label it describes is +written from the script. + +The failure is quiet in both directions: a desktop visitor gets a sheet +that cannot do the one thing they pressed for, and a `share()` that +rejects for any reason other than a cancelled sheet leaves the button +doing nothing at all. Only `AbortError` returns early; everything else +falls through to the clipboard. + +The browser pane cannot verify the clipboard half of this. +`navigator.clipboard.writeText` throws `NotAllowedError` there because the +pane's document is never focused, so the copy path can be read but not +run. Press the button in a real browser before believing it. + ## The inline script has one scope `index.html` holds a single long inline script. Two consequences: diff --git a/index.html b/index.html index ab341a2..f5c1001 100644 --- a/index.html +++ b/index.html @@ -911,10 +911,11 @@

Export & share

- +