From 628d693f798c9c5d007981a8895e79349e3d6481 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 28 Aug 2026 19:44:44 -0700 Subject: Renewals earn a line in the ledger: "Renewed ↻" rows in history showing what the round cost (or "free"), with the in-and-out detail behind them — and the toast is simply "Renewal requested" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi --- src/ark/manager.js | 2 +- src/features/ark.js | 31 ++++++++++++++++++++++++------- src/i18n.js | 3 ++- 3 files changed, 27 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/ark/manager.js b/src/ark/manager.js index 1129c38..a2c69d8 100644 --- a/src/ark/manager.js +++ b/src/ark/manager.js @@ -1627,7 +1627,7 @@ export class ArkManager { } for (const v of inputRecs) v.state = 'spent'; action.step = 'done'; - this._movement({ type: 'refresh', amountSat: action.outAmountSat, status: 'complete', detail: `${inputRecs.length} in -> ${newVtxos.length} out${action.feeSat ? ` · fee ${action.feeSat} sat` : ''}` }); + this._movement({ type: 'refresh', amountSat: action.outAmountSat, feeSat: action.feeSat || 0, status: 'complete', detail: `${inputRecs.length} in -> ${newVtxos.length} out${action.feeSat ? ` · fee ${action.feeSat} sat` : ''}` }); this._save(); } } diff --git a/src/features/ark.js b/src/features/ark.js index 12362cc..59c293f 100644 --- a/src/features/ark.js +++ b/src/features/ark.js @@ -839,10 +839,23 @@ export function arkFeature(ctx) { : null; function arkHistoryItem(m) { - const incoming = !['send', 'offboard', 'exit', 'ln-send'].includes(m.type); + const incoming = !['send', 'offboard', 'exit', 'ln-send', 'refresh'].includes(m.type); const label = m.type === 'receive' ? t('received') : m.type === 'board' ? t('arkBoarded') : m.type === 'ln-send' ? t('arkLnPaidHistory') : m.type === 'ln-receive' ? t('arkLnReceivedHistory') - : m.type === 'offboard' ? t('arkOffboarded') : m.type === 'exit' ? t('arkExited') : t('sent'); + : m.type === 'offboard' ? t('arkOffboarded') : m.type === 'exit' ? t('arkExited') + : m.type === 'refresh' ? t('arkRenewedHistory') : t('sent'); + // a renewal moves nothing anywhere — its history amount is what it COST + if (m.type === 'refresh') { + return h('div', { class: 'item', style: 'cursor:pointer', onClick: () => { ui.arkMoveDetail = m.id; render(); } }, + h('div', { class: 'ico' }, '↻'), + h('div', { class: 'grow' }, + h('div', {}, label), + h('div', { class: 'small faint' }, timeAgo(m.ts / 1000))), + h('div', { style: 'text-align:right' }, + m.feeSat > 0 + ? h('div', { class: 'amount-neg' }, '-' + fmtAmount(m.feeSat)) + : h('div', { class: 'small faint' }, t('arkDepthFree')))); + } return h( 'div', { class: 'item', style: 'cursor:pointer', onClick: () => { ui.arkMoveDetail = m.id; render(); } }, @@ -924,10 +937,11 @@ export function arkFeature(ctx) { } function arkMoveDetailView(m) { - const incoming = !['send', 'offboard', 'exit', 'ln-send'].includes(m.type); + const incoming = m.type === 'refresh' ? false : !['send', 'offboard', 'exit', 'ln-send'].includes(m.type); const label = m.type === 'receive' ? t('received') : m.type === 'board' ? t('arkBoarded') : m.type === 'ln-send' ? t('arkLnPaidHistory') : m.type === 'ln-receive' ? t('arkLnReceivedHistory') - : m.type === 'offboard' ? t('arkOffboarded') : m.type === 'exit' ? t('arkExited') : t('sent'); + : m.type === 'offboard' ? t('arkOffboarded') : m.type === 'exit' ? t('arkExited') + : m.type === 'refresh' ? t('arkRenewedHistory') : t('sent'); const row = (k, v) => h('div', { class: 'row between', style: 'gap:12px' }, h('span', { class: 'small muted', style: 'flex-shrink:0' }, k), h('span', { class: 'small', style: 'text-align:right;word-break:break-all' }, v)); const url = m.txid ? wallet.api.explorerTx(m.txid) : null; @@ -940,8 +954,11 @@ export function arkFeature(ctx) { m.type.startsWith('ln-') ? h('span', { style: 'font-size:18px' }, '⚡') : h('span', { html: ARK_ICON(18) }), h('h3', { style: 'margin:0' }, label), m.status !== 'complete' ? h('span', { class: 'tag pending' }, m.status) : null), - h('div', { class: incoming ? 'amount-pos' : 'amount-neg', style: 'font-size:20px' }, - (incoming ? '+' : '-') + fmtAmount(m.amountSat) + ' ' + unitLabel()), + m.type === 'refresh' + ? h('div', { class: m.feeSat > 0 ? 'amount-neg' : 'small faint', style: 'font-size:20px' }, + m.feeSat > 0 ? '-' + fmtAmount(m.feeSat) + ' ' + unitLabel() : t('arkDepthFree')) + : h('div', { class: incoming ? 'amount-pos' : 'amount-neg', style: 'font-size:20px' }, + (incoming ? '+' : '-') + fmtAmount(m.amountSat) + ' ' + unitLabel()), row(t('dateLabel'), new Date(m.ts).toLocaleString()), (() => { const pk = zapNoteFor(m); @@ -2479,7 +2496,7 @@ export function arkFeature(ctx) { .filter((a) => a.type === 'exit' && !['done', 'failed'].includes(a.step)) .map((a) => ({ time: exitStartedTs(a), render: () => arkExitActionItem(a) })); return exits.concat((s.movements || []) - .filter((m) => ['receive', 'send', 'board', 'offboard', 'exit', 'ln-send', 'ln-receive'].includes(m.type) && m.status === 'complete') + .filter((m) => ['receive', 'send', 'board', 'offboard', 'exit', 'ln-send', 'ln-receive', 'refresh'].includes(m.type) && m.status === 'complete') .map((m) => ({ time: m.ts, render: () => arkHistoryItem(m) }))); }, historyDetail() { diff --git a/src/i18n.js b/src/i18n.js index ac9abd2..42ad2e0 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -199,10 +199,11 @@ const STR = { onbBoardTitle: 'Top up Spending', onbBoardBody: 'Move some sats from Savings into Spending — payments there are instant, with no mining fees.', topUpFromSavings: 'Top up from Savings', + arkRenewedHistory: 'Renewed', arkDepthNotice: 'Self-custody exit currently costs about {fee} sats in mining fees. Renew for {renew} to bring that down to about {after} sats.', arkDepthFree: 'free', arkDepthBtn: 'Renew now', - arkDepthRenewed: 'Renewal requested — it completes with the next round.', + arkDepthRenewed: 'Renewal requested', arkUniUneconomical: 'A trustless on-chain exit would cost about {fee} sats in mining fees — more than the {n} sats it would move, so it isn’t offered. For a balance this size, send it out over Lightning instead: no mining fees at all.', arkUniFallbackBody: 'The server didn’t cooperate, but your money can still move without it: your whole Spending balance exits to Savings on-chain. It takes about a day, and roughly {fee} sats of mining fees will be paid from Savings. Funds land in Savings — send them on from there once it completes.', arkUniFallbackShort: 'Savings holds {have} sats — about {need} sats are needed for the mining fees. Top up Savings first.', -- cgit v1.2.3