Skip to content

feat: the avatar hand and the profile pill (TASK-22142) - #2989

Closed
abalinda wants to merge 4 commits into
devfrom
feat/22142-avatar-hand
Closed

abalinda wants to merge 4 commits into
devfrom
feat/22142-avatar-hand

Conversation

@abalinda

@abalinda abalinda commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #2929, from the "Avatar Hand" design (https://claude.ai/code/artifact/8e3ad40a-544f-48e7-ab5b-88e227b5320b). Three things the shipped v2 got wrong, fixed:

  • Self profile → one pill. The avatar, the own name row and the share pill said the same thing three times. They are one 72px pill now, three hit areas inside one border and never a button nested in a button: the avatar opens the picker, the handle copies peanut.me/<handle> ("Link copied" toast), the share icon runs the existing ShareButton flow. Public profiles and the edit screen keep the stacked header.
  • Picker → a hand of eight. No title, no description, no buttons. One 3×3 grid: slot 1 is always the user's initial, slots 2–8 are dealt by dealHand (at least one earned badge avatar when the user holds a badge with art, the current pick kept, the rest from the basics), slot 9 is the die. A tap saves at once (the serialized-save logic from feat: badge-linked profile avatars (TASK-22142) #2929 is unchanged); rolling deals again and never changes the pick. "Use my initial" is now tile 1, not a button. Earned avatars carry a yellow border and an "Earned" chip.
  • Toast deep link carries the badge. "Choose avatar" opens /profile?avatarPicker=true&badge=CODE, so the first hand is guaranteed to hold the new badge's art. Closing the drawer clears both params.

No API change: the pool the server validates a pick against is the same 20 basics plus what the user's badges unlock.

Task

TASK-22142 — https://app.notion.com/p/peanutprotocol/Build-badge-linked-avatar-system-v2-3cf83811757981efa8afecf6667d2662

Risks / breaking changes

  • Behaviour change on the self profile only: the full name no longer renders in the header (it lives in Personal details); REFERRAL_CTA_CLICKED now fires on the share segment only — copy taps are not counted (impressions unchanged).
  • ShareButton gains an optional shadowSize (default '4', unchanged for every existing caller); the pill passes null so its ghost share segment draws no shadow of its own instead of covering the shadow class from the call site.
  • Review fixes on the head (/code-review high): the "Earned" chip no longer overlaps the sticker (XL band above the art), arrow keys stay in their column in the 3-wide hand, a pick this bundle's manifest does not know is not dealt, the verified tick has an sr-only name. Not changed on purpose: the die inside the radiogroup (moving it out needs display: contents, which has its own screen-reader history), the alternating spin direction, and Chip's two minor threads.
  • The API half (feat: wall of love #1510) is on main; against an API without it the pill shows the initial and a pick fails with the friendly toast (same as feat: badge-linked profile avatars (TASK-22142) #2929). Staging needs api#1519 (dev sync).
  • i18n: avatar.{initial,earned,rollDie} added, seven keys dropped, mirrored in es-419 / pt-BR (translations are mine — native check welcome).

Design — flagged, not changed (design.md "building or migrating a screen")

The artifact was written in raw px/hex; each value is snapped to the DS scale:

Artifact Built Why
handle 17px/800, domain 17px/500 grey text-heading-card + text-body-l text-foreground-secondary 17px is off the type scale; never stack a weight on a token
pill rounded-full bg-white border-black btn-shadow-primary-4 rounded-round bg-background-default border-border-default shadow-4 semantic tokens only; 72px (h-18) is not a DS height — flagged
spacing 14 / 9 / 18 12 / 4 / 16 spacing scale
share icon 18 20 icon steps 16/20/24
verified check text-success-1 text-green-500 (what VerifiedUserLabel uses) success-1 is legacy palette (ratchet at 0); no semantic success foreground token exists — owed
earned fill #FFF6CC + 16px yellow bordered "Earned" pill, 8.5px caps border-action-secondary (precedent: BadgeEarnToast) + StatusBadge status="custom" (Label/M) no hex; no yellow tint token (secondary-4 is legacy); 8.5px is below the 12px floor; "don't: custom colored pills with raw classes"
tile 100px tall p-4 + 64px sticker = 96px off-scale height
roll tile dashed 1.5px, 36px die border border-dashed border-border-default, <Icon name="dice" size={24}> dashed has one precedent (receipt dividers) — flagged; new registry entry dice → lucide Dices
die spins a full turn (0.5s bezier) class parity rotate-360/rotate-0 + motion-safe:duration-slow ease-spring motion tokens + reduced motion; an inline style would raise the inlineStyle ratchet (direction alternates per tap — accepted)
deal-in flip animation (mock CSS only) skipped decorative, not in the spec text
tile aria-labels from a cast table AVATAR_CAST (slug → name), English like today's slug labels the API-side slug rename is still open

scripts/ds-lint-counts.mjs --check is green (two counts went down).

QA

  • Fixtures (no backend): /profile?__fixture=profile (the pill), /profile?avatarPicker=true&__fixture=avatar-picker (the hand, beetle selected, three Bug Whisperer tiles marked "Earned"), /profile?avatarPicker=true&badge=BUG_WHISPERER&__fixture=avatar-picker (deep link), /home?__fixture=home-avatar (unchanged). ds-shots diffs them per push.
  • Sandbox: tap a basic, tap the initial, roll (die spins, pick stays), tap the handle (toast), tap the share icon, tap a badge tile without the badge (API 400 → toast + snap back).
  • Tests: dealHand (length, initial first, pick kept, guarantee, prefer, seeded determinism), AvatarPicker (hand + earned chips, no-badge hand, deep-link prefer, save/snap-back/serialized saves, die, initial), ProfileHeader (visibility, impression, handle vs name, copy + toast, share capture, picker beside both buttons), BadgeEarnToast (deep link with badge). Full suite 465/465, typecheck, prettier, ds-lint ratchet all green locally.

Screenshots

375 wide, fixtures (no backend), this head. Assets live on the pr-assets-2989 branch, deleted after merge. ds-shots visual diff on the head commit: see the sticky comment on this PR.

State Shot
Self profile: the pill — avatar (opens the picker), peanut.me/handle + verified check (copies the link), share icon (share flow). ?__fixture=profile profile pill
The hand: initial first, seven dealt, Bug Whisperer avatars marked "Earned", beetle selected, the die ninth. ?avatarPicker=true&__fixture=avatar-picker avatar hand

Two sections, a title, a description and three buttons were the wrong
sheet for a pick that is tap-and-done. The picker is one 3×3 hand now:
the initial always in slot 1, seven dealt by dealHand — at least one
earned badge avatar when the user holds a badge with art, the current
pick kept, the rest from the basics — and the die in slot 9. Rolling
deals again and never touches the pick, so "use my initial" is a tile,
not a button. The badge-earned toast passes its badge along
(?badge=CODE) so the first hand holds the new art.
The avatar, the own name and the share pill said the same thing three
times on one's own profile. They are one 72px pill now, three hit areas
inside one border and never a button nested in a button: the avatar
opens the picker, the handle copies peanut.me/handle, the share icon
runs the existing share flow. Public profiles and the edit screen keep
the stacked header.
@vercel

vercel Bot commented Sep 4, 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 5, 2026 8:28am UTC

Request Review

@coderabbitai

coderabbitai Bot commented Sep 4, 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: e92db8e1-f322-455b-92ad-680da3aee084

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.

@notion-workspace

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 7218.03 → 7222.48 (+4.45)
Findings: +3 net (+30 new, -27 resolved)

🆕 New findings (30)

  • critical complexity — src/components/Global/Icons/Icon.tsx — CC 92, MI 73.08, SLOC 286
  • high hotspot — src/components/Profile/index.tsx — 48 commits, +338/-282 lines since 6 months ago
  • high complexity — src/components/Avatar/avatar.utils.ts — CC 34, MI 65.58, SLOC 82
  • high complexity — src/components/Global/ShareButton/index.tsx — CC 27, MI 47.06, SLOC 80
  • medium high-mdd — src/components/Profile/index.tsx:30 — Profile: MDD 88.4 (uses across many lines from declarations)
  • medium high-mdd — src/components/Profile/components/ProfileHeader.tsx:33 — ProfileHeader: MDD 51.1 (uses across many lines from declarations)
  • medium high-mdd — src/components/Avatar/AvatarPicker.tsx:32 — AvatarPicker: MDD 43.6 (uses across many lines from declarations)
  • medium high-mdd — src/components/Badges/BadgeEarnToast.tsx:37 — BadgeEarnToast: MDD 41.9 (uses across many lines from declarations)
  • medium high-mdd — src/components/Global/ShareButton/index.tsx:32 — ShareButton: MDD 34.3 (uses across many lines from declarations)
  • medium high-mdd — src/app/(mobile-ui)/dev/ds/foundations/icons/page.tsx:89 — IconsPage: MDD 31.7 (uses across many lines from declarations)
  • medium high-mdd — src/components/Global/ShareButton/index.tsx:50 — : MDD 27.0 (uses across many lines from declarations)
  • medium complexity — src/components/Avatar/AvatarPicker.tsx — CC 26, MI 59.84, SLOC 116
  • medium complexity — src/components/Profile/index.tsx — CC 26, MI 61.9, SLOC 93
  • medium high-mdd — src/components/Badges/BadgeEarnToast.tsx:49 — : MDD 24.8 (uses across many lines from declarations)
  • medium method-complexity — src/components/Global/ShareButton/index.tsx:50 — CC 21 SLOC 68
  • medium complexity — src/components/Profile/components/ProfileHeader.tsx — CC 19, MI 58.06, SLOC 72
  • medium complexity — src/components/Badges/BadgeEarnToast.tsx — CC 18, MI 58.66, SLOC 101
  • medium complexity — src/components/Avatar/avatarPicker.utils.ts — CC 8, MI 59.97, SLOC 31
  • medium complexity — src/components/Avatar/avatar.consts.ts — CC 3, MI 58.3, SLOC 32
  • medium nextjs-raw-img — src/app/(mobile-ui)/dev/ds/foundations/icons/page.tsx:172 — Use next/image

…and 10 more.

✅ Resolved (27)

  • src/components/Global/Icons/Icon.tsx — CC 91, MI 73.05, SLOC 283
  • src/components/Profile/index.tsx — 47 commits, +323/-276 lines since 6 months ago
  • src/components/Global/ShareButton/index.tsx — CC 27, MI 47.09, SLOC 80
  • src/components/Profile/index.tsx:30 — Profile: MDD 82.5 (uses across many lines from declarations)
  • src/components/Avatar/AvatarPicker.tsx:27 — AvatarPicker: MDD 54.6 (uses across many lines from declarations)
  • src/components/Badges/BadgeEarnToast.tsx:37 — BadgeEarnToast: MDD 41.5 (uses across many lines from declarations)
  • src/components/Profile/components/ProfileHeader.tsx:28 — ProfileHeader: MDD 35.3 (uses across many lines from declarations)
  • src/components/Global/ShareButton/index.tsx:30 — ShareButton: MDD 34.3 (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/Global/ShareButton/index.tsx:47 — : MDD 27.0 (uses across many lines from declarations)
  • src/components/Avatar/avatar.utils.ts — CC 26, MI 67.27, SLOC 59
  • src/components/Avatar/AvatarPicker.tsx — CC 26, MI 60.91, SLOC 126
  • src/components/Badges/BadgeEarnToast.tsx:49 — : MDD 24.0 (uses across many lines from declarations)
  • src/components/Profile/index.tsx — CC 24, MI 61.88, SLOC 84
  • src/components/Global/ShareButton/index.tsx:47 — CC 21 SLOC 68
  • src/components/Badges/BadgeEarnToast.tsx — CC 17, MI 57.87, SLOC 96
  • src/components/Profile/components/ProfileHeader.tsx — CC 14, MI 60.69, SLOC 38
  • src/components/Avatar/avatarPicker.utils.ts — CC 4, MI 62.96, SLOC 18
  • src/app/(mobile-ui)/dev/ds/foundations/icons/page.tsx:171 — Use next/image
  • src/components/Avatar/AvatarPicker.tsx:84 — small useEffect that only sets state from deps

…and 7 more.

📈 Painscore deltas (top movers)

File Before After Δ
src/components/Avatar/avatar.consts.ts 2.1 4.2 +2.1
src/components/Profile/components/ProfileHeader.tsx 9.5 10.2 +0.7
src/components/Avatar/avatar.utils.ts 4.7 5.4 +0.6

@github-actions

github-actions Bot commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

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

📊 Coverage (unit)

metric %
statements 74.7%
branches 60.0%
functions 68.5%
lines 75.7%
⏱ 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 › 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_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 › MANTECA_USER_NOT_PROVISIONED fails fast with copy that names the real cause
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
3.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Going offline blames the connection, and reconnecting clears it for the recovered scan
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@abalinda

abalinda commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

/chip review

@github-actions

github-actions Bot commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

🖼 Visual diff — 9 screens moved

13 of 66 shots changed · 53 identical · baseline d5f8d49 → head 46f4255

worst % screen widths
40.77% avatar-picker 320, 430
10.56% hugo-long-username 320, 430
10.39% long-full-name 320, 430
9.65% profile 320, 430
0.07% add-money 320
0.07% home 320
0.03% identity-verification 430
0.03% unverified 430
0.03% withdraw 430

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.

@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

The avatar hand and self-profile pill match the intended flow, but a fast close/reopen can temporarily lose the selected tile and the new copy segment does not expose its action to assistive technology.

Findings

  • MINOR · src/components/Avatar/AvatarPicker.tsx:90 · Deal from the pending pick when reopening
    If a user selects an avatar, closes the drawer before the serialized save/refetch finishes, and immediately reopens it, pending still identifies the visibly selected avatar while saved is the old server value. This line deals from that stale value, so the fresh random hand can omit the pending avatar and render with no radio checked even though the pill already shows the new pick. Deal from pick here (and cover the close/reopen-during-save case) so the stated current-pick guarantee also holds during an in-flight save.

  • MINOR · src/components/Profile/components/ProfileHeader.tsx:106 · Name the profile-link copy action
    The pill's avatar and share segments expose action names, but this new button's accessible name is only the rendered URL. A non-visual user therefore hears a URL button without learning that activation copies it rather than opening or sharing it. Add a localized aria-label such as Copy profile link (optionally including the handle) and assert that action name in the header test.

Checked clean

  • Confirmed the detached worktree head, trusted author, base ref, supplied base SHA, and merge base all match the request.
  • Reviewed hand construction for no-badge, earned-avatar, current-pick, preferred-badge, reroll, save-failure, and serialized-save paths.
  • Reviewed badge-toast deep-link creation and profile query-state cleanup, including the first-hand preference contract.
  • Reviewed the self-profile pill's avatar, copy, share, verification, truncation, analytics, focus, and public-profile fallback behavior against the project handoff and design rules.
  • All exact-head GitHub checks completed successfully, including unit, typecheck, eslint, format, native export, design-system lint, analysis, preview, and visual snapshots.
  • The exact diff passes git diff --check; focused local Jest was unavailable because the detached worktree has no installed dependencies.
  • Security and slop passes found no credential, authorization, injection, unsafe data-flow, or additional maintainability defect worth reporting.

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: 1039407448a6 · Context: repo, design, project · Took 15m

Comment thread src/components/Avatar/AvatarPicker.tsx Outdated
useEffect(() => {
if (open) setOffer(offerBasics(saved))
// deal once per open; the pick joins the row by being picked from it
if (open) setHand(dealHand(saved, unlocked, { prefer }))

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.

MINOR: Deal from the pending pick when reopening

If a user selects an avatar, closes the drawer before the serialized save/refetch finishes, and immediately reopens it, pending still identifies the visibly selected avatar while saved is the old server value. This line deals from that stale value, so the fresh random hand can omit the pending avatar and render with no radio checked even though the pill already shows the new pick. Deal from pick here (and cover the close/reopen-during-save case) so the stated current-pick guarantee also holds during an in-flight save.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

nit, not doing

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Fixed in 46f4255. The reopen effect now deals from pick (which prefers pending) instead of saved, so a close/reopen during an in-flight save keeps the visibly selected avatar in the hand. Covered by a new test that fails against the old line: deals from the pending pick when reopened during an in-flight save.

{pillVisible && (
<span className="flex h-full shrink-0 items-center pl-3">{ownAvatar('small')}</span>
)}
<button

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.

MINOR: Name the profile-link copy action

The pill's avatar and share segments expose action names, but this new button's accessible name is only the rendered URL. A non-visual user therefore hears a URL button without learning that activation copies it rather than opening or sharing it. Add a localized aria-label such as Copy profile link (optionally including the handle) and assert that action name in the header test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

nit, not actioning

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Fixed in 46f4255. Added an sr-only action label to the copy segment (global.copyToClipboard.copyProfileLink, en/es-419/pt-BR — es-AR carries no delta for that namespace), matching the pattern the sibling share segment already uses in this file. Accessible name is now Copy profile link peanut.me/<handle> Verified, so the action, the url and the verified state are all exposed rather than the aria-label replacing them. Asserted in names the copy action, the url and the verified state in the accessible name, and the suite-wide copyButton() matcher now requires the action name too.

@abalinda
abalinda marked this pull request as ready for review September 4, 2026 22:37
Copilot AI lite review requested due to automatic review settings September 4, 2026 22:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@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

Both previously raised minor findings remain at this exact head: a fast close/reopen can temporarily lose the selected tile, and the profile-link copy segment still does not expose its action to assistive technology.

Findings

  • MINOR · src/components/Avatar/AvatarPicker.tsx:90 · Deal from the pending pick when reopening
    This remains present at the exact head. If a user selects an avatar, closes the drawer before the serialized save/refetch finishes, and immediately reopens it, pending still identifies the visibly selected avatar while saved is the old server value. This line deals from that stale value, so the fresh random hand can omit the pending avatar and render with no radio checked even though the pill already shows the new pick. Deal from pick here and cover the close/reopen-during-save case.

  • MINOR · src/components/Profile/components/ProfileHeader.tsx:106 · Name the profile-link copy action
    This remains present at the exact head. The pill's avatar and share segments expose action names, but this button's accessible name is only the rendered URL. A non-visual user therefore hears a URL button without learning that activation copies it rather than opening or sharing it. Add a localized aria-label such as Copy profile link and assert that action name in the header test.

Checked clean

  • Confirmed the detached worktree head, trusted author, base ref, supplied base SHA, and merge base all match the request.
  • Rechecked P1 against the pending/saved state model, serialized save loop, reopen effect, reroll path, and existing AvatarPicker tests; no guard or test covers the close/reopen race.
  • Rechecked P2 against the three pill segments and ProfileHeader tests; avatar and share have action names while the copy segment still exposes only the URL.
  • Reviewed the complete avatar-hand, badge deep-link, query cleanup, self-profile pill, public-profile fallback, localization, correctness, security, adversarial, and slop surfaces; no additional actionable finding survived.
  • All exact-head GitHub checks completed successfully, including unit, typecheck, eslint, format, native export, design-system lint, analysis, preview, and visual snapshots.
  • The exact diff passes git diff --check; focused local Jest remains unavailable because the detached worktree has no installed dependencies.

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: 1039407448a6 · Context: repo, design, project · Took 7m

Comment thread src/components/Avatar/AvatarPicker.tsx Outdated
useEffect(() => {
if (open) setOffer(offerBasics(saved))
// deal once per open; the pick joins the row by being picked from it
if (open) setHand(dealHand(saved, unlocked, { prefer }))

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.

MINOR: Deal from the pending pick when reopening

This remains present at the exact head. If a user selects an avatar, closes the drawer before the serialized save/refetch finishes, and immediately reopens it, pending still identifies the visibly selected avatar while saved is the old server value. This line deals from that stale value, so the fresh random hand can omit the pending avatar and render with no radio checked even though the pill already shows the new pick. Deal from pick here and cover the close/reopen-during-save case.

{pillVisible && (
<span className="flex h-full shrink-0 items-center pl-3">{ownAvatar('small')}</span>
)}
<button

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.

MINOR: Name the profile-link copy action

This remains present at the exact head. The pill's avatar and share segments expose action names, but this button's accessible name is only the rendered URL. A non-visual user therefore hears a URL button without learning that activation copies it rather than opening or sharing it. Add a localized aria-label such as Copy profile link and assert that action name in the header test.

The "Earned" chip sat on the top of the sticker in every locale and covered
its whole top edge in pt-BR, so the tile keeps an XL band above the art. Arrow
keys drifted a column on the way up and down, because eight tiles in three
columns are not a multiple of the column count; up and down now stay in their
column and wrap within it. A pick this bundle's manifest does not know is no
longer dealt as a second, checked "initial" tile. The pill asks ShareButton for
no shadow instead of covering its shadow class from the outside, and the
verified tick carries its name for screen readers. The picker tests deal a
fixed hand rather than pinning Math.random to the deal's pool order.

@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

Both previously raised minor findings remain at this exact head: a fast close/reopen can temporarily lose the selected tile, and the profile-link copy segment still does not expose its action to assistive technology.

Findings

  • MINOR · src/components/Avatar/AvatarPicker.tsx:90 · Deal from the pending pick when reopening
    If a user selects an avatar, closes the drawer before the serialized save/refetch finishes, and immediately reopens it, pending still identifies the visibly selected avatar while saved is the old server value. This line deals from that stale value, so the fresh random hand can omit the pending avatar and render with no radio checked even though the pill already shows the new pick. Deal from pick here and cover the close/reopen-during-save case. The supplied reply does not dispute this reachable state or move the fix to follow-up work, so it is neither a refutation nor a deferral.

  • MINOR · src/components/Profile/components/ProfileHeader.tsx:107 · Name the profile-link copy action
    The pill's avatar and share segments expose action names, but this button's accessible name is only the rendered URL (plus Verified when applicable). A non-visual user therefore hears a URL button without learning that activation copies it rather than opening or sharing it. Add a localized aria-label such as Copy profile link and assert that action name in the header test. The supplied reply offers no evidence that the action is already exposed and names no follow-up, so it is neither a refutation nor a deferral.

Checked clean

  • Confirmed the detached worktree head, trusted author, dev base ref, supplied base SHA, and merge base all match the request.
  • Rechecked P1 against the pending/saved state model, serialized save loop, reopen effect, reroll path, and AvatarPicker tests; no guard or test covers the close/reopen race.
  • Rechecked P2 against the three pill segments and ProfileHeader tests; avatar and share have action names while the copy segment still exposes only the URL and verification state.
  • P3 and P4 duplicate the same two surviving defects, so they are not emitted as duplicate findings.
  • Reviewed the latest review-fix commit: manifest-gated picks, three-column keyboard navigation, earned-chip spacing, verified-state naming, and nested share-shadow removal introduce no additional actionable defect.
  • All exact-head GitHub checks completed successfully, including unit, typecheck, eslint, format, native export, design-system lint, analysis, preview, and visual snapshots.
  • The exact diff passes git diff --check; focused local Jest is unavailable because the detached worktree has no installed dependencies.

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: 676da2fcacd9 · Context: repo · Took 12m

Comment thread src/components/Avatar/AvatarPicker.tsx Outdated
useEffect(() => {
if (open) setOffer(offerBasics(saved))
// deal once per open; the pick joins the row by being picked from it
if (open) setHand(dealHand(saved, unlocked, { prefer }))

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.

MINOR: Deal from the pending pick when reopening

If a user selects an avatar, closes the drawer before the serialized save/refetch finishes, and immediately reopens it, pending still identifies the visibly selected avatar while saved is the old server value. This line deals from that stale value, so the fresh random hand can omit the pending avatar and render with no radio checked even though the pill already shows the new pick. Deal from pick here and cover the close/reopen-during-save case. The supplied reply does not dispute this reachable state or move the fix to follow-up work, so it is neither a refutation nor a deferral.

{pillVisible && (
<span className="flex h-full shrink-0 items-center pl-3">{ownAvatar('small')}</span>
)}
<button

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.

MINOR: Name the profile-link copy action

The pill's avatar and share segments expose action names, but this button's accessible name is only the rendered URL (plus Verified when applicable). A non-visual user therefore hears a URL button without learning that activation copies it rather than opening or sharing it. Add a localized aria-label such as Copy profile link and assert that action name in the header test. The supplied reply offers no evidence that the action is already exposed and names no follow-up, so it is neither a refutation nor a deferral.

Chip on #2989:

- AvatarPicker dealt the reopened hand from `saved`, the server value. A
  close/reopen while a save is still draining could therefore drop the
  avatar the pill already shows and leave no tile checked. Deal from
  `pick`, which prefers the pending value.
- The pill's copy segment exposed only the url as its accessible name, so
  it read as a link rather than as an action. Add an sr-only action label,
  as the sibling share segment already does; the url and the verified
  state stay in the name.
@innolope-dev

Copy link
Copy Markdown
Collaborator

/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

The two unique prior defects and their duplicate entries are fixed at this exact head; no additional actionable finding survived the full review.

Checked clean

  • Confirmed the detached worktree head, trusted author, dev base ref, supplied base SHA, and merge base all match the request.
  • Rechecked P1, P3, and P5 against the pending/saved state model and close/reopen regression test: the reopen effect now deals from the pending-aware pick.
  • Rechecked P2, P4, and P6 against the rendered profile pill and accessibility regression test: the copy segment now exposes the localized action, URL, and verified state.
  • Reviewed the complete avatar hand, badge deep link, picker query cleanup, serialized save behavior, self-profile pill, public-profile fallback, localization, correctness, security, adversarial, and slop surfaces; no additional actionable finding survived.
  • Exact-head unit, typecheck, eslint, format, native export, design-system lint, analysis, preview, provenance, and aggregate CI checks completed successfully; the advisory visual-snapshot job was still in progress after aggregate CI passed.
  • The exact diff passes git diff --check; focused local Jest was unavailable because the detached worktree has no installed dependencies.

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: 46f425519ea3 · Context: repo · Took 10m

@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

The two unique prior defects and their duplicate entries are fixed at this exact head; no additional actionable finding survived the full review.

Checked clean

  • Confirmed the detached worktree head, trusted author, dev base ref, supplied base SHA, and merge base all match the request.
  • Rechecked P1, P3, and P5 against the pending/saved state model and close/reopen regression test: the reopen effect now deals from the pending-aware pick.
  • Rechecked P2, P4, and P6 against the rendered profile pill and accessibility regression test: the copy segment now exposes the localized action, URL, and verified state.
  • Reviewed the complete avatar hand, badge deep link, picker query cleanup, serialized save behavior, self-profile pill, public-profile fallback, localization, correctness, security, adversarial, and slop surfaces; no additional actionable finding survived.
  • All exact-head GitHub checks completed successfully, including unit, typecheck, eslint, format, native export, design-system lint, analysis, preview, provenance, aggregate CI, and visual snapshots.
  • The exact diff passes git diff --check; focused local Jest was unavailable because the detached worktree has no installed dependencies.

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: 46f425519ea3 · Context: repo · Took 11m

@innolope-dev
innolope-dev self-requested a review September 5, 2026 09:58

@innolope-dev innolope-dev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This can't be used from the design and UX perspective. More on discord

@0xkkonrad

Copy link
Copy Markdown
Contributor

Superseded by #3014, so closing this one.

@abalinda — thank you, this was not wasted. The deal logic is yours: dealHand, the art-path dedupe, the guaranteed earned card and the serialised save queue all came across, along with the tests that pin them, and they are the part of #3014 Chip had nothing to say about. What changed is the shape around them, after Slava's four points and Konrad's rulings on 7 September: option B on the profile (avatar above the pill, pill back to h-10), no header on the sheet, names and lines on every tile, the 26-letter Initials group replaced by slot 1 as the user's own initial, no shadow on the selected tile, and a menu button where the home avatar chip was.

Final spec: https://claude.ai/code/artifact/b0d808c5-0924-4e1d-9c86-140a4993eb7c

The four points and how each is answered are in #3014's description under Slava's points, answered. Worth a look if you want to see where your code ended up.

This branch was successfully deployed

1 active deployment
Preview — 46f42551 Deployed Sep 5, 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.

4 participants