feat: reasoning timeline on armed-trigger rows (click to expand)

The day's relevant names live in ARMED TRIGGERS — the reasoning panel now
opens right there: click a trigger row for the score/rvol timelines,
criteria lanes, and worded reasons, same renderer as the watch panel.
Caret + hover affordance; open panels survive snapshot re-renders.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
momentum devandClaude Fable 5 committed 2026-07-31 20:11:02 -05:00
1 parent 2be23673be
commit a6da02f809
1 file changed
+33 -3
+33 -3
View File
@@ -303,6 +303,11 @@
border-bottom: 1px solid var(--border); font: 600 12px var(--mono); }
.trig-row:last-child { border-bottom: 0; }
.trig-row:nth-child(even) { background: rgba(255,255,255,.016); }
.trig-row { cursor: pointer; }
.trig-row:hover { background: rgba(91,163,255,.05); }
.trig-row.open { background: rgba(91,163,255,.07); border-bottom: 0; }
.trig-detail { border-bottom: 1px solid var(--border-soft);
background: rgba(13,17,23,.4); padding-bottom: 6px; }
.trig-sym { width: 68px; font-weight: 700; font-size: 15px; letter-spacing: .2px; }
.trig-state { width: 56px; font-size: 9px; font-weight: 700; text-transform: uppercase;
letter-spacing: .5px; text-align: center; padding: 3px 0; border-radius: 4px;
@@ -1263,7 +1268,27 @@ function trigCatalyst(adv, rawHeadline) {
return '<span class="trig-adv" title="' + esc(tip) + '">' + dot + ' ' + esc(typ) + badge + '</span>';
}
// click a trigger row -> same reasoning timeline as the watch panel, right
// where the day's relevant names live
const trigExpanded = new Set();
let lastTrigArgs = null;
async function toggleTrigReasoning(symbol) {
if (trigExpanded.has(symbol)) trigExpanded.delete(symbol); else trigExpanded.add(symbol);
if (lastTrigArgs) renderTriggers(lastTrigArgs[0], lastTrigArgs[1]);
}
function fillTrigReasoning() {
trigExpanded.forEach(async (symbol) => {
const hostEl = document.querySelector('[data-trig-crit="' + CSS.escape(symbol) + '"]');
if (hostEl && hostEl.querySelector(".empty")) {
try {
const r = await fetch("/api/criteria?symbol=" + encodeURIComponent(symbol));
hostEl.innerHTML = renderCriteriaGrid(await r.json());
} catch (e) {}
}
});
}
function renderTriggers(tw, catMap) {
lastTrigArgs = [tw, catMap];
const arr = (tw && tw.triggers) || [];
const cat = catMap || {};
$("trig-count").textContent = tw && tw.armed != null ? tw.armed : 0;
@@ -1285,8 +1310,10 @@ function renderTriggers(tw, catMap) {
const distTxt = fired ? '▲' : (dist != null ? (dist >= 0 ? '▲ ' : '') + dist.toFixed(1) + '%' : '—');
const distCls = dist == null ? '' : (dist >= 0 ? 'pos' : dist > -1 ? 'warn' : 'neg');
const near = (t.state === 'armed' && dist != null && dist > -1.0) ? ' near' : '';
return '<div class="trig-row st-' + esc(t.state) + near + '">'
+ '<div class="trig-sym">' + (t.catalyst ? '📰 ' : '') + esc(t.symbol) + '</div>'
const isOpen = trigExpanded.has(t.symbol);
return '<div class="trig-row st-' + esc(t.state) + near + (isOpen ? ' open' : '')
+ '" onclick="toggleTrigReasoning(\'' + esc(t.symbol) + '\')" title="click for the reasoning timeline">'
+ '<div class="trig-sym"><span class="caret ' + (isOpen ? "open" : "") + '">▶</span> ' + (t.catalyst ? '📰 ' : '') + esc(t.symbol) + '</div>'
+ '<div class="trig-state ' + (String(t.state).toLowerCase() === "armed" ? "armed" : "") + '">' + esc(t.state) + '</div>'
+ '<div class="trig-meta">' + (t.gap != null ? 'gap ' + t.gap.toFixed(0) + '% ' : '') + (t.rvol != null ? t.rvol.toFixed(1) + 'x' : '') + (t.rvol_tod != null ? ' \u00B7 tod ' + (t.rvol_tod >= 100 ? t.rvol_tod.toFixed(0) : t.rvol_tod.toFixed(1)) + 'x' : '') + '</div>'
+ '<div class="trig-levels">entry <b>' + fmt(trig) + '</b> stop <b>' + fmt(stop) + '</b> now ' + fmt(price) + '</div>'
@@ -1295,8 +1322,11 @@ function renderTriggers(tw, catMap) {
+ '</div>'
+ '<div class="trig-dist ' + distCls + '">' + distTxt + '</div>'
+ '<div class="trig-cat">' + trigCatalyst(cat[(t.symbol || '').toUpperCase()] || cat[t.symbol], t.catalyst) + '</div>'
+ '</div>';
+ '</div>'
+ (isOpen ? '<div class="trig-detail" data-trig-crit="' + esc(t.symbol)
+ '"><div class="empty" style="padding:12px 16px">loading…</div></div>' : '');
}).join('');
fillTrigReasoning();
}
// catalyst engine status chip (shows the LLM news engine is live + its activity)