Customizable nav lanes: pin moods / topics / discovery tags

Readers can now choose which quick-access lanes sit above the feed; "Today"
stays pinned. The pool (goodnews/lanes.py, served at /api/lanes) is one source
of truth over three lane kinds the feed already renders: moods, primary topics,
and high-volume Discovery tags. Selection lives in the existing prefs blob
(localStorage + /api/prefs sync); the filter parser ignores the new `lanes`
field, so it rides along harmlessly. Default = today's moods, unchanged.

Food/Space stay grouping tags rather than primary topics (per review): `space`
already existed; added `food` to the Mind & Craft family so the classifier
assigns it, and seeded the Food lane by re-tagging the two food sources.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-06 18:19:58 +00:00
parent 8653a46fd4
commit 722bcf6317
9 changed files with 289 additions and 6 deletions
+13
View File
@@ -37,6 +37,7 @@ from .filters import filter_articles, prefs_from_json
from .hero import safe_to_lead
from .llm import LocalModelClient
from .moods import MOODS, mood_filter
from .lanes import build_lane_pool
from .paywall import is_paywalled
from .taxonomy import FAMILIES, FLAVORS, TOPICS
@@ -866,6 +867,18 @@ def create_app() -> FastAPI:
# client merges with the user's own Calm Filters.
return MOODS
@app.get("/api/lanes")
def lanes() -> dict:
# The customizable quick-access rail: 'today' is always pinned, and the
# reader pins any subset of these moods / topics / Discovery tags. Live
# counts let the client gate empty lanes and show volume.
with get_conn() as conn:
tagc = queries.tag_counts(conn)
topicc: dict[str, int] = {}
for row in queries.category_counts(conn):
topicc[row["topic"]] = topicc.get(row["topic"], 0) + int(row["count"])
return build_lane_pool(topicc, tagc)
@app.get("/api/families")
def families() -> list[dict]:
# Grouping vocabulary organised into calm families for the Explore UI.