ccdde6bb8ea2

show only the latest release large on builds, and older releases as archive-style rows with their downloads in view (v3.1.1)

time
2026-10-05 13:02 UTC · 1 week ago
machine
Junes-MacBook-Pro.local
agent
claude-opus-5-5
session
compact older builds
parent
ca2b1efa21b8
4 files package.json+1 −1public/css/kepr.css+17 −4src/pages/releases.ts+47 −25tests/releases.test.ts+7 −1
package.json+1 −1
@@ -1,6 +1,6 @@11 {22 	"name": "kepr",3-	"version": "3.1.0",3+	"version": "3.1.1",44 	"private": true,55 	"type": "module",66 	"scripts": {

view file

public/css/kepr.css+17 −4
@@ -1468,12 +1468,25 @@14681468 	font-size: 0.6875rem;14691469 	margin: 16px 8px 0;14701470 }1471-.release-older > summary {1472-	padding: 16px 8px;1473-	font-size: 0.75rem;1471+.release-earlier {1472+	font-size: 0.6875rem;1473+	margin: 24px 8px 4px;1474+}1475+.release-row .release-version {1476+	flex: 0 0 80px;1477+	font-size: 0.8125rem;1478+}1479+.release-row .archive-links {1480+	gap: 4px 24px;1481+}1482+.release-more > summary {1483+	width: fit-content;1484+	font-size: 0.6875rem;14741485 	color: var(--text-muted);14751486 	cursor: pointer;1476-	border-bottom: 1px solid var(--border);1487+}1488+.release-more[open] > summary {1489+	margin-bottom: 10px;14771490 }14781491 .release-head .history-id:hover {14791492 	color: var(--accent);

view file

src/pages/releases.ts+47 −25
@@ -47,18 +47,12 @@4747 <section class="repo-section">4848 ${4949 	releases.length > 050-		? releases.map((release, index) =>51-				index === 052-					? html`<div class="release-latest">50+		? html`<div class="release-latest">5351 <p class="muted mono">latest release</p>54-${releaseBlock(repo, release, counts)}55-</div>`56-					: html`<details class="release-older">57-<summary class="mono">${index === 1 ? "earlier releases · " : ""}<span>v${release.version}</span>58-· ${formatDate(release.publishedAt)}</summary>59-${releaseBlock(repo, release, counts)}60-</details>`,61-			)52+${releaseBlock(repo, releases[0] as Release, counts)}53+</div>54+${releases.length > 1 && html`<p class="release-earlier muted mono">earlier releases</p>`}55+${releases.slice(1).map((release) => olderRow(repo, release, counts))}`6256 		: html`<p class="muted mono save-empty">no releases yet</p>`6357 }6458 </section>`,@@ -68,33 +62,61 @@6862 }6963 7064 function releaseBlock(repo: PublicRepo, release: Release, counts: Map<string, number>): Html {71-	const name = repo.settings.name;65+	return html`<article class="release" id="${release.version}">66+<p class="release-head"><span class="release-version">v${67+		release.version68+	}</span>${saveLink(repo, release)}<span class="history-when">${formatDate(release.publishedAt)}</span></p>69+${releaseDetails(repo, release, counts)}70+</article>`;71+}72+73+// An older release reads like an archive row: one line, its downloads, the rest folded away.74+function olderRow(repo: PublicRepo, release: Release, counts: Map<string, number>): Html {7275 	const save = saveByPrefix(repo.mirror, release.save);76+	return html`<div class="archive-row release-row" id="${release.version}">77+<p class="archive-save"><span class="release-version">v${78+		release.version79+	}</span>${saveLink(repo, release)}${html`<span class="history-msg archive-msg">${save?.message.split("\n")[0] ?? ""}</span>`}${html`<span class="history-when">${formatDate(release.publishedAt)}</span>`}</p>80+<p class="archive-links mono">${release.files.map(81+		(file) =>82+			html`<a href="${downloadHref(repo.settings.name, release, file)}">↓ ${PLATFORM_LABELS[file.platform]}</a>`,83+	)}</p>84+<details class="release-more">85+<summary class="mono">notes · checksums</summary>86+${releaseDetails(repo, release, counts)}87+</details>88+</div>`;89+}90+91+function saveLink(repo: PublicRepo, release: Release): Html {92+	const save = saveByPrefix(repo.mirror, release.save);7393 	// A checkpoint has no page of its own (§21.4), and a save the mirror lacks has none either.74-	const saveLink =75-		save && !save.is_checkpoint76-			? html`<a class="history-id" href="/${name}/saves/${save.id}">${save.id}</a>`77-			: html`<span class="history-id">${release.save}</span>`;94+	return save && !save.is_checkpoint95+		? html`<a class="history-id" href="/${repo.settings.name}/saves/${save.id}">${save.id}</a>`96+		: html`<span class="history-id">${release.save}</span>`;97+}98+99+function releaseDetails(repo: PublicRepo, release: Release, counts: Map<string, number>): Html {100+	const name = repo.settings.name;78101 	// §11.2: empty notes show the release save's message, else "Release <v> of <repo>."79102 	const notes =80103 		release.notes !== ""81104 			? renderMarkdown(release.notes, { repo: name, dir: "", at: null })82-			: html`<p>${save?.message ?? `Release ${release.version} of ${name}.`}</p>`;83-	return html`<article class="release" id="${release.version}">84-<p class="release-head"><span class="release-version">v${85-		release.version86-	}</span>${saveLink}<span class="history-when">${formatDate(release.publishedAt)}</span></p>87-<div class="release-notes markdown">${notes}</div>105+			: html`<p>${saveByPrefix(repo.mirror, release.save)?.message ?? `Release ${release.version} of ${name}.`}</p>`;106+	return html`<div class="release-notes markdown">${notes}</div>88107 <div class="release-files">89108 ${release.files.map((file) =>90109 	fileRow(91-		`/${name}/releases/${release.version}/${encodeURIComponent(downloadName(name, release, file))}`,110+		downloadHref(name, release, file),92111 		file,93112 		counts.get(`${release.version}/${file.platform}`) ?? 0,94113 	),95114 )}96-</div>97-</article>`;115+</div>`;116+}117+118+function downloadHref(name: string, release: Release, file: ReleaseFile): string {119+	return `/${name}/releases/${release.version}/${encodeURIComponent(downloadName(name, release, file))}`;98120 }99121 100122 function fileRow(href: string, file: ReleaseFile, downloads: number): Html {

view file

tests/releases.test.ts+7 −1
@@ -176,8 +176,14 @@176176 		expect(order).toEqual([...order].sort((x, y) => x - y));177177 		expect(order[0]).toBeGreaterThan(0);178178 		expect(body).toContain('class="release-latest"');179-		expect(body).toContain('class="release-older"');179+		// Older releases are compact rows, as on archives, with every file's link in view.180+		expect(body).toContain('class="archive-row release-row" id="0.10.0"');180181 		expect(body).toContain("earlier releases");182+		const older = body.slice(183+			body.indexOf('id="0.10.0"'),184+			body.indexOf("<details", body.indexOf('id="0.10.0"')),185+		);186+		expect(older).toContain('href="/tool/releases/0.10.0/tool-0.10.0.apk"');181187 		expect(body).toContain(`href="/tool/saves/${saves.tool}"`);182188 		expect(body).toContain("2026-09-22 18:00");183189 		expect(body).toContain("<h2>Fixes</h2>");

view file