From d1a578f3a2d0d8ac12f82c706677e34e870c511f Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Fri, 3 Jul 2026 21:24:13 -0400 Subject: [PATCH] profile modal: always-visible "Acting as" identity card (#43) Identity pill top-center on every pane, straddling the header divider, so users always see which profile they're acting as (e.g. before uploading). Populated in renderSavedProfiles (same resolveMeta path as the Account pane card, so it refreshes on profile switch), hidden when logged out, and click-through jumps to the Account pane. Co-Authored-By: Claude Fable 5 --- components/profile-modal.html | 29 ++++++++++ css/tailwind.source.css | 73 +++++++++++++++++++++++++ js/ui/profileModalController.js | 31 +++++++++++ tests/profile-modal-controller.test.mjs | 53 ++++++++++++++++++ 4 files changed, 186 insertions(+) diff --git a/components/profile-modal.html b/components/profile-modal.html index 39d716a6..a4ee0f30 100644 --- a/components/profile-modal.html +++ b/components/profile-modal.html @@ -62,6 +62,35 @@ + + +
{ + this.selectPane("account"); + }); + } + if (this.permissionPromptCtaButton instanceof HTMLElement) { this.permissionPromptCtaButton.addEventListener("click", () => { if (this.permissionPromptCtaButton?.dataset?.action === "retry-auth-sync") { @@ -2564,6 +2577,24 @@ export class ProfileModalController { : this.globalProfileAvatar.alt || "Profile avatar"; } + // #43: keep the always-visible identity card in sync (hidden when logged + // out so the modal doesn't claim an identity that isn't active). + if (this.identityCard instanceof HTMLElement) { + this.identityCard.classList.toggle("hidden", !hasActiveProfile); + this.identityCard.setAttribute( + "aria-hidden", + hasActiveProfile ? "false" : "true", + ); + } + if (this.identityCardName instanceof HTMLElement) { + this.identityCardName.textContent = activeDisplayName; + } + if (this.identityCardAvatar instanceof HTMLImageElement) { + if (this.identityCardAvatar.src !== activeAvatarSrc) { + this.identityCardAvatar.src = activeAvatarSrc; + } + } + const listEl = this.switcherList; if (listEl instanceof HTMLElement) { listEl.textContent = ""; diff --git a/tests/profile-modal-controller.test.mjs b/tests/profile-modal-controller.test.mjs index 49f0aa24..74d773e0 100644 --- a/tests/profile-modal-controller.test.mjs +++ b/tests/profile-modal-controller.test.mjs @@ -726,6 +726,59 @@ for (const _ of [0]) { assert.equal(switcherNpubEl.textContent, expectedSwitcher); }); + // #43: the always-visible identity card mirrors the active profile on every + // pane (populated by renderSavedProfiles, so it also refreshes on account + // switch), hides when logged out, and click-through opens the Account pane. + test('identity card shows the acting profile, hides logged out, opens Account', async () => { + const sampleProfiles = [ + { + pubkey: defaultActorHex, + npub: 'npub1abcdefghijkmnopqrstuvwxyz1234567890example', + name: 'Primary Account', + picture: '', + providerId: 'nip07', + authType: 'nip07', + }, + ]; + + const controller = createController({ + services: { + formatShortNpub, + }, + }); + + await controller.load(); + + controller.state.setSavedProfiles(sampleProfiles); + controller.state.setActivePubkey(defaultActorHex); + controller.renderSavedProfiles(); + + assert.ok(controller.identityCard, 'identity card element is cached'); + assert.equal( + controller.identityCard.classList.contains('hidden'), + false, + 'visible while a profile is active', + ); + assert.equal(controller.identityCard.getAttribute('aria-hidden'), 'false'); + assert.equal(controller.identityCardName.textContent, 'Primary Account'); + + controller.identityCard.click(); + assert.equal( + controller.getActivePane(), + 'account', + 'click-through jumps to the Account pane', + ); + + controller.state.setActivePubkey(null); + controller.renderSavedProfiles(); + assert.equal( + controller.identityCard.classList.contains('hidden'), + true, + 'hidden when logged out (no claimed identity)', + ); + assert.equal(controller.identityCard.getAttribute('aria-hidden'), 'true'); + }); + test('renderSavedProfiles applies provider metadata', async () => { const sampleProfiles = [ {