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

93 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.
15. 🔴 **[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
5. 🔴 **[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
6. 🔴 **[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.
7. 🔴 **[client] SSID-based trusted networks.** Needs Access WiFi Information entitlement
(device + paid account). Approximated by LAN reachability today.
8. 🔴 **[client] Optional in-app WireGuard tunnel.** NetworkExtension; entitlement + device. Deferred.
9. 🔴 **[client] App icon + launch assets.** `AppIcon` is an empty placeholder.
10. 🔴 **[client] Flesh out Latest / Search tabs.** Saved is now bookmarks; Latest/Search
still minimal.
11. 🔴 **[client] Verify hero image loading.** `AsyncImage` vs real feed `imageUrl`s.
12. 🔴 **[client] Offline-first polish.** Pre-cache top stories.
## CI
13. 🔴 **[ci] Add unit/UI tests + a test matrix leg.** Build matrix compiles, runs no tests.
14. 🔴 **[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).