Skip to content

fix(drawer): shadow clipped at drawer edge — scroll area owns the L/16 padding - #3019

Merged
kushagrasarathe merged 3 commits into
devfrom
fix/drawer-shadow-clip
Sep 7, 2026
Merged

fix(drawer): shadow clipped at drawer edge — scroll area owns the L/16 padding#3019
kushagrasarathe merged 3 commits into
devfrom
fix/drawer-shadow-clip

Conversation

@kushagrasarathe

@kushagrasarathe kushagrasarathe commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Global fix for clipped button shadows inside drawers (Kush's repro: the avatar-change drawer's "Tirar los dados" / "Listo" buttons with their right shadow cut flush).

Task: TASK-21446 (DS umbrella; drawer anatomy per design.md)

Root cause

DrawerContent's scroll wrapper is overflow-auto, and CSS clips painting at that element's own box edge. Consumers put the drawer's horizontal padding on the panel AROUND the wrapper, so a w-full button spanned the whole scroll box and its 4px offset shadow fell outside it — cut in a straight vertical line. The bug class had already collected local workarounds (AvatarPicker's scrollAreaClassName="px-4" on 2026-09-05, QRBottomDrawer's pr-1 gutter) — proof it was a shared-component problem, not a per-screen one.

Fix (shared component, one rule)

The scroll wrapper now owns the design.md container padding (px-4, L/16) INSIDE the overflow box, so shadows always land in the padded, unclipped region. DrawerHeader/DrawerFooter go py-4 to match. A comment in Drawer/index.tsx states the law: never re-add horizontal padding on the panel or on drawer content.

Consumer sweep (all now-redundant horizontal padding removed — content width is unchanged everywhere, the 16px moved inside the clip box, not added):

  • panel-padded (were clipping): HomeActionDrawers, KycStatusDrawer (pb-12 also moved into the scroll area so the retry button's bottom shadow paints), TokenSelector, QRBottomDrawer
  • child-padded (were safe, would have double-padded): InitiateKycModal ×2, KycActionRequired/RegionRestricted/Processing/Failed modals, CancelDepositActions, UnlockMethodModal, PublicProfile, CancelSendLinkDrawer, OnrampConfirmationModal, OnrampOptions, TransactionDetailsDrawer, BadgeStatusDrawer, ContributorsDrawer, CardUnlockDrawer
  • workarounds retired: AvatarPicker scrollAreaClassName="px-4", QRBottomDrawer pr-1 gutter
  • docs updated: /dev/ds/patterns/drawer live example + code snippet no longer teach the child-px-4 pattern

Screenshots (375x667, mock data via fixtures)

Avatar drawer — the reported bug. Before = the pre-workaround tree (8a1ccdbab~1, panel p-4 — the state Kush reproduced); after = this branch.

before (shadow cut flush) after (full offset)
before after

DS pattern-page drawer (second shadowed-button drawer): before/after pixel-identical — that example already padded inside the scroll area, confirming the migration adds no double padding.

before after
ds before ds after

The pr-assets-3019 branch is deleted after merge.

ds-shots expectation

Drawer-bearing fixture shots should be identical or differ only by previously-clipped shadow pixels: every consumer's content width is unchanged. The avatar-picker fixture was already workaround-fixed on dev, so it should not diff (beyond its known dice-randomness flake). Anything else diffing is a bug.

Observed: 7 screens moved, all in the known flaky set at their usual magnitudes (profile 3.19%, avatar-picker 1.35% — below its usual 1.78% dice flake, rest <=0.07%) — same screens move on sibling PRs #3011-#3015 that touch none of them. No drawer-bearing screen shows a layout shift.

Risks

  • FE-only, class moves. The one behavioral edge: any FUTURE drawer that wants edge-to-edge content must pass scrollAreaClassName="px-0" — none exists today (verified the full consumer census).
  • Scroll behavior untouched (overflow, max-h, safe-bottom all as before).

QA

prettier clean · typecheck clean · jest 485 suites / 6077 tests green · ds-lint ratchet green

Round 2 — touch behavior (Kush, 2026-09-07)

1. Sheet background: WHITE stays — ruling flipped on 2026-09-07. An earlier commit on this branch (5422fac) moved the sheet to bg-background-page, reversing Slava's deliberate white surface from #2984 (e55b5d1, 2026-09-04). Kush reversed that decision the same day: the white sheet stays, and 4e80963 reverts the background part only. No net background change ships in this PR — the shadow-clip fix, padding ownership, touch/drag fixes, and scrollbar fix all stay. Sheet on the current head: white sheet

2. Touch/drag — one root cause for the pass-through blip and the unclear grab area. DrawerContent carried onTouchMove={(e) => e.stopPropagation()} since the original drawer commit. That handler silenced vaul's OWN document-level touchmove handlers — the ones that do scroll containment and drag coordination — so body-drags misbehaved and touches leaked to page-level listeners. Meanwhile the app's custom pull-to-refresh also listens on document, so a drawer drag could pull the home screen behind the sheet (the "blip"). Fix:

  • removed the stopPropagation hack; vaul handles its own containment
  • usePullToRefresh now ignores any touch starting on [data-vaul-drawer] / [data-vaul-overlay] (root fix + regression test)
  • whole-sheet drag was always vaul's design and now works: verified with synthesized CDP touch swipes — dragging down from the avatar drawer's BODY dismisses it, the P2R indicator stays hidden mid-swipe, and the scroll area still scrolls (vaul only drags when the scroll is at top)

3. Android scrollbar flash: scrollbar-none on the drawer scroll container (tailwind-scrollbar utility; same overflow chain as the shadow fix).

On-device checks for Kush (not fully verifiable in Chromium headless): the Android scrollbar flash disappearing, real-finger drag feel on iOS/Android, and the iOS rubber-band interaction with pull-to-refresh.

…r clip

Root cause: DrawerContent's overflow-auto scroll wrapper clips painting
at its own box edge. Consumers put the horizontal padding on the panel
AROUND that wrapper (or on the panel via className), so a w-full
button's 4px offset shadow fell outside the clip box and was cut in a
straight vertical line — the avatar drawer, KYC status drawer, token
selector and QR drawer all hit or worked around it locally.

Fix: the scroll wrapper now carries the design.md container padding
(px-4, L/16) INSIDE the overflow box, so shadows always land in the
padded, unclipped region. DrawerHeader/DrawerFooter drop their own
horizontal padding (py-4) to match. Every consumer's now-redundant
horizontal padding is removed — content width is unchanged everywhere
(16px moved inside the clip box, not added). Local workarounds retired:
AvatarPicker's scrollAreaClassName px-4, QRBottomDrawer's pr-1 gutter.
KycStatusDrawer's pb-12 moved into the scroll area so the retry
button's bottom shadow paints too.
@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 5:37pm 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: 85d76185-dbec-4352-a48c-11f0f9e38fe0

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: 7260.1 → 7262.35 (+2.25)
Findings: +1 net (+8 new, -7 resolved)

🆕 New findings (8)

  • critical complexity — src/hooks/usePullToRefresh.ts — CC 54, MI 57.8, SLOC 237
  • medium high-mdd — src/hooks/usePullToRefresh.ts:103 — : MDD 58.0 (uses across many lines from declarations)
  • medium high-mdd — src/hooks/usePullToRefresh.ts:82 — usePullToRefresh: MDD 57.2 (uses across many lines from declarations)
  • medium high-dlt — src/hooks/usePullToRefresh.ts:82 — usePullToRefresh: DLT 35 (calls 35 distinct functions — high context load)
  • medium high-dlt — src/hooks/usePullToRefresh.ts:103 — : DLT 32 (calls 32 distinct functions — high context load)
  • medium complexity — src/components/Global/Drawer/index.tsx — CC 20, MI 65.62, SLOC 65
  • medium complexity — src/components/Kyc/KycStatusDrawer.tsx — CC 13, MI 61.86, SLOC 56
  • low high-mdd — src/components/Global/Drawer/index.tsx:100 — : MDD 13.3 (uses across many lines from declarations)

✅ Resolved (7)

  • src/hooks/usePullToRefresh.ts — CC 53, MI 57.96, SLOC 234
  • src/hooks/usePullToRefresh.ts:82 — usePullToRefresh: MDD 54.6 (uses across many lines from declarations)
  • src/hooks/usePullToRefresh.ts:103 — : MDD 55.3 (uses across many lines from declarations)
  • src/hooks/usePullToRefresh.ts:82 — usePullToRefresh: DLT 34 (calls 34 distinct functions — high context load)
  • src/hooks/usePullToRefresh.ts:103 — : DLT 31 (calls 31 distinct functions — high context load)
  • src/components/Global/Drawer/index.tsx — CC 21, MI 66.2, SLOC 68
  • src/components/Kyc/KycStatusDrawer.tsx — CC 13, MI 61.85, SLOC 56

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 6106 ran, 0 failed, 0 skipped, 2.0m

📊 Coverage (unit)

metric %
statements 74.7%
branches 60.4%
functions 68.7%
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_USER_NOT_PROVISIONED 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 › 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 › a refused idempotency key tells the user to scan again, not to contact support
3.1s 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 › 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`.

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

🖼 Visual diff — 5 screens moved

5 of 68 shots changed · 63 identical · baseline 5c52afa → head 4e80963

worst % screen widths
1.70% avatar-picker 430
0.07% request 320
0.07% badges 320
0.07% withdraw 320
0.03% add-money 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.

@kushagrasarathe
kushagrasarathe marked this pull request as ready for review September 7, 2026 11:18

@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 drawer padding migration is internally consistent and exact-head CI is green, but the canonical drawer recipe still teaches the old padding contract and will double-pad new callers.

Findings

  • MAJOR · src/components/Global/Drawer/index.tsx:129 · Update the canonical drawer recipe with the new padding owner
    The canonical design-system drawer recipe still tells callers to pass className="p-4" to DrawerContent, and its nested-drawer example does the same. This line now adds px-4 to a separate inner scroll wrapper, so a caller following that recipe gets 16px on the panel plus 16px in the scroll area (32px total horizontal inset), directly contradicting the new rule in the adjacent comment. Update the canonical design.md drawer and nested-drawer recipes as part of this rollout, including the scrollAreaClassName="px-0" escape hatch for intentionally edge-to-edge content.

Checked clean

  • Detached worktree HEAD, trusted author, base ref, and base SHA match the supplied review coordinates.
  • Exact-head required CI, unit tests, typecheck, lint, design-system lint, and visual snapshots completed successfully.
  • Reviewed the full DrawerContent consumer census; existing horizontal padding and shadow-gutter workarounds were removed consistently.
  • Checked overflow, snap-point, nested-drawer, safe-bottom, and scroll-area override behavior; no runtime defect was found in the changed consumers.

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: 49b5a902d658 · Context: repo, design · Took 7m

Comment thread src/components/Global/Drawer/index.tsx Outdated
Three Kush findings, one commit because they share the drawer chrome:

1. Sheet background back to bg-background-page. e55b5d1 (#2984)
   deliberately went white; Kush's 2026-09-07 ruling reverts it — the
   sheet matches the app page background. Flagged in the PR body since
   it reverses a 3-day-old deliberate change.

2. Removed the Content-level onTouchMove stopPropagation. It silenced
   vaul's own document-level touchmove handlers (scroll containment +
   drag coordination), which is why dragging the sheet from its body
   felt broken and touches seemed to reach the page behind. The reason
   it existed — the document-level pull-to-refresh reacting to drawer
   touches — is now fixed at the root: usePullToRefresh ignores any
   touch starting on [data-vaul-drawer]/[data-vaul-overlay] (with a
   regression test). Whole-sheet drag-to-dismiss and no-P2R-during-drag
   verified with synthesized CDP touch swipes.

3. scrollbar-none on the drawer scroll container — Android flashes a
   scrollbar there while the sheet is dragged (tailwind-scrollbar
   utility, same overflow chain as the shadow fix).

@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 drawer implementation is coherent, but the canonical drawer recipes still prescribe the old padding owner and now create double horizontal padding.

Findings

  • MAJOR · src/components/Global/Drawer/index.tsx:129 · Update the canonical drawer recipe with the new padding owner — also flagged by claude-opus
    This new invariant says the inner scroll wrapper owns the L/16 horizontal padding, but design/design.md still shows both ordinary and nested drawers with DrawerContent className="p-4" and explicitly says the panel owns that inset. A caller following the canonical recipe now gets the panel's 16px inset plus the scroll wrapper's new 16px inset, rendering content 32px from each edge and contradicting the rule introduced here. Update both canonical examples and the padding paragraph to document the scroll-area default and the scrollAreaClassName="px-0" edge-to-edge escape hatch.

Checked clean

  • Confirmed the detached worktree head and merge base match the supplied SHAs.
  • Reviewed the full Drawer consumer padding sweep; no remaining production consumer unintentionally double-pads at this head.
  • Traced the vaul drawer and overlay touch targets through the pull-to-refresh guard; the focused Drawer and usePullToRefresh suites pass (17 tests).
  • Required exact-head CI is green; advisory ds-shots is 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: 1 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: 5422facba2d8 · Context: repo, design · Took 9m

Comment thread src/components/Global/Drawer/index.tsx
Kush's 2026-09-07 ruling reversed: the sheet stays white (Slava's #2984
clean-surface choice), not bg-background-page. Only the background part
of 5422fac reverts — the touch/drag fixes, pull-to-refresh guard, and
scrollbar-none all stay.

@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 drawer padding migration and Vaul touch handling are sound. The previously raised canonical-doc mismatch remains, but the author explicitly moved that accepted fix to open mono PR #139, so it is deferred there.

Answered by a human, not raised again

These are still present as far as this review can tell. You resolved the thread or deferred the fix, so Chip files them instead of repeating them.

  • MAJOR · src/components/Global/Drawer/index.tsx:129 · Finish the deferred canonical drawer recipe updatetask

Checked clean

  • Confirmed the detached worktree HEAD and merge base exactly match the supplied head and base SHAs.
  • Reviewed every DrawerContent consumer migration for preserved horizontal inset and shadow clearance, including the QR snap-point drawer and KYC bottom padding.
  • Reviewed removal of the content touchmove propagation guard and the pull-to-refresh exclusion for Vaul drawer and overlay targets.
  • Exact-head required CI is green; advisory ds-shots remains in progress.
  • Focused local Jest execution was unavailable because the detached worktree has no node_modules; exact-head unit CI passed.

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: 4e809631320b · Context: repo, design · Took 8m

@kushagrasarathe
kushagrasarathe merged commit 1e88f9e into dev Sep 7, 2026
24 checks passed
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.

1 participant