aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-12 19:07:50 -0700
committerAdam Soltys <[email protected]>2026-09-12 19:07:50 -0700
commit31d6dd340cac259ff556001c3045b2d6e748a2e0 (patch)
treeb9900ced25c0345a3019054da3466fdfd12f8b5c
parentce61a0f20a2c535e0c69f59836a17f31a3d88468 (diff)
downloadcoinosv3-31d6dd340cac259ff556001c3045b2d6e748a2e0.tar.gz
coinosv3-31d6dd340cac259ff556001c3045b2d6e748a2e0.zip
The accounts screen says whose each wallet is.
Three signed-in identities produced three rows all called "Savings", one under the other, with nothing to tell them apart — indistinguishable from duplicates, which is exactly what they looked like. The wallets ARE per-identity; the screen just never said so. They're grouped under the person they belong to now: face, name, and a mark on the identity you're currently signed in as. One identity on the device gets no heading, because there's nothing to tell apart. "Whose wallet is this" also becomes one function rather than two: the accounts screen and the identity switcher were each working it out, and they have to agree or they describe different worlds. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx
-rw-r--r--src/app.js60
-rw-r--r--src/i18n.js1
2 files changed, 53 insertions, 8 deletions
diff --git a/src/app.js b/src/app.js
index 5872a74..4914528 100644
--- a/src/app.js
+++ b/src/app.js
@@ -1437,6 +1437,20 @@ function loginPkOf(a) {
} catch { return null; }
}
+// Whose wallet this is: the nostr login it carries, else the key its seed
+// derives, else whatever was last stamped on the record. The accounts screen
+// and the identity switcher have to agree on this or they describe different
+// worlds.
+function identityPkOf(a) {
+ if (!a) return null;
+ let dir = [];
+ try { dir = JSON.parse(localStorage.getItem(WATCH_KEY) || '[]'); } catch {}
+ const stamped = (a.xpub && (dir.find((d) => d.xpub === a.xpub) || {}).nostrPk) || null;
+ return loginPkOf(a)
+ || (a.mnemonic ? seedPubkey(a.mnemonic, a.passphrase || '', a.deriveIndex || 0) : null)
+ || a.nostrPk || stamped;
+}
+
// The identities on this device — one per seed (a seed's Spending and
// Savings faces are the same person), each with the nostr key its header
// avatar wears: the nostr login when there is one, else the seed's own key.
@@ -1444,12 +1458,7 @@ function loginPkOf(a) {
// (saveDirectory) and in the durable directory by xpub is only the fallback,
// for a watch-only entry whose seed isn't here to ask.
function identities() {
- let dir = [];
- try { dir = JSON.parse(localStorage.getItem(WATCH_KEY) || '[]'); } catch {}
- const dirPk = (a) => (a.xpub && (dir.find((d) => d.xpub === a.xpub) || {}).nostrPk) || null;
- const pkOf = (a) => loginPkOf(a)
- || (a.mnemonic ? seedPubkey(a.mnemonic, a.passphrase || '', a.deriveIndex || 0) : null)
- || a.nostrPk || dirPk(a);
+ const pkOf = identityPkOf;
const bySeed = new Map();
for (const a of accounts) {
if (a.provisional) continue;
@@ -2733,7 +2742,13 @@ function accountsScreen() {
h('div', { class: 'card col' },
h('h3', {}, t('accounts')),
h('div', { class: 'col', style: 'gap:0' },
- accounts.flatMap((a) => viewsOf(a).map((view) => {
+ // Grouped by whose they are. Three signed-in identities used to give
+ // three rows all called "Savings", indistinguishable from each other
+ // and from duplicates — the wallets are per-identity, so the screen
+ // has to say which identity.
+ ...groupAccounts().flatMap(({ pk, rows }) => [
+ identityHeading(pk),
+ ...rows.flatMap((a) => viewsOf(a).map((view) => {
const isActive = a.id === activeId && accountSel() === view;
const netTag = a.network && a.network !== 'mainnet' ? ' · ' + a.network : '';
const seedTag = '';
@@ -2762,7 +2777,8 @@ function accountsScreen() {
a.persisted ? t('forgetDevice') : t('saveDevice'))
: null
);
- }))
+ })),
+ ])
),
h('button', { class: 'btn-block', onClick: () => { ui.addWallet = { kind: 'spending', from: 'new' }; render(); } }, t('addWallet')),
hasVault() ? h('button', { class: 'btn-ghost btn-block', onClick: startChangePw }, t('changePassword')) : null,
@@ -2772,6 +2788,34 @@ function accountsScreen() {
);
}
+// The accounts on this device, gathered under the identity each belongs to,
+// in the order they first appear.
+function groupAccounts() {
+ const out = [];
+ const byPk = new Map();
+ for (const a of accounts) {
+ const pk = identityPkOf(a) || 'none';
+ if (!byPk.has(pk)) { const g = { pk: pk === 'none' ? null : pk, rows: [] }; byPk.set(pk, g); out.push(g); }
+ byPk.get(pk).rows.push(a);
+ }
+ return out;
+}
+
+// Who a group of wallets belongs to: their face and name, and a mark when
+// it's the identity you're currently using. One identity on the device needs
+// no heading at all — there's nothing to tell apart.
+function identityHeading(pk) {
+ if (!pk || groupAccounts().length < 2) return null;
+ const me = ctx.shownPubkey && ctx.shownPubkey();
+ const chip = featureHook('profileChip', pk);
+ return h('div', {
+ class: 'row gap6',
+ style: 'align-items:center;padding:10px 0 4px;border-bottom:1px solid var(--line)',
+ },
+ chip || h('span', { class: 'small muted' }, (npubOf(pk) || pk).slice(0, 14) + '…'),
+ pk === me ? h('span', { class: 'tag conf', style: 'margin-left:auto' }, t('accountsThisOne')) : null);
+}
+
function renameAccount(id) {
const acc = accounts.find((a) => a.id === id);
if (acc) {
diff --git a/src/i18n.js b/src/i18n.js
index 6f29a77..d432d2e 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -194,6 +194,7 @@ const STR = {
msgZap: 'Zap',
zapTallyTitle: '{n} sats zapped',
close: 'Close',
+ accountsThisOne: 'signed in',
worthThen: 'Worth then',
worthNow: 'Worth now',
worthNowInline: '{v} now',