diff --git a/api/static/dashboard.html b/api/static/dashboard.html
index f4d2b96..ccaf1f3 100644
--- a/api/static/dashboard.html
+++ b/api/static/dashboard.html
@@ -322,6 +322,10 @@
.cf-nums { width: 40px; text-align: right; flex: none; color: var(--muted); font: 500 10px var(--mono); }
.cf-text { flex: 1; min-width: 0; }
.cf-head { color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+ /* one-click trace-back to the source article (NN/g: citations visible and
+ styled distinctly, next to the claim) */
+ .cf-link { color: inherit; text-decoration: underline dotted; text-decoration-color: var(--accent); text-underline-offset: 3px; }
+ .cf-link:hover { color: var(--accent); }
.cf-why { color: var(--muted); font-style: italic; font-size: 10px; overflow: hidden;
text-overflow: ellipsis; white-space: nowrap; }
/* clickable engine chip + its filter indicator */
@@ -1281,7 +1285,9 @@ function renderCatalystFeed(feed) {
+ '
'
- + '
' + esc(r.headline || '') + '
'
+ + '
'
+ (r.rationale ? '
“' + esc(r.rationale) + '”
' : '')
+ '
'
+ '';
diff --git a/storage/projections.py b/storage/projections.py
index ac64e4d..31be1dd 100644
--- a/storage/projections.py
+++ b/storage/projections.py
@@ -984,7 +984,12 @@ def query_catalyst_feed(store, limit: int = 40, lookback_hours: int = 24) -> lis
try:
rows = store.con.execute(
"SELECT symbol, catalyst_type, sentiment, conviction, is_dilutive, "
- "rationale, headline, source, model, enriched_at "
+ "rationale, headline, source, model, enriched_at, "
+ # source URL for one-click trace-back (cache stores no URL; the raw
+ # item that produced the headline does)
+ "(SELECT r.raw_url FROM raw_news_items r "
+ " WHERE r.raw_title = news_catalyst_cache.headline "
+ " AND r.raw_url IS NOT NULL LIMIT 1) AS url "
"FROM news_catalyst_cache WHERE enriched_at >= ? "
"ORDER BY enriched_at DESC LIMIT ?",
[cutoff, int(limit)],
@@ -992,10 +997,11 @@ def query_catalyst_feed(store, limit: int = 40, lookback_hours: int = 24) -> lis
except Exception: # noqa: BLE001 — table may not exist / Ollama never ran
return []
out: list[dict] = []
- for sym, ctype, sentiment, conv, is_dil, rationale, headline, source, model, ts in rows:
+ for sym, ctype, sentiment, conv, is_dil, rationale, headline, source, model, ts, url in rows:
conv_f = float(conv) if conv is not None else None
is_dil_b = bool(is_dil)
out.append({
+ "url": url,
"symbol": (sym or "").upper(),
"catalyst_type": ctype,
"sentiment": round(float(sentiment), 3) if sentiment is not None else None,