Add a Changelog button at the bottom of the page

Opens a dialog listing the loaded build's full changelog, grouped by
date. Fetched cache-first through the service worker, so the list always
matches the running version; the update banner still covers what a
waiting build adds.
This commit is contained in:
Alexander Heldt
2026-07-12 17:36:44 +00:00
parent c8dceb14d4
commit 68964b55e4
4 changed files with 95 additions and 0 deletions
+45
View File
@@ -1916,6 +1916,51 @@
settingsDialog.close(); settingsDialog.close();
}); });
// ---------- changelog dialog ----------
// Shows the *loaded* build's full changelog: the plain URL is served
// cache-first by the controlling service worker, so the list always matches
// the version actually running (the update banner handles what's newer).
const changelogDialog = document.getElementById("changelog-dialog");
const changelogList = document.getElementById("changelog-list");
const changelogEmpty = document.getElementById("changelog-empty");
function formatChangelogDate(iso) {
const [y, m, d] = (iso || "").split("-").map(Number);
if (!y || !m || !d) return iso || "";
return new Date(y, m - 1, d).toLocaleDateString(undefined, {
year: "numeric", month: "short", day: "numeric",
});
}
document.getElementById("changelog-btn").addEventListener("click", async () => {
let entries = [];
try {
const res = await fetch("changelog.json");
if (res.ok) {
const body = await res.json();
if (Array.isArray(body)) entries = body.filter(e => e && e.text);
}
} catch { /* fall through to empty state */ }
changelogList.innerHTML = "";
changelogEmpty.hidden = entries.length > 0;
let lastDate = null;
for (const e of entries) {
if (e.date !== lastDate) {
lastDate = e.date;
const dt = document.createElement("li");
dt.className = "changelog-date";
dt.textContent = formatChangelogDate(e.date);
changelogList.appendChild(dt);
}
const li = document.createElement("li");
li.className = "changelog-entry";
li.textContent = e.text;
changelogList.appendChild(li);
}
changelogDialog.showModal();
});
// ---------- exercise dialog (add / edit a training exercise) ---------- // ---------- exercise dialog (add / edit a training exercise) ----------
const exerciseDialog = document.getElementById("exercise-dialog"); const exerciseDialog = document.getElementById("exercise-dialog");
const exerciseForm = document.getElementById("exercise-form"); const exerciseForm = document.getElementById("exercise-form");
+1
View File
@@ -1,4 +1,5 @@
[ [
{ "date": "2026-07-12", "text": "Browse the full changelog from the bottom of the page" },
{ "date": "2026-07-12", "text": "Finer-grained y-axis on the daily counts chart" }, { "date": "2026-07-12", "text": "Finer-grained y-axis on the daily counts chart" },
{ "date": "2026-07-12", "text": "The update banner now lists what changed in the new version" }, { "date": "2026-07-12", "text": "The update banner now lists what changed in the new version" },
{ "date": "2026-07-12", "text": "Fixed adding an exercise on iPhone: stale app updates, and the keyboard's Go key discarding the input" }, { "date": "2026-07-12", "text": "Fixed adding an exercise on iPhone: stale app updates, and the keyboard's Go key discarding the input" },
+15
View File
@@ -230,8 +230,23 @@
<p id="empty-state" class="empty">No events logged for this day.</p> <p id="empty-state" class="empty">No events logged for this day.</p>
</section> </section>
</main> </main>
<footer class="app-footer">
<button type="button" id="changelog-btn" class="linklike">Changelog</button>
</footer>
</div><!-- /#app --> </div><!-- /#app -->
<dialog id="changelog-dialog">
<form method="dialog" id="changelog-form">
<h3>Changelog</h3>
<ul id="changelog-list" class="changelog-list"></ul>
<p id="changelog-empty" class="empty" hidden>No changelog available.</p>
<menu>
<button value="close">Close</button>
</menu>
</form>
</dialog>
<dialog id="settings-dialog"> <dialog id="settings-dialog">
<form method="dialog" id="settings-form"> <form method="dialog" id="settings-form">
<h3>Puppy settings</h3> <h3>Puppy settings</h3>
+34
View File
@@ -695,6 +695,40 @@ input.switch:checked::after { transform: translateX(18px); }
opacity: 0.92; opacity: 0.92;
} }
.update-changelog[hidden] { display: none; } .update-changelog[hidden] { display: none; }
.app-footer {
text-align: center;
padding: 4px 0 24px;
}
.app-footer .linklike { color: var(--muted); font-size: 0.8rem; }
.changelog-list {
list-style: none;
margin: 0 0 12px;
padding: 0;
max-height: 60vh;
overflow-y: auto;
}
.changelog-date {
color: var(--muted);
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.04em;
margin: 10px 0 4px;
}
.changelog-date:first-child { margin-top: 0; }
.changelog-entry {
font-size: 0.9rem;
line-height: 1.45;
padding: 3px 0 3px 14px;
position: relative;
}
.changelog-entry::before {
content: "•";
position: absolute;
left: 2px;
color: var(--accent);
}
.update-banner-msg { font-size: 0.9rem; font-weight: 600; } .update-banner-msg { font-size: 0.9rem; font-weight: 600; }
.update-banner-actions { display: flex; gap: 8px; } .update-banner-actions { display: flex; gap: 8px; }
.update-banner-btn { .update-banner-btn {