diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5e1ed0f..cf4ed70 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,16 +9,6 @@ former name, "Daybook".)
_Nothing yet._
-## [0.4.1] — 2026-09-05
-
-### Changed
-- Live feed updates now also keep the **source sidebar** current: as new posts
- arrive, per-source and total unread counts bump, and a source posting for the
- first time is added to the list (alphabetically) — all derived client-side
- from the polled rows, no extra request. Sources added live stay
- keyboard-navigable. Caveat: while filtered to a single source, *other* new
- sources still only appear on reload (the poll is scoped to the selected view).
-
## [0.4.0] — 2026-09-04
### Added
@@ -165,8 +155,7 @@ 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.4.1...HEAD
-[0.4.1]: https://git.cygnus.systems/peter/daybook/compare/v0.4.0...v0.4.1
+[Unreleased]: https://git.cygnus.systems/peter/daybook/compare/v0.4.0...HEAD
[0.4.0]: https://git.cygnus.systems/peter/daybook/compare/v0.3.2...v0.4.0
[0.3.2]: https://git.cygnus.systems/peter/daybook/compare/v0.3.1...v0.3.2
[0.3.1]: https://git.cygnus.systems/peter/daybook/compare/v0.3.0...v0.3.1
diff --git a/daybook_project/__init__.py b/daybook_project/__init__.py
index 53f5709..bc935aa 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.4.1"
+__version__ = "0.4.0"
diff --git a/templates/artifacts/feed.html b/templates/artifacts/feed.html
index 78764fe..a240ebc 100644
--- a/templates/artifacts/feed.html
+++ b/templates/artifacts/feed.html
@@ -195,8 +195,7 @@
const artifactList = document.querySelector("[data-artifact-list]");
// Rows grow as older pages load, so always read them live rather than caching an array.
const allRows = () => Array.from(document.querySelectorAll(".artifact-row"));
- // Live-queried so sources added by live updates are keyboard-navigable too.
- const sourceLinks = () => Array.from(document.querySelectorAll(".source-link"));
+ const sourceLinks = Array.from(document.querySelectorAll(".source-link"));
const csrfToken = document.querySelector("[name=csrfmiddlewaretoken]")?.value || "";
const escapeHtml = (value) => value.replace(/[&<>"]/g, (character) => ({
@@ -384,66 +383,6 @@
});
};
- const findSourceLink = (name) =>
- sourceLinks().find((link) => link.querySelector(".source-name")?.textContent.trim() === name);
-
- const bumpSourceBadge = (link, delta) => {
- let badge = link.querySelector(".source-count");
- if (!badge) {
- badge = document.createElement("span");
- badge.className = "source-count";
- badge.textContent = "0";
- link.appendChild(badge);
- }
- badge.textContent = String((Number(badge.textContent) || 0) + delta);
- };
-
- const addSourceLink = (name, count) => {
- const params = new URLSearchParams();
- params.set("source", name);
- if (feed.dataset.selectedLabel) params.set("label", feed.dataset.selectedLabel);
- if (feed.dataset.selectedKind) params.set("kind", feed.dataset.selectedKind);
- if (feed.dataset.titleQuery) params.set("q", feed.dataset.titleQuery);
- params.set("view", feed.dataset.selectedView || "rendered");
- params.set("preview", "none");
-
- const link = document.createElement("a");
- link.className = "source-link";
- link.href = `?${params.toString()}`;
- link.innerHTML =
- `${escapeHtml(name)}` +
- `${count}`;
-
- // Insert alphabetically among the per-source links (after "All sources").
- 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);
- };
-
- // From the newly-arrived rows: bump each source's unread count (adding a
- // sidebar entry for a source seen for the first time) plus the total.
- const registerNewSources = (rows) => {
- const counts = {};
- rows.forEach((row) => {
- const source = row.dataset.source;
- if (source) counts[source] = (counts[source] || 0) + 1;
- });
- let total = 0;
- Object.entries(counts).forEach(([source, n]) => {
- total += n;
- const existing = findSourceLink(source);
- if (existing) bumpSourceBadge(existing, n);
- else addSourceLink(source, n);
- });
- const allLink = findSourceLink("All sources");
- if (allLink && total) bumpSourceBadge(allLink, total);
- };
-
const renderPreview = (row, pushState = true) => {
navigationMode = "artifacts";
markRead(row);
@@ -499,13 +438,12 @@
};
const moveSource = (direction) => {
- const links = sourceLinks();
- if (!links.length) return;
+ if (!sourceLinks.length) return;
navigationMode = "sources";
const current = document.querySelector(".source-link.selected");
- const currentIndex = current ? links.indexOf(current) : 0;
- const nextIndex = Math.max(0, Math.min(links.length - 1, currentIndex + direction));
- const nextSource = links[nextIndex];
+ const currentIndex = current ? sourceLinks.indexOf(current) : 0;
+ const nextIndex = Math.max(0, Math.min(sourceLinks.length - 1, currentIndex + direction));
+ const nextSource = sourceLinks[nextIndex];
if (nextSource && nextSource !== current) window.location.href = nextSource.href;
};
@@ -685,7 +623,6 @@
node = node.nextElementSibling;
}
markArrived(inserted);
- registerNewSources(inserted); // update sidebar: counts + any new source
liveSince = data.newest;
const dayCount = group.querySelector(".day-separator-count");
if (dayCount) dayCount.textContent = String((parseInt(dayCount.textContent, 10) || 0) + data.count);