-
Notifications
You must be signed in to change notification settings - Fork 14
refactor(assets): name the mascot a mascot, not a logo #2317
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
42 commits
Select commit
Hold shift + click to select a range
1522abf
fix(kyc): surface per-label reject copy in action-required drawer
jjramirezn b0a1b09
fix(kyc): collapse duplicated reject-labels branch + test real copy
jjramirezn 1f7fab0
fix(card): grey + "Soon!" the guest claim-to-bank option (under maint…
Hugo0 ece9cb5
Merge pull request #2294 from peanutprotocol/hotfix/guest-bank-soon
Hugo0 cfc9955
Merge pull request #2298 from peanutprotocol/dev
Hugo0 323fa61
fix(card): bring launch CTA in line with the activation CTAs
Hugo0 8d78395
fix(card): match activation-CTA border weight (1px) + spell shhhh
Hugo0 80ffc2d
Merge pull request #2299 from peanutprotocol/hotfix/card-launch-cta-d…
Hugo0 91be7b6
Merge pull request #2293 from peanutprotocol/fix/kyc-reject-label-pre…
Hugo0 248c33e
fix(card): wait for the card-face canvas before capturing the share a…
Hugo0 aeb2c46
Merge pull request #2302 from peanutprotocol/hotfix/share-asset-blank…
Hugo0 574ce1b
feat(card): end the waitlist flow on the rejection/appeal screen (dro…
Hugo0 4e8af84
fix(card): match joined-state indicator to button height (no layout s…
Hugo0 622968b
Merge pull request #2303 from peanutprotocol/hotfix/waitlist-end-on-r…
Hugo0 eb6646b
fix(card): address CodeRabbit on the terminal waitlist screen
Hugo0 55f7677
Merge pull request #2304 from peanutprotocol/hotfix/rejection-screen-…
Hugo0 4b0c8e3
test(card): regression guard for the blank share-asset capture race
Hugo0 1bd95df
Merge pull request #2305 from peanutprotocol/test/share-asset-capture…
Hugo0 7cd807a
fix(card): raise apply-for-card fetch timeout to 60s
jjramirezn 2ec8b5d
Merge pull request #2306 from peanutprotocol/hotfix/rain-apply-timeout
Hugo0 3dbc068
feat(card): real FOMO door tally on rejection screen
Hugo0 b6e187d
fix(card): bulletproof share-asset capture — faithful shadows + deter…
Hugo0 2b621d1
fix(card): make waitlistTotal/admittedTotal optional on CardInfoResponse
Hugo0 4092778
feat(kyc): make EEA-uplift verification a mandatory gate + funnel ana…
kushagrasarathe 06928d6
test(card): decode captured PNG in-browser, drop the sharp dep
Hugo0 09345b9
tune(card): door-tally FOMO multiplier x3 -> x5
Hugo0 2793c87
Merge pull request #2307 from peanutprotocol/feat/door-tally-real-counts
Hugo0 33f7d7e
fix(analytics): clear eea-uplift start on KYC abandon
kushagrasarathe 9de604b
Merge pull request #2308 from peanutprotocol/fix/share-asset-bulletproof
Hugo0 e6a6aa9
fix(kyc): address review — fire uplift-completed at approval; sync mo…
kushagrasarathe 0185213
feat(card): show the specific reason on the rejected-card screen + re…
jjramirezn 3780741
test(card): silence next/image stub lint warning in rejected-screen test
jjramirezn 216912a
fix(card): render rejected screen immediately + assert reason placeme…
jjramirezn e187dcc
fix(card): force space in physical-waitlist position copy via templat…
Hugo0 70272d1
Merge pull request #2310 from peanutprotocol/feat/card-rejection-reason
Hugo0 ee38e7d
Merge pull request #2311 from peanutprotocol/hotfix/card-waitlist-pos…
Hugo0 fdda3db
Merge pull request #2309 from peanutprotocol/feat/eea-uplift-hard-gate
Hugo0 a2e8f33
fix(card): render share-asset hand as <img>, not a runtime <canvas>
Hugo0 4399a9b
refactor(assets): name the mascot a mascot, not a logo
0xkkonrad 8e5802d
chore(assets): add public/peanutman.svg alias for the mascot
0xkkonrad 0a82a0f
chore(assets): drop redundant public/peanutman.svg alias
kushagrasarathe ffddcc1
Merge dev card share-asset fix into rename branch
kushagrasarathe File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| /** | ||
| * Share-asset capture regression — the card face must NOT be blank. | ||
| * | ||
| * The launch-day bug: <PixelatedCardFace /> painted its pixelated hand into a | ||
| * runtime <canvas>, which html-to-image silently dropped when it couldn't | ||
| * serialise it (toDataURL() returns empty on iOS Safari for an SVG-sourced | ||
| * canvas) → blank pink card, and the capture SUCCEEDED so nothing reached Sentry. | ||
| * | ||
| * The fix renders the hand as a plain pre-pixelated <img> (the same reliable | ||
| * path the badge stickers take), and still gates Save/Share on PixelatedCardFace's | ||
| * `onReady` (the hand <img> loading) so a capture can't fire before it's ready. | ||
| * | ||
| * This spec proves the guard end-to-end against the real html-to-image path: | ||
| * 1. /dev/share-builder renders <ShareAssetD3 /> and wires "Save image" to | ||
| * captureShareAsset + downloadBlob (the same capture code the in-app | ||
| * Share/Save buttons use). | ||
| * 2. Save stays disabled until the card face signals ready — we wait for it | ||
| * to enable (proves the gate releases only after the hand <img> loads). | ||
| * 3. We click Save, intercept the downloaded PNG, decode it IN-BROWSER (an | ||
| * <img> + <canvas>.getImageData — no extra Node dep), and sample a region | ||
| * in the CENTRE of the card — the hand's territory, away from the top-left | ||
| * logo and bottom-left card number. If that region is ENTIRELY the card | ||
| * pink (#FF90E8) the hand never rendered → the blank-card bug. We assert | ||
| * it contains non-background pixels (the hand was captured). | ||
| * | ||
| * No harness auth needed — /dev/share-builder is a pure client-render dev page. | ||
| */ | ||
|
|
||
| import { test, expect } from '@playwright/test' | ||
| import { readFileSync } from 'fs' | ||
|
|
||
| // Asset + card geometry — mirror of shareAssetLayout.ts (CANVAS_W/H, CARD_*). | ||
| // Hardcoded (not imported) to match the e2e convention of not pulling app code | ||
| // through the '@/' alias into the Playwright tsconfig. | ||
| const CANVAS_W = 1200 | ||
| const CANVAS_H = 900 | ||
|
|
||
| // Card-pink (PixelatedCardFace background) and asset-blue (ShareAssetD3 bg) as | ||
| // [r,g,b]. "Background" = either of these; the hand is neither. | ||
| const CARD_PINK: [number, number, number] = [0xff, 0x90, 0xe8] | ||
| const ASSET_BLUE: [number, number, number] = [0x90, 0xa8, 0xed] | ||
| const COLOR_TOL = 12 | ||
|
|
||
| // Central card region in 1200×900 canvas coords. The card is centred | ||
| // (CARD_LEFT 220, CARD_TOP 210, CARD_W 760, CARD_H ≈ 479 → centre ≈ 600,449). | ||
| // This window sits well inside the card and is HAND-ONLY: it excludes the | ||
| // peanut logo (top-left ~248–300 px) and the "????" number (bottom-left, | ||
| // canvas-y ≳ 615). So a blank card → this window is pure pink; the hand | ||
| // present → many non-background pixels. Small enough that the card's -8° | ||
| // final rotation can't rotate any background (blue) into it. | ||
| const REGION = { x0: 430, y0: 330, x1: 770, y1: 570 } | ||
| const SAMPLE_STEP = 6 // sample every 6th canvas-px → a dense grid | ||
|
|
||
| test.describe('Share-asset capture (card face is not blank)', () => { | ||
| test('captured PNG card region contains the hand, not just background', async ({ page }, testInfo) => { | ||
| await page.goto('/dev/share-builder', { waitUntil: 'domcontentloaded' }) | ||
|
|
||
| const saveBtn = page.getByTestId('save-image') | ||
|
|
||
| // The readiness gate: Save is disabled until the card face's hand <img> | ||
| // loads (onReady). If it never enables, the gate is broken OR the card | ||
| // never painted — both are failures this spec must catch. | ||
| await expect(saveBtn, 'Save must enable once the card face signals ready').toBeEnabled({ timeout: 60_000 }) | ||
|
|
||
| // Let the card-slide / sticker-drop animations settle to their final | ||
| // frame so the card sits at its designed (centred, -8°) position before | ||
| // we capture. The capture itself overrides only the root scale; the | ||
| // card's own transform is whatever frame it's on. | ||
| await page.waitForTimeout(2_500) | ||
|
|
||
| // Click Save → captureShareAsset(node) → downloadBlob() fires a real | ||
| // download of the native-resolution PNG. | ||
| const downloadPromise = page.waitForEvent('download', { timeout: 30_000 }) | ||
| await saveBtn.click() | ||
| const download = await downloadPromise | ||
| const pngPath = testInfo.outputPath('share-asset-capture.png') | ||
| await download.saveAs(pngPath) | ||
|
|
||
| // Decode + sample IN-BROWSER: load the PNG into an <img>, paint it to a | ||
| // <canvas>, read it back with getImageData, and count non-background | ||
| // pixels in the central card window. Done in the page (not Node) so we | ||
| // need no PNG-decoder dependency. | ||
| const base64 = readFileSync(pngPath).toString('base64') | ||
| const result = await page.evaluate( | ||
| async ({ b64, canvasW, canvasH, region, pink, blue, tol, step }) => { | ||
| const img = new Image() | ||
| img.src = `data:image/png;base64,${b64}` | ||
| await img.decode() | ||
|
|
||
| const cv = document.createElement('canvas') | ||
| cv.width = img.naturalWidth | ||
| cv.height = img.naturalHeight | ||
| const ctx = cv.getContext('2d') | ||
| if (!ctx) throw new Error('no 2d context') | ||
| ctx.drawImage(img, 0, 0) | ||
|
|
||
| const width = cv.width | ||
| const height = cv.height | ||
| const buf = ctx.getImageData(0, 0, width, height).data | ||
|
|
||
| // Output is captured at pixelRatio 2 (≈2400×1800) — scale canvas | ||
| // coords into output pixels. | ||
| const sx = width / canvasW | ||
| const sy = height / canvasH | ||
| const near = (r: number, g: number, b: number, c: number[]): boolean => | ||
| Math.abs(r - c[0]) <= tol && Math.abs(g - c[1]) <= tol && Math.abs(b - c[2]) <= tol | ||
|
|
||
| let sampled = 0 | ||
| let nonBackground = 0 | ||
| for (let cy = region.y0; cy <= region.y1; cy += step) { | ||
| for (let cx = region.x0; cx <= region.x1; cx += step) { | ||
| const px = Math.min(width - 1, Math.round(cx * sx)) | ||
| const py = Math.min(height - 1, Math.round(cy * sy)) | ||
| const idx = (py * width + px) * 4 | ||
| const r = buf[idx] | ||
| const g = buf[idx + 1] | ||
| const b = buf[idx + 2] | ||
| sampled++ | ||
| if (!(near(r, g, b, pink) || near(r, g, b, blue))) nonBackground++ | ||
| } | ||
| } | ||
| return { width, height, sampled, nonBackground } | ||
| }, | ||
| { | ||
| b64: base64, | ||
| canvasW: CANVAS_W, | ||
| canvasH: CANVAS_H, | ||
| region: REGION, | ||
| pink: CARD_PINK as number[], | ||
| blue: ASSET_BLUE as number[], | ||
| tol: COLOR_TOL, | ||
| step: SAMPLE_STEP, | ||
| } | ||
| ) | ||
|
|
||
| const fraction = result.nonBackground / result.sampled | ||
| // Blank card → fraction ≈ 0 (pure pink). Hand present → a large chunk of | ||
| // the centre is non-pink. A 2% floor cleanly separates the two while | ||
| // staying far below the hand's real coverage (avoids flake). | ||
| expect( | ||
| fraction, | ||
| `card centre is ${(fraction * 100).toFixed(1)}% non-background ` + | ||
| `(${result.nonBackground}/${result.sampled} px; output ${result.width}×${result.height}). ` + | ||
| `≈0% means the pixelated hand never rendered — the blank-card capture bug.` | ||
| ).toBeGreaterThan(0.02) | ||
| }) | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: peanutprotocol/peanut-ui
Length of output: 27691
🏁 Script executed:
Repository: peanutprotocol/peanut-ui
Length of output: 17036
🏁 Script executed:
Repository: peanutprotocol/peanut-ui
Length of output: 4754
🏁 Script executed:
Repository: peanutprotocol/peanut-ui
Length of output: 4740
Reset the uplift latch on terminal KYC failures
REJECTED/ACTION_REQUIREDcloses the KYC modal without clearing the EEA uplift start state, so a later approved resubmission on the same page can still emiteea_uplift_completedfor the earlier attempt. Reset the funnel on those terminal outcomes, or scope completion to the specific launch that started it.🤖 Prompt for AI Agents