v0.7.0: mark all as read by clicking the unread badge

- Click a source's unread count in the sidebar to mark all of that source's
  messages read (clears the badge + reduces the All-sources total); click the
  All-sources total to mark everything read.
- POST /feed/mark-read (session, owner-scoped, excludes trashed). Visible feed
  rows drop their unread state too. Badge is keyboard-activatable (Enter/Space).

156 tests pass (4 new); inline JS re-validated with node --check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
peter 2026-09-07 22:26:48 +01:00
parent 7b161f1276
commit 89936ebf58
7 changed files with 123 additions and 5 deletions

View file

@ -128,3 +128,31 @@ class TrashKeepViewTestCase(TestCase):
self.client.post(reverse("artifact-delete", args=[fresh.id]))
self.assertFalse(Artifact.objects.filter(pk=old.id).exists())
self.assertTrue(Artifact.objects.filter(pk=fresh.id, deleted_at__isnull=False).exists())
def test_mark_all_read_across_sources(self):
self.make(source="a")
self.make(source="b")
r = self.client.post(reverse("feed-mark-read"))
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()["marked"], 2)
self.assertFalse(Artifact.objects.filter(owner=self.user, read_at__isnull=True).exists())
def test_mark_all_read_scoped_to_source(self):
a = self.make(source="hermes")
b = self.make(source="janus")
self.assertEqual(self.client.post(reverse("feed-mark-read"), {"source": "hermes"}).json()["marked"], 1)
a.refresh_from_db()
b.refresh_from_db()
self.assertIsNotNone(a.read_at)
self.assertIsNone(b.read_at)
def test_mark_all_read_owner_scoped(self):
theirs = self.make(owner=self.other)
self.client.post(reverse("feed-mark-read"))
theirs.refresh_from_db()
self.assertIsNone(theirs.read_at)
def test_mark_all_read_excludes_trashed(self):
a = self.make()
Artifact.objects.filter(pk=a.id).update(deleted_at=timezone.now())
self.assertEqual(self.client.post(reverse("feed-mark-read")).json()["marked"], 0)