aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/features/ark.js21
-rw-r--r--src/i18n.js2
-rw-r--r--tools/ark/custody-ui-test.js25
3 files changed, 24 insertions, 24 deletions
diff --git a/src/features/ark.js b/src/features/ark.js
index df33e0f..bc5bd10 100644
--- a/src/features/ark.js
+++ b/src/features/ark.js
@@ -2604,6 +2604,7 @@ export function arkFeature(ctx) {
h('td', { class: 'num' }, '—'),
h('td', { class: 'num' }, h('span', { class: 'tag pending' }, t('arkCoinsRenewing'))))))))),
renewingCard,
+ embedded ? custodyCard() : null,
h('div', { class: 'card col', style: 'gap:8px' },
h('h4', { style: 'margin:0' }, t('arkCoinsRenewTitle')),
// the round cadence comes from the server (it has been an hour and
@@ -2678,18 +2679,16 @@ export function arkFeature(ctx) {
{ id: 'self', label: t('arkCustodySelf'), desc: t('arkCustodySelfDesc', { days: lifeDays }), cost: t('arkCustodyCostSelf', { base: baseCost }) },
{ id: 'exit', label: t('arkCustodyExit'), desc: t('arkCustodyExitDesc', { hops }), cost: t('arkCustodyCostExit', { pct: earlyPct, base: baseCost }) },
].filter((x) => honors || x.id !== 'custodial');
+ const chosen = tiers.find((x) => x.id === cur) || tiers[0];
+ // A drop-down, and only the chosen strategy's description under it: three
+ // paragraphs at once read as a wall, and two of them are about choices
+ // the reader has not made.
return h('div', { class: 'card col ark-custody', style: 'gap:10px' },
- h('h3', { style: 'margin:0' }, t('arkCustodyTitle')),
+ h('h4', { style: 'margin:0' }, t('arkCustodyTitle')),
h('p', { class: 'small muted', style: 'margin:0' }, t('arkCustodyDesc', { days: lifeDays })),
- h('div', { class: 'col', style: 'gap:8px' }, ...tiers.map((x) => h('label', {
- class: 'row gap6 ark-custody-opt' + (x.id === cur ? ' active' : ''), style: 'align-items:flex-start;cursor:pointer',
- 'data-tier': x.id,
- },
- h('input', { type: 'radio', name: 'arkCustody', value: x.id, checked: x.id === cur, disabled: !honors, style: 'flex:0 0 auto;margin:3px 0 0',
- onChange: () => applyCustody(x.id) }),
- h('div', { class: 'col', style: 'gap:2px;flex:1' },
- h('div', { class: 'row between gap6', style: 'align-items:baseline' }, h('span', { style: 'white-space:nowrap' }, x.label), h('span', { class: 'small' + (x.id === 'custodial' ? ' faint' : ''), style: 'text-align:right' }, x.cost)),
- h('div', { class: 'small muted' }, x.desc))))),
+ h('select', { id: 'ark-custody-select', disabled: !honors, onChange: (e) => applyCustody(e.target.value) },
+ ...tiers.map((x) => h('option', { value: x.id, selected: x.id === cur }, x.label + ' · ' + x.cost))),
+ h('p', { class: 'small muted ark-custody-desc', style: 'margin:0' }, chosen.desc),
honors ? null : h('p', { class: 'small faint', style: 'margin:0' }, t('arkCustodyNotHere')));
}
@@ -3809,7 +3808,7 @@ export function arkFeature(ctx) {
// the Spending face's settings page: the custody choice, then its coins
spendingSettings() {
if (wallet.watchOnly) return null; // a locked wallet can't renew or exit
- return h('div', { class: 'col', style: 'gap:16px' }, custodyCard(), arkCoinsPage({ embedded: true }));
+ return arkCoinsPage({ embedded: true }); // the custody strategy sits inside, after the coins
},
// the wallet's own pages keep the top nav: these are pages, not takeovers
screenView() {
diff --git a/src/i18n.js b/src/i18n.js
index 16b93d4..aa66f3a 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -783,7 +783,7 @@ const STR = {
arkCoopSelectedAmt: '{n} selected coins · {amount}',
arkCoopSelectedAmtOne: '1 selected coin · {amount}',
arkCustodyTitle: 'Custody',
- arkCustodyDesc: 'Each coin expires about {days} days after it was made. Until then you can bring it on-chain yourself, without Coinos. Renewing a coin before it expires keeps that right for another {days} days and costs a small fee. Coinos keeps honoring expired coins, so renewing is a choice:',
+ arkCustodyDesc: 'Each coin expires about {days} days after it was made. Until then you can bring it on-chain yourself, without Coinos; after that, Coinos keeps honoring it. Renewing before expiry keeps your own exit, for a fee, so it is a choice:',
arkCustodyCustodial: 'Allow coins to expire',
arkCustodyCustodialDesc: 'Nothing renews and no renewal fees are paid. A coin keeps its own exit until it expires. After that, paying still works, but bringing it on-chain needs Coinos’s cooperation.',
arkCustodySelf: 'Minimize renewal cost',
diff --git a/tools/ark/custody-ui-test.js b/tools/ark/custody-ui-test.js
index 7ebc2ea..419ed21 100644
--- a/tools/ark/custody-ui-test.js
+++ b/tools/ark/custody-ui-test.js
@@ -77,20 +77,20 @@ try {
let tx = await text();
assert.match(tx, /Custody/);
- assert.match(tx, /Allow coins to expire[\s\S]*no fees/);
- assert.match(tx, /keeps its own exit until it expires/);
- assert.doesNotMatch(tx, /timelock|Spending coin carries|old Coinos/, 'no jargon on the card');
- assert.match(tx, /Minimize renewal cost[\s\S]*300 sats per renewal/);
- assert.match(tx, /Minimize exit cost[\s\S]*300 sats near expiry, 0\.2% \+ 300 sats when early/);
- assert.match(tx, /4 hops deep/);
- const checked = await page.$$eval('.ark-custody input[type=radio]', (els) => els.filter((e) => e.checked).map((e) => e.value));
- assert.deepEqual(checked, ['custodial'], 'coinos holds the coins by default');
+ const opts = await page.$$eval('.ark-custody option', (es) => es.map((e) => e.value + '=' + e.textContent));
+ assert.deepEqual(opts, ['custodial=Allow coins to expire · no fees', 'self=Minimize renewal cost · 300 sats per renewal', 'exit=Minimize exit cost · 300 sats near expiry, 0.2% + 300 sats when early']);
+ assert.equal(await page.$eval('#ark-custody-select', (e) => e.value), 'custodial', 'coins expire by default');
assert.equal(await page.evaluate(() => coopFeature.getCustody()), 'custodial');
+ assert.match(await page.$eval('.ark-custody-desc', (e) => e.textContent), /keeps its own exit until it expires/);
+ assert.doesNotMatch(tx, /Coins renew in the last hours|Every transfer adds a hop/, 'only the chosen strategy is described');
+ assert.doesNotMatch(tx, /timelock|Spending coin carries|old Coinos/, 'no jargon on the card');
+ const order = await page.$$eval('.card h4, .card h3', (es) => es.map((e) => e.textContent).filter((x) => /Spending coins|Custody|Renewal/.test(x)));
+ assert.deepEqual(order, ['Spending coins', 'Custody', 'Renewal'], 'coins first, then the strategy, then renewal');
assert.match(tx, /held by Coinos/, 'the coin past its timelock is held, not "expired"');
assert.doesNotMatch(tx, /5,000 sats\s+expired/, 'the row never says "expired"');
assert.match(tx, /With “Allow coins to expire”, nothing renews on its own/);
if (process.env.SHOT) await page.screenshot({ path: process.env.SHOT, fullPage: true });
- console.log('✓ the custody card: three tiers with their prices, coinos holding by default, a past-timelock coin shown as held');
+ console.log('✓ the custody card: a drop-down of three strategies with their prices, coins expire by default, one description, a past-timelock coin shown as held');
// the unilateral exit only counts the coin still inside its timelock
assert.match(tx, /Exit now/);
@@ -103,13 +103,14 @@ try {
console.log('✓ a unilateral exit counts only the coin still inside its timelock');
// opt into keeping the exit: persisted, the page reprices, renewal starts covering
- await page.click('.ark-custody-opt[data-tier=self] input'); await sleep(400);
+ await page.select('#ark-custody-select', 'self'); await sleep(400);
assert.equal(await page.evaluate(() => coopFeature.getCustody()), 'self');
assert.equal(await page.evaluate(() => coopApp.wallet.loadFeatureState('arkCustody')), 'self', 'saved with the wallet');
tx = await text();
assert.match(tx, /Custody choice saved/);
assert.doesNotMatch(tx, /nothing renews on its own/);
assert.match(tx, /renews coins automatically once they are close to expiring/);
+ assert.match(await page.$eval('.ark-custody-desc', (e) => e.textContent), /Coins renew in the last hours/);
const calls = await page.evaluate(() => coopFeature.autoRefreshCalls);
assert.deepEqual(calls[0], ['ensure', 6, 8, 1], 'backstop orders registered at once (regtest margin 6)');
assert.deepEqual(calls[1], ['refresh', ['coin-1', 'coin-2']], 'the coin past its timelock is renewed now with the small change; the deep healthy one is left alone');
@@ -117,12 +118,12 @@ try {
// the exit tier also flattens the deep coin — but not the deep 800-sat one on its own merits
await page.evaluate(() => { coopFeature.autoRefreshCalls.length = 0; });
- await page.click('.ark-custody-opt[data-tier=exit] input'); await sleep(400);
+ await page.select('#ark-custody-select', 'exit'); await sleep(400);
assert.equal(await page.evaluate(() => coopFeature.getCustody()), 'exit');
const calls2 = await page.evaluate(() => coopFeature.autoRefreshCalls);
assert.deepEqual(calls2[1], ['refresh', ['coin-1', 'coin-0', 'coin-2']], 'expired + deep (worth it) + small change ride one round');
console.log('✓ "Keep my exit cheap" renews the deep coin too, when the fee is under a tenth of it');
- await page.click('.ark-custody-opt[data-tier=custodial] input'); await sleep(300);
+ await page.select('#ark-custody-select', 'custodial'); await sleep(300);
assert.equal(await page.evaluate(() => coopFeature.getCustody()), 'custodial');
assert.match(await text(), /nothing renews on its own/);
console.log('✓ the other tiers switch the same way, and back');