feat: news story cards in briefing — backend embeds structured RSS items in metadata

Previously metadata only stored rss_item_ids (integers); the full item data
was discarded after LLM synthesis. Now rss_items (id, title, url, source,
snippet, published_at) is also stored so clients can render per-story cards
without additional API calls.

Web BriefingView: replaces bare reaction-row buttons with news cards showing
source, headline (linked), 2-line snippet, and 👍/👎 per card.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-27 00:08:21 -04:00
parent bc5f1679d5
commit 2a1644e571
2 changed files with 121 additions and 30 deletions
@@ -416,6 +416,18 @@ async def run_compilation(
max_items=10,
)
rss_item_ids = [item["id"] for item in filtered_rss if item.get("id")]
rss_items_meta = [
{
"id": item["id"],
"title": item.get("title", ""),
"url": item.get("url", ""),
"source": item.get("feed_title", ""),
"snippet": (item.get("content") or "")[:300],
"published_at": item.get("published_at"),
}
for item in filtered_rss
if item.get("id")
]
# Weather staleness gate — returns None if data is >24h old
weather_card = parse_weather_card_data(weather_rows[0], temp_unit) if weather_rows else None
@@ -449,7 +461,7 @@ async def run_compilation(
# ── Post-processing ─────────────────────────────────────────────────────────
await upsert_task_snapshots(user_id, all_tasks)
metadata: dict = {"rss_item_ids": rss_item_ids, "weather": weather_card}
metadata: dict = {"rss_item_ids": rss_item_ids, "rss_items": rss_items_meta, "weather": weather_card}
if not internal_text and not external_text:
logger.warning("Briefing compilation produced no content for user %d slot %s", user_id, slot)