Skip to content

fix(native): route peanut.me/<username> to an in-app public profile - #2726

Merged
jjramirezn merged 1 commit into
devfrom
fix/native-profile-deep-link
Aug 18, 2026
Merged

fix(native): route peanut.me/<username> to an in-app public profile#2726
jjramirezn merged 1 commit into
devfrom
fix/native-profile-deep-link

Conversation

@innolope-dev

Copy link
Copy Markdown
Collaborator

Why

Two users reported in the last 24h that peanut.me/jwei opens the app with no visible content. The backend is healthy (public URL and profile API both return 200 with profile data) — the failure is native deep-link routing.

/<username> only exists on the web as the [...recipient] catch-all, which scripts/native-build.js prunes from the static export. On dev today the mapper's recipient block funnels a bare username into /send?recipient=, so a scanned or deep-linked profile link lands on the send form instead of the profile; on the builds users are running now it fell through to the raw web path and left them on a blank screen.

What

  • New /profile/view?username= page — the same ValidatedUsernameWrapper + PublicProfile pair the web catch-all's profile branch renders. Public route, so a logged-out deep link still resolves (guest view).
  • profileUrl() points at it in Capacitor, so in-app call sites (history, rewards, invites, contributors) open the profile instead of the send form.
  • The deep-link mapper's recipient block is split: a single, username-shaped segment → the profile; everything else (amount segment, user@chain, address, ENS) → the send dispatcher, unchanged.

Scope

Cherry-picked from 21ad30ae0 on mobile-release, narrowed to the profile fix — the /invite half of that commit is already on dev via #2697. The same change reaches dev eventually through #2702 (the 232-file mobile-releasedev merge); this is the small, reviewable slice so the fix doesn't wait on that.

Verification

  • native-routes, TransactionCard, InvitesPage suites: 122 tests pass.
  • tsc --noEmit clean for the touched files (only pre-existing TS2307 asset/submodule errors in a fresh worktree).
  • prettier --write run on all touched files.

A scanned or deep-linked peanut.me/<username> has no native stand-in: the
[...recipient] catch-all is pruned from the static export, so the mapper's
recipient block funnelled bare usernames into the send form (and, on the
builds users are running now, dumped them at home with a blank screen).
Two users reported it in the last day on peanut.me/jwei.

Adds the /profile/view?username= page — the same ValidatedUsernameWrapper +
PublicProfile pair the web catch-all's profile branch renders, public so a
logged-out deep link still resolves — points profileUrl() at it, and splits
the mapper's recipient block: a bare username goes to the profile, payment
shapes (amount segment, user@chain, address, ENS) keep going to the send
dispatcher.

Cherry-picked from 21ad30a on mobile-release, narrowed to the profile fix
(dev already routes /invite via #2697).
@innolope-dev innolope-dev self-assigned this Aug 18, 2026
@vercel

vercel Bot commented Aug 18, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview Aug 18, 2026 11:47am

Request Review

@coderabbitai

coderabbitai Bot commented Aug 18, 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: Pro

Run ID: 244397f1-017e-4e99-9c1e-d73539522fc5

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

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

@github-actions

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 7159.84 → 7165.21 (+5.37)
Findings: +1 net (+8 new, -7 resolved)

🆕 New findings (8)

  • critical complexity — src/utils/native-routes.ts — CC 71, MI 59.07, SLOC 167
  • medium method-complexity — src/utils/native-routes.ts:112 — mapDeepLinkPath CC 22 SLOC 62
  • medium method-complexity — src/utils/native-routes.ts:183 — rewriteMethodPath CC 16 SLOC 31
  • medium complexity — src/constants/routes.ts — CC 15, MI 61.83, SLOC 54
  • low high-dlt — src/utils/native-routes.ts:112 — mapDeepLinkPath: DLT 21 (calls 21 distinct functions — high context load)
  • low high-mdd — src/utils/native-routes.ts:112 — mapDeepLinkPath: MDD 17.6 (uses across many lines from declarations)
  • low high-mdd — src/utils/native-routes.ts:183 — rewriteMethodPath: MDD 11.7 (uses across many lines from declarations)
  • low missing-return-type — src/app/(mobile-ui)/profile/view/page.tsx:16 — ProfileViewPage: exported fn missing return type annotation

✅ Resolved (7)

  • src/utils/native-routes.ts — CC 69, MI 59.44, SLOC 162
  • src/utils/native-routes.ts:112 — mapDeepLinkPath CC 20 SLOC 57
  • src/utils/native-routes.ts:174 — rewriteMethodPath CC 16 SLOC 31
  • src/constants/routes.ts — CC 14, MI 61.4, SLOC 46
  • src/utils/native-routes.ts:112 — mapDeepLinkPath: DLT 19 (calls 19 distinct functions — high context load)
  • src/utils/native-routes.ts:112 — mapDeepLinkPath: MDD 14.7 (uses across many lines from declarations)
  • src/utils/native-routes.ts:174 — rewriteMethodPath: MDD 11.7 (uses across many lines from declarations)

📈 Painscore deltas (top movers)

File Before After Δ
src/app/(mobile-ui)/profile/view/page.tsx 0.0 5.0 +5.0

@github-actions

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 3064 ran, 0 failed, 0 skipped, 40.0s

📊 Coverage (unit)

metric %
statements 66.9%
branches 52.0%
functions 57.2%
lines 67.7%
⏱ 10 slowest test cases
time test
2.7s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
0.8s src/utils/__tests__/demo-api.test.ts › isDemoMode() is false when not running under Capacitor
0.3s src/utils/__tests__/sentry.utils.test.ts › defaults to the client budget under a browser global
0.3s src/app/(mobile-ui)/withdraw/__tests__/withdraw-states.test.tsx › Bank withdrawal keeps the $1 minimum for sub-$1 amounts
0.3s src/utils/__tests__/sentry.utils.test.ts › still lets a per-call timeoutMs win over the default
0.2s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.2s src/utils/__tests__/auth-token.test.ts › authReady does not park — hydrates the plain token without an unlock
0.2s src/utils/__tests__/auth-token.test.ts › ignores the guarded marker and falls back to the plain token
0.2s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in validateInviteCode
0.2s src/utils/__tests__/auth-token.test.ts › is none — never guarded — when only the guarded marker is present
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@jjramirezn
jjramirezn merged commit d62f4af into dev Aug 18, 2026
20 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.

2 participants