Compare commits

..

No commits in common. "v0.4.1" and "v0.4.0" have entirely different histories.

3 changed files with 7 additions and 81 deletions

View file

@ -9,16 +9,6 @@ former name, "Daybook".)
_Nothing yet._ _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 ## [0.4.0] — 2026-09-04
### Added ### Added
@ -165,8 +155,7 @@ 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.4.1...HEAD [Unreleased]: https://git.cygnus.systems/peter/daybook/compare/v0.4.0...HEAD
[0.4.1]: https://git.cygnus.systems/peter/daybook/compare/v0.4.0...v0.4.1
[0.4.0]: https://git.cygnus.systems/peter/daybook/compare/v0.3.2...v0.4.0 [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.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 [0.3.1]: https://git.cygnus.systems/peter/daybook/compare/v0.3.0...v0.3.1

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.4.1" __version__ = "0.4.0"

View file

@ -195,8 +195,7 @@
const artifactList = document.querySelector("[data-artifact-list]"); const artifactList = document.querySelector("[data-artifact-list]");
// Rows grow as older pages load, so always read them live rather than caching an array. // Rows grow as older pages load, so always read them live rather than caching an array.
const allRows = () => Array.from(document.querySelectorAll(".artifact-row")); 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 csrfToken = document.querySelector("[name=csrfmiddlewaretoken]")?.value || "";
const escapeHtml = (value) => value.replace(/[&<>"]/g, (character) => ({ 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 =
`<span class="source-name">${escapeHtml(name)}</span>` +
`<span class="source-count">${count}</span>`;
// 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) => { const renderPreview = (row, pushState = true) => {
navigationMode = "artifacts"; navigationMode = "artifacts";
markRead(row); markRead(row);
@ -499,13 +438,12 @@
}; };
const moveSource = (direction) => { const moveSource = (direction) => {
const links = sourceLinks(); if (!sourceLinks.length) return;
if (!links.length) return;
navigationMode = "sources"; navigationMode = "sources";
const current = document.querySelector(".source-link.selected"); const current = document.querySelector(".source-link.selected");
const currentIndex = current ? links.indexOf(current) : 0; const currentIndex = current ? sourceLinks.indexOf(current) : 0;
const nextIndex = Math.max(0, Math.min(links.length - 1, currentIndex + direction)); const nextIndex = Math.max(0, Math.min(sourceLinks.length - 1, currentIndex + direction));
const nextSource = links[nextIndex]; const nextSource = sourceLinks[nextIndex];
if (nextSource && nextSource !== current) window.location.href = nextSource.href; if (nextSource && nextSource !== current) window.location.href = nextSource.href;
}; };
@ -685,7 +623,6 @@
node = node.nextElementSibling; node = node.nextElementSibling;
} }
markArrived(inserted); markArrived(inserted);
registerNewSources(inserted); // update sidebar: counts + any new source
liveSince = data.newest; liveSince = data.newest;
const dayCount = group.querySelector(".day-separator-count"); const dayCount = group.querySelector(".day-separator-count");
if (dayCount) dayCount.textContent = String((parseInt(dayCount.textContent, 10) || 0) + data.count); if (dayCount) dayCount.textContent = String((parseInt(dayCount.textContent, 10) || 0) + data.count);