Skip to content

Latest commit

 

History

History
114 lines (96 loc) · 9.4 KB

File metadata and controls

114 lines (96 loc) · 9.4 KB

PRD: Earth View News

Domain vocabulary in this document (Story, Article, Source, Location, Global Mode, Vietnam Mode, Tour, Reveal, Ingest) is defined in CONTEXT.md. Architectural decisions are recorded in docs/adr/.

Problem Statement

I want to feel the day's news happening across the world (and across Vietnam) at a glance, without reading a feed. Scrolling a list of headlines is flat and placeless — it doesn't convey where things are happening or give me a calm, ambient way to absorb a day's news.

Solution

A personal, localhost web app: a photorealistic 3D Earth rotating on a starfield (the "Terra" look). It runs a looping Tour of the day's Stories — the camera eases to a stop over each newsworthy Location, a red point appears, an arrow extends, and a sci-fi popup opens showing the story's image, headline, and a ~50-word summary sized for a 30-second read. Then it closes and the camera moves on. I can toggle between Global Mode (the whole Earth) and Vietnam Mode (the same globe flown down over Vietnam, sweeping north↔south).

Behind the scenes, an Ingest job runs every 5 minutes: it polls curated newspaper feeds, cleans each new Article with Defuddle, and uses DeepSeek to extract the single primary Location and a short summary, storing the result as a Story in a rolling 24-hour window.

User Stories

  1. As a viewer, I want a photorealistic 3D Earth rotating on a black starfield, so that the app feels like the "Terra" reference and is pleasant to leave running.
  2. As a viewer, I want the globe to run an automatic looping Tour with no input from me, so that I can watch it as an ambient display.
  3. As a viewer, I want the camera to ease to a smooth stop over each Story's Location, so that the motion feels natural rather than abrupt.
  4. As a viewer, I want a red point to appear at the exact city of a Story, so that I can see where the news is happening.
  5. As a viewer, I want an arrow to extend from the point to the popup, so that the reveal feels like a sci-fi interface.
  6. As a viewer, I want a popup showing the Story's image, headline, and a short summary, so that I understand the story at a glance.
  7. As a viewer, I want the summary sized to be readable in ~30 seconds, so that I can finish reading before the popup closes.
  8. As a viewer, I want each Reveal to last a fixed ~30 seconds then auto-close, so that the Tour advances on its own.
  9. As a viewer, I want the Tour to visit Stories ordered west→east by longitude in Global Mode, so that each stop is a small natural turn of the Earth.
  10. As a viewer, I want the Tour to loop back to the start after the last Story, so that the display runs indefinitely.
  11. As a viewer, I want to toggle between Global Mode and Vietnam Mode, so that I can focus on the news I care about.
  12. As a viewer, I want Vietnam Mode to fly the same globe down over Vietnam and pan between cities north↔south, so that I get a focused local view without a jarring change of visuals.
  13. As a viewer, I want Vietnam Mode to show only Vietnam Stories, so that the local view is not cluttered with world news.
  14. As a viewer, I want each loop to show the latest ~30 Stories, so that the loop stays short (~15 min) and always feels fresh.
  15. As a viewer, I want Stories no older than 24 hours, so that I only ever see the current day's news.
  16. As a viewer, I want the app to keep running gracefully when there are no Stories yet, so that a cold start shows a calmly rotating Earth rather than an error.
  17. As a viewer, I want each popup to show which Source the Story came from, so that I know who reported it.
  18. As a viewer, I want Vietnamese Stories shown in Vietnamese and world Stories in their original language, so that summaries read naturally.
  19. As the operator, I want an Ingest job to run every 5 minutes automatically, so that the news stays current without my intervention.
  20. As the operator, I want Ingest to discover new Articles from a curated list of famous world and Vietnamese newspaper feeds, so that the news is from sources I trust.
  21. As the operator, I want each Article cleaned by Defuddle before analysis, so that the summary and Location are based on real article text, not feed boilerplate.
  22. As the operator, I want DeepSeek to return the single primary Location for each Article as {city, country, lat, lng, confidence}, so that each Story has one accurate pin.
  23. As the operator, I want DeepSeek to also return a 40–60 word summary in the Article's original language, so that popups are consistent length.
  24. As the operator, I want each Article geocoded and summarized once and cached by URL, so that I don't pay repeatedly for the same Article.
  25. As the operator, I want Stories with no confident Location dropped, so that the globe never shows a pin in the wrong place.
  26. As the operator, I want Stories older than 24 hours removed on each Ingest, so that the store stays a rolling one-day window.
  27. As the operator, I want to edit the list of Sources in configuration, so that I can add or remove newspapers without code changes.
  28. As the operator, I want my DeepSeek API key supplied via environment configuration, so that no secret is committed to the repo.
  29. As the operator, I want a single command to run the whole app (Ingest + API + frontend), so that starting the display is trivial.

Implementation Decisions

  • One Node/TypeScript app + SQLite (see docs/adr/0001). A single process runs the Ingest cron, an HTTP API, and serves the built frontend. SQLite is the rolling 24-hour Story store.
  • Ingest pipeline (see docs/adr/0001): curated RSS feeds for discovery → Defuddle (npm package, in-process, with a linkedom DOM shim) to fetch + clean each Article → DeepSeek API for {city, country, lat, lng, confidence} + a 40–60 word summary in one call, cached by Article URL. Defuddle is not the news source; it is only a content cleaner.
  • Story store: SQLite table keyed by Article URL (natural dedup of re-fetches). Each Ingest deletes Stories older than 24h. Stories below a Location-confidence threshold are not stored.
  • Stories API: a read endpoint returning the current Tour set as JSON — the latest ~30 Stories, filtered by mode (all vs Vietnam-only). The tour cap is a configuration value.
  • Frontend: React + Vite + globe.gl (three.js). Global Mode renders all Stories; Vietnam Mode reuses the same globe, flown down and constrained over Vietnam (no separate map renderer).
  • Tour ordering: Global Mode sorts the current set by longitude (W→E); Vietnam Mode by latitude (N→S).
  • Scripted Tour, not free spin (see docs/adr/0002): the camera deterministically flies from Location to Location and eases to a halt; it does not spin at constant velocity and stop on reticle overlap.
  • Reveal: a staged sequence at each stop — camera settle → red point → arrow extend → popup (image, headline, Source, summary) → auto-close — on a fixed ~30s timeline exposed as configuration. After the last Story the Tour loops.
  • Configuration: Source list, tour cap, and dwell duration are config values; DEEPSEEK_API_KEY comes from the environment (.env, git-ignored).
  • Default mode: Global Mode on load.

Testing Decisions

Good tests here exercise external behavior at the highest seam, not implementation details, and avoid asserting on rendering internals.

  • Seam 1 — Ingest orchestration. A single runIngest(deps) entry point with the feed reader, Defuddle cleaner, DeepSeek client, and Story store injected as dependencies. Tested by feeding a canned RSS fixture plus stubbed Defuddle and DeepSeek responses and asserting the correct Stories (and only those) land in a temporary SQLite database — including: low-confidence Locations are dropped, re-fetched Articles are not duplicated, and Stories older than 24h are pruned.
  • Seam 2 — Tour / Reveal logic. The pure ordering functions (longitude for Global, latitude for Vietnam), the tour-cap selection, and the Reveal timeline state machine are tested in isolation, with no globe.gl involvement.
  • Not unit-tested: globe.gl/three.js rendering, camera easing visuals, and the exact popup styling — these are verified by eye when running the app.

No prior art exists yet (greenfield); these two seams establish the pattern.

Out of Scope

  • Deduplicating stories about the same event across multiple Sources — v1 may show several nearby pins for one big event; clustering is deferred.
  • Translation — summaries stay in the Article's original language.
  • Public deployment — v1 is a personal localhost display; the higher copyright/ToS bar of a public site is not addressed.
  • User accounts, favorites, history, search, or reading the full article in the app (the popup links out instead).
  • A second, dedicated 2D map renderer for Vietnam — Vietnam Mode reuses the globe.
  • Free physics-based globe spin (explicitly rejected in docs/adr/0002).

Further Notes

  • Build order is a visual slice first: build the globe + scripted Tour + Reveal against a handful of hardcoded stub Stories with known lat/lng, get it looking right, then swap the stub for the real Ingest pipeline.
  • Starter Source list to refine: Reuters, BBC, AP, Al Jazeera, The Guardian (world) + VnExpress, Tuổi Trẻ, Thanh Niên, VietnamNet (Vietnam).
  • Copyright posture: personal/local use only; popups show a short DeepSeek summary (not full text) plus Source attribution and a link out.