aboutsummaryrefslogtreecommitdiffstats
path: root/src/app.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-08-02 16:18:38 -0700
committerAdam Soltys <[email protected]>2026-08-02 16:18:38 -0700
commit6308a2669388db05943f234b260f7d93f760f9a0 (patch)
tree303955e07c2a3e6b7e619550067ed60ee4e0637e /src/app.js
parent5a444e260b0b6aa36953b22678e9b98448cd28f0 (diff)
downloadcoinosv3-6308a2669388db05943f234b260f7d93f760f9a0.tar.gz
coinosv3-6308a2669388db05943f234b260f7d93f760f9a0.zip
Settings: surface wallet settings, add Nostr section, tuck rarities into Advanced
The main Settings tab now inlines the active wallet's own cards (recovery phrase, name, public key) instead of linking to a sub-page, followed by payment address, Ark status, network, and data source. All nostr cards — address, account link, wallet connect, device sync — sit under one Nostr heading via a new nostrSettingsCards seam. Offline transfer, address rescan, and auto-logout move to an Advanced sub-page. The Ark provider picker is gone: mainnet always rides coinos (a stale stored choice no longer wins); dev networks keep their per-network defaults. The per-account screen (Accounts → ⚙) reuses the same card builders unchanged. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
Diffstat (limited to 'src/app.js')
-rw-r--r--src/app.js215
1 files changed, 118 insertions, 97 deletions
diff --git a/src/app.js b/src/app.js
index 63220fc..fd60248 100644
--- a/src/app.js
+++ b/src/app.js
@@ -333,7 +333,7 @@ wallet.subscribe(scheduleRender);
// buttons (and Android/system back) move between screens we've actually viewed.
// We snapshot only the navigation-relevant `ui` fields, so incidental re-renders
// (typing, polling, balance updates) don't create history entries.
-const NAV_FIELDS = ['screen', 'tab', 'txDetail', 'bump', 'giftMode', 'claimStep', 'msgView', 'msgCommunity', 'msgPeer'];
+const NAV_FIELDS = ['screen', 'tab', 'txDetail', 'bump', 'giftMode', 'claimStep', 'msgView', 'msgCommunity', 'msgPeer', 'settingsPage'];
function navSnapshot() {
const s = {};
for (const f of NAV_FIELDS) s[f] = ui[f] ?? null;
@@ -1425,30 +1425,33 @@ function addressScanView() {
function settingsTab() {
if (ui.addrScan && !wallet.offline) return addressScanView();
+ if (ui.settingsPage === 'advanced') return advancedSettingsView();
+ const a = activeAccount();
+ const nostrCards = featureAll('nostrSettingsCards');
return h(
'div',
{ class: 'col', style: 'gap:16px' },
- // Quick link to the active wallet's own settings (name, seed, pubkey, auto-logout).
- activeAccount()
- ? h('div', { class: 'card col' },
- h('h3', {}, activeAccount().label),
- h('p', { class: 'small muted', style: 'margin:0' }, t('walletSettingsDesc')),
- h('button', { class: 'btn-primary btn-block', onClick: () => openAccountSettings(activeId) }, '⚙ ' + t('walletSettings'))
- )
- : null,
- // Recovery phrase + public key live on each wallet's own settings page now
- // (Accounts → ⚙). Watch-only wallets can still add their seed here.
- wallet.watchOnly
- ? (ui.loadSeed
- ? loadSeedCard()
- : h('div', { class: 'card col' },
- h('h3', {}, t('watchOnly')),
- h('p', { class: 'small muted', style: 'margin:0' }, t('watchOnlyNote')),
- activeAccount() && activeAccount().xpub
- ? h('button', { class: 'btn-primary btn-block', style: 'margin-top:4px', onClick: () => startLoadSeed() }, t('loadSeedBtn'))
- : null
- ))
+ // The active wallet's own settings, inlined (auto-logout lives in Advanced).
+ ...(a ? [recoveryCard(a), walletNameCard(a), pubkeyCard(a)] : []),
+ ...featureAll('settingsCards'),
+ networkCard(),
+ explorerCard(),
+ // Everything nostr — identity, sync, wallet connect — under one heading.
+ nostrCards.length || (a && nostrNpubCard(a))
+ ? h('h3', { class: 'settings-section' }, t('nostrSection'))
: null,
+ a ? nostrNpubCard(a) : null,
+ ...nostrCards,
+ h('button', { class: 'btn-ghost btn-block', onClick: () => { ui.settingsPage = 'advanced'; render(); } }, t('advancedSettings'))
+ );
+}
+
+// Rarely used, easy to fat-finger: offline transfer, address rescan, auto-logout.
+function advancedSettingsView() {
+ const a = activeAccount();
+ return h(
+ 'div',
+ { class: 'col', style: 'gap:16px' },
wallet.watchOnly
? null
: h(
@@ -1466,10 +1469,8 @@ function settingsTab() {
? null
: h('button', { onClick: () => { ui.addrScan = true; ui.addrScanPage = 0; render(); } }, t('rescanAddresses'))
),
- networkCard(),
- ...featureAll('settingsCards'),
- explorerCard(),
- null
+ a ? autolockCard(a) : null,
+ h('button', { class: 'btn-ghost btn-block', onClick: () => { ui.settingsPage = null; render(); } }, t('back'))
);
}
@@ -1738,91 +1739,110 @@ function openAccountSettings(id) {
render();
}
-// Per-wallet settings: name, auto-logout, recovery phrase, and public key — for
-// any account (not just the active one; full accounts keep their seed in memory).
-function accountSettingsScreen() {
- const a = accounts.find((x) => x.id === ui.settingsId) || activeAccount();
- if (!a) { ui.screen = 'accounts'; return accountsScreen(); }
+// Per-wallet settings cards, shared by the per-account screen (Accounts → ⚙)
+// and the main Settings tab (which inlines them for the active wallet).
+
+function recoveryCard(a) {
const isWatch = a.type === 'watch' || (!a.mnemonic && !a.xprv);
const shown = ui.revealShown;
const words = a.mnemonic ? a.mnemonic.split(' ') : [];
- let zpub = '';
- try { if (a.xpub) zpub = xpubToZpub(a.xpub); } catch {}
+ if (isWatch) {
+ return ui.loadSeed && ui.loadSeed.accId === a.id
+ ? loadSeedCard()
+ : h('div', { class: 'card col' },
+ h('h3', {}, t('recoveryPhrase')),
+ h('p', { class: 'small muted', style: 'margin:0' }, t('watchOnlyNote')),
+ a.xpub
+ ? h('button', { class: 'btn-primary btn-block', style: 'margin-top:4px', onClick: () => startLoadSeed({ accId: a.id }) }, t('loadSeedBtn'))
+ : null
+ );
+ }
+ if (!a.mnemonic)
+ return h('div', { class: 'card col' }, h('h3', {}, t('importedKey')), h('p', { class: 'small muted', style: 'margin:0' }, t('importedKeyNote')));
+ return h('div', { class: 'card col' },
+ h('h3', {}, t('recoveryPhrase')),
+ h('div', { class: 'warn-box' }, t('recoveryWarn')),
+ h('div', { class: 'words' }, words.map((w, i) =>
+ h('div', { class: 'w' + (shown ? '' : ' masked') }, h('span', { class: 'n' }, i + 1), h('span', { class: 't' }, shown ? w : '••••••')))),
+ shown && a.passphrase
+ ? h('div', { class: 'col gap6' }, h('span', { class: 'lab' }, t('bip39Passphrase')), h('div', { class: 'addr-box' }, a.passphrase))
+ : null,
+ shown
+ ? h('div', { class: 'row gap6 wrap' },
+ copyBtn(a.mnemonic, t('copyPhrase')),
+ a.passphrase ? copyBtn(a.passphrase, t('copyPassphrase')) : null,
+ h('button', { class: 'btn-sm grow', onClick: () => { ui.revealShown = false; render(); } }, t('hide')))
+ : h('button', { class: 'btn-primary btn-block', onClick: () => { ui.revealShown = true; render(); } }, t('revealRecovery'))
+ );
+}
+
+function walletNameCard(a) {
const saveName = () => {
const v = (ui.editLabel || '').trim();
if (v && v !== a.label) { a.label = v; persistAccounts(); if (a.persisted) writeVault(); }
ui.editLabel = null;
render();
};
+ return h('div', { class: 'card col' },
+ h('h3', {}, t('walletName')),
+ h('div', { class: 'row gap6' },
+ h('input', { type: 'text', style: 'flex:1', value: ui.editLabel != null ? ui.editLabel : a.label,
+ onInput: (e) => { ui.editLabel = e.target.value; },
+ onKeyDown: (e) => { if (e.key === 'Enter') saveName(); } }),
+ h('button', { class: 'btn-sm', onClick: saveName }, t('save'))
+ )
+ );
+}
+
+function pubkeyCard(a) {
+ let zpub = '';
+ try { if (a.xpub) zpub = xpubToZpub(a.xpub); } catch {}
+ return h('div', { class: 'card col' },
+ h('h3', {}, t('publicKey')),
+ h('p', { class: 'small muted', style: 'margin:0' }, t('publicKeyDesc')),
+ ui.pubkeyShown
+ ? h('div', { class: 'col gap6' },
+ h('div', { class: 'addr-box break', style: 'font-size:12px' }, zpub),
+ h('div', { class: 'row gap6 wrap' },
+ copyBtn(zpub, t('copyKey')),
+ h('button', { class: 'btn-sm grow', onClick: () => { ui.pubkeyShown = false; render(); } }, t('hide'))))
+ : h('button', { class: 'btn-block', onClick: () => { ui.pubkeyShown = true; render(); } }, t('showPublicKey'))
+ );
+}
+
+// Nostr address — share it so others can send you locked gifts.
+function nostrNpubCard(a) {
+ if (a.id !== activeId || !wallet.nostrNpub || !wallet.nostrNpub()) return null;
+ return h('div', { class: 'card col' },
+ h('h3', {}, t('nostrKeyTitle')),
+ h('p', { class: 'small muted', style: 'margin:0' }, t('nostrKeyDesc')),
+ h('div', { class: 'addr-box break', style: 'font-size:12px' }, wallet.nostrNpub()),
+ copyBtn(wallet.nostrNpub(), t('copyKey')));
+}
+
+function autolockCard(a) {
+ return h('div', { class: 'card col' },
+ h('h3', {}, t('autolockTitle')),
+ h('p', { class: 'small muted', style: 'margin:0' }, t('autolockDesc')),
+ h('select', { onChange: (e) => { a.autoLock = Number(e.target.value) || 0; persistAccounts(); if (a.persisted) writeVault(); render(); } },
+ AUTOLOCK_OPTIONS.map((o) => h('option', { value: String(o.ms), selected: o.ms === accAutoLock(a) }, t(o.label))))
+ );
+}
+
+// Per-wallet settings screen — reached from Accounts → ⚙ for any account
+// (not just the active one; full accounts keep their seed in memory).
+function accountSettingsScreen() {
+ const a = accounts.find((x) => x.id === ui.settingsId) || activeAccount();
+ if (!a) { ui.screen = 'accounts'; return accountsScreen(); }
return h(
'div',
{ class: 'col', style: 'gap:16px' },
brandHeader(false),
- // Recovery phrase (top)
- isWatch
- ? (ui.loadSeed && ui.loadSeed.accId === a.id
- ? loadSeedCard()
- : h('div', { class: 'card col' },
- h('h3', {}, t('recoveryPhrase')),
- h('p', { class: 'small muted', style: 'margin:0' }, t('watchOnlyNote')),
- a.xpub
- ? h('button', { class: 'btn-primary btn-block', style: 'margin-top:4px', onClick: () => startLoadSeed({ accId: a.id }) }, t('loadSeedBtn'))
- : null
- ))
- : !a.mnemonic
- ? h('div', { class: 'card col' }, h('h3', {}, t('importedKey')), h('p', { class: 'small muted', style: 'margin:0' }, t('importedKeyNote')))
- : h('div', { class: 'card col' },
- h('h3', {}, t('recoveryPhrase')),
- h('div', { class: 'warn-box' }, t('recoveryWarn')),
- h('div', { class: 'words' }, words.map((w, i) =>
- h('div', { class: 'w' + (shown ? '' : ' masked') }, h('span', { class: 'n' }, i + 1), h('span', { class: 't' }, shown ? w : '••••••')))),
- shown && a.passphrase
- ? h('div', { class: 'col gap6' }, h('span', { class: 'lab' }, t('bip39Passphrase')), h('div', { class: 'addr-box' }, a.passphrase))
- : null,
- shown
- ? h('div', { class: 'row gap6 wrap' },
- copyBtn(a.mnemonic, t('copyPhrase')),
- a.passphrase ? copyBtn(a.passphrase, t('copyPassphrase')) : null,
- h('button', { class: 'btn-sm grow', onClick: () => { ui.revealShown = false; render(); } }, t('hide')))
- : h('button', { class: 'btn-primary btn-block', onClick: () => { ui.revealShown = true; render(); } }, t('revealRecovery'))
- ),
- // Name
- h('div', { class: 'card col' },
- h('h3', {}, t('walletName')),
- h('div', { class: 'row gap6' },
- h('input', { type: 'text', style: 'flex:1', value: ui.editLabel != null ? ui.editLabel : a.label,
- onInput: (e) => { ui.editLabel = e.target.value; },
- onKeyDown: (e) => { if (e.key === 'Enter') saveName(); } }),
- h('button', { class: 'btn-sm', onClick: saveName }, t('save'))
- )
- ),
- // Public key (zpub)
- h('div', { class: 'card col' },
- h('h3', {}, t('publicKey')),
- h('p', { class: 'small muted', style: 'margin:0' }, t('publicKeyDesc')),
- ui.pubkeyShown
- ? h('div', { class: 'col gap6' },
- h('div', { class: 'addr-box break', style: 'font-size:12px' }, zpub),
- h('div', { class: 'row gap6 wrap' },
- copyBtn(zpub, t('copyKey')),
- h('button', { class: 'btn-sm grow', onClick: () => { ui.pubkeyShown = false; render(); } }, t('hide'))))
- : h('button', { class: 'btn-block', onClick: () => { ui.pubkeyShown = true; render(); } }, t('showPublicKey'))
- ),
- // Nostr address — share it so others can send you locked gifts.
- (a.id === activeId && wallet.nostrNpub && wallet.nostrNpub())
- ? h('div', { class: 'card col' },
- h('h3', {}, t('nostrKeyTitle')),
- h('p', { class: 'small muted', style: 'margin:0' }, t('nostrKeyDesc')),
- h('div', { class: 'addr-box break', style: 'font-size:12px' }, wallet.nostrNpub()),
- copyBtn(wallet.nostrNpub(), t('copyKey')))
- : null,
- // Auto-logout (bottom)
- h('div', { class: 'card col' },
- h('h3', {}, t('autolockTitle')),
- h('p', { class: 'small muted', style: 'margin:0' }, t('autolockDesc')),
- h('select', { onChange: (e) => { a.autoLock = Number(e.target.value) || 0; persistAccounts(); if (a.persisted) writeVault(); render(); } },
- AUTOLOCK_OPTIONS.map((o) => h('option', { value: String(o.ms), selected: o.ms === accAutoLock(a) }, t(o.label))))
- ),
+ recoveryCard(a),
+ walletNameCard(a),
+ pubkeyCard(a),
+ nostrNpubCard(a),
+ autolockCard(a),
h('button', { class: 'btn-ghost btn-block', onClick: () => { ui.editLabel = null; ui.revealShown = false; ui.pubkeyShown = false; ui.loadSeed = null; goBack(() => { ui.screen = 'accounts'; }); } }, t('back'))
);
}
@@ -1967,6 +1987,7 @@ function tabsBar() {
ui.arkMoveDetail = null;
ui.giftDetail = null;
ui.addrScan = false; // and back to the main Settings, not the address list
+ ui.settingsPage = null;
ui.bump = null;
ui.giftMode = false;
render();