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
↓ kepr-ccdde6bb.tar.gzatlas at this save
files changed4 files
package.json+1 −1public/css/kepr.css+17 −4src/pages/releases.ts+47 −25tests/releases.test.ts+7 −1package.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": {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);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 {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>");