diff options
| author | Adam Soltys <[email protected]> | 2026-06-14 23:50:02 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-06-14 23:50:02 -0700 |
| commit | ee7dd47f3443afdadb4c37c2ba1a6e6613c6036f (patch) | |
| tree | 6a4ad5fe32e476e28060f6b2dc1ecefd87e3ff1e | |
| parent | ec4a20885355beb6db8207151689d7979c5ef9ea (diff) | |
| download | coinosv3-ee7dd47f3443afdadb4c37c2ba1a6e6613c6036f.tar.gz coinosv3-ee7dd47f3443afdadb4c37c2ba1a6e6613c6036f.zip | |
Remove Tools tab; move snapshot import/export to Coins
The Tools page held a now-redundant manual offline toggle (we auto-detect),
a small wallet-info blurb, and the offline snapshot import/export. Drop the
page and relocate snapshot import/export to the Coins tab. Offline fallback
and goHome now land on Coins; the offline banner points there.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
| -rw-r--r-- | src/app.js | 82 |
1 files changed, 28 insertions, 54 deletions
@@ -347,7 +347,7 @@ function enterOfflineFallback() { wallet.setOffline(true); wallet.deriveWindow(40); ui.offlineFallback = true; - ui.tab = 'tools'; + ui.tab = 'coins'; render(); } @@ -405,7 +405,7 @@ function brandHeader(withLock) { function goHome() { if (ui.screen === 'wallet') { - ui.tab = wallet.offline ? 'tools' : 'receive'; + ui.tab = wallet.offline ? 'coins' : 'receive'; ui.draft = null; ui.sendResult = null; ui.sendError = ''; @@ -436,7 +436,7 @@ function offlineBanner() { return h( 'div', { class: 'notice info row between', style: 'margin:12px 0 0' }, - h('span', {}, "Can't reach the network — working offline. Import a snapshot to load coins."), + h('span', {}, "Can't reach the network — working offline. Import a snapshot on the Coins tab to load coins."), h('button', { class: 'btn-sm', onClick: retryOnline }, 'Retry') ); } @@ -467,7 +467,6 @@ function tabsBar() { ['send', 'Send'], ['history', 'History'], ['coins', 'Coins'], - ['tools', 'Tools'], ]; return h( 'div', @@ -487,7 +486,6 @@ function tabContent() { case 'send': return sendTab(); case 'history': return historyTab(); case 'coins': return coinsTab(); - case 'tools': return toolsTab(); } } @@ -841,8 +839,16 @@ function historyTab() { // ---------------------------------------------------------------- Coins function coinsTab() { if (wallet.scanning && !wallet.loaded) return h('div', { class: 'card center' }, h('span', { class: 'spinner' })); - if (!wallet.utxos.length) - return h('div', { class: 'card' }, h('p', { class: 'muted center', style: 'margin:0' }, wallet.offline ? 'Import a wallet snapshot in Tools to load coins.' : 'No coins (UTXOs) yet.')); + const coin = wallet.netCfg.coin; + if (!wallet.utxos.length) { + return h( + 'div', + { class: 'card col' }, + h('p', { class: 'muted center', style: 'margin:0' }, wallet.offline ? 'Import a snapshot to load coins.' : 'No coins (UTXOs) yet.'), + h('hr', { class: 'divider' }), + snapshotActions() + ); + } return h( 'div', { class: 'card' }, @@ -859,7 +865,7 @@ function coinsTab() { { class: 'item' }, h('div', { class: 'grow' }, h('div', { class: 'mono small break' }, shortAddr(u.address, 16, 10)), - h('div', { class: 'path' }, `m/84'/${wallet.netCfg.coin}'/0'/${u.chain}/${u.index} · ${shortTxid(u.txid)}:${u.vout}`) + h('div', { class: 'path' }, `m/84'/${coin}'/0'/${u.chain}/${u.index} · ${shortTxid(u.txid)}:${u.vout}`) ), h('div', { style: 'text-align:right' }, h('div', { class: 'amount' }, fmtBtc(u.value)), @@ -867,61 +873,29 @@ function coinsTab() { ) ) ) - ) + ), + h('hr', { class: 'divider' }), + snapshotActions() ); } -// ---------------------------------------------------------------- Tools -function toolsTab() { - const coin = wallet.netCfg.coin; +// Offline snapshot exchange: export coins on an online device, import on an +// offline (air-gapped) one to sign without internet. +function snapshotActions() { return h( 'div', - { class: 'col', style: 'gap:16px' }, - // Connectivity - h( - 'div', - { class: 'card col' }, - h('h3', {}, 'Connectivity'), - h('p', { class: 'small muted', style: 'margin:0' }, wallet.offline - ? 'Offline: no network requests are made. Load coins from a snapshot file, sign locally, and broadcast elsewhere.' - : 'Online: fetching balances and history from mempool.space.'), - wallet.offline - ? h('button', { class: 'btn-primary', onClick: async () => { wallet.setOffline(false); toast('Going online…'); await wallet.scan(); wallet.startRealtime(); ui.tab = 'receive'; render(); } }, 'Go online & scan') - : h('button', { onClick: () => { wallet.setOffline(true); toast('Offline mode'); render(); } }, 'Switch to offline') - ), - // Offline snapshot exchange - h( - 'div', - { class: 'card col' }, - h('h3', {}, 'Offline transfer'), - h('p', { class: 'small muted', style: 'margin:0' }, 'On an online device, export a snapshot of your coins. Move the file to your offline device (with this wallet + seed) to spend without internet.'), - h('div', { class: 'row gap6 wrap' }, - h('button', { class: wallet.offline ? '' : 'btn-primary', disabled: wallet.offline, onClick: exportSnapshot }, '⤓ Export snapshot'), - h('label', { class: 'btn btn-sm', style: 'cursor:pointer' }, 'Import snapshot…', - h('input', { type: 'file', accept: 'application/json,.json', style: 'display:none', onChange: importSnapshotFile }) - ) - ), - wallet.offline && wallet.utxos.length - ? h('div', { class: 'small muted' }, `Loaded ${wallet.utxos.length} coin(s), ${fmtBtc(wallet.total)} BTC from snapshot.`) - : null - ), - // Wallet info - h( - 'div', - { class: 'card col' }, - h('h3', {}, 'Wallet'), - infoLine('Type', 'BIP84 · Native SegWit (p2wpkh)'), - infoLine('Account path', `m/84'/${coin}'/0'`), - !wallet.offline && infoLine('Receive addresses scanned', String(wallet.receive.length)), - h('button', { class: 'btn-danger mt8', onClick: lock }, 'Logout') + { class: 'col gap6' }, + h('p', { class: 'small muted', style: 'margin:0' }, + 'Offline transfer — export your coins on an online device, then import the file on an offline device (with this wallet + seed) to sign without internet.'), + h('div', { class: 'row gap6 wrap' }, + h('button', { class: 'btn-sm', disabled: !wallet.utxos.length, onClick: exportSnapshot }, '⤓ Export snapshot'), + h('label', { class: 'btn btn-sm', style: 'cursor:pointer' }, 'Import snapshot…', + h('input', { type: 'file', accept: 'application/json,.json', style: 'display:none', onChange: importSnapshotFile }) + ) ) ); } -function infoLine(k, v) { - return h('div', { class: 'row between small' }, h('span', { class: 'muted' }, k), h('span', { class: 'mono' }, v)); -} - function exportSnapshot() { const snap = wallet.exportSnapshot(); const stamp = new Date().toISOString().slice(0, 10); |
