From 7a9f447231b911b0c6d2df9e96fe145dd905d426 Mon Sep 17 00:00:00 2001 From: Matt Van Horn Date: Tue, 24 Feb 2026 19:41:14 -0800 Subject: [PATCH] fix(ordering): move HN after YouTube in stats, sort priority, and SKILL.md HN was appearing before YouTube in the stats block, sort tiebreaker, and source status. Now consistently: Reddit > X > YouTube > HN > Web. Also restored emoji + box-drawing chars in test skill SKILL.md. Co-Authored-By: Claude Opus 4.6 --- SKILL.md | 6 +- ...26-02-24-fix-hn-ordering-and-emoji-plan.md | 86 +++++++++++++++++++ scripts/lib/render.py | 16 ++-- scripts/lib/score.py | 6 +- scripts/lib/ui.py | 8 +- tests/test_hackernews.py | 10 +-- 6 files changed, 109 insertions(+), 23 deletions(-) create mode 100644 docs/plans/2026-02-24-fix-hn-ordering-and-emoji-plan.md diff --git a/SKILL.md b/SKILL.md index 3c06576..cdccedb 100644 --- a/SKILL.md +++ b/SKILL.md @@ -113,7 +113,7 @@ The script will automatically: - Run Reddit/X/YouTube/Hacker News searches - Output ALL results including YouTube transcripts and HN comments -**Read the ENTIRE output.** It contains FOUR data sections in this order: Reddit items, X items, Hacker News items, and YouTube items. If you miss sections, you will produce incomplete stats. +**Read the ENTIRE output.** It contains FIVE data sections in this order: Reddit items, X items, YouTube items, Hacker News items, and WebSearch items. If you miss sections, you will produce incomplete stats. **YouTube items in the output look like:** `**{video_id}** (score:N) {channel_name} [N views, N likes]` followed by a title, URL, and optional transcript snippet. Count them and include them in your synthesis and stats block. @@ -304,8 +304,8 @@ KEY PATTERNS from the research: ✅ All agents reported back! ├─ 🟠 Reddit: {N} threads │ {N} upvotes │ {N} comments ├─ 🔵 X: {N} posts │ {N} likes │ {N} reposts -├─ 🟡 HN: {N} stories │ {N} points │ {N} comments ├─ 🔴 YouTube: {N} videos │ {N} views │ {N} with transcripts +├─ 🟡 HN: {N} stories │ {N} points │ {N} comments ├─ 🌐 Web: {N} pages (supplementary) └─ 🗣️ Top voices: @{handle1} ({N} likes), @{handle2} │ r/{sub1}, r/{sub2} --- @@ -475,7 +475,7 @@ After delivering a prompt, end with: ``` --- 📚 Expert in: {TOPIC} for {TARGET_TOOL} -📊 Based on: {n} Reddit threads ({sum} upvotes) + {n} X posts ({sum} likes) + {n} HN stories ({sum} points) + {n} YouTube videos ({sum} views) + {n} web pages +📊 Based on: {n} Reddit threads ({sum} upvotes) + {n} X posts ({sum} likes) + {n} YouTube videos ({sum} views) + {n} HN stories ({sum} points) + {n} web pages Want another prompt? Just tell me what you're creating next. ``` diff --git a/docs/plans/2026-02-24-fix-hn-ordering-and-emoji-plan.md b/docs/plans/2026-02-24-fix-hn-ordering-and-emoji-plan.md new file mode 100644 index 0000000..bf863d6 --- /dev/null +++ b/docs/plans/2026-02-24-fix-hn-ordering-and-emoji-plan.md @@ -0,0 +1,86 @@ +--- +title: "fix: HN ordering and stats block emoji formatting" +type: fix +status: completed +date: 2026-02-24 +--- + +# fix: HN Ordering and Stats Block Emoji Formatting + +## Overview + +HN source appears before YouTube in several places (stats block, sort priority, source status) but should be last among the main sources (after YouTube, before Web). The test skill SKILL.md also has plain ASCII instead of box-drawing characters and colored circle emojis. + +Desired order everywhere: Reddit > X > YouTube > HN > Web + +## Problem Statement / Motivation + +When testing `/last30daysHN`, the stats block output shows: +- No ✅ emoji, no 🟠 🔵 🟡 🔴 🌐 🗣️ colored circles +- Plain `|-` instead of `├─` and `|` instead of `│` +- HN appears before YouTube in the stats + +The canonical format (established in commit `7c36866`) is: +``` +--- +✅ All agents reported back! +├─ 🟠 Reddit: {N} threads │ {N} upvotes │ {N} comments +├─ 🔵 X: {N} posts │ {N} likes │ {N} reposts +├─ 🔴 YouTube: {N} videos │ {N} views │ {N} with transcripts +├─ 🟡 HN: {N} stories │ {N} points │ {N} comments +├─ 🌐 Web: {N} pages (supplementary) +└─ 🗣️ Top voices: @{handle1} ({N} likes), @{handle2} │ r/{sub1}, r/{sub2} +--- +``` + +## Technical Approach + +### Files to Modify + +#### `scripts/lib/score.py` - sort_items() + +- [x] Swap HN and YouTube priority in `sort_items()`: + - YouTube: priority 2 (was 3) + - HN: priority 3 (was 2) + +#### `scripts/lib/render.py` - render_source_status() + +- [x] Move HN section (lines ~318-324) to AFTER YouTube section (lines ~326-334) +- [x] Verify render_compact() already has correct order (YouTube before HN) - no change expected + +#### `SKILL.md` (private repo root) + +- [x] Move the 🟡 HN stats line AFTER the 🔴 YouTube stats line in the template +- [x] Move HN line after YouTube in the footer summary template too + +#### `~/.claude/skills/last30daysHN/SKILL.md` (test skill) + +- [x] Restore full emoji + box-drawing stats template with correct ordering +- [x] Use canonical format: ✅ ├─ 🟠 🔵 🔴 🟡 🌐 🗣️ └─ │ + +#### `scripts/lib/ui.py` - show_complete() + +- [x] Move HN output after YouTube in both TTY and non-TTY code paths + +### Files that are already correct (no change needed) + +- `render_compact()` in render.py - already YouTube before HN +- `render_context_snippet()` - score-based ordering, no fixed order +- `last30days.py` pipeline - processing order doesn't affect display + +## Acceptance Criteria + +- [x] Stats block shows: Reddit > X > YouTube > HN > Web (in that order) +- [x] Stats block has all emojis: ✅ 🟠 🔵 🔴 🟡 🌐 🗣️ +- [x] Stats block uses box-drawing chars: ├─ └─ │ +- [x] sort_items() tiebreaker: YouTube before HN +- [x] render_source_status() shows YouTube before HN +- [x] ui.py show_complete() shows YouTube before HN +- [x] All existing tests still pass +- [x] Run sync.sh to deploy after fixes + +## Sources & References + +- Canonical emoji format established in commit `7c36866` ("Fix v2 output quality") +- YouTube emoji added in commit `c66ca7f` ("feat: Add YouTube as 4th research source") +- HN added in commit `38a7ea2` ("feat(hackernews): add Hacker News as 5th research source") diff --git a/scripts/lib/render.py b/scripts/lib/render.py index e51aa80..58823f0 100644 --- a/scripts/lib/render.py +++ b/scripts/lib/render.py @@ -315,14 +315,6 @@ def render_source_status(report: schema.Report, source_info: dict = None) -> str reason = source_info.get("x_skip_reason", "No Bird CLI or XAI_API_KEY") lines.append(f" ⏭️ X: skipped — {reason}") - # Hacker News - if report.hackernews_error: - lines.append(f" ❌ HN: error - {report.hackernews_error}") - elif report.hackernews: - lines.append(f" ✅ HN: {len(report.hackernews)} stories") - else: - lines.append(" ⏭️ HN: 0 stories found") - # YouTube if report.youtube_error: lines.append(f" ❌ YouTube: error — {report.youtube_error}") @@ -333,6 +325,14 @@ def render_source_status(report: schema.Report, source_info: dict = None) -> str reason = source_info.get("youtube_skip_reason", "yt-dlp not installed (brew install yt-dlp)") lines.append(f" ⏭️ YouTube: skipped — {reason}") + # Hacker News + if report.hackernews_error: + lines.append(f" ❌ HN: error - {report.hackernews_error}") + elif report.hackernews: + lines.append(f" ✅ HN: {len(report.hackernews)} stories") + else: + lines.append(" ⏭️ HN: 0 stories found") + # Web if report.web_error: lines.append(f" ❌ Web: error — {report.web_error}") diff --git a/scripts/lib/score.py b/scripts/lib/score.py index a497b26..efca6d6 100644 --- a/scripts/lib/score.py +++ b/scripts/lib/score.py @@ -412,14 +412,14 @@ def sort_items(items: List[Union[schema.RedditItem, schema.XItem, schema.WebSear date = item.date or "0000-00-00" date_key = -int(date.replace("-", "")) - # Tertiary: source priority (Reddit > X > HN > YouTube > WebSearch) + # Tertiary: source priority (Reddit > X > YouTube > HN > WebSearch) if isinstance(item, schema.RedditItem): source_priority = 0 elif isinstance(item, schema.XItem): source_priority = 1 - elif isinstance(item, schema.HackerNewsItem): - source_priority = 2 elif isinstance(item, schema.YouTubeItem): + source_priority = 2 + elif isinstance(item, schema.HackerNewsItem): source_priority = 3 else: # WebSearchItem source_priority = 4 diff --git a/scripts/lib/ui.py b/scripts/lib/ui.py index 6382757..07eba20 100644 --- a/scripts/lib/ui.py +++ b/scripts/lib/ui.py @@ -289,17 +289,17 @@ class ProgressDisplay: sys.stderr.write(f"{Colors.DIM}({elapsed:.1f}s){Colors.RESET}\n") sys.stderr.write(f" {Colors.YELLOW}Reddit:{Colors.RESET} {reddit_count} threads ") sys.stderr.write(f"{Colors.CYAN}X:{Colors.RESET} {x_count} posts") - if hn_count: - sys.stderr.write(f" {Colors.YELLOW}HN:{Colors.RESET} {hn_count} stories") if youtube_count: sys.stderr.write(f" {Colors.RED}YouTube:{Colors.RESET} {youtube_count} videos") + if hn_count: + sys.stderr.write(f" {Colors.YELLOW}HN:{Colors.RESET} {hn_count} stories") sys.stderr.write("\n\n") else: parts = [f"Reddit: {reddit_count} threads", f"X: {x_count} posts"] - if hn_count: - parts.append(f"HN: {hn_count} stories") if youtube_count: parts.append(f"YouTube: {youtube_count} videos") + if hn_count: + parts.append(f"HN: {hn_count} stories") sys.stderr.write(f"✓ Research complete ({elapsed:.1f}s) - {', '.join(parts)}\n") sys.stderr.flush() diff --git a/tests/test_hackernews.py b/tests/test_hackernews.py index 9272e6b..ff776dd 100644 --- a/tests/test_hackernews.py +++ b/tests/test_hackernews.py @@ -197,8 +197,8 @@ class TestScoreHackernewsItems(unittest.TestCase): class TestSortItemsWithHN(unittest.TestCase): - def test_hn_priority_between_x_and_youtube(self): - """HN should sort between X and YouTube at same score.""" + def test_hn_priority_after_youtube(self): + """HN should sort after YouTube at same score.""" x_item = schema.XItem(id="X1", text="test", url="", author_handle="user") x_item.score = 50 @@ -209,10 +209,10 @@ class TestSortItemsWithHN(unittest.TestCase): yt_item.score = 50 sorted_items = score.sort_items([yt_item, hn_item, x_item]) - # Same score, so sorted by source priority: X > HN > YouTube + # Same score, so sorted by source priority: X > YouTube > HN self.assertIsInstance(sorted_items[0], schema.XItem) - self.assertIsInstance(sorted_items[1], schema.HackerNewsItem) - self.assertIsInstance(sorted_items[2], schema.YouTubeItem) + self.assertIsInstance(sorted_items[1], schema.YouTubeItem) + self.assertIsInstance(sorted_items[2], schema.HackerNewsItem) if __name__ == "__main__":