diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1d6c6c6..1366216 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,13 @@ former name, "Daybook".)
_Nothing yet._
+## [0.6.2] — 2026-09-07
+
+### Changed
+- Source sidebar is now ordered by **most recent message** (Telegram-style)
+ instead of alphabetically; "All sources" stays pinned at the top. Live
+ updates move a source to the top when it receives a new message.
+
## [0.6.1] — 2026-09-07
### Changed
@@ -214,7 +221,8 @@ First tagged release.
- Dockerised (Gunicorn + whitenoise), SQLite, `git`-based deploy with a
runbook (`deploy/cloud_agent_deploy.md`) and health check.
-[Unreleased]: https://git.cygnus.systems/peter/daybook/compare/v0.6.1...HEAD
+[Unreleased]: https://git.cygnus.systems/peter/daybook/compare/v0.6.2...HEAD
+[0.6.2]: https://git.cygnus.systems/peter/daybook/compare/v0.6.1...v0.6.2
[0.6.1]: https://git.cygnus.systems/peter/daybook/compare/v0.6.0...v0.6.1
[0.6.0]: https://git.cygnus.systems/peter/daybook/compare/v0.5.1...v0.6.0
[0.5.1]: https://git.cygnus.systems/peter/daybook/compare/v0.5.0...v0.5.1
diff --git a/artifacts/views.py b/artifacts/views.py
index 1d2aff0..055c861 100644
--- a/artifacts/views.py
+++ b/artifacts/views.py
@@ -19,7 +19,7 @@ from django.contrib.auth import views as auth_views
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.db import OperationalError, transaction
-from django.db.models import Count, Q, Sum
+from django.db.models import Count, Max, Q, Sum
from django.http import FileResponse, Http404, HttpResponse, HttpResponseForbidden, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.template.defaultfilters import date as date_filter, slugify
@@ -586,10 +586,12 @@ def feed(request):
source_scoped = apply_feed_filters(
live, "", selected_label, selected_kind, title_query
)
+ # Sources ordered by most recent message first (Telegram-style); "All
+ # sources" is rendered separately and stays pinned at the top.
source_counts = (
source_scoped.values("source")
- .annotate(count=Count("id", filter=Q(read_at__isnull=True)))
- .order_by("source")
+ .annotate(count=Count("id", filter=Q(read_at__isnull=True)), latest=Max("created_at"))
+ .order_by("-latest")
)
keep_count = live.filter(is_kept=True).count()
trash_count = Artifact.objects.filter(owner=request.user, deleted_at__isnull=False).count()
diff --git a/daybook_project/__init__.py b/daybook_project/__init__.py
index 6227cb2..28b8174 100644
--- a/daybook_project/__init__.py
+++ b/daybook_project/__init__.py
@@ -1,4 +1,4 @@
# Single source of truth for the app version. Bump this, commit, then tag the
# commit `vX.Y.Z` and deploy. Surfaced on Settings -> About and in
# GET /api/capabilities ("version").
-__version__ = "0.6.1"
+__version__ = "0.6.2"
diff --git a/templates/artifacts/feed.html b/templates/artifacts/feed.html
index 54b3d20..a8e9cca 100644
--- a/templates/artifacts/feed.html
+++ b/templates/artifacts/feed.html
@@ -443,15 +443,17 @@
`${escapeHtml(name)}` +
`${count}`;
- // Insert alphabetically among the per-source links (after "All sources").
+ // Newest activity goes to the top of the per-source list (just under
+ // the pinned "All sources"), matching the recency ordering.
const list = document.querySelector(".source-list");
- const others = sourceLinks().filter(
- (l) => l.querySelector(".source-name")?.textContent.trim() !== "All sources",
- );
- const after = others.find(
- (l) => (l.querySelector(".source-name")?.textContent.trim() || "") > name,
- );
- list.insertBefore(link, after || null);
+ const firstSource = list.querySelector(".source-link:not(.all-sources-link)");
+ list.insertBefore(link, firstSource);
+ };
+
+ const moveSourceToTop = (link) => {
+ const list = document.querySelector(".source-list");
+ const firstSource = list.querySelector(".source-link:not(.all-sources-link)");
+ if (firstSource !== link) list.insertBefore(link, firstSource);
};
// From the newly-arrived rows: bump each source's unread count (adding a
@@ -466,8 +468,12 @@
Object.entries(counts).forEach(([source, n]) => {
total += n;
const existing = findSourceLink(source);
- if (existing) bumpSourceBadge(existing, n);
- else addSourceLink(source, n);
+ if (existing) {
+ bumpSourceBadge(existing, n);
+ moveSourceToTop(existing); // most-recent activity -> top (recency order)
+ } else {
+ addSourceLink(source, n);
+ }
});
const allLink = findSourceLink("All sources");
if (allLink && total) bumpSourceBadge(allLink, total);