Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Preview deployment for your docs. Learn more about Mintlify Previews.
|
Greptile SummaryThis PR introduces E3 story persistence tracking: a Redis-backed pipeline that hashes story titles across digest cycles, accumulates mention/source counts, derives lifecycle phases (BREAKING → DEVELOPING → SUSTAINED → FADING), and surfaces them as Two P1 issues need to be resolved before merging:
Confidence Score: 4/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant Client
participant buildDigest
participant sha256Hex
participant Redis
Client->>buildDigest: GET /list-feed-digest
Note over buildDigest: Fetch & parse all RSS feeds
buildDigest->>sha256Hex: normalizeTitle(item.title) × N items
sha256Hex-->>buildDigest: titleHash (16-char hex)
Note over buildDigest: Build corroborationMap<br/>titleHash → Set<sourceName>
buildDigest->>Redis: HMGET story:track:v1:<hash> × uniqueHashes (pipeline)
Redis-->>buildDigest: existing StoryTrack data
Note over buildDigest: Derive phase per track<br/>Attach StoryMeta to proto items<br/>Truncate to MAX_ITEMS_PER_CATEGORY
buildDigest-->>Client: ListFeedDigestResponse (with StoryMeta)
Note over buildDigest: Fire-and-forget write (async)
buildDigest->>Redis: HINCRBY mentionCount 1<br/>HSET lastSeen/sourceCount/severity<br/>HSETNX firstSeen/currentScore/peakScore<br/>SADD story:sources:v1:<hash><br/>EXPIRE ×2 (48h TTL)<br/>per story in 80-item pipeline chunks
|
Adds cross-cycle story tracking layer to the RSS digest pipeline: - Proto: StoryMeta message + StoryPhase enum on NewsItem (fields 9-11). importanceScore and corroborationCount stubs added for E1. - list-feed-digest.ts: builds corroboration map across ALL items before truncation; batch-reads existing story:track hashes from Redis; writes HINCRBY/HSET/HSETNX/SADD/EXPIRE per story in 80-story pipeline chunks; attaches StoryMeta (firstSeen, mentionCount, sourceCount, phase) to each proto item using read-back data. - cache-keys.ts: STORY_TRACK_KEY_PREFIX, STORY_SOURCES_KEY_PREFIX, DIGEST_ACCUMULATOR_KEY_PREFIX, STORY_TRACKING_TTL_S. - src/types/index.ts: StoryMeta, StoryPhase, NewsItem extended. - data-loader.ts: protoItemToNewsItem maps STORY_PHASE_* → client phase. - NewsPanel.ts: BREAKING/DEVELOPING/ONGOING phase badges in item rows. New story first appearance: phase=BREAKING. After 2 mentions within 2h: DEVELOPING. After 6+ mentions or >2h: SUSTAINED. If score drops below 50% of peak: FADING (used by E1; defaults to SUSTAINED for now). Redis keys per story (48h TTL): story:track:v1:<hash16> → hash (firstSeen,lastSeen,mentionCount,...) story:sources:v1:<hash16> → set (feed names, for cross-source count)
P1 — storyMeta was always one cycle behind because storyTracks was read before writeStoryTracking ran. Fix: keep read-before-write but compute storyMeta from merged in-memory state (stale.mentionCount + 1, fresh sourceCount from corroborationMap). New stories get mentionCount=1 and phase=BREAKING in the same cycle they first appear — no extra Redis round-trip needed. P2 — mentionCount incremented once per item occurrence, so a story seen in 3 sources in its first cycle was immediately stored as mentionCount=3. Fix: deduplicate by titleHash in writeStoryTracking so each unique story gets exactly one HINCRBY per digest cycle regardless of source count. SADD still collects all sources for the set key.
P1 — normalizeTitle used [^\w\s] without the u flag; \w is ASCII-only
so every Arabic/CJK/Cyrillic title stripped to "" and shared one Redis
hash. Fixed: use /[^\p{L}\p{N}\s]/gu (Unicode property escapes require
the u flag).
P1 — ALERT badge was gated on !item.storyMeta, suppressing the indicator
for any tracked story regardless of isAlert. Phase and alert are
orthogonal signals; ALERT now renders unconditionally when isAlert=true.
P2 — FADING branch is intentionally inactive until E1 ships real scores
(currentScore/peakScore placeholder 0 via HSETNX). Added comment to
document the intentional ordering.
…ectBest Sustained and fading story phases are already well-covered by the feed; only breaking and developing phases warrant a banner interrupt. Items without storyMeta (phase unspecified) pass through unchanged. Fixes gap C from docs/plans/2026-04-02-003-fix-news-alerts-pr-gaps-plan.md
Removes a stray closing brace, duplicate ASCII normalizeTitle (Unicode-aware version from the fix commit is correct), and a leftover storyPhase assignment that references a removed field. All typecheck and typecheck:api pass clean.
1b58fc2 to
863719f
Compare
…dges All other PR changes (types, data-loader cast, NewsPanel) are now in main via E3 (#2620) and E1 (#2621). Only the CSS for .phase-badge.breaking/.developing/.sustained was missing. Class names corrected to match NewsPanel.ts output (phase-badge + modifier vs the original phase-breaking/phase-developing selectors).
…dges (#2608) All other PR changes (types, data-loader cast, NewsPanel) are now in main via E3 (#2620) and E1 (#2621). Only the CSS for .phase-badge.breaking/.developing/.sustained was missing. Class names corrected to match NewsPanel.ts output (phase-badge + modifier vs the original phase-breaking/phase-developing selectors).
Summary
StoryMetamessage +StoryPhaseenum added toNewsItem(fields 9-11).importanceScoreandcorroborationCountstubs added for E1.list-feed-digest.ts: Builds a corroboration map across ALL items before truncation (so cross-category mentions of the same story are visible). Batch-reads existingstory:trackhashes from Redis and attachesStoryMetato each proto item. WritesHINCRBY/HSET/HSETNX/SADD/EXPIREper story in 80-story pipeline chunks (~9 commands/story).cache-keys.ts:STORY_TRACK_KEY_PREFIX,STORY_SOURCES_KEY_PREFIX,DIGEST_ACCUMULATOR_KEY_PREFIX,STORY_TRACKING_TTL_S(48h).src/types/index.ts:StoryMeta,StoryPhasetypes;NewsItemextended withimportanceScore?,corroborationCount?,storyMeta?.data-loader.ts:protoItemToNewsItemmapsSTORY_PHASE_*→ clientStoryPhase.NewsPanel.ts: Phase badges — BREAKING (red), DEVELOPING ×N (orange), ONGOING (muted).How story phases work
BREAKINGDEVELOPINGSUSTAINEDFADINGRedis keys per story (48h TTL)
First request writes the keys. Second request (15 min later, after cache miss) reads them back and populates
StoryMetain the response.Dependency
This is the Redis foundation E1 (Composite Importance Score) needs. E1 will overwrite
currentScore/peakScorein the hash and addZADD GTonstory:peak:v1:<hash>when it ships.Test plan
typecheck+typecheck:apipass (both clean)test:datapasses (2748 tests)story:track:v1:*keys appear in Upstash within 15 min of first digest buildPost-Deploy Monitoring & Validation
[list-feed-digest] writeStoryTracking— should be silent on success, warn on Redis failureKEYS story:track:v1:*count should be 200-500 within one digest cyclewriteStoryTracking failedappears in logs, story tracking is not writing but digest delivery is unaffected (fire-and-forget error path)