fix(profile-urls): friendly NIP-05 /u/ paths and search card identifier#403
fix(profile-urls): friendly NIP-05 /u/ paths and search card identifier#403jalcine wants to merge 4 commits into
Conversation
🚀 Preview Deployment
|
- /u/_%40jacky.divine.video -> /u/jacky (bare local part on default apex) - /u/_%40jacky.dvine.video -> /u/jacky.dvine.video (alternate apex) - /u/alice%40primal.net -> /u/alice.primal.net (third-party NIP-05) - /u/_%40jacky.divine.video and /u/jacky.divine.video and /u/jacky all resolve the same profile (kind-0 match against either canonical form, DNS NIP-05 fallback when the relay has no match) - Drop the misleading openvine.co legacy fallback for subdomain-shaped inputs (was: 'Could not find a user with legacy Vine username or NIP-05 identifier: jacky.divine.video' on /u/jacky.divine.video) - Add URL_GUIDE.md entries for the new friendly-path forms Refs: /u/_%40jacky.divine.video 404 in browser
The UserCard on /search?q=... only displayed the kind-0 name field, falling back to a random genUserName(pubkey) when that was empty. For users with a NIP-05 set, that meant the card showed e.g. @minimal Mouse 1 when the user's actual handle is _@jacky.divine.video — completely disconnected from the network identity that buildProfileLinkPath now routes to /u/jacky. Prefer the NIP-05 in the displayed secondary line when present, using the same format ProfileHeader uses (@jacky.divine.video for divine NIP-05s, @<friendly-form> for third-party, @<name> as last resort). Keeps the legacy @<kind-0 name> only as a fallback for users without a NIP-05.
658d073 to
1a5405c
Compare
Deploying divine-web with
|
| Latest commit: |
3ff4fce
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://8c00a11a.divine-web.pages.dev |
| Branch Preview URL: | https://fix-friendly-nip05-urls.divine-web.pages.dev |
…IP-05 segments - Add normalizeUrlSegmentToFriendly to convert raw NIP-05 /u/ URL segments (e.g. _@jimmyhere.divine.video) to the canonical friendly form (jimmyhere). - UniversalUserPage now renders ProfilePage directly via a new pubkeyOverride prop instead of navigating away to /<npub>, so the /u/<username> URL persists in the address bar. - ProfilePage replaces the noisy full-page subdomain redirect (window.location.href) with a silent in-place replaceState swap to the /u/<username> vanity URL when a verified divine.video NIP-05 is detected on the apex domain.
…apex list - Inline the normalizeUrlSegmentToFriendly logic into UniversalUserPage as a regex-based stripNip05Envelope helper. We deliberately do NOT resolve the raw _@<sub>.<apex> / <sub>@<apex> NIP-05 form — only the canonical /u/<sub> path runs through the lookup. - Promote DIVINE_APEX_DOMAINS in nip05Utils.ts to a public export and feed the new regex from it (single source of truth for the apex list). - Simplify the ProfilePage URL-rewrite pathname guard: the only reachable legacy entry point for ProfilePage is /profile/<npub>. - Add two UniversalUserPage integration tests for percent-encoded and third-party segment passthrough.
rabble
left a comment
There was a problem hiding this comment.
Thanks — this is a thorough fix for #402 and the test coverage on the happy paths is great. Two functional issues need addressing before merge, plus a coordination question:
-
Third-party NIP-05 links 404.
buildProfileLinkPathemits/u/alice.primal.net, butnip05CandidatesFromUrlSegmentreturns only the literal dotted segment for non-apex domains — it never matches a kind-0nip05(those contain@), and the DNS fallback skips candidates without@. So every third-party link the app now generates lands on "User not found" (URL_GUIDE advertises this form as working). The comment in profileLinks.ts says callers derivelocal@domainseparately, butUniversalUserPagedoesn't. Suggest yieldingalice@primal.net/_@alice.primal.netcandidates for non-apex segments so the DNS fallback can try them. -
NIP05_ENVELOPE_PATTERNcrashes on/u/dvine.video. The alternation([^@]+)@divine\.video|dvine\.videoisn't grouped, so the bare stringdvine.videomatches with groups 2 and 3 undefined, and(match[2] ?? match[3]).toLowerCase()throws inside the effect (verified in node). A profile withnip05: "_@dvine.video"makes the app generate exactly that link. Please wrap the apex list in(?:…), escape the dots in the first branch too, and guard against undefined groups. -
Search card shows unverified NIP-05.
UserCardrendersmetadata.nip05straight from kind-0 with nouseNip05Validation, unlike ProfileHeader — anyone can claim_@jack.divine.videoand show up as@jack.divine.videoin search. Could we validate before display, or style it as clearly unverified? -
Heads-up: PR #404 overlaps. It removes
nip05frombuildProfileLinkPathcall sites in VideoCard/NoteContent (direct npub links), which is the opposite strategy to this PR. They don't conflict textually but merging both gives inconsistent profile URLs across surfaces — worth syncing on which approach is canonical before either lands.
Also flagging that the verified-NIP-05 subdomain redirect in ProfilePage becomes an apex-side /u/ rewrite — deliberate per the commit message, but since subdomain canonicalization is load-bearing in the router architecture, want to confirm that change has product sign-off. Smaller notes: prefer navigate(path, { replace: true }) over raw replaceState so React Router stays in sync, and confirm dropping the @openvine.co bare-name fallback is intentional.
Closes #402
Summary
/u/_%40jacky.divine.videoand similar percent-encoded NIP-05 URLs now resolve, and the same profile is reachable as/u/jacky,/u/jacky.divine.video, and/u/jacky.dvine.video. Bare local part on the default apex means the default apex. Third-party NIP-05s become/u/alice.primal.net./search?q=...shows the NIP-05 as the secondary identifier when one is set, instead of a randomgenUserName(pubkey)or a stale legacy Vine handle. The card link and the displayed handle now point at the same identity.Motivation
Two layered bugs combined to make the URL in the issue fail end-to-end. First, every in-app author link was produced by
buildProfileLinkPathas/u/${encodeURIComponent(nip05)}, which for_@jacky.divine.videoyielded the ugly+%40...form. Second, the lookup atuseUniversalUserLookuponly matched the literalmetadata.nip05field on a kind-0 event (limit 500) and fell back toname@openvine.cofor any input that didn't contain@, producing the misleading "Could not find a user with legacy Vine username or NIP-05 identifier" page. The search card compounded the confusion by only showing the kind-0namefield and never the NIP-05, so a user had no way to know that@Minimal Mouse 1was the same identity asjacky.divine.video.What changed
src/lib/profileLinks.ts: newtoFriendlyPath(nip05)helper and newnip05CandidatesFromUrlSegment(segment)inverse.buildProfileLinkPathemits/u/jackyfor_@jacky.divine.video/jacky@divine.video,/u/jacky.dvine.videofor the alternate apex,/u/alice.primal.netfor third-party NIP-05s, and falls back to/{npub}when the NIP-05 is missing or malformed.src/pages/UniversalUserPage.tsx:useUniversalUserLookupnow expands the URL segment into the candidate list vianip05CandidatesFromUrlSegment, matches kind-0 against any candidate (catching both_@x.yandx@yforms), falls back to NIP-05 DNS resolution viaresolveNip05ToPubkeyfor candidates containing@, and drops the openvine.co legacy fallback for any subdomain-shaped input.src/pages/SearchPage.tsx:UserCardnow readsmetadata.nip05and shows it as the secondary line, formatted viagetDivineNip05Info(@jacky.divine.video) for divine NIP-05s and viatoFriendlyPath(@alice.primal.net) for third-party. Legacymetadata.nameis still shown when no NIP-05 is set.URL_GUIDE.md: documents the new friendly-path forms and removes the now-satisfied "NIP-05 username lookups in/u/route" future-enhancement bullet.Testing
npm run test— 1114 vitest tests pass (24 new insrc/lib/profileLinks.test.ts, 5 new insrc/pages/UniversalUserPage.test.tsx, 4 new insrc/pages/SearchPage.test.tsx).tsc -p tsconfig.app.json --noEmit— 0 errors.eslint src/— 0 errors (14 pre-existing warnings in unrelated files)./u/jackyand confirm the user's profile loads; visit/u/_%40jacky.divine.videoand confirm it lands on the same profile.Visuals