Files
jarvis/docs/BACKLOG.md
Kutesir afec870919
All checks were successful
CI / Build · Debug (push) Successful in 22s
CI / Build · Release (push) Successful in 22s
fix: display story age from firstSeenAt, not updatedAt
Old stories were reading "1 hr ago" because updatedAt bumps every time
the backend attaches another article to a cluster, however loosely
related. firstSeenAt is when the cluster actually first appeared and
is already in the API response — just wasn't decoded.

Verified live: story_1a37c858235b (F1 Austria GP recap) has
firstSeenAt 2026-06-16 but updatedAt 2026-07-11, freshnessScore 0.96 —
looked brand new despite being 25 days old. Also confirmed the API's
isStale field is unusable as a stopgap (always false regardless of age).

Ranking still surfaces old stories at the top — that's server-side
(signalScore/freshnessScore) and out of scope for the client per the
architecture rule against re-ranking. Logged in BACKLOG #15.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 01:51:35 +03:00

6.0 KiB
Raw Permalink Blame History

Backlog & status

Source-of-truth tracker. The Gitea issue tracker is currently empty (0 issues) — run scripts/create-issues.sh with a token to populate it. [client] = iOS, [backend] = the platform service on the Docker host, [ci] = build/test.

Status: 🔴 open · 🟡 mitigated client-side (real fix still pending) · 🟢 done.

Backend — data quality (highest priority)

  1. 🟡 [backend] F1 mis-classified into tech/finance/politics. No sport category, so F1 (100% F1 sources) scatters across topics: "Monaco Grand Prix…" → tech, "F1 reports 35% CO2…" → finance, "McLaren launch appeal…" → politics. Add a sport/f1 topic and route motorsport sources there. Client mitigation shipped: F1 pill matches clear F1 headlines + a majority of F1 sources, and F1 is excluded from All/Tech/Finance. Backend still mis-tags.
  2. 🔴 [backend] Catastrophic over-clustering — unrelated articles merged on a single shared word. Newly confirmed on-device: a story merged a Russian-film-director article + "Who's afraid of Michael Jackson?" + "Chelsea Jackson Roberts" (wellness) — grouped on the surname Jackson. Another merged "Gunfire at Niger airport" + "L.A. County Whiteman Airport crash" + "Niger fintech". Result: wrong headline, summary, topic, and inflated signal. Raise the TF-IDF threshold hard (~0.22 → 0.450.55) and require multiple shared distinctive terms / named-entity overlap, with a tight time window. If still over-merging, move to sentence embeddings (all-MiniLM-L6-v2, cosine ≥ ~0.6). Make canonical headline + summary come from the same representative article. No client fix is possible — the story object itself is wrong.
  3. 🔴 [backend] Add accurate per-story tags (topic + region). So the client can read tags (sport, uganda, south-africa, …) instead of keyword-guessing. Unblocks #6.
  4. 🔴 [backend] Topic-classifier coverage for the 47 feeds. science/world/tech mapping.
  5. 🔴 [backend] Freshness score decays from lastUpdatedAt (cluster last touched), not from the actual news event age — old stories never leave the top of the feed. Confirmed live on /api/v1/stories: 13 of the top 20 stories are 5+ days old by firstSeenAt but still score 90+; the #1 story (story_511c67eb064d) has firstSeenAt: 2026-06-17, lastUpdatedAt: 2026-07-09 (21 days later), freshnessScore: 1.0, signal 100. Compounds with #2 (over-clustering): a loose cluster keeps absorbing tangential articles indefinitely (this story's timeline includes "Security forces ready for June 30 marches: Ramaphosa" — unrelated to the football story it's attached to), each attachment resets lastUpdatedAt to now, so the cluster's clock never runs out and its canonical headline drifts to whatever was clustered last (list view showed "South Africa stun South Korea…"; story detail showed "Updated FIFA rankings after Bafana Bafana exit" for the same story_id). Fix: decay freshness from firstSeenAt (or the newest article whose relevance to the cluster core is verified), not from "last touched." Tightening the TF-IDF threshold in #2 should also reduce how often unrelated articles keep a stale cluster's clock alive. Also confirmed the API's own isStale field is unusable as a stopgap — it reads false on every story checked regardless of actual age, so it isn't computed from a real staleness threshold. Client mitigation shipped: age display (row meta line, hero card) now reads from firstSeenAt instead of updatedAt, so the UI no longer shows "1 hr ago" on stories that are actually weeks old. Ranking is still wrong — old stories still surface at the top of the feed, because signalScore/freshnessScore (used for ordering) are computed server-side and the client must not re-rank (architecture rule). The real fix is still backend-side.

Backend — features

  1. 🔴 [backend] Server-side push for breaking alerts (APNs or ntfy). For breaking-story notifications while the app is closed. Client does local notifications + background refresh only.

Client

  1. 🔴 [client] Move interest filters server-side once tags (#3) exist. Today's pills are client-side keyword/headline matches on loaded stories. Refined since: regions match headline-only (no polluted summaries/outlets), regions exclusive, F1 by source majority, East Africa removed.
  2. 🔴 [client] SSID-based trusted networks. Needs Access WiFi Information entitlement (device + paid account). Approximated by LAN reachability today.
  3. 🔴 [client] Optional in-app WireGuard tunnel. NetworkExtension; entitlement + device. Deferred.
  4. 🔴 [client] App icon + launch assets. AppIcon is an empty placeholder.
  5. 🔴 [client] Flesh out Latest / Search tabs. Saved is now bookmarks; Latest/Search still minimal.
  6. 🔴 [client] Verify hero image loading. AsyncImage vs real feed imageUrls.
  7. 🔴 [client] Offline-first polish. Pre-cache top stories.

CI

  1. 🔴 [ci] Add unit/UI tests + a test matrix leg. Build matrix compiles, runs no tests.
  2. 🔴 [ci] Backend CI build matrix. Python 3.113.13 + lint + Docker build/health probe.

🟢 Shipped

Signal feed + detail + reader + feed manager · LAN⇄Tailscale connectivity · notifications (morning/evening briefings + breaking alerts) with Open-Meteo weather · device code signing · feed resilience (no blank-on-refresh, ignore cancelled, coalesced WS refresh) · clearer empty states (polling / unreachable+host / offline / caught-up) · deterministic feed order · 6-line summary preview · pinned filters (F1 split out, regions exclusive & headline-matched, Finance/Tech by topic) · swipe actions (short→Save / long→Mark read, right→Share) · read/seen handling (seen-tracking with per-load snapshot, read stories collapse into a "Read" shelf with compact rows, "all caught up" state).