Compare commits

...

1 commit

Author SHA1 Message Date
c1be2face7 v0.6.2: order sources by most recent message (recency)
- Sidebar sources ordered by Max(created_at) desc instead of alphabetically;
  "All sources" stays pinned at top (rendered separately).
- Live updates move a source to the top when it gets a new message, so the
  recency order holds without a reload.

152 tests pass; JS re-validated with node --check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 21:18:49 +01:00
4 changed files with 31 additions and 15 deletions

View file

@ -9,6 +9,13 @@ former name, "Daybook".)
_Nothing yet._ _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 ## [0.6.1] — 2026-09-07
### Changed ### Changed
@ -214,7 +221,8 @@ First tagged release.
- Dockerised (Gunicorn + whitenoise), SQLite, `git`-based deploy with a - Dockerised (Gunicorn + whitenoise), SQLite, `git`-based deploy with a
runbook (`deploy/cloud_agent_deploy.md`) and health check. 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.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.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 [0.5.1]: https://git.cygnus.systems/peter/daybook/compare/v0.5.0...v0.5.1

View file

@ -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.decorators import login_required
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import OperationalError, transaction 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.http import FileResponse, Http404, HttpResponse, HttpResponseForbidden, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.template.defaultfilters import date as date_filter, slugify from django.template.defaultfilters import date as date_filter, slugify
@ -586,10 +586,12 @@ def feed(request):
source_scoped = apply_feed_filters( source_scoped = apply_feed_filters(
live, "", selected_label, selected_kind, title_query 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_counts = (
source_scoped.values("source") source_scoped.values("source")
.annotate(count=Count("id", filter=Q(read_at__isnull=True))) .annotate(count=Count("id", filter=Q(read_at__isnull=True)), latest=Max("created_at"))
.order_by("source") .order_by("-latest")
) )
keep_count = live.filter(is_kept=True).count() keep_count = live.filter(is_kept=True).count()
trash_count = Artifact.objects.filter(owner=request.user, deleted_at__isnull=False).count() trash_count = Artifact.objects.filter(owner=request.user, deleted_at__isnull=False).count()

View file

@ -1,4 +1,4 @@
# Single source of truth for the app version. Bump this, commit, then tag the # 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 # commit `vX.Y.Z` and deploy. Surfaced on Settings -> About and in
# GET /api/capabilities ("version"). # GET /api/capabilities ("version").
__version__ = "0.6.1" __version__ = "0.6.2"

View file

@ -443,15 +443,17 @@
`<span class="source-name">${escapeHtml(name)}</span>` + `<span class="source-name">${escapeHtml(name)}</span>` +
`<span class="source-count">${count}</span>`; `<span class="source-count">${count}</span>`;
// 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 list = document.querySelector(".source-list");
const others = sourceLinks().filter( const firstSource = list.querySelector(".source-link:not(.all-sources-link)");
(l) => l.querySelector(".source-name")?.textContent.trim() !== "All sources", list.insertBefore(link, firstSource);
); };
const after = others.find(
(l) => (l.querySelector(".source-name")?.textContent.trim() || "") > name, const moveSourceToTop = (link) => {
); const list = document.querySelector(".source-list");
list.insertBefore(link, after || null); 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 // From the newly-arrived rows: bump each source's unread count (adding a
@ -466,8 +468,12 @@
Object.entries(counts).forEach(([source, n]) => { Object.entries(counts).forEach(([source, n]) => {
total += n; total += n;
const existing = findSourceLink(source); const existing = findSourceLink(source);
if (existing) bumpSourceBadge(existing, n); if (existing) {
else addSourceLink(source, n); bumpSourceBadge(existing, n);
moveSourceToTop(existing); // most-recent activity -> top (recency order)
} else {
addSourceLink(source, n);
}
}); });
const allLink = findSourceLink("All sources"); const allLink = findSourceLink("All sources");
if (allLink && total) bumpSourceBadge(allLink, total); if (allLink && total) bumpSourceBadge(allLink, total);