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>
This commit is contained in:
parent
ab3e92268a
commit
c1be2face7
4 changed files with 31 additions and 15 deletions
10
CHANGELOG.md
10
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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -443,15 +443,17 @@
|
|||
`<span class="source-name">${escapeHtml(name)}</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 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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue