Skip to content

feat(urls): handle-forward URL scheme (root-level, DID-share hybrid) - #141

Merged
holkexyz merged 5 commits into
stagingfrom
feat/handle-forward-urls
Jun 6, 2026
Merged

feat(urls): handle-forward URL scheme (root-level, DID-share hybrid)#141
holkexyz merged 5 commits into
stagingfrom
feat/handle-forward-urls

Conversation

@holkexyz

@holkexyz holkexyz commented Jun 6, 2026

Copy link
Copy Markdown
Member

What

Replaces the inconsistent URL system with one consistent, readable, shareable scheme modeled on bsky.app / GitHub / Mastodon.

Before After
Profile /profile/{handle-or-did} /{handle}
Record /activity/{did%3A…}/{rkey} /{handle}/activity/{rkey}
Record /project/{did%3A…}/{rkey} /{handle}/project/{rkey}
Edit /activity|project/{did}/{rkey}/edit /{handle}/{type}/{rkey}/edit

The 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.ts is now a thin compat shim over it.
  • New root routes /[actor], /[actor]/[type]/[rkey], /[actor]/[type]/[rkey]/edit (detail + edit dispatch by type, resolve actor→DID, canonicalize DID→handle). Old big pages extracted into route components.
  • next.config.ts — 308 redirects from old /profile, /activity, /project paths so shared links survive. Old */edit paths 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.
  • Root-level [actor] invariant — handles always contain a dot, app routes never do. Enforced by RESERVED_ROUTES/parseActor, documented in AGENTS.md. Any new top-level route MUST be a dotless word.
  • Codemod-converted ~35 inline link builders; hand-updated auth post-signin path rewriting + subject-input parser. Added urls.ts tests.

Also removes the docs/ folder (separate commit) at the maintainer's request.

Verification

  • tsc --noEmit clean; full next build passes (route manifest shows new routes in, old detail/edit routes gone).
  • URL unit tests pass (urls.test.ts, updated activity-uri.test.ts, moved project-edit test). Only pre-existing localStorage-mock test failures remain (present on main too).
  • Dev-server probes confirm every redirect: old /profile|/activity|/project → 308 to new form; literal at:// paste resolves end-to-end (two-hop) to 200; page-less collections → profile fallback; reserved routes (/settings, /explore) still 200.

Reviewer notes

  • Redirect paths to spot-check are tracked in a companion issue.
  • Lint (npm run lint) can't run in my environment — pre-existing react-hooks flat-config plugin-resolution error in eslint.config.mjs, unrelated to this change.

🤖 Generated with Claude Code

holkexyz and others added 4 commits June 6, 2026 19:16
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>

@claude claude Bot 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.

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.

@vercel

vercel Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
certified-app Ready Ready Preview, Comment Jun 6, 2026 7:38pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown

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: defaults

Review profile: CHILL

Plan: Pro

Run ID: af4e1282-6947-46af-b443-a35d20ef8e0a

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
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/handle-forward-urls

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 and usage tips.

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>
@holkexyz
holkexyz merged commit 16ba21d into staging Jun 6, 2026
4 checks passed
@holkexyz
holkexyz deleted the feat/handle-forward-urls branch June 6, 2026 19:39
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