Persist replacements across refresh (device-local, no account)

A reader who swaps a story away should keep that swap after a refresh; before,
the server re-served the original brief.
- localStorage now persists seen / dismissed / history (loadJSON/saveJSON).
- /api/brief accepts an exclude list; dismissed (replaced-away) ids are dropped
  and the highlights refill around them, so swaps stick and stay full.
- Replace records the swap to dismissed+seen and persists; the seen-set
  (persisted) keeps Replace from recycling across refreshes too.
- History panel survives refresh and gains 'Clear what I've seen (start fresh)'
  so it never feels suffocating. Saved history/favorites still come with sign-in.

Tests: brief exclude + refill (90 total).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jay
2026-05-31 13:22:41 +00:00
parent 803da64e16
commit 0ccd5554d2
4 changed files with 94 additions and 23 deletions
+8
View File
@@ -48,3 +48,11 @@ def test_brief_refill_respects_avoid_terms(client):
client_resp = client.get("/api/brief", params={"limit": 3, "prefs": json.dumps({"avoid_terms": ["t1"]})}).json()
assert len(client_resp["items"]) == 3
assert all(i["id"] != 1 for i in client_resp["items"])
def test_brief_excludes_dismissed_and_refills(client):
# Dismiss the first brief item; the highlights should stay full and not show it.
out = client.get("/api/brief", params={"limit": 3, "exclude": "1"}).json()
ids = [i["id"] for i in out["items"]]
assert len(out["items"]) == 3
assert 1 not in ids