TUI News page: 2x2 grid of the four news briefs, one per quadrant
1ed966def06aa687b465245b368765f4f31af3c7
humdrum <me@humdrum.me> · 2026-07-08 17:15
parent 816c2461
TUI News page: 2x2 grid of the four news briefs, one per quadrant Replace the News page's headlines/briefs/feeds/hacker-news mix with a 2x2 grid of the four screamer brief categories (Politics/Tech/Sports/Culture), each in its own quadrant with the category name as the panel title. Drop the now-dead render_feeds/render_briefs helpers. Feeds still flows to the printed paper. TASK-037 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 files changed
- → News-page-2x2-grid-of-the-four-briefs-only.md +24 −0
@@ -0,0 +1,24 @@
+---
+id: TASK-037
+title: 'News page: 2x2 grid of the four briefs only'
+status: To Do
+assignee: []
+created_date: '2026-07-09 00:15'
+labels:
+ - feature
+dependencies: []
+priority: medium
+ordinal: 37000
+---
+
+## Description
+
+<!-- SECTION:DESCRIPTION:BEGIN -->
+Replace News page (headlines/briefs/feeds/hackernews) with a 2x2 grid of the four screamer brief categories, one per quadrant. Panel titles = category names. Removed dead render_feeds/render_briefs; feeds still feeds the paper.
+<!-- SECTION:DESCRIPTION:END -->
+
+## Acceptance Criteria
+<!-- AC:BEGIN -->
+- [ ] #1 News page shows 4 brief-category quadrants, titled by category
+- [ ] #2 No headlines/feeds/hackernews on News page
+<!-- AC:END -->
tui/dashboard.py +31 −54
@@ -395,27 +395,6 @@ return Text("No headlines.", style="dim italic")
return t
-def render_feeds(ps: list[dict], cap: int = 12) -> Text:
- t, seen, n = Text(), set(), 0
- for p in ps:
- for item in p.get("items", []):
- if n >= cap:
- break
- title = item.get("title", "?")
- if title in seen:
- continue
- seen.add(title)
- n += 1
- url = item.get("link", "")
- t.append("• ", style="dim")
- t.append(f"{title}\n", style=_link(url) if url else "")
- if src := item.get("source") or item.get("feedTitle"):
- t.append(f" {src}\n", style="dim")
- if n == 0:
- return Text("No feed items.", style="dim italic")
- return t
-
-
def render_hackernews(p: dict, cap: int = 10) -> Text:
stories = p.get("stories", [])[:cap]
if not stories:
@@ -542,19 +521,19 @@ t.append("\n")
return t
-def render_briefs(p: dict) -> Text:
- cats = [c for c in p.get("categories", []) if c.get("stories")]
- if not cats:
- return Text("No briefs.", style="dim italic")
+def render_brief_category(cat: dict) -> Text:
+ """One brief category for a News-page quadrant — the panel border carries
+ the category name, so this just lists its stories (title + full summary)."""
+ stories = cat.get("stories", [])
+ if not stories:
+ return Text("No stories.", style="dim italic")
t = Text()
- for cat in cats:
- t.append(f"{cat.get('name', '?').upper()}\n", style="bold")
- for s in cat.get("stories", [])[:3]:
- url = s.get("link") or ""
- t.append(" • ", style="dim")
- t.append(f"{s.get('title', '?')}\n", style=_link(url) if url else "")
- if summ := s.get("summary"):
- t.append(f" {summ[:120]}\n", style="dim")
+ for s in stories:
+ url = s.get("link") or ""
+ t.append("• ", style="dim")
+ t.append(f"{s.get('title', '?')}\n", style=_link(url) if url else "")
+ if summ := s.get("summary"):
+ t.append(f" {summ}\n\n", style="dim")
return t
@@ -1019,10 +998,7 @@ #sports { width: 2.5fr; }
#news { width: 2fr; }
#hackernews { width: 1fr; }
- #np-news { width: 1.25fr; }
- #np-briefs { width: 1fr; }
- #np-feeds { width: 1fr; }
- #np-hackernews { width: 1fr; }
+ #brief-0, #brief-1, #brief-2, #brief-3 { width: 1fr; }
#sys-cpu, #sys-memdisk, #sys-net, #sys-power { width: 1fr; }
@@ -1095,12 +1071,14 @@ yield DashPanel("Scores", "sports")
yield DashPanel("Headlines", "news")
yield DashPanel("Hacker News", "hackernews")
with Vertical(id="page-news"):
+ # 2×2 of the four news briefs from screamer — one per quadrant.
+ # Titles are set per-refresh from the brief category names.
with Horizontal(classes="row"):
- yield DashPanel("Headlines", "np-news")
- yield DashPanel("Briefs", "np-briefs")
+ yield DashPanel("Brief", "brief-0")
+ yield DashPanel("Brief", "brief-1")
with Horizontal(classes="row"):
- yield DashPanel("Feeds", "np-feeds")
- yield DashPanel("Hacker News", "np-hackernews")
+ yield DashPanel("Brief", "brief-2")
+ yield DashPanel("Brief", "brief-3")
with Vertical(id="page-system"):
with Horizontal(classes="row"):
yield DashPanel("CPU", "sys-cpu")
@@ -1142,27 +1120,26 @@ )
panel("sports").set_content(
render_sports(p) if (p := payload(sources, "sports")) else no_data
)
- feed_ps = payloads(sources, "feeds")
- panel("np-feeds").set_content(
- render_feeds(feed_ps, cap=24) if feed_ps else no_data
- )
news_ps = payloads(sources, "news")
panel("news").set_content(
render_news(news_ps) if news_ps else no_data
- )
- panel("np-news").set_content(
- render_news(news_ps, cap=24) if news_ps else no_data
)
hn_p = payload(sources, "hackernews")
panel("hackernews").set_content(
render_hackernews(hn_p) if hn_p else no_data
)
- panel("np-hackernews").set_content(
- render_hackernews(hn_p, cap=20) if hn_p else no_data
- )
- panel("np-briefs").set_content(
- render_briefs(p) if (p := payload(sources, "briefs")) else no_data
- )
+ # News page: one brief category per quadrant (config order).
+ briefs_p = payload(sources, "briefs")
+ brief_cats = (briefs_p or {}).get("categories", [])
+ for i in range(4):
+ pnl = panel(f"brief-{i}")
+ if i < len(brief_cats):
+ cat = brief_cats[i]
+ pnl.border_title = cat.get("name", "Brief")
+ pnl.set_content(render_brief_category(cat))
+ else:
+ pnl.border_title = "—"
+ pnl.set_content(no_data)
panel("uptime").set_content(
render_uptime(p) if (p := payload(sources, "uptime")) else no_data
)