Skip to content

feat(admin): show canonical registry plugin names - #3080

Merged
ascorbic merged 3 commits into
mainfrom
codex/plugin-public-names
Sep 12, 2026
Merged

feat(admin): show canonical registry plugin names#3080
ascorbic merged 3 commits into
mainfrom
codex/plugin-public-names

Conversation

@ascorbic

Copy link
Copy Markdown
Collaborator

What does this PR do?

Adds canonical public names for registry plugins in the @publisher.example/plugin-slug format.

  • Displays verified public names in registry results, plugin details, and the installed plugins list.
  • Adds dedicated /plugins/registry/@publisher.example/plugin-slug deep links.
  • Resolves exact public-name searches directly instead of treating them as free text.
  • Shows a prominent INVALID HANDLE state and prevents installation when a handle conclusively fails identity verification.
  • Uses the stable publisher DID and slug when handle resolution is temporarily unavailable; existing installations are not deactivated by an identity lookup.

Related Discussion: #296

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). Do not include messages.po changes except in translation PRs — a workflow extracts catalogs on merge to main.
  • I have added and reviewed the user-facing changeset (if this PR changes a published package)
  • New features link to an approved Discussion: Marketplace Discussion #296
  • I have included screenshots below if this PR changes the UI

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: GPT-5 Codex

Screenshots / test output

Registry listing — English

Registry listing showing verified public plugin names and invalid handle warnings in English

Registry listing — Arabic RTL

Registry listing showing verified public plugin names and invalid handle warnings in Arabic right-to-left layout

Validated on current origin/main:

  • pnpm build
  • pnpm typecheck
  • pnpm lint
  • pnpm lint:json | jq '.diagnostics | length'0
  • pnpm --filter @emdash-cms/admin test --run → 169 files, 2,317 tests passed
  • pnpm --dir docs build
  • Live registry rendering in English and Arabic RTL

@changeset-bot

changeset-bot Bot commented Sep 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 106e516

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
Name Type
@emdash-cms/admin Minor
emdash Minor
@emdash-cms/cloudflare Minor
@emdash-cms/sandbox-workerd Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
@emdash-cms/auth Minor
@emdash-cms/blocks Minor
create-emdash Minor
@emdash-cms/gutenberg-to-portable-text Minor
@emdash-cms/x402 Minor
@emdash-cms/auth-atproto Patch
@emdash-cms/release-service Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 12, 2026

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://codex-plugin-public-names.try.emdashcms.com, https://codex-plugin-public-names-emdash-playground.emdash-cms.workers.dev (commit 106e516)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://6f5e6623.try.emdashcms.com, https://6f5e6623-emdash-playground.emdash-cms.workers.dev 106e516 2026-09-12T10:02:45.307Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://aa49fa0e.try.emdashcms.com, https://aa49fa0e-emdash-playground.emdash-cms.workers.dev c57ee09 2026-09-12T09:04:18.005Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://2f375a6e.try.emdashcms.com, https://2f375a6e-emdash-playground.emdash-cms.workers.dev e850c3b 2026-09-12T08:48:03.674Z Visit the dashboard ↗

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
docs e850c3b Sep 12 2026, 08:47 AM

@pkg-pr-new

pkg-pr-new Bot commented Sep 12, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@3080

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@3080

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@3080

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@3080

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@3080

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@3080

emdash

npm i https://pkg.pr.new/emdash@3080

create-emdash

npm i https://pkg.pr.new/create-emdash@3080

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@3080

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@3080

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@3080

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@3080

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@3080

@emdash-cms/registry-moderation

npm i https://pkg.pr.new/@emdash-cms/registry-moderation@3080

@emdash-cms/registry-verification

npm i https://pkg.pr.new/@emdash-cms/registry-verification@3080

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@3080

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@3080

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@3080

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@3080

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@3080

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@3080

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@3080

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@3080

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@3080

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@3080

commit: 106e516

@emdashbot emdashbot 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.

The approach is sound: this PR adds canonical, mutable-handle-based public names for registry plugins, dedicated deep links, exact public-name search, and a clear invalid-handle state, all while keeping install/update trust on the immutable DID. The implementation follows EmDash conventions well — all new UI strings are wrapped for Lingui, Tailwind classes are RTL-safe (ms/me, dir="auto"), a changeset is included, and there are focused component + unit tests.

I read the full diff, the changed admin components/lib, related registry API code, and the updated docs. No core/runtime code is touched, so SQL safety, authorization, locale-filtering, and logged-out query counts are not in scope here.

Headline issues:

  1. Handle case is not normalized before it becomes part of the identity/URL. formatRegistryPublicName lowercases the handle for display, but registryIdentity stores the resolver’s raw handle and registryIdentityPublisherParam uses it verbatim for deep links. AT Protocol handles are case-insensitive, so a resolver may return mixed case; that would yield URLs like @Example.com/... which diverge from the canonical lowercase public name and may break case-sensitive routing or bookmarks. Normalize the handle when building RegistryIdentity.

  2. Two stale docstrings are now misleading because of this feature. RegistryBrowse.tsx still says cards link to /plugins/marketplace/$pluginId, and PublisherHandle.tsx claims browse/detail surfaces only approved author names/shortened DIDs and that routing uses the full DID. Both are contradicted by the new public-name links/handle-based routing and should be updated.

  3. Minor CSS-override fragility in PluginManager. The invalid-identity card adds border-kumo-danger to an element whose only border class is generic border. The red override relies on generated CSS order; use an explicit border-kumo-border default like RegistryPackageCard does.

Tests, localization, and the changeset itself look good: the changeset names the user-facing surface, the docs explain behavior accurately, and the new tests verify observable behavior rather than asserting implementation details back at themselves.


Findings

  • [needs fixing] packages/admin/src/lib/registry-identity.ts:46

    registryIdentity stores the resolved handle verbatim and registryIdentityPublisherParam uses it for deep-link URLs, but formatRegistryPublicName lowercases the handle for display. AT Protocol handles are case-insensitive, so a resolver may return a mixed-case handle, producing URLs like /plugins/registry/@Example.com/slug that diverge from the canonical lowercase public name and may break case-sensitive routing or bookmarks.

    Normalize the handle when constructing the identity so the stored value and the URL are always lowercase:

    	if (resolution.status === "ok") {
    		return {
    			status: "ok",
    			did,
    			slug,
    			handle: resolution.handle.toLowerCase(),
    			publicName: formatRegistryPublicName(resolution.handle, slug),
    		};
    	}
    

    (Alternatively, lower-case inside formatRegistryPublicName callers and/or registryIdentityPublisherParam as well, but fixing it at the identity source keeps a single canonical value.)

  • [suggestion] packages/admin/src/components/PluginManager.tsx:378

    The invalid-identity branch adds border-kumo-danger to an element whose only border class is the generic border utility. The red override is therefore dependent on generated CSS order; if border ends up later in the stylesheet, the danger border is lost. The sibling RegistryPackageCard avoids this by explicitly using border-kumo-border as the default and border-kumo-danger only for the invalid case.

    Use an explicit default border color:

    			className={cn(
    				"rounded-lg border border-kumo-border bg-kumo-base transition-colors",
    				!plugin.enabled && "opacity-75",
    				registryIdentity?.status === "invalid" && "border-kumo-danger",
    			)}
    
  • [suggestion] packages/admin/src/components/RegistryBrowse.tsx:8-12

    This docstring is stale after the public-name changes. Cards now navigate to the dedicated /plugins/registry/$publisher/$slug route, and an exact public-name search is resolved directly via resolveRegistryPackageStatus rather than being sent to the free-text searchPackages endpoint.

    Update the comment to describe the current routing and search behavior, e.g.:

     * Cards navigate to `/plugins/registry/$publisher/$slug`. A search query
     * that matches the canonical public-name form (`@handle/slug`) is resolved
     * directly instead of being sent to the free-text search endpoint.
    
  • [suggestion] packages/admin/src/components/PublisherHandle.tsx:4-6

    This docstring is now misleading because of the public-name feature added in this PR. Registry browse cards render verified @handle/slug public names (not just approved author names/shortened DIDs), and routing uses handle-based URLs with a DID fallback. The claim that browse/detail surfaces only approved names and that routing always uses the full DID is no longer accurate.

    Update the comment to reflect that PublisherIdentity is now a fallback detail-line component, while public names and handle-based links live in RegistryPluginIdentity and registry-identity.ts.

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-review No maintainer or bot review yet labels Sep 12, 2026
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review cla: signed and removed review/awaiting-author Reviewed; waiting on the author to respond labels Sep 12, 2026
@ascorbic

Copy link
Copy Markdown
Collaborator Author

@emdashbot review

The mixed-case handle regression, explicit border color, and stale comments from the previous review are fixed at c57ee09b0.

~ 🤖 Codex

@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Sep 12, 2026

@emdashbot emdashbot 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.

The approach remains sound: canonical, mutable-handle-based public names for registry plugins, dedicated deep links, exact public-name search, and a clear invalid-handle state, all keeping install/update trust on the immutable DID. This re-read confirms all three of my prior findings are fixed.

What’s fixed:

  • Handle case normalizationregistryIdentity now lowercases resolution.handle at the source and uses that normalized value for publicName and registryIdentityPublisherParam URLs. Tests cover the case-insensitive resolver output path.
  • CSS override fragilityPluginManager now uses an explicit border-kumo-border default and border-kumo-danger only for the invalid branch, matching RegistryPackageCard.
  • Stale docstringsPublisherHandle.tsx and RegistryBrowse.tsx docstrings now accurately describe public-name rendering, handle-based links, and direct public-name resolution.

What’s new:

  • I read the updated RegistryPluginDetail.tsx, RegistryPluginIdentity.tsx, registry-identity.ts, the new tests, the docs change, the changeset, and the router additions. Everything is localized, RTL-safe, and the tests verify observable behavior.
  • One remaining suggestion: the top docstring in RegistryPluginDetail.tsx was not updated and is now misleading about the route shape. It still claims the component is identified by ${handle}/${slug} and wrapped on the same route as marketplace detail, ignoring the new dedicated /plugins/registry/$publisher/$slug route and DID-based fallback URLs. I’ve left a line-anchored suggestion.

Overall this is close to clean; only that docstring needs a refresh.


Findings

  • [suggestion] packages/admin/src/components/RegistryPluginDetail.tsx:10-13

    The top docstring is now stale. The component no longer only handles ${handle}/${slug} marketplace-route plugin IDs: the new dedicated route uses /plugins/registry/$publisher/$slug (where $publisher may be @handle or a DID), and old marketplace deep links can also pass DID-based IDs. Keeping the description accurate avoids confusing future readers about where this component is mounted and what pluginId can contain.

     * Identified by a `pluginId` of `${publisher}/${slug}`, where `publisher`
     * is either a verified handle (with or without the leading `@`) or a
     * stable DID. The dedicated registry route renders this component at
     * `/plugins/registry/$publisher/$slug`; the marketplace detail route also
     * forwards matching legacy `${did}/{slug}` deep links here for backward
     * compatibility.
    

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Sep 12, 2026
@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-rereview Author pushed changes since the last review labels Sep 12, 2026
@github-actions github-actions Bot added the review/needs-rereview Author pushed changes since the last review label Sep 12, 2026
@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Sep 12, 2026
@github-actions github-actions Bot removed the review/awaiting-author Reviewed; waiting on the author to respond label Sep 12, 2026

@emdashbot emdashbot 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.

This is a re-review of PR #3080. The approach remains sound: canonical, mutable-handle-based public names for registry plugins, a dedicated registry detail route, exact public-name search, and a clear invalid-handle state, all while keeping install/update trust on the immutable DID. I re-read the full diff and the relevant source files.

The one remaining suggestion from my previous review has been addressed: packages/admin/src/components/RegistryPluginDetail.tsx now has an accurate top docstring describing the ${publisher}/${slug} pluginId shape, the dedicated /plugins/registry/$publisher/$slug route, and the legacy marketplace-route fallback.

I checked the updated code for new issues:

  • registry-identity.ts normalizes handles at the source, parses the @handle/slug form, and cleanly distinguishes ok / invalid / missing / pending states.
  • RegistryPluginIdentity and useRegistryPluginIdentity are localized, RTL-safe, and avoid nested anchor elements when used inside card links.
  • RegistryBrowse routes exact public-name searches through resolveRegistryPackageStatus and otherwise uses the existing free-text search; public-name card links now go to the new registry route.
  • RegistryPluginDetail strips the optional leading @, resolves both DID-based and handle-based IDs, renders the canonical public-name byline, and blocks install when the handle is conclusively invalid.
  • PluginManager displays canonical public names for installed registry plugins and highlights invalid handles without deactivating existing installations.
  • The new /plugins/registry/$publisher/$slug route is wired correctly, and the legacy marketplace detail route still forwards DID-shaped IDs for backward compatibility.
  • Tests cover case normalization, invalid/missing handle rendering, exact-name search, and public-name links.
  • The changeset is user-facing and proportional; the docs prose matches the implemented behavior, including the distinction between conclusive invalid-handle failure and temporary lookup failure.

No remaining needs_fixing items and no new blockers. LGTM.

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Sep 12, 2026
@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-rereview Author pushed changes since the last review labels Sep 12, 2026
@ascorbic
ascorbic merged commit 7bbd8ea into main Sep 12, 2026
57 checks passed
@ascorbic
ascorbic deleted the codex/plugin-public-names branch September 12, 2026 10:46
@emdashbot emdashbot Bot mentioned this pull request Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant