diff --git a/stacks/news-digest/digest.py b/stacks/news-digest/digest.py
index 4ffffcb..9826f00 100644
--- a/stacks/news-digest/digest.py
+++ b/stacks/news-digest/digest.py
@@ -353,6 +353,54 @@ def write_output(html: str, generated_at: datetime) -> None:
index_tmp.write_text(html, encoding="utf-8")
index_tmp.rename(index)
log(f"wrote {index} (and archive {archive.name})")
+ write_archive_index(generated_at)
+
+ARCHIVE_FNAME_RE = re.compile(r"^edition-(\d{4}-\d{2}-\d{2})-(am|pm)\.html$")
+
+def write_archive_index(generated_at: datetime) -> None:
+ """Render /output/archive.html — list every edition-*.html in
+ OUTPUT_DIR, newest-first. Cheap (re-runs every digest fire);
+ template loads from the same TEMPLATE_DIR."""
+ editions = []
+ for p in OUTPUT_DIR.glob("edition-*.html"):
+ m = ARCHIVE_FNAME_RE.match(p.name)
+ if not m:
+ continue
+ date_str, ed = m.group(1), m.group(2)
+ try:
+ date = datetime.strptime(date_str, "%Y-%m-%d").date()
+ except ValueError:
+ continue
+ editions.append({
+ "filename": p.name,
+ "date": date,
+ "edition": "morning" if ed == "am" else "evening",
+ "edition_short": ed.upper(),
+ "date_long": date.strftime("%A %B %-d, %Y"),
+ # Sort key: date descending, then PM before AM (within a day,
+ # PM is the most recent edition).
+ "_sort": (date, 1 if ed == "pm" else 0),
+ })
+ editions.sort(key=lambda e: e["_sort"], reverse=True)
+
+ env = Environment(
+ loader=FileSystemLoader(str(TEMPLATE_DIR)),
+ autoescape=select_autoescape(["html"]),
+ trim_blocks=True,
+ lstrip_blocks=True,
+ )
+ tpl = env.get_template("archive.html.j2")
+ html = tpl.render(
+ editions=editions,
+ generated_at=generated_at,
+ total=len(editions),
+ )
+
+ out = OUTPUT_DIR / "archive.html"
+ out_tmp = out.with_suffix(".html.tmp")
+ out_tmp.write_text(html, encoding="utf-8")
+ out_tmp.rename(out)
+ log(f"wrote {out} ({len(editions)} editions indexed)")
# ── main ─────────────────────────────────────────────────────────────
diff --git a/stacks/news-digest/templates/archive.html.j2 b/stacks/news-digest/templates/archive.html.j2
new file mode 100644
index 0000000..3272e01
--- /dev/null
+++ b/stacks/news-digest/templates/archive.html.j2
@@ -0,0 +1,83 @@
+
+
+
+
+
+ Daily Digest · Archive
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/stacks/news-digest/templates/digest.html.j2 b/stacks/news-digest/templates/digest.html.j2
index 9d479d6..749f971 100644
--- a/stacks/news-digest/templates/digest.html.j2
+++ b/stacks/news-digest/templates/digest.html.j2
@@ -17,6 +17,7 @@
diff --git a/stacks/news-digest/templates/style.css b/stacks/news-digest/templates/style.css
index 3857c3a..f313c7b 100644
--- a/stacks/news-digest/templates/style.css
+++ b/stacks/news-digest/templates/style.css
@@ -131,6 +131,14 @@ a:hover { color: var(--accent); }
gap: 18px;
font-family: var(--font-mono);
}
+.archive-link {
+ color: var(--fg-dim);
+ font-size: 11px;
+ letter-spacing: .14em;
+ border-bottom: 1px dotted var(--fg-faint);
+ padding-bottom: 1px;
+}
+.archive-link:hover { color: var(--accent); border-color: var(--accent-dim); }
.edition-num {
color: var(--fg-faint);
font-size: 11px;
@@ -400,26 +408,132 @@ a:hover { color: var(--accent); }
.item-foot {
display: flex;
flex-wrap: wrap;
- align-items: baseline;
- gap: 8px;
+ align-items: center;
+ gap: 16px;
font-family: var(--font-mono);
font-size: 10.5px;
letter-spacing: .06em;
color: var(--fg-faint);
}
-.item-link {
- color: var(--fg-dim);
- border-bottom: 1px dotted var(--fg-faint);
- padding-bottom: 1px;
+/* Action chips — explicit "go here" buttons. Reddit items get TWO of
+ these when the post links externally: SOURCE (the linked content)
+ and REDDIT THREAD (the discussion). Non-Reddit items get just SOURCE. */
+.item-actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
}
-.item-link:hover { color: var(--accent); border-color: var(--accent-dim); }
-.item-meta { font-variant-numeric: tabular-nums; }
+.action {
+ display: inline-flex;
+ align-items: baseline;
+ gap: 6px;
+ padding: 4px 10px;
+ border: 1px solid var(--rule-bright);
+ border-radius: 2px;
+ font-family: var(--font-mono);
+ font-size: 10.5px;
+ font-weight: 600;
+ letter-spacing: .08em;
+ text-transform: uppercase;
+ color: var(--fg-dim);
+ transition: border-color 120ms, color 120ms, background 120ms;
+}
+.action-arrow {
+ color: var(--accent-dim);
+ font-size: 11px;
+}
+.action-label { color: var(--fg); }
+.action-domain {
+ color: var(--fg-dim);
+ font-weight: 500;
+ letter-spacing: .04em;
+ text-transform: lowercase;
+}
+.action:hover {
+ border-color: var(--accent);
+ background: rgba(66, 220, 209, 0.08);
+}
+.action:hover .action-arrow,
+.action:hover .action-label,
+.action:hover .action-domain { color: var(--accent); }
+
+.action-source { /* default styling above is "source" */ }
+.action-thread .action-arrow { color: var(--aus-bright-blue); }
+.action-thread:hover { border-color: var(--aus-bright-blue); background: rgba(164, 196, 255, 0.08); }
+.action-thread:hover .action-arrow,
+.action-thread:hover .action-label { color: var(--aus-bright-blue); }
+
+.item-meta-row {
+ display: flex;
+ align-items: baseline;
+ gap: 8px;
+ margin-left: auto; /* pushes timestamp to the right */
+}
+.item-meta { font-variant-numeric: tabular-nums; color: var(--fg-faint); }
.sep { color: var(--rule-bright); }
/* Visual de-emphasize purely meme/skip items if any slip through. */
.item[data-tag="meme"] { opacity: .58; }
.item[data-tag="other"] { opacity: .85; }
+/* -------------------------------------------------------------------------
+ archive page — list of all editions
+ ------------------------------------------------------------------------- */
+
+.archive-list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: grid;
+ gap: 0;
+ border: 1px solid var(--rule);
+ border-radius: 2px;
+ overflow: hidden;
+}
+.archive-item + .archive-item { border-top: 1px solid var(--rule); }
+
+.archive-row {
+ display: grid;
+ grid-template-columns: 1fr auto auto;
+ align-items: center;
+ gap: 24px;
+ padding: 16px 20px;
+ background: var(--surface);
+ transition: background 120ms;
+}
+.archive-row:hover {
+ background: var(--surface-hi);
+ color: var(--fg);
+}
+
+.archive-date {
+ font-family: var(--font-display);
+ font-size: 17px;
+ font-weight: 500;
+ letter-spacing: -0.005em;
+ color: var(--fg);
+ font-variation-settings: "opsz" 36;
+}
+
+.archive-edition {
+ font-family: var(--font-mono);
+ font-size: 10.5px;
+ font-weight: 700;
+ letter-spacing: .14em;
+ padding: 3px 9px;
+ border: 1px solid currentColor;
+ border-radius: 2px;
+}
+.archive-edition-am { color: var(--aus-yellow); }
+.archive-edition-pm { color: var(--aus-bright-cyan); }
+
+.archive-arrow {
+ font-family: var(--font-mono);
+ color: var(--fg-faint);
+ font-size: 14px;
+}
+.archive-row:hover .archive-arrow { color: var(--accent); }
+
/* -------------------------------------------------------------------------
empty state + colophon
------------------------------------------------------------------------- */