Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions src/css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -246,15 +246,9 @@ tr.lot-child.la-sol:hover > td{background:rgba(153,69,255,.08)}
.hcard-hero{padding:13px 13px 9px}
.hcard-hero-lbl{font-size:.55rem;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--mu);margin-bottom:4px;font-family:var(--mono)}
.hcard-hero-val{font-size:1.3rem;font-weight:700;color:var(--orange);letter-spacing:-.5px;line-height:1;font-family:var(--mono)}
.hcard-hero-sub{font-size:.6rem;color:var(--mu2);margin-top:5px;font-family:var(--mono)}
.hcard-hero-sub span{color:var(--green);font-weight:600}
.hcard-bar-wrap{padding:0 13px 12px}
.hcard-bar-labels{display:flex;justify-content:space-between;font-size:.54rem;color:var(--mu);letter-spacing:.8px;text-transform:uppercase;margin-bottom:5px;font-family:var(--mono)}
.hcard-bar-track{height:3px;background:var(--bd2);overflow:hidden}
.hcard-bar-fill{height:100%;background:linear-gradient(90deg,var(--green),rgba(0,214,143,.5))}
.hcard-stats{display:grid;grid-template-columns:1fr 1fr;border-top:1px solid var(--bd)}
.hcard-stats{display:grid;grid-template-columns:1fr 1fr 1fr;border-top:1px solid var(--bd)}
.hcard-stat{padding:9px 13px}
.hcard-stat:first-child{border-right:1px solid var(--bd)}
.hcard-stat + .hcard-stat{border-left:1px solid var(--bd)}
.hcard-stat-lbl{font-size:.54rem;font-weight:700;text-transform:uppercase;letter-spacing:1.2px;color:var(--mu);margin-bottom:3px;font-family:var(--mono)}
.hcard-stat-val{font-size:.8rem;font-weight:600;color:var(--text2);font-family:var(--mono)}
.hcard-stat-val.green{color:var(--green)}
Expand Down
11 changes: 3 additions & 8 deletions src/js/06-render-table.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,8 +312,7 @@ function rTable(displayRows, streams, lots) {
assetLots.forEach(lot => {
openLotCount++;
const nc = lot.netCost;
const reduction = lot.costBasis - nc;
const reductionPct = lot.costBasis > 0 ? (reduction / lot.costBasis * 100) : 0;
const reductionPct = lot.costBasis > 0 ? ((lot.costBasis - nc) / lot.costBasis * 100) : 0;
const lotBadge = totalAssetLots > 1 ? '<span class="lot-badge">Lot ' + lot.lotNum + '</span>' : '';
const holdingTrade = trades.find(t => t.id === lot.tradeIds[0]);
const isManualHolding = holdingTrade && holdingTrade.type === 'HOLDING';
Expand Down Expand Up @@ -362,16 +361,12 @@ function rTable(displayRows, streams, lots) {
+ '<div class="hcard-hero has-tip" data-tip="Net Cost = costBasis − (lotPremiums / size). A premium-reduced entry-price lens — what you effectively paid per token after the wheel premiums worked for you. Different from Unrealised P&amp;L, which marks the lot to spot against raw costBasis (not netCost).">'
+ '<div class="hcard-hero-lbl">Net Cost / ' + a + ' <span class="tip-ico" aria-hidden="true">&#9432;</span></div>'
+ '<div class="hcard-hero-val">$' + fmt(nc) + '</div>'
+ '<div class="hcard-hero-sub">basis $' + fmt(lot.costBasis) + ' &mdash; saved <span>$' + fmt(reduction) + ' (' + reductionPct.toFixed(1) + '%)</span></div>'
+ '</div>'
+ spotBlock
+ '<div class="hcard-bar-wrap">'
+ '<div class="hcard-bar-labels"><span>Premium reduction</span><span style="color:var(--green)">' + reductionPct.toFixed(1) + '%</span></div>'
+ '<div class="hcard-bar-track"><div class="hcard-bar-fill" style="width:' + Math.min(reductionPct, 100).toFixed(1) + '%"></div></div>'
+ '</div>'
+ '<div class="hcard-stats">'
+ '<div class="hcard-stat"><div class="hcard-stat-lbl">CC Premiums</div><div class="hcard-stat-val green">$' + fmt(lot.lotPremiums) + '</div></div>'
+ '<div class="hcard-stat"><div class="hcard-stat-lbl">Cost Basis</div><div class="hcard-stat-val">$' + fmt(lot.costBasis) + '</div></div>'
+ '<div class="hcard-stat"><div class="hcard-stat-lbl">CC Premiums</div><div class="hcard-stat-val green">$' + fmt(lot.lotPremiums) + '</div></div>'
+ '<div class="hcard-stat"><div class="hcard-stat-lbl">Premium Reduction %</div><div class="hcard-stat-val green">' + reductionPct.toFixed(1) + '%</div></div>'
+ '</div>'
+ '</div>';
});
Expand Down
70 changes: 70 additions & 0 deletions test/integration/holding-card-trim.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const test = require('node:test');
const assert = require('node:assert');
const { setupJsdom } = require('../helpers/setupJsdom');

// Issue #51: Trim redundant content from holding cards.
// - Hero sub-line ("basis $X — saved $Y") removed.
// - Premium-reduction bar removed.
// - Footer stats: Cost Basis | CC Premiums | Premium Reduction % (3 cols, in order).

function getHcard(window) {
const card = window.document.querySelector('.hcard');
assert.ok(card, 'expected a .hcard element on the page');
return card;
}

const baseTrades = [
{ id: 1, asset: 'ETH', type: 'HOLDING', date: '2026-01-01', expiry: '',
dte: null, strike: 3000, size: 1, premium: 0, outcome: 'OPEN',
closeCost: 0, platform: 'SPOT' },
{ id: 2, asset: 'ETH', type: 'CALL', date: '2026-01-15', expiry: '2026-01-29',
dte: 14, strike: 3500, size: 1, premium: 60, outcome: 'EXPIRED',
closeCost: 0, platform: 'RYSK' },
];

test('holding card has no hero sub-line', (t) => {
const { window, teardown } = setupJsdom({ trades: baseTrades });
t.after(teardown);
const card = getHcard(window);
assert.strictEqual(card.querySelector('.hcard-hero-sub'), null,
'.hcard-hero-sub should be removed');
});

test('holding card has no premium-reduction bar', (t) => {
const { window, teardown } = setupJsdom({ trades: baseTrades });
t.after(teardown);
const card = getHcard(window);
assert.strictEqual(card.querySelector('.hcard-bar-wrap'), null,
'.hcard-bar-wrap should be removed');
});

test('holding card footer has 3 stats: Cost Basis | CC Premiums | Premium Reduction %', (t) => {
const { window, teardown } = setupJsdom({ trades: baseTrades });
t.after(teardown);
const card = getHcard(window);

const stats = card.querySelectorAll('.hcard-stats .hcard-stat');
assert.strictEqual(stats.length, 3, 'footer should have exactly 3 stat cells');

const labels = Array.from(stats).map(s =>
s.querySelector('.hcard-stat-lbl').textContent.trim());
assert.deepStrictEqual(labels, ['Cost Basis', 'CC Premiums', 'Premium Reduction %'],
'stat labels must appear in this order');

// Net cost = 3000 - 60 = 2940; reduction = 60/3000 = 2.0%.
const values = Array.from(stats).map(s =>
s.querySelector('.hcard-stat-val').textContent.trim());
assert.match(values[0], /\$3,?000(\.\d+)?$/, `Cost Basis value, got "${values[0]}"`);
assert.match(values[1], /\$60(\.\d+)?$/, `CC Premiums value, got "${values[1]}"`);
assert.match(values[2], /^2\.0%$/, `Reduction % value, got "${values[2]}"`);
});

test('Cost Basis appears exactly once on the card', (t) => {
const { window, teardown } = setupJsdom({ trades: baseTrades });
t.after(teardown);
const card = getHcard(window);
const labels = Array.from(card.querySelectorAll('.hcard-stat-lbl'))
.map(el => el.textContent.trim());
const cbCount = labels.filter(l => /^Cost Basis$/i.test(l)).length;
assert.strictEqual(cbCount, 1, 'Cost Basis should appear exactly once');
});
Loading