From c9aa2f546646f1f9c91f7c7e7634369aa2fead89 Mon Sep 17 00:00:00 2001 From: 0xkkonrad Date: Wed, 22 Jul 2026 13:33:11 +0000 Subject: [PATCH 1/3] feat: Peanut Welcome Club onboarding quiz (static HTML in public/) The Welcome-@anon handbook's real pitfalls (comms rules, urgency hierarchy, task ownership, DoD, sick/weekend policy, security, invoicing) as a 23-question quiz, so new joiners actually retain them. Single self-contained file in public/ per the quiz paved path: zero deps, zero build-graph impact. Linked from /dev with a plain (public/ files aren't app routes). Prettier-ignored like dev-quiz: hand-tuned inline CSS/JS. --- .prettierignore | 3 + public/onboarding-quiz/index.html | 623 ++++++++++++++++++++++++++++++ src/app/(mobile-ui)/dev/page.tsx | 46 ++- 3 files changed, 655 insertions(+), 17 deletions(-) create mode 100644 public/onboarding-quiz/index.html diff --git a/.prettierignore b/.prettierignore index cf17c2fdac..ad8cbf1437 100644 --- a/.prettierignore +++ b/.prettierignore @@ -16,3 +16,6 @@ src/components/TransactionDetails/__tests__/fixtures/render-baseline.json # prettier touch them creates pointless diff churn on every regen. src/types/api.openapi.json src/types/api.generated.ts + +# static single-file quiz (hand-tuned inline CSS/JS; not prettier-formatted) +public/onboarding-quiz/ diff --git a/public/onboarding-quiz/index.html b/public/onboarding-quiz/index.html new file mode 100644 index 0000000000..726444bdf3 --- /dev/null +++ b/public/onboarding-quiz/index.html @@ -0,0 +1,623 @@ + + + + + +♡ PEANUT WELCOME CLUB ♡ — onboarding quiz + + + + + + +
☆ NEW HIRE ALERT ☆
+
♡ BE NICE OR ELSE ♡
+ +
+ ☆*:.。. o(≧▽≦)o .。.:*☆ PEANUT WELCOME CLUB ☆*:.。. o(≧▽≦)o .。.:*☆ WELCOME HOME ANON ♡ 100% WHOLESOME ✧ 0% CORPORATE (ironic) ✧ THE HANDBOOK LOVES YOU ✧ UR HIRED ✧ UR VALID ✧  + ☆*:.。. o(≧▽≦)o .。.:*☆ PEANUT WELCOME CLUB ☆*:.。. o(≧▽≦)o .。.:*☆ WELCOME HOME ANON ♡ 100% WHOLESOME ✧ 0% CORPORATE (ironic) ✧ THE HANDBOOK LOVES YOU ✧ UR HIRED ✧ UR VALID ✧  +
+ +
+

PEANUT WELCOME CLUB

+
☆*: .。. o(≧▽≦)o .。.:*☆  onboarding quiz  (❁´◡`❁)
+ + + + +
+
+
+
+ +
+ + + + + + + +
+
+ +
+ ٩(◕‿◕)۶ HANDBOOK TL;DR ✧ CLOSE UR THREADS ✧ SILENT REPLY = SHIFT+REPLY ✧ DONE MEANS DONE ✧ LOUD-FAIL, NEVER SILENT-FAIL ✧ FULL LEAVE > HALF LEAVE ✧ WE LOVE U ♡  + ٩(◕‿◕)۶ HANDBOOK TL;DR ✧ CLOSE UR THREADS ✧ SILENT REPLY = SHIFT+REPLY ✧ DONE MEANS DONE ✧ LOUD-FAIL, NEVER SILENT-FAIL ✧ FULL LEAVE > HALF LEAVE ✧ WE LOVE U ♡  +
+ + + + diff --git a/src/app/(mobile-ui)/dev/page.tsx b/src/app/(mobile-ui)/dev/page.tsx index 640a25356e..4afde58e0d 100644 --- a/src/app/(mobile-ui)/dev/page.tsx +++ b/src/app/(mobile-ui)/dev/page.tsx @@ -6,7 +6,8 @@ import Link from 'next/link' import { Icon, type IconName } from '@/components/Global/Icons/Icon' export default function DevToolsPage() { - const tools: { name: string; description: string; path: string; icon: IconName }[] = [ + // static: true → plain (file in public/, not an app route — Next Link can't client-navigate to it) + const tools: { name: string; description: string; path: string; icon: IconName; static?: boolean }[] = [ { name: 'Points Leaderboard', description: 'Real-time leaderboard with customizable time filters for event competitions', @@ -39,6 +40,14 @@ export default function DevToolsPage() { path: '/dev/debug', icon: 'dollar', }, + { + name: 'Peanut Welcome Club', + description: + 'Onboarding quiz: the Welcome-@anon handbook pitfalls (comms, tasks, security, invoicing) as an ironically kawaii quiz. Single static HTML in public/ — zero build impact.', + path: '/onboarding-quiz/index.html', + icon: 'trophy', + static: true, + }, { name: 'Home CTAs', description: @@ -60,24 +69,27 @@ export default function DevToolsPage() {

- {tools.map((tool) => ( - - -
-
-
- -
-
-

{tool.name}

-

{tool.description}

+ {tools.map((tool) => { + const LinkComponent = tool.static ? 'a' : Link + return ( + + +
+
+
+ +
+
+

{tool.name}

+

{tool.description}

+
+
- -
- - - ))} + + + ) + })}
From ea0f55f38b0df662dd991c154e20c69ee509624e Mon Sep 17 00:00:00 2001 From: 0xkkonrad Date: Wed, 22 Jul 2026 13:48:02 +0000 Subject: [PATCH 2/3] fix: share-card robustness + idle canvas guard (review findings) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Safari drops transient user activation across the awaited toBlob, so clipboard.write always rejected and COPY silently became a download — construct the ClipboardItem synchronously around the Promise. Guard the null-blob path (createObjectURL(null) threw uncaught and killed both buttons), defer revokeObjectURL past the download fetch, skip petal-rain repaints while the tab is hidden, and derive the boot banner count from QUESTIONS.length. --- public/onboarding-quiz/index.html | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/public/onboarding-quiz/index.html b/public/onboarding-quiz/index.html index 726444bdf3..2c58ceac11 100644 --- a/public/onboarding-quiz/index.html +++ b/public/onboarding-quiz/index.html @@ -368,6 +368,7 @@

PEANUT WELCOME CLUB

} sizeRain(); addEventListener('resize', sizeRain); setInterval(() => { + if (document.hidden) return; rctx.fillStyle = 'rgba(255,246,251,0.16)'; rctx.fillRect(0, 0, rain.width, rain.height); rctx.font = fs + 'px monospace'; cols.forEach((y, i) => { @@ -569,20 +570,28 @@

PEANUT WELCOME CLUB

x.fillText('the handbook loves you ♡ close ur threads', 540, 840); x.fillStyle = '#B268C8'; x.font = '26px monospace'; x.fillText('peanut.me/onboarding-quiz — join the club ♡', 540, 990); - const blob = await new Promise((res) => c.toBlob(res, 'image/png')); + // ClipboardItem must be constructed synchronously in the tap's task, wrapping the + // Promise — awaiting toBlob first expires Safari's transient user activation. + const blobPromise = new Promise((res, rej) => c.toBlob((b) => (b ? res(b) : rej(new Error('toBlob failed'))), 'image/png')); try { if (mode === 'copy' && navigator.clipboard && window.ClipboardItem) { - await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]); + await navigator.clipboard.write([new ClipboardItem({ 'image/png': blobPromise })]); $('btnCopy').textContent = '📋 COPIED ♡'; setTimeout(() => ($('btnCopy').textContent = '📋 COPY SHARE CARD'), 1600); return; } throw new Error('fallback'); } catch { - const a = document.createElement('a'); - a.href = URL.createObjectURL(blob); - a.download = `peanut-welcome-club-${correctCount}-${order.length}.png`; - a.click(); URL.revokeObjectURL(a.href); - $('btnDl').textContent = '💾 SAVED ♡'; setTimeout(() => ($('btnDl').textContent = '💾 DOWNLOAD CARD'), 1600); + try { + const blob = await blobPromise; + const a = document.createElement('a'); + a.href = URL.createObjectURL(blob); + a.download = `peanut-welcome-club-${correctCount}-${order.length}.png`; + a.click(); + setTimeout(() => URL.revokeObjectURL(a.href), 2000); + $('btnDl').textContent = '💾 SAVED ♡'; setTimeout(() => ($('btnDl').textContent = '💾 DOWNLOAD CARD'), 1600); + } catch { + $('btnDl').textContent = '💾 hmm, that didn\'t work — try again ♡'; + } } } @@ -615,7 +624,7 @@

PEANUT WELCOME CLUB

[null, '> brewing welcome tea .................... done ♡'], [null, '> fluffing your onboarding pillow ........ done ♡'], ['warn', '> scanning for bad vibes ................. NONE FOUND!!'], - [null, '> 23 questions · 3 levels · pts + streaks · infinite emotional support'], + [null, `> ${QUESTIONS.length} questions · 3 levels · pts + streaks · infinite emotional support`], ['ok', '> WELCOME HOME, ANON ♡ the club has been expecting you'], ], () => $('btnStart').classList.remove('hidden')); From 3208af584ec21ec7429b5e8f42c0ee5cea468bc1 Mon Sep 17 00:00:00 2001 From: 0xkkonrad Date: Wed, 22 Jul 2026 13:53:39 +0000 Subject: [PATCH 3/3] fix: route share-card fallback feedback to the clicked button (CodeRabbit) --- public/onboarding-quiz/index.html | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/public/onboarding-quiz/index.html b/public/onboarding-quiz/index.html index 2c58ceac11..6d75f0ede5 100644 --- a/public/onboarding-quiz/index.html +++ b/public/onboarding-quiz/index.html @@ -581,6 +581,9 @@

PEANUT WELCOME CLUB

} throw new Error('fallback'); } catch { + // feedback goes on the button the user actually clicked, even when copy falls back to download + const btn = mode === 'copy' ? $('btnCopy') : $('btnDl'); + const idle = mode === 'copy' ? '📋 COPY SHARE CARD' : '💾 DOWNLOAD CARD'; try { const blob = await blobPromise; const a = document.createElement('a'); @@ -588,9 +591,9 @@

PEANUT WELCOME CLUB

a.download = `peanut-welcome-club-${correctCount}-${order.length}.png`; a.click(); setTimeout(() => URL.revokeObjectURL(a.href), 2000); - $('btnDl').textContent = '💾 SAVED ♡'; setTimeout(() => ($('btnDl').textContent = '💾 DOWNLOAD CARD'), 1600); + btn.textContent = '💾 SAVED ♡'; setTimeout(() => (btn.textContent = idle), 1600); } catch { - $('btnDl').textContent = '💾 hmm, that didn\'t work — try again ♡'; + btn.textContent = 'hmm, that didn\'t work — try again ♡'; setTimeout(() => (btn.textContent = idle), 2400); } } }