From c87ec907eff52d2c2a7230061d29276f0276b321 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Thu, 28 May 2026 22:04:22 -0400 Subject: [PATCH] Premium: honest soft-gate copy in unlock confirm + publish ack MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PREMIUM_DESIGNS_FEATURE.md §9.1 + §13.5. Two surfaces where the honest framing was missing: Unlock confirm dialog (buyer side) - Added a trailing line under the price breakdown: "Soft-gate, not strong DRM — once unlocked you can save / export / share the design like any other Artstr file." - The buyer sees the limitation BEFORE paying so the expectation is set up-front: the design will be theirs to use, and there's no client-side trick that prevents them from re-sharing afterwards. Publish modal ack checkbox (creator side) - New #publishConfirmSoftGateAck checkbox inside the premium- details panel: "I understand this is a static soft-gate, not strong DRM. A determined buyer can extract / re-share the design after unlocking. Artstr protects against drive-by scraping, not piracy." - handlePublishConfirmPublish hard-blocks the publish when state.publish.zapGate is on and the ack isn't ticked, routing through the existing publishConfirmError surface. - updatePublishConfirmZapGate resets the ack to unchecked on every modal open so creators have to re-acknowledge each time. Inheriting a tick across publishes would defeat the point. Tested via CDP: ack present, initial state unchecked, Publish without tick produces the corrective error and does NOT call the onConfirm callback. Tick + Publish succeeds (onConfirm fires). Regression smoke still passes. The remaining honest-copy surfaces (publish modal toggle label, preview-pane locked note, Premium tab hint) were already in place from earlier steps. The wallet panel itself doesn't get the soft-gate copy — NWC is general-purpose for tips + zaps, and the soft-gate framing belongs where the creator is publishing or the buyer is paying. Co-Authored-By: Claude Opus 4.7 --- src/index.html | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/index.html b/src/index.html index bd0648b..5539463 100644 --- a/src/index.html +++ b/src/index.html @@ -4363,6 +4363,10 @@

+ @@ -5643,7 +5647,8 @@ const ok = confirm( `Unlock "${row.title || '(untitled)'}" for ${min} sats?\n\n` + `• Creator (${shortPubkey(row.e.pubkey)}): ${creatorSats} sats\n` + - `• Artstr platform fee: ${platformSats} sats (${(PLATFORM_FEE_BPS / 100).toFixed(0)}%)` + `• Artstr platform fee: ${platformSats} sats (${(PLATFORM_FEE_BPS / 100).toFixed(0)}%)\n\n` + + `Soft-gate, not strong DRM — once unlocked you can save / export / share the design like any other Artstr file.` ); if (!ok) return; // Live status line; reuses the toast system for simplicity. @@ -17862,6 +17867,11 @@ toggle.checked = !!pub.zapGate; minInput.value = Math.max(10, Math.floor(Number(pub.zapMin) || 100)); details.style.display = toggle.checked ? '' : 'none'; + // Soft-gate ack always resets to unchecked on each modal open — we + // want the creator to acknowledge it deliberately every time, not + // accidentally inherit it from a previous publish. + const ack = $('publishConfirmSoftGateAck'); + if (ack) ack.checked = false; // Update split note + validate. const min = Math.max(10, Math.floor(Number(minInput.value) || 100)); const { creatorSats, platformSats } = platform.splitZapAmount(min); @@ -17963,6 +17973,15 @@ if (btn) { btn.disabled = false; btn.textContent = 'Publish'; } return; } + // PREMIUM_DESIGNS_FEATURE.md §9.1 — creator must acknowledge that + // this is a soft-gate, not strong DRM. The checkbox lives in the + // premium-details panel; we hard-block publish without it. + const ack = $('publishConfirmSoftGateAck'); + if (ack && !ack.checked) { + if (errEl) { errEl.textContent = 'Tick the soft-gate acknowledgement above before publishing a premium design.'; errEl.style.display = ''; } + if (btn) { btn.disabled = false; btn.textContent = 'Publish'; } + return; + } } try { await target.onConfirm();