From 55385b35a9f92eff35ac63e86b069e81cb2e7ea4 Mon Sep 17 00:00:00 2001 From: Sergey Chernyshev Date: Mon, 29 Jun 2026 00:16:06 -0400 Subject: [PATCH 1/2] Atmosphere branding + handle in the Login Credentials list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Login Credentials" section on the profile page still showed, for an atproto credential, the old Bluesky butterfly, the bare label "Atproto", and only the DID. Fixes (in both the server and client renderers, which must match — the client re-renders over the SSR output): - Icon: the Atmosphere "union" mark (a stale butterfly remained in profile.html's client-side getProviderIcon; the SSR copy was already updated). - Label: "Atmosphere / ATproto" instead of the capitalized provider key. - Identifier: show the handle with the DID in parens, e.g. "alice.bsky.social (did:plc:...)", falling back to the bare DID when no handle was stored. To support that, persist the atproto handle at login: add UserProfile.handle, set it from the resolved identity in exchange(), and surface it from UserDO.listCredentials(). Existing credentials (logged in before this change) have no stored handle and show the DID alone until next login. Tests: assert the handle is persisted, and that the SSR'd profile page shows the branded label + "handle (did)" and never the butterfly mark. Co-Authored-By: Claude Opus 4.8 --- public/users/profile.html | 23 +++++++++++++++++++---- src/auth/AtprotoProvider.ts | 2 +- src/auth/OAuthProvider.ts | 2 ++ src/handlers/ssr.ts | 18 ++++++++++++++++-- src/storage/UserDO.ts | 1 + test/atproto.spec.ts | 2 ++ test/integration.spec.ts | 13 +++++++++++++ 7 files changed, 54 insertions(+), 7 deletions(-) diff --git a/public/users/profile.html b/public/users/profile.html index 50b77dc..972f473 100644 --- a/public/users/profile.html +++ b/public/users/profile.html @@ -136,6 +136,9 @@

Link another account

diff --git a/src/auth/AtprotoProvider.ts b/src/auth/AtprotoProvider.ts index 7ca4c49..7b2bf2f 100644 --- a/src/auth/AtprotoProvider.ts +++ b/src/auth/AtprotoProvider.ts @@ -239,7 +239,7 @@ export class AtprotoProvider extends OAuthProvider { const did = tokenData.sub || flow.did; const { name, picture } = await fetchProfile(flow.pds, did, flow.handle); - const profile: UserProfile = { id: did, name: name || flow.handle || did, picture }; + const profile: UserProfile = { id: did, name: name || flow.handle || did, picture, handle: flow.handle }; const token: OAuthTokenResponse = { access_token: tokenData.access_token, refresh_token: tokenData.refresh_token, diff --git a/src/auth/OAuthProvider.ts b/src/auth/OAuthProvider.ts index 4a8bfda..82e2115 100644 --- a/src/auth/OAuthProvider.ts +++ b/src/auth/OAuthProvider.ts @@ -13,6 +13,8 @@ export interface UserProfile { name?: string; picture?: string; verified_email?: boolean; + /** atproto only: the user's handle (e.g. `alice.bsky.social`), distinct from the `id` (a DID). */ + handle?: string; } import type { Entitlements } from '../entitlements/types'; diff --git a/src/handlers/ssr.ts b/src/handlers/ssr.ts index f4f80e5..6c6b7c1 100644 --- a/src/handlers/ssr.ts +++ b/src/handlers/ssr.ts @@ -193,10 +193,10 @@ function renderCredentialsList(credentials: any[], currentProvider?: string): st
- ${c.provider.charAt(0).toUpperCase() + c.provider.slice(1)} + ${providerLabel(c.provider)} ${isCurrent ? 'logged in' : ''}
-
${c.email || c.subject_id}
+
${credentialIdentifier(c)}