From 521cccbb60546190a91ae126164a456cc3b749bd Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 10 Sep 2026 14:00:36 -0700 Subject: Identity switching moves to the profile page, with faces. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Accounts screen is about a seed's Spending/Savings faces — its rows say nothing about WHO each pair belongs to, so a "Sign into another account" button there was the wrong door. It's gone from there. On your own profile, "Sign into another account" now opens a switcher: every identity already signed in on this device as an avatar + name row (one per seed, wearing the nostr key its header avatar shows — the login key when there is one, else the seed's own; remembered on the account and in the durable directory by xpub, so vault-restored accounts have a face too), tap to switch with nothing logged out, plus "Sign into a new account" for the additive sign-in doors. With no other identity on the device the button goes straight to those doors. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01URmfo8VimCNkw6Y7JndYrv --- src/features/messages.js | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) (limited to 'src/features') diff --git a/src/features/messages.js b/src/features/messages.js index e85d5d7..574c3c5 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -2314,6 +2314,35 @@ export function messagesFeature(ctx) { onClick: () => { ui.logoutConfirm = 'forget'; render(); }, }, t('logoutForget')) : null, h('button', { class: 'btn-ghost btn-block', onClick: () => { ui.logoutConfirm = null; render(); } }, t('back')))); + // The identity switcher: every account signed in on this device as an + // avatar + name row (tap = switch, nothing logged out), and the door to + // add a new one. + const switchPop = () => { + if (!ui.switchIdPop) return null; + const close = () => { ui.switchIdPop = null; render(); }; + const others = (ctx.identities ? ctx.identities() : []).filter((x) => !x.active); + return h('div', { + class: 'confirm-pop-backdrop', + onClick: (e) => { if (e.target === e.currentTarget) close(); }, + }, + h('div', { class: 'card col confirm-pop', style: 'gap:10px' }, + h('h3', { style: 'margin:0' }, t('switchIdentityTitle')), + h('p', { class: 'small muted', style: 'margin:0' }, t('switchIdentityDesc')), + h('div', { class: 'col', style: 'gap:0' }, + others.map((x) => h('div', { + class: 'row gap6 clickable', style: 'align-items:center;padding:8px 0;border-bottom:1px solid var(--line)', + onClick: () => { ui.switchIdPop = null; ctx.switchIdentity(x.id); }, + }, + x.pk ? avatar(x.pk, 'chat-avatar', false) : h('div', { class: 'chat-avatar fallback' }), + h('div', { class: 'col grow', style: 'min-width:0;gap:1px' }, + h('div', { class: 'chat-name', style: 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap' }, x.pk ? displayName(x.pk) : x.label), + h('div', { class: 'small muted', style: 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap' }, + (x.pk ? x.label : t('identityNoKey')) + + (x.network && x.network !== 'mainnet' ? ' · ' + x.network : '') + + (x.watch ? ' · ' + t('watchOnlyTag') : '')))))), + h('button', { class: 'btn-primary btn-block', onClick: () => { ui.switchIdPop = null; ctx.signInAnother(); } }, t('signInNew')), + h('button', { class: 'btn-ghost btn-block', onClick: close }, t('back')))); + }; const full = fullProfiles.get(pk); // Your own profile reads like anyone else's — npub, bio, latest posts — // with the editor behind an explicit Edit button (it used to BE the @@ -2517,7 +2546,12 @@ export function messagesFeature(ctx) { // on the Accounts screen — logout stays for actually leaving ctx.signInAnother ? h('button', { class: 'btn-block', style: 'display:flex;align-items:center;justify-content:center;gap:8px', - onClick: () => ctx.signInAnother(), + onClick: () => { + // other identities on this device → pick from a list; + // none yet → straight to the sign-in doors + const others = ctx.identities ? ctx.identities().filter((x) => !x.active) : []; + if (others.length) { ui.switchIdPop = true; render(); } else ctx.signInAnother(); + }, }, h('span', { style: 'display:flex', html: '' }), t('signInAnother')) : null, @@ -2573,7 +2607,8 @@ export function messagesFeature(ctx) { : null); })(), h('button', { class: 'btn-ghost btn-block', onClick: () => { ui.profilePk = null; ui.profOverThread = false; ui.pubProf = null; ui.profEdit = null; ui.profEditFilled = false; ui.profCompose = null; render(); } }, t('back')), - mine ? logoutPop() : null); + mine ? logoutPop() : null, + mine ? switchPop() : null); } const backBtn = (onClick) => h('button', { class: 'iconbtn chat-back', onClick }, '‹'); -- cgit v1.2.3