40f1e0ee00
Two upgrades to make the digest actually readable:
1) Article-grounded 2-3 sentence summaries (everywhere)
The old prompt got just the title + miniflux's content excerpt,
which for HN/Lobsters/wire feeds is barely more than the title
itself — so summaries paraphrased the title and added nothing.
Now every URL gets fetched and main-content-extracted via
trafilatura on a parallel pre-pass (10 workers, ~15s for ~50
URLs). Extracted text caches to /output/.article-cache.json with
a 7-day TTL so repeat runs in the same window don't re-pull.
Headlines also get summarized now — one batched LLM call per
category (world / local). Rendered as a paragraph below the
title with source + time on the right rail.
Prompt rewrites tell the model to pull names/numbers/places
from the body and explicitly forbid restating the title.
Result: real specifics ("71% saw no pay increase globally",
"third time in less than two weeks", "Islamabad and Moscow
intermediaries") instead of title paraphrase.
2) Per-desk collapse buttons
Chevron next to .desk-count toggles a .is-collapsed class.
Collapsed state is per-device (localStorage by section id) since
collapse is a viewing preference, not content state.
302 lines
13 KiB
Django/Jinja
302 lines
13 KiB
Django/Jinja
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||
<title>Daily Digest · {{ date_long }} · {{ edition_short }}</title>
|
||
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
|
||
{# Cache-bust on every digest run so frontend asset updates land
|
||
immediately — browsers were serving stale app.js / style.css after
|
||
rebuilds, which was hiding headline desks (old JS treated them as
|
||
empty because it only counted .item, not .headline). #}
|
||
<link rel="stylesheet" href="style.css?v={{ generated_at.strftime('%Y%m%d%H%M%S') }}" />
|
||
</head>
|
||
<body>
|
||
{# ────── header ────── #}
|
||
<header class="masthead" role="banner">
|
||
<div class="masthead-inner">
|
||
<a class="masthead-brand" href="index.html" title="Latest edition">
|
||
<span class="brand-glyph" aria-hidden="true">◢</span>
|
||
<span class="brand-mark">DAILY DIGEST</span>
|
||
</a>
|
||
<div class="masthead-edition">
|
||
<span class="edition-num">№ {{ generated_at.strftime("%j") }}</span>
|
||
<span class="edition-stamp">{{ edition|upper }} EDITION</span>
|
||
<a class="archive-link" href="archive.html">ARCHIVE →</a>
|
||
</div>
|
||
<div class="masthead-meta">
|
||
<div class="meta-row">
|
||
<span class="meta-label">FILED</span>
|
||
<span class="meta-value">{{ date_long|upper }} · {{ time_short }} {{ tz }}</span>
|
||
</div>
|
||
<div class="meta-row">
|
||
<span class="meta-label">DESK</span>
|
||
<span class="meta-value">WORLD · LOCAL · SUBREDDITS · TECH FEEDS</span>
|
||
</div>
|
||
<div class="meta-row">
|
||
<span class="meta-label">CURATED BY</span>
|
||
<span class="meta-value">{{ model }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{# A thin aurora-glow rule under the masthead — the only sanctioned
|
||
Australis gradient. #}
|
||
<div class="aurora-rule" aria-hidden="true"></div>
|
||
</header>
|
||
|
||
{# ────── nav strip ────── #}
|
||
<nav class="jumpnav" aria-label="sections">
|
||
<ol>
|
||
{% if world_headlines %}
|
||
<li><a href="#world"><span class="jump-num">01</span> WORLD</a></li>
|
||
{% endif %}
|
||
{% if local_headlines %}
|
||
<li><a href="#local"><span class="jump-num">02</span> LOCAL</a></li>
|
||
{% endif %}
|
||
{% if reddit_sources %}
|
||
<li><a href="#reddit"><span class="jump-num">03</span> REDDIT</a></li>
|
||
{% endif %}
|
||
{% if tech_sources %}
|
||
<li><a href="#tech"><span class="jump-num">04</span> TECH FEEDS</a></li>
|
||
{% endif %}
|
||
</ol>
|
||
</nav>
|
||
|
||
<main class="brief">
|
||
|
||
{# ────── WORLD HEADLINES ────── #}
|
||
{% if world_headlines %}
|
||
<section id="world" class="desk desk-headlines">
|
||
<header class="desk-head">
|
||
<span class="desk-num">01</span>
|
||
<h2 class="desk-title">World</h2>
|
||
<span class="desk-sub">top of last {{ generated_at.hour < 14 and "8" or "8" }} hours · headlines</span>
|
||
<span class="desk-count">{{ world_total }} items</span>
|
||
<button class="desk-collapse" type="button" aria-label="Collapse section" title="Collapse / expand">▾</button>
|
||
</header>
|
||
<ol class="headlines-list">
|
||
{% for h in world_headlines %}
|
||
<li class="headline" data-id="{{ h.id }}">
|
||
<button class="item-hide" type="button" title="Hide this headline" aria-label="Hide this headline">×</button>
|
||
<h3 class="headline-title">
|
||
<a href="{{ h.url }}" target="_blank" rel="noopener">{{ h.title }}</a>
|
||
</h3>
|
||
<span class="headline-source">{{ h.source }}</span>
|
||
<span class="headline-time">{{ h.posted_at|humanago }}</span>
|
||
{% if h.tldr %}
|
||
<p class="headline-tldr">{{ h.tldr }}</p>
|
||
{% endif %}
|
||
</li>
|
||
{% endfor %}
|
||
</ol>
|
||
</section>
|
||
{% endif %}
|
||
|
||
{# ────── LOCAL HEADLINES ────── #}
|
||
{% if local_headlines %}
|
||
<section id="local" class="desk desk-headlines">
|
||
<header class="desk-head">
|
||
<span class="desk-num">02</span>
|
||
<h2 class="desk-title">Local</h2>
|
||
<span class="desk-sub">orange county · greater LA · last 8 hours</span>
|
||
<span class="desk-count">{{ local_total }} items</span>
|
||
<button class="desk-collapse" type="button" aria-label="Collapse section" title="Collapse / expand">▾</button>
|
||
</header>
|
||
<ol class="headlines-list">
|
||
{% for h in local_headlines %}
|
||
<li class="headline" data-id="{{ h.id }}">
|
||
<button class="item-hide" type="button" title="Hide this headline" aria-label="Hide this headline">×</button>
|
||
<h3 class="headline-title">
|
||
<a href="{{ h.url }}" target="_blank" rel="noopener">{{ h.title }}</a>
|
||
</h3>
|
||
<span class="headline-source">{{ h.source }}</span>
|
||
<span class="headline-time">{{ h.posted_at|humanago }}</span>
|
||
{% if h.tldr %}
|
||
<p class="headline-tldr">{{ h.tldr }}</p>
|
||
{% endif %}
|
||
</li>
|
||
{% endfor %}
|
||
</ol>
|
||
</section>
|
||
{% endif %}
|
||
|
||
{# ────── REDDIT SECTION ────── #}
|
||
{% if reddit_sources %}
|
||
<section id="reddit" class="desk">
|
||
<header class="desk-head">
|
||
<span class="desk-num">03</span>
|
||
<h2 class="desk-title">Reddit</h2>
|
||
<span class="desk-sub">top of last {{ generated_at.hour < 14 and "12" or "12" }} hours · score-filtered</span>
|
||
<span class="desk-count">{{ reddit_total }} items</span>
|
||
<button class="desk-collapse" type="button" aria-label="Collapse section" title="Collapse / expand">▾</button>
|
||
</header>
|
||
|
||
{% for src in reddit_sources %}
|
||
<article class="source">
|
||
<header class="source-head">
|
||
<a class="source-name" href="{{ src.href }}" target="_blank" rel="noopener">
|
||
{{ src.name }}
|
||
</a>
|
||
<span class="source-count">{{ src.items|length }}</span>
|
||
</header>
|
||
<ol class="items">
|
||
{% for it in src.items %}
|
||
<li class="item" data-tag="{{ it.tag }}" data-id="{{ it.id }}">
|
||
<button class="item-hide" type="button" title="Hide this item" aria-label="Hide this item">×</button>
|
||
<div class="item-rail" aria-hidden="true">
|
||
{% if it.score %}
|
||
<span class="chip chip-score" title="upvotes">▲ {{ it.score }}</span>
|
||
{% endif %}
|
||
{% if it.comments %}
|
||
<span class="chip chip-comments" title="comments">{{ it.comments }} ⌥</span>
|
||
{% endif %}
|
||
{% if it.tag and it.tag != 'other' %}
|
||
<span class="chip chip-tag chip-tag-{{ it.tag }}">{{ it.tag }}</span>
|
||
{% endif %}
|
||
</div>
|
||
<div class="item-body">
|
||
<h3 class="item-title">
|
||
<a href="{{ it.url or it.permalink }}" target="_blank" rel="noopener">{{ it.title }}</a>
|
||
</h3>
|
||
{% if it.tldr %}
|
||
<p class="item-tldr">{{ it.tldr }}</p>
|
||
{% endif %}
|
||
<footer class="item-foot">
|
||
<div class="item-actions">
|
||
{% if it.url and it.url != it.permalink %}
|
||
<a class="action action-source" href="{{ it.url }}" target="_blank" rel="noopener">
|
||
<span class="action-arrow">↗</span>
|
||
<span class="action-label">SOURCE</span>
|
||
<span class="action-domain">{{ it.url|domain }}</span>
|
||
</a>
|
||
{% endif %}
|
||
<a class="action action-thread" href="{{ it.permalink }}" target="_blank" rel="noopener">
|
||
<span class="action-arrow">⌥</span>
|
||
<span class="action-label">REDDIT THREAD</span>
|
||
</a>
|
||
</div>
|
||
<div class="item-meta-row">
|
||
<span class="item-meta">{{ it.posted_at|humanago }} ago</span>
|
||
<span class="sep">·</span>
|
||
<span class="item-meta">u/{{ it.author }}</span>
|
||
</div>
|
||
</footer>
|
||
</div>
|
||
</li>
|
||
{% endfor %}
|
||
</ol>
|
||
</article>
|
||
{% endfor %}
|
||
</section>
|
||
{% endif %}
|
||
|
||
{# ────── TECH FEEDS SECTION ────── #}
|
||
{% if tech_sources %}
|
||
<section id="tech" class="desk">
|
||
<header class="desk-head">
|
||
<span class="desk-num">04</span>
|
||
<h2 class="desk-title">Tech Feeds</h2>
|
||
<span class="desk-sub">non-reddit · last {{ generated_at.hour < 14 and "12" or "12" }} hours</span>
|
||
<span class="desk-count">{{ tech_total }} items</span>
|
||
<button class="desk-collapse" type="button" aria-label="Collapse section" title="Collapse / expand">▾</button>
|
||
</header>
|
||
|
||
{% for src in tech_sources %}
|
||
<article class="source">
|
||
<header class="source-head">
|
||
<a class="source-name" href="{{ src.href }}" target="_blank" rel="noopener">
|
||
{{ src.name }}
|
||
</a>
|
||
<span class="source-count">{{ src.items|length }}</span>
|
||
</header>
|
||
<ol class="items">
|
||
{% for it in src.items %}
|
||
<li class="item" data-tag="{{ it.tag }}" data-id="{{ it.id }}">
|
||
<button class="item-hide" type="button" title="Hide this item" aria-label="Hide this item">×</button>
|
||
<div class="item-rail" aria-hidden="true">
|
||
{% if it.tag and it.tag != 'other' %}
|
||
<span class="chip chip-tag chip-tag-{{ it.tag }}">{{ it.tag }}</span>
|
||
{% endif %}
|
||
</div>
|
||
<div class="item-body">
|
||
<h3 class="item-title">
|
||
<a href="{{ it.url }}" target="_blank" rel="noopener">{{ it.title }}</a>
|
||
</h3>
|
||
{% if it.tldr %}
|
||
<p class="item-tldr">{{ it.tldr }}</p>
|
||
{% endif %}
|
||
<footer class="item-foot">
|
||
{% if it.url %}
|
||
<div class="item-actions">
|
||
<a class="action action-source" href="{{ it.url }}" target="_blank" rel="noopener">
|
||
<span class="action-arrow">↗</span>
|
||
<span class="action-label">SOURCE</span>
|
||
<span class="action-domain">{{ it.url|domain }}</span>
|
||
</a>
|
||
</div>
|
||
{% endif %}
|
||
<div class="item-meta-row">
|
||
<span class="item-meta">{{ it.posted_at|humanago }} ago</span>
|
||
{% if it.author %}
|
||
<span class="sep">·</span>
|
||
<span class="item-meta">{{ it.author }}</span>
|
||
{% endif %}
|
||
</div>
|
||
</footer>
|
||
</div>
|
||
</li>
|
||
{% endfor %}
|
||
</ol>
|
||
</article>
|
||
{% endfor %}
|
||
</section>
|
||
{% endif %}
|
||
|
||
{% if not reddit_sources and not tech_sources and not world_headlines and not local_headlines %}
|
||
<section class="desk empty">
|
||
<p class="empty-msg">
|
||
No items cleared the filters in the last window.<br>
|
||
Lower <code>DIGEST_MIN_SCORE</code> or widen
|
||
<code>DIGEST_REDDIT_HOURS</code> if this looks wrong.
|
||
</p>
|
||
</section>
|
||
{% endif %}
|
||
|
||
{# ────── HIDDEN TRAY ──────
|
||
Hidden by default. app.js unhides it once it has anything to show
|
||
(i.e. at least one .item on this page is in the user's hidden set).
|
||
Click the header to expand/collapse; click a row's restore button
|
||
to bring the item back into the desk it came from. #}
|
||
<section id="hidden-tray" class="hidden-tray" hidden>
|
||
<header class="hidden-tray-head">
|
||
<button class="hidden-tray-toggle" type="button" aria-expanded="false">
|
||
<span class="tray-glyph" aria-hidden="true">▾</span>
|
||
<span class="tray-label">Hidden</span>
|
||
<span class="tray-count" id="hidden-tray-count">0</span>
|
||
</button>
|
||
</header>
|
||
<ol class="hidden-tray-list" id="hidden-tray-list" hidden></ol>
|
||
</section>
|
||
|
||
</main>
|
||
|
||
<footer class="colophon">
|
||
<div class="colophon-inner">
|
||
<div class="colophon-block">
|
||
<span class="meta-label">FILED</span>
|
||
<span class="meta-value">{{ generated_at.isoformat(timespec="seconds") }}</span>
|
||
</div>
|
||
<div class="colophon-block">
|
||
<span class="meta-label">NEXT EDITION</span>
|
||
<span class="meta-value">{{ next_edition|upper }} · 12 H</span>
|
||
</div>
|
||
<div class="colophon-block">
|
||
<span class="meta-label">PIPELINE</span>
|
||
<span class="meta-value">REDDIT JSON + MINIFLUX → {{ model }} → JINJA2</span>
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
<script src="app.js?v={{ generated_at.strftime('%Y%m%d%H%M%S') }}" defer></script>
|
||
</body>
|
||
</html>
|