Skip to content

feat(avatar): the picker as a hand, identity on profile, a menu button on home (TASK-22142) - #3014

Closed
0xkkonrad wants to merge 13 commits into
devfrom
feat/22142-avatar-hand-final
Closed

0xkkonrad wants to merge 13 commits into
devfrom
feat/22142-avatar-hand-final

Conversation

@0xkkonrad

@0xkkonrad 0xkkonrad commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Rebased replacement: #3047. It preserves the eight-tile hand and home hamburger, with simplification and regression fixes. Repository rules block force-pushes, so this branch retains its original history.

Summary

The avatar picker, the profile identity block and the home top nav, rebuilt to the final mockup. This supersedes #2989, which Slava blocked on 5 September; his four points are quoted and answered below.

The picker is a hand. The sheet has no header — it is eight dealt tiles and a die, three across. Slot 1 is always your own initial and saving it writes a real letter.<x> pick. The rest come from dealHand: at least one earned avatar when you hold any badge, your current pick kept in place, the rest from the twenty basics, deduped by art path so no two tiles draw the same picture. The die deals a new hand and never changes the pick. Every tile carries its character's name and line, so the sheet reads as a cast rather than a grid of stickers. The 26-letter Initials group from #2998 is gone — slot 1 replaces it.

Profile identity is a column. The 64px round avatar button sits above the share pill, and the pill stays at its shipped 40px. The pill is two hit areas: the handle copies peanut.me/<handle> with a toast and a PROFILE_LINK_COPIED event, the trailing glyph opens the share sheet.

Home loses the avatar chip. A 40px round menu button linking to /profile takes its place. The sticker is identity; identity lives on the profile and in the picker, and up in the nav a lone sticker read as decoration.

Copy: 20 cast names and lines plus the earned, roll and copy-link labels in en, es-419 and pt-BR, with three Argentine overrides in es-AR. Seven keys the rebuild stopped rendering are deleted.

Task

TASK-22142 — Build badge-linked avatar system v2. Supersedes #2989.

Slava's points, answered

"Home screen: no one will understand that the avatar is actionable."

His option A. The chip and its chevron are gone. In their place is a bordered 40px circle with a menu icon, aria-label="Open your profile", a 44px hit area and the stroke press — a control, not a picture. src/features/home/views/HomeTopNav.tsx.

"The pill's height is enormous, the avatar inside it."

The avatar came out of the pill. It is now a 64px round button 8px above, and the pill is back to the h-10 it ships with today. src/components/Profile/components/ProfileHeader.tsx.

"Duplicated icons at rolling the dice."

The hand is deduped by art path, not by key, so two badge codes that share a picture can never both be dealt. Letters live outside the deck, so slot 1 can never repeat a tile. All 94 files under public/avatars/ were checked byte-distinct, so path dedupe is sufficient in fact and not only in theory. A 20-seed test asserts seven distinct art paths on every deal. src/components/Avatar/avatar.utils.ts.

"Using shadow for one element and no shadow for others is against DS."

No tile carries a shadow. The selected tile is border-2 and nothing else. The only two shadowed controls added are the avatar button and the pill — both of them, so there is no one-element case — and both are shadow-4 with the DS pressed state.

Also gone with the rebuild: the shipped picker's cropped buttons, and "use initials" that did nothing. Slot 1 is the initial and it saves.

Risks / breaking changes

⚠️ Cross-repo deploy order. peanut-api-ts accepts letter.<a-z> avatar keys on dev since api#1530 (5 Sep), but not on main yet. Until that API release ships, a letter pick on production is rejected and falls back to the device-local mirror — the behaviour that already ships from #2998 — with no error toast and no lost pick, and it promotes itself to the durable server copy on the next accepted write. Badge and basic picks are unaffected. The next dev → main release must carry both repos.

This branch carries a merge of dev. Two siblings landed while it was open: #3013 split the badge-earned toast into two sequential toasts and dropped the badge from the picker link — the merge keeps dev's two-toast shape and the badge code — and #2953 lowered the ds-lint baseline, which the CARD_SURFACE extraction above answers.

Blast radius is otherwise three surfaces: the profile header self branch, the avatar picker drawer, the home top nav. The counterparty and public-profile branches of ProfileHeader are untouched and pinned by tests. /profile/edit and the public profile still render the 96px avatar with no button.

Removed exports (offerBasics, letterAvatarKeys) have zero remaining references. useHomeFlow no longer computes an avatar key for a chip that is gone.

QA

No backend or provider state is needed — the picker, the pill and the nav all render from the user object.

  1. /profile — the avatar sits above the pill; tap it to open the picker.
  2. In the sheet: eight tiles and the die, three across, no header. Slot 1 is your initial and reads "Just <X> / Your initial". Tap it; it stays checked after the sheet closes and reopens.
  3. Tap the die a few times: the hand changes, your pick stays checked, no save fires.
  4. With a badge on the account: at least one tile carries the "Earned" tag every deal.
  5. Earn a badge, then "Choose avatar" on the toast: the sheet opens with that badge's art in the hand, and ?badge= is gone from the URL after you close it.
  6. On the pill: tap the handle → "Link copied" toast and PROFILE_LINK_COPIED; tap the glyph → the share sheet and REFERRAL_CTA_CLICKED.
  7. /home — a menu button top-left, no sticker, no chevron; it goes to /profile.
  8. At 320px: three equal-height rows, whole names on two lines, nothing clipped.

Automated: 41 suites / 464 tests over src/components/Avatar, src/components/Profile, src/features/home, src/components/Badges, src/i18n, src/dev/fixtures. Full suite green (6080 passing). tsc --noEmit clean. ds-lint-counts --check green — no metric increased, iconOffScale fell 70 → 68.

Screenshots

ds-shots fixture captures of the commit under review — no backend, the fixture answers every call. The full run is in the visual diff comment: 10 screens moved, 18 of 68 shots changed, baseline ecd7c0b → head 0bf49bd.

Shown at 320px, the narrowest phone we support and the width where the predecessor's copy was cut off.

before (dev) after
picker
profile
home

The whole hand, 430px — eight tiles, the die, the selected tile's border-2, the Earned tags:

Assets live on the pr-assets-3014 branch and are deleted when this PR closes (pr-assets-cleanup.yml).

Several avatars from one badge in a hand: by design

That hand deals two Bug Whisperer avatars, Shell and Beetle, and an earlier deal of the same fixture drew all three. Each is a different art file with its own name; the dedupe is by art path and is doing its job. Konrad ruled on 7 September that a hand may carry several avatars from the same badge, so there is no cap and nothing to change here.

Design notes / accepted trade-offs

Fixed from Chip's review of 340d8ac8f: the earn toast now deep-links the newest badge that actually has art (38 of 54 codes ship none, so a batch pairing an artless newest with an older unlock sent the user to a hand holding nothing they just earned); and the share glyph's 44px hit box no longer reaches back over the handle, so a tap on the end of the url copies rather than shares. Both have tests.

Fixed before that, from the DS, behaviour and adversarial passes: the die became the on-scale 24px icon; the radiogroup wraps the eight tiles only (display:contents) so the die is not a non-radio child; the ?badge= param clears on close; the avatar button and the pill both press; the share glyph is inline rather than an icon-only button; a cold-loaded deep link re-deals once the user resolves; the grid has py-1 so the 3px focus ring is not clipped by the scroll box; tile names wrap to two lines so 320px keeps them whole; the home menu button is one Link with the button recipe rather than a Button inside a Link; the home flow's orphaned avatarKey is gone.

Flagged, not changed — each one needs a ruling, none of them is a build error:

element what it does precedent / why
roll tile border border-[1.5px] border-dashed border-border-default ❓ one precedent in the DS (the receipt card's divide-dashed). --border-width-s: 1.5px exists in globals.css but is deliberately kept out of @theme, so there is no utility to write it with.
pill share glyph 16px icon, no box of its own ❓ design.md's icon-only-button recipe is a 40px box with a 20px icon. The mockup and the pill we ship today both draw 16px inline. Hit area is 44px via the after: inset.
tile name line-clamp-2 h-8 — two lines ❓ the mockup draws one truncated line on a 390px phone. At 320px that cut "Grumpy Raincloud" to eleven characters, and a ragged tile height breaks the grid. Two fixed lines instead.
verified check text-green-500 ❓ a primitive, not a semantic token — there is no foreground-success. It is the class UserHeader and VerifiedUserLabel already use. Which green is the verified check?
earned marker StatusBadge status="custom" ❓ custom has no board row (a pre-existing open conflict in design.md). Renders the mockup's exact badge colours; no coloured border, no yellow.
die spin rotate-360 / rotate-0 by turn parity ❓ the first rotate-<number> off the stock steps. Valid Tailwind v4, and it alternates turn direction — an inline style would push the inlineStyle ratchet.
radiogroup className="contents" ❓ display:contents dropped the a11y node in Safari before 15.5. On iOS 15.0-15.4 the tiles still work; they stop being announced as one group.
NavHeader still <Link><Button> out of scope. HomeTopNav no longer nests them; NavHeader is not this PR's file.

Deliberate drift from the mockup, so nobody puts it back: tile side padding 6px → px-1; the tile line 11/14px → text-body-xs 12/16; the pill's right inset 8px → pr-4, the value it ships with; the pill renders peanut.me/handle in one bold run where the mockup greys the domain — that is dev parity, not new.

Card now exports its surface as CARD_SURFACE. The picker tile needs that chrome on a <button> (a role="radio" cannot be the div Card renders), and it used to retype the three classes — the exact duplication the retypedCardLiteral ratchet counts, which dev tightened under us while this PR was open. One string, two users, count back where dev put it.

Follow-ups, not in this PR — code: export the NavHeader circle recipe instead of duplicating half of it in HomeTopNav; point the home-avatar dev fixture at /profile or delete it (it routes to /home, which renders no avatar now); delete avatarPool, which has no production caller; fix the UserHeader and dev/ds/audit comments that still name the home sticker.

Follow-ups, not in this PR — mono docs (content and code never travel together, so none of this is here): design/design.md:124 still says user avatars are always AvatarWithBadge with a circle and a 1px border, which #2929 already made false and this PR compounds; design/design.md:461's figma↔code map has no row for UserAvatar.tsx or the picker; the open-conflicts table wants a row for the dashed container border, and one for a pressed frame that holds two independent hit areas; product/support-answers/rewards-points-badges.md never says a badge unlocks avatar art, so support under-answers it. No legal impact — the one new event is a PostHog capture on a local clipboard write, and PostHog and "taps and interactions" are already named in the privacy policy (§2, §4, §6).

…er (TASK-22142)

The shipped picker stacked a title, three section headings and an Initials
group over five-column rows, and its successor was blocked for the yellow
bordered, shadowed tiles. This is the hand from the final mockup: eight dealt
tiles plus the die, slot 1 the user's initial as a real letter pick, at least
one earned card when any badge is held, no two tiles sharing an art file, and
every character named under its sticker. A selected tile carries a 2px edge and
no shadow; an earned tile carries the Earned badge and no coloured border.
Rolling re-deals and never touches the pick. The earn toast can point the first
hand at a badge. Adds the dice and menu icons to the registry.
…opy and share (TASK-22142)

The avatar inside the pill made the pill twice the height of any button in the
system and pushed the whole header off the design system. The avatar goes back
above, as a bordered round button that opens the picker, and the pill keeps its
shipped 40px chrome with two hit areas: the handle copies the profile link and
says so, the icon shares as before. The verified check shows once, in the name
row when there is one, else in the pill.
…inks the picker to its badge (TASK-22142)

Nobody read the avatar chip as the door to the whole menu. A 40px round menu
button, the nav circle recipe, replaces it with the same label and route. The
toast's "Choose avatar" now carries the badge code so the first hand shows that
badge's card.
…he copy-link label; drop the header and group keys (TASK-22142)

Twenty basics get a name and a line in en, es-419 and pt-BR, with three
Argentine overrides in es-AR. The keys that only the old header and groups
rendered are gone.
…-22142)

The hand-drawn 36px die put a radius and six-pixel pips outside the spacing
and radius scales and tripped the DS ratchet. The roll tile now shows the
single five-pip die icon at 24, the one size the icon scale allows here, and
keeps its dashed edge, white fill, label and one-turn spin.
…nk clears on close (TASK-22142)

A button that is not a radio cannot be a direct child of a radiogroup; the
group now wraps the eight tiles only and the die is its sibling in the grid.
The badge parameter from the earn toast used to outlive the first open, so
every later hand kept preferring that badge; closing the drawer clears it.
…d control (TASK-22142)

The framed avatar carried the hard shadow without the press that goes with
it, and the split pill had lost the press the shipped pill shows. The avatar
is the stroke Button now, so its press is the Button's own; the pill frame
presses as one surface on either segment, the shipped pill's values. The
share glyph is the pill's trailing 16px icon again, not a 32px box, with the
44px hit area kept.
…ASK-22142)

A Link around a Button is two tab stops and nested interactive content. The
link now carries the stroke button recipe itself, same circle, icon, label,
hit area and press, with one focus stop and the honest role for a control
that navigates.
…, names that survive 320px (TASK-22142)

A deep link from the earn toast opened the picker before the user had
loaded, so the hand dealt with no badges and an empty initial; the deal now
waits for the user and happens once per open. The focus ring was clipped by
the scroll box on the first and last rows. Sixteen-character names truncated
to eleven on a 320px screen; names wrap to two lines like their line, with a
fixed box so every tile keeps the same height.
@notion-workspace

Copy link
Copy Markdown

@vercel

vercel Bot commented Sep 7, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
peanut-wallet Ready Ready Preview Sep 7, 2026 10:41am UTC

Request Review

@coderabbitai

coderabbitai Bot commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 171f1495-322a-4acc-abaf-a31ed251fe65

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 7252.27 → 7255.59 (+3.32)
Findings: +1 net (+30 new, -29 resolved)

🆕 New findings (30)

  • critical complexity — src/components/Global/Icons/Icon.tsx — CC 93, MI 73.1, SLOC 289
  • critical complexity — src/constants/analytics.consts.ts — CC 1, MI 29.95, SLOC 223
  • high hotspot — src/constants/analytics.consts.ts — 70 commits, +487/-29 lines since 6 months ago
  • high complexity — src/components/Avatar/avatar.utils.ts — CC 44, MI 64.74, SLOC 110
  • high complexity — src/components/Avatar/AvatarPicker.tsx — CC 41, MI 57.96, SLOC 174
  • medium high-mdd — src/components/Avatar/AvatarPicker.tsx:46 — AvatarPicker: MDD 61.5 (uses across many lines from declarations)
  • medium high-mdd — src/components/Badges/BadgeEarnToast.tsx:41 — BadgeEarnToast: MDD 52.8 (uses across many lines from declarations)
  • medium high-mdd — src/components/Profile/components/ProfileHeader.tsx:37 — ProfileHeader: MDD 51.5 (uses across many lines from declarations)
  • medium high-dlt — src/components/Avatar/AvatarPicker.tsx:46 — AvatarPicker: DLT 40 (calls 40 distinct functions — high context load)
  • medium high-mdd — src/app/(mobile-ui)/dev/ds/foundations/icons/page.tsx:90 — IconsPage: MDD 31.7 (uses across many lines from declarations)
  • medium high-mdd — src/components/Badges/BadgeEarnToast.tsx:58 — : MDD 27.5 (uses across many lines from declarations)
  • medium complexity — src/components/Badges/BadgeEarnToast.tsx — CC 23, MI 61.48, SLOC 133
  • medium complexity — src/components/Profile/components/ProfileHeader.tsx — CC 19, MI 59.61, SLOC 64
  • medium complexity — src/components/Avatar/avatarPicker.utils.ts — CC 8, MI 59.97, SLOC 31
  • medium complexity — src/features/home/useHomeFlow.ts — CC 7, MI 60.48, SLOC 60
  • medium complexity — src/features/home/HomePage.tsx — CC 4, MI 64.79, SLOC 17
  • medium nextjs-raw-img — src/app/(mobile-ui)/dev/ds/foundations/icons/page.tsx:173 — Use next/image
  • low high-dlt — src/components/Badges/BadgeEarnToast.tsx:41 — BadgeEarnToast: DLT 29 (calls 29 distinct functions — high context load)
  • low high-dlt — src/components/Profile/components/ProfileHeader.tsx:37 — ProfileHeader: DLT 19 (calls 19 distinct functions — high context load)
  • low high-dlt — src/components/Badges/BadgeEarnToast.tsx:58 — : DLT 17 (calls 17 distinct functions — high context load)

…and 10 more.

✅ Resolved (29)

  • src/components/Global/Icons/Icon.tsx — CC 91, MI 73.05, SLOC 283
  • src/constants/analytics.consts.ts — CC 1, MI 30, SLOC 222
  • src/constants/analytics.consts.ts — 69 commits, +482/-28 lines since 6 months ago
  • src/components/Avatar/avatar.utils.ts — CC 32, MI 66.94, SLOC 75
  • src/components/Avatar/AvatarPicker.tsx:38 — AvatarPicker: MDD 66.3 (uses across many lines from declarations)
  • src/components/Badges/BadgeEarnToast.tsx:41 — BadgeEarnToast: MDD 50.4 (uses across many lines from declarations)
  • src/components/Profile/components/ProfileHeader.tsx:29 — ProfileHeader: MDD 33.4 (uses across many lines from declarations)
  • src/app/(mobile-ui)/dev/ds/foundations/icons/page.tsx:88 — IconsPage: MDD 31.7 (uses across many lines from declarations)
  • src/components/Avatar/AvatarPicker.tsx:38 — AvatarPicker: DLT 32 (calls 32 distinct functions — high context load)
  • src/components/Avatar/AvatarPicker.tsx — CC 28, MI 59.8, SLOC 137
  • src/components/Badges/BadgeEarnToast.tsx:58 — : MDD 26.1 (uses across many lines from declarations)
  • src/components/Badges/BadgeEarnToast.tsx — CC 22, MI 61.23, SLOC 127
  • src/features/home/views/HomeTopNav.tsx:21 — HomeTopNav: MDD 20.0 (uses across many lines from declarations)
  • src/components/Profile/components/ProfileHeader.tsx — CC 14, MI 60.11, SLOC 40
  • src/features/home/useHomeFlow.ts — CC 7, MI 59.72, SLOC 63
  • src/components/Avatar/avatarPicker.utils.ts — CC 4, MI 62.96, SLOC 18
  • src/features/home/HomePage.tsx — CC 4, MI 63.89, SLOC 18
  • src/app/(mobile-ui)/dev/ds/foundations/icons/page.tsx:171 — Use next/image
  • src/components/Avatar/AvatarPicker.tsx:114 — small useEffect that only sets state from deps
  • src/components/Badges/BadgeEarnToast.tsx:41 — BadgeEarnToast: DLT 27 (calls 27 distinct functions — high context load)

…and 9 more.

📈 Painscore deltas (top movers)

File Before After Δ
src/components/Avatar/avatar.consts.ts 2.1 3.1 +1.0
src/components/Avatar/avatar.utils.ts 5.0 5.8 +0.8
src/components/Profile/components/ProfileHeader.tsx 9.6 10.2 +0.7

@github-actions

github-actions Bot commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • ✅ unit: 6111 ran, 0 failed, 0 skipped, 1.9m

📊 Coverage (unit)

metric %
statements 74.7%
branches 60.4%
functions 68.8%
lines 75.6%
⏱ 10 slowest test cases
time test
🐢 9.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Network failure keeps loading while retries remain, then shows the generic error
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_SOURCE_OVER_MONTHLY_CAP fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_MERCHANT_RECENT_REFUND fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › User KYC not approved fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_MERCHANT_VOLUME_NEAR_CAP fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › a refused idempotency key tells the user to scan again, not to contact support
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › routes the KYC rejection on its wire code, and does not retry it
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_USER_NOT_PROVISIONED fails fast with copy that names the real cause
3.2s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
3.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Scan that recovers on the retry lands on the payment screen, not an error
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@github-actions

github-actions Bot commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

🖼 Visual diff — 15 screens moved

24 of 68 shots changed · 44 identical · baseline ecd7c0b → head 70fc583

worst % screen widths
33.76% avatar-picker 320, 430
10.92% long-full-name 320, 430
10.64% hugo-long-username 320, 430
10.09% profile 320, 430
6.09% home-avatar 320, 430
5.93% home 320, 430
2.35% empty-home 320, 430
0.99% reconsent 320, 430
0.07% badges 320
0.07% withdraw 320
0.03% add-money 430
0.03% empty-accounts 320, 430
0.03% send 430
0.03% unverified 430
0.01% settings-language 320

job summary · before/after/diff images — artifact

Fixture screenshots, no backend. Advisory — this check never blocks a merge. Posted from the default branch by ds-shots-comment.yml; the report it renders is untrusted data.

@0xkkonrad

Copy link
Copy Markdown
Contributor Author

/chip review

@chip-peanut-bot chip-peanut-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chip review — no blocking findings — this is not an approval

Exact-head CI is green and the picker, profile, and home rebuild is broadly coherent. Two minor interaction edge cases remain: a mixed badge batch can deep-link the wrong badge, and the share segment's expanded hit box overlaps the copy segment.

Findings

  • MINOR · src/components/Badges/BadgeEarnToast.tsx:97 · Deep-link the badge that actually unlocked avatars
    For a coalesced batch whose newest badge has no art but an older badge does (for example PRODUCT_HUNT followed by SHHHHH), avatarCount is non-zero because SHHHHH has avatars, but this routes with badge=PRODUCT_HUNT. dealHand finds no preferred PRODUCT_HUNT key and falls back to any avatar from every badge the user holds, so it can deal an older badge's art and show none of the newly unlocked SHHHHH art. Pick the newest pending code for which badgeAvatarKeys([code]) is non-empty, route with that code, and cover the mixed art/no-art batch.

  • MINOR · src/components/Profile/components/ProfileHeader.tsx:182 · Keep the share hit box out of the copy segment
    The 16px share button's after:-inset-3.5 extends its clickable pseudo-element 14px in every direction. With only the 4px ml-1 gap between siblings, the later share button overlays roughly 10px of the copy button, so tapping near the right edge of the handle can open sharing instead of copying even though these are meant to be independent hit areas. Reserve a real non-overlapping 44px share segment, or expand only vertically while allocating the horizontal target, and cover clicks at the segment boundary.

Checked clean

  • Confirmed the detached worktree HEAD, trusted author, base ref, supplied base SHA, and merge base exactly match the requested review target.
  • Reviewed the hand-dealing algorithm, manifest-backed unlock checks, initial-letter behavior, deep-link lifecycle, save serialization, failure fallback, keyboard navigation, and focused tests.
  • Reviewed the self-profile identity column, copy/share analytics and failure handling, counterparty branches, home menu navigation, icon registrations, fixtures, and locale changes.
  • Checked the repository button CSS and the canonical design guidance for icon sizing, pressed states, and 44px touch targets.
  • Exact-head CI completed successfully, including unit, typecheck, eslint, format, native-export, design-system lint, screenshots, and aggregate ci-success checks. The detached worktree has no installed Jest binary for an additional local focused run.

Security review: did not run — this change has no security, privacy or money surface, so it was not asked. This review is one reviewer short.

Third opinion by claude-opus: 0 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.

Exact head: 340d8ac8f2e5 · Context: repo, design · Took 19m

Comment thread src/components/Badges/BadgeEarnToast.tsx Outdated
Comment thread src/components/Profile/components/ProfileHeader.tsx Outdated
…off the handle (TASK-22142)

Both from Chip's review of 340d8ac.

Most badges ship no avatar art, so a coalesced earn toast can pair a newest
badge with none and an older one with three. The link named the newest, and
`prefer` on a code with no art falls back to any avatar already held — so the
user tapped through to a hand holding none of what was just unlocked. Name the
newest code that has art instead.

The share glyph reaches 44px through a 14px `after:` inset, but sat 4px from
the handle, so the last ~10px of the url opened sharing instead of copying.
16px of margin puts the two hit areas side by side with nothing shared, and
stays on the spacing scale.
dev's #3013 split the badge-earned toast into two sequential toasts and dropped
the badge from the picker link; dev's #2953 lowered the ds-lint baseline.

Kept both: the avatar toast stays a delayed second toast, and its link keeps the
badge code — the newest one that actually has art.

The tighter `retypedCardLiteral` baseline no longer had room for the picker
tile, which retyped Card's surface because a `role="radio"` must be a `<button>`
and `Card` is a `div`. Card now exports that surface and both use it.
@0xkkonrad

Copy link
Copy Markdown
Contributor Author

/chip review

@chip-peanut-bot chip-peanut-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chip review — no blocking findings — this is not an approval

P1 and P2 are fixed at this exact head, and all exact-head checks are green. One new minor issue remains: the new copy segment is only 40px tall while the adjacent share segment correctly expands to the 44px touch-target floor.

Findings

  • MINOR · src/components/Profile/components/ProfileHeader.tsx:154 · Extend the copy segment to the 44px touch target
    The pill frame is h-10, and this new button is only h-full; unlike the adjacent share button, it has no pseudo-element extending its hit area. A touch in the 2px strip immediately above or below the handle falls inside the standard 44px target envelope around the visible 40px pill but does nothing. Make the copy button relative and extend it vertically by 2px without growing it horizontally, which preserves P2's non-overlap fix.

Checked clean

  • Confirmed the detached worktree head, trusted author, dev base ref, supplied base SHA, and merge base all match the request.
  • P1 is fixed: the toast filters the newest-first badge codes to those with avatar art, deep-links the first survivor, and covers the supplied PRODUCT_HUNT plus SHHHHH case.
  • P2 is fixed: the share button now has a 16px left margin against a 14px horizontal hit-area expansion, leaving the copy and share targets disjoint; the regression test pins that arithmetic.
  • Traced the hand deal, art-path deduplication, preferred-badge draw, current-pick retention, cold auth resolution, query cleanup, keyboard roving, and serialized save/fallback paths; no additional correctness defect survived.
  • Reviewed the self-profile avatar and copy/share split, counterparty and public-profile branches, home menu replacement, localization overlays, analytics success gating, and changed design-system primitives.
  • Checked the policy-branch API sibling: letter picks are accepted and badge picks are revalidated against held badges; the avatar pools used by this UI remain compatible.
  • All exact-head GitHub checks completed successfully, including unit, typecheck, eslint, format, native export, design-system lint, analysis, visual snapshots, deploy preview, provenance, and aggregate CI.
  • The exact diff passes git diff --check. Focused local Jest could not run because the detached worktree has no installed dependencies.
  • Security, privacy, and money: the URL badge parameter only changes client-side deal preference, and persisted avatar picks remain server-authorized; no new credential, amount, or privilege surface was introduced.
  • Slop and history: reviewed all commits and changed hotspots; no actionable duplication, dead runtime code, speculative abstraction, or architecture drift survived beyond the touch-target finding above.

Security review: did not run — this change has no security, privacy or money surface, so it was not asked. This review is one reviewer short.

Third opinion by claude-opus: 0 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.

Exact head: 0bf49bd05e60 · Context: repo, ci, design, api-sibling · Took 25m

Comment thread src/components/Profile/components/ProfileHeader.tsx Outdated
…SK-22142)

From Chip's review of 0bf49bd. The pill is 40px and the handle was `h-full`,
so the 2px strip above and below it did nothing while the share glyph beside it
already answered there. It grows through the same `after:` pseudo-element —
vertically only, so the two hit areas stay disjoint.
@0xkkonrad

Copy link
Copy Markdown
Contributor Author

/chip review

@chip-peanut-bot chip-peanut-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chip review — no blocking findings — this is not an approval

Clean at the pinned head: the three prior touch/deep-link findings are fixed, and the rebuilt avatar picker, profile identity block, and home navigation have no remaining actionable defects.

Checked clean

  • P1 fixed: coalesced badge batches now select the newest code that actually ships avatar art, so [PRODUCT_HUNT, SHHHHH] deep-links SHHHHH.
  • P2 fixed: the share control's 16px left margin exceeds its 14px pseudo-element growth, keeping its hit area out of the copy segment.
  • P3 fixed: the 40px copy segment now grows by 2px vertically on each edge to reach 44px without widening into the share target.
  • Avatar hand composition, art-path deduplication, preferred-badge dealing, current-pick retention, cold auth resolution, URL cleanup, radio navigation, and serialized save/refetch failure paths.
  • Profile self/counterparty branching, copy/share attribution, clipboard failure behavior, analytics firing, truncation, pressed states, and disjoint 44px touch targets.
  • Home menu navigation, single interactive element semantics, haptics, rewards visibility, icon registration, shared card surface extraction, and localized cast/copy labels.
  • The sibling API policy branch accepts letter. keys for every user and validates badge avatars against held badge codes.
  • Exact-head required CI is green, including unit, typecheck, eslint, format, ds-lint, native-export, author, and baseline gates; advisory ds-shots was still in progress.

Security review: did not run — this change has no security, privacy or money surface, so it was not asked. This review is one reviewer short.

Third opinion by claude-opus: 0 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.

Exact head: 70fc5833ca6d · Context: repo, other_repo · Took 16m

@0xkkonrad
0xkkonrad marked this pull request as ready for review September 7, 2026 11:01
@0xkkonrad

Copy link
Copy Markdown
Contributor Author

@innolope-dev — you blocked #2989 on 5 September, so this is the one to look at instead. #2989 is closed; this replaces it.

Your four points are quoted and answered one by one in the description, under Slava's points, answered. Short version: the home avatar is your option A, a labelled 40px menu button with a 44px hit area; the pill is back to its shipped h-10 with the 64px avatar as a sibling above it; the hand is deduped by art path rather than by key, and all 94 files under public/avatars/ were checked byte-distinct; and no tile carries a shadow at all — the selected one is border-2 and nothing else.

One thing I did not decide for you, in One open question the 430px capture raises: a user holding a single badge with three unlocks can be dealt two or three of them in the same hand. Different art, different names, so the dedupe is doing its job — but they share a line, the badge's name, and at 320px they read as the same sticker. That is your third objection reached by a different route. Capping earned tiles at one per badge is a few lines in dealHand; it changes what the picker offers, so it is your call, not mine.

Eight DS rows ship flagged, not changed in the same section — the dashed roll border and the 16px inline share glyph are the two most likely to matter to you.

@kushagrasarathe is the requested reviewer; no need to approve, an opinion on the two above is what would help.

@chip-peanut-bot chip-peanut-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chip review — no blocking findings — this is not an approval

Clean at the pinned head: the three prior touch/deep-link findings are fixed, and the rebuilt avatar picker, profile identity block, and home navigation have no remaining actionable defects.

Checked clean

  • P1 fixed: coalesced badge batches now select the newest code that actually ships avatar art, so [PRODUCT_HUNT, SHHHHH] deep-links SHHHHH.
  • P2 fixed: the share control's 16px left margin exceeds its 14px pseudo-element growth, keeping its hit area out of the copy segment.
  • P3 fixed: the 40px copy segment now grows by 2px vertically on each edge to reach 44px without widening into the share target.
  • Avatar hand composition, art-path deduplication, preferred-badge dealing, current-pick retention, cold auth resolution, URL cleanup, radio navigation, and serialized save/refetch failure paths.
  • Profile self/counterparty branching, copy/share attribution, clipboard failure behavior, analytics firing, truncation, pressed states, and disjoint 44px touch targets.
  • Home menu navigation, single interactive element semantics, haptics, rewards visibility, icon registration, shared card surface extraction, and localized cast/copy labels.
  • The sibling API policy branch accepts letter. keys for every user and validates badge avatars against held badge codes.
  • All exact-head CI completed successfully, including unit, typecheck, eslint, format, ds-lint, native-export, ds-shots, author, baseline, and aggregate gates.

Security review: did not run — this change has no security, privacy or money surface, so it was not asked. This review is one reviewer short.

Third opinion by claude-opus: 0 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.

Exact head: 70fc5833ca6d · Context: repo, other_repo · Took 7m

@0xkkonrad

Copy link
Copy Markdown
Contributor Author

Ruling from Konrad (7 Sep): several avatars from the same badge may appear in one hand. That is by design, so the "one card per badge" question in the description is closed with no cap; the art differs and the line under each tile names the badge. @innolope-dev this PR is yours to review and merge; Kushagra can weigh in on the DS rows if you want a second pair of eyes.

@0xkkonrad

Copy link
Copy Markdown
Contributor Author

Superseded by #3047 (rebased replacement, merged to dev 8 Sep). Every file changed here is in #3047; the only extra changes there are the removal of the unused animated-picker files from dev. Closing.

@0xkkonrad 0xkkonrad closed this Sep 9, 2026

This branch was successfully deployed

1 active deployment
Preview — 70fc5833 Deployed Sep 7, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants