feat(urls): handle-forward URL scheme (root-level, DID-share hybrid) - #141
Conversation
Typing in the top-bar global search still shows the live results dropdown. Pressing Enter now navigates to /explore?q=<query> (the Explore search box seeds from ?q=) instead of activating the highlighted dropdown row. Clicking a row still deep-links to that person/activity. Adds an opt-in `onSubmit` prop to the Combobox primitive that takes precedence over row selection on Enter, with tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Projects tab was missing the search box and sort control that the Activities and Groups tabs already have. Mirror the Activities tab's chrome: a search input filtering by title/description and a Popover sort menu (newest/oldest/title A-Z/Z-A), reusing the shared .profile-certs__* search/sort styles. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the inconsistent URL system (readable handle profiles but raw,
percent-encoded DID record URLs) with one consistent, readable, shareable
scheme modeled on bsky.app/GitHub/Mastodon:
profile: /{handle} (was /profile/{handle-or-did})
record: /{handle}/activity|project/{rkey} (was /activity|project/{did}/{rkey})
The handle is what we display and link internally; the DID is the durable
transport form used for sharing (it cannot rot when a handle is reassigned).
A DID-addressed URL canonicalizes to the handle form on load.
- src/lib/urls.ts: single source of truth — builders (profileUrl/recordUrl/
share*), type<->collection map, parseActor (handle/did/invalid +
RESERVED_ROUTES), at-uri parsing. src/lib/atproto/activity-uri.ts is now a
thin compat shim over it.
- New root routes: /[actor] (profile), /[actor]/[type]/[rkey] (record),
/[actor]/[type]/[rkey]/edit. Record detail + edit dispatch by type and
resolve actor->DID; old detail/edit pages extracted into route components.
- next.config.ts: 308 redirects from the old /profile, /activity, /project
paths so previously shared links don't break. Old *-edit paths are not
redirected (owner-only) and render a graceful not-found.
- src/proxy.ts (Next 16 proxy): pdsls.dev interop — pasted at-uri paths
redirect into the scheme.
- Root-level [actor] relies on the invariant that handles always contain a
dot and app routes never do; documented in AGENTS.md + RESERVED_ROUTES.
- Codemod-converted ~35 inline link builders; hand-updated auth post-signin
path rewriting and the subject-input parser. Tests added for urls.ts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The docs/ tree (design audits, plans, review rounds) is removed from the repo; it lived only as working notes and is no longer maintained here. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.
Tip: disable this comment in your organization's Code Review settings.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
The DSA/Terms/Imprint pages cross-linked each other with raw <a> tags, tripping @next/next/no-html-link-for-pages (error). Pre-existing on staging; surfaced by this PR's CI run (the first `verify` run in a while). Convert to <Link> to get CI green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
Replaces the inconsistent URL system with one consistent, readable, shareable scheme modeled on bsky.app / GitHub / Mastodon.
/profile/{handle-or-did}/{handle}/activity/{did%3A…}/{rkey}/{handle}/activity/{rkey}/project/{did%3A…}/{rkey}/{handle}/project/{rkey}/activity|project/{did}/{rkey}/edit/{handle}/{type}/{rkey}/editThe handle is what we display and link internally; the DID is the durable transport form used when sharing (it can't rot when a handle is reassigned). A DID-addressed URL canonicalizes to the handle form on load.
Why
Profiles were readable but record URLs were raw, percent-encoded DIDs (
/activity/did%3Aplc%3A…/3lcjou2) — unreadable, owner-opaque, ugly to share. URL construction was scattered inline with no single source of truth. Design decided with the maintainer after surveying bsky.app, pdsls.dev, and ~20 other apps.How
src/lib/urls.ts— single source of truth: builders (profileUrl/recordUrl/share*), type↔collection map,parseActor(+RESERVED_ROUTES), at-uri parsing.src/lib/atproto/activity-uri.tsis now a thin compat shim over it./[actor],/[actor]/[type]/[rkey],/[actor]/[type]/[rkey]/edit(detail + edit dispatch bytype, resolve actor→DID, canonicalize DID→handle). Old big pages extracted into route components.next.config.ts— 308 redirects from old/profile,/activity,/projectpaths so shared links survive. Old*/editpaths aren't redirected (owner-only) and render a graceful not-found.src/proxy.ts(Next 16 proxy) — pdsls.dev interop: pasted at-uri paths redirect into the scheme.[actor]invariant — handles always contain a dot, app routes never do. Enforced byRESERVED_ROUTES/parseActor, documented inAGENTS.md. Any new top-level route MUST be a dotless word.urls.tstests.Also removes the
docs/folder (separate commit) at the maintainer's request.Verification
tsc --noEmitclean; fullnext buildpasses (route manifest shows new routes in, old detail/edit routes gone).urls.test.ts, updatedactivity-uri.test.ts, moved project-edit test). Only pre-existing localStorage-mock test failures remain (present onmaintoo)./profile|/activity|/project→ 308 to new form; literalat://paste resolves end-to-end (two-hop) to 200; page-less collections → profile fallback; reserved routes (/settings,/explore) still 200.Reviewer notes
npm run lint) can't run in my environment — pre-existingreact-hooksflat-config plugin-resolution error ineslint.config.mjs, unrelated to this change.🤖 Generated with Claude Code