c337f277612e

restore issue titles and back navigation inside repo panels (v3.1.23)

time
2026-10-08 18:15 UTC · 4 days ago
machine
Junes-MacBook-Pro.local
agent
gpt-6
session
restore issue thread context
parent
aa7d0740011f
4 files package.json+1 −1public/js/kepr.js+5 −3src/issues/pages.ts+8 −3tests/issues.test.ts+6 −1
package.json+1 −1
@@ -1,6 +1,6 @@11 {22 	"name": "kepr",3-	"version": "3.1.22",3+	"version": "3.1.23",44 	"private": true,55 	"type": "module",66 	"scripts": {

view file

public/js/kepr.js+5 −3
@@ -251,7 +251,7 @@251251 	return event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;252252 }253253 254-// §21.9's panels open inline under the tabs, as Kepr 2's did: the panel page without its heading.254+// §21.9's panels omit the root heading; nested pages need their own title and way back.255255 // The panel's own pages (an issue, the report form) and its forms stay inside it; a link elsewhere,256256 // or to a file (a download), navigates. The open tab takes the panel back to its first page, and257257 // closes it from there.@@ -268,8 +268,10 @@268268 			location.href = url;269269 			return;270270 		}271-		main.querySelector(".breadcrumb")?.remove();272-		main.querySelector(".file-page-title")?.remove();271+		if (new URL(url).pathname === new URL(open.href).pathname) {272+			main.querySelector(".breadcrumb")?.remove();273+			main.querySelector(".file-page-title")?.remove();274+		}273275 		host.replaceChildren(...main.childNodes);274276 		copyButtons(host);275277 		host.hidden = false;

view file

src/issues/pages.ts+8 −3
@@ -184,9 +184,10 @@184184 	c.header("Referrer-Policy", "no-referrer");185185 }186186 187-function crumbs(repo: string): Html {187+function crumbs(repo: string, closed = false): Html {188188 	return html`<p class="breadcrumb mono">189-<a href="/${repo}">${repo}</a> / <a href="/${repo}/issues">issues</a></p>`;189+<a href="/${repo}">${repo}</a> /190+<a href="/${repo}/issues${closed ? "?status=closed" : ""}">back to issues</a></p>`;190191 }191192 192193 function listPage(@@ -323,7 +324,7 @@323324 		title: `#${issue.number} ${issue.title} · ${name}`,324325 		description: issue.title,325326 		path: `/${name}/issues/${issue.number}`,326-		body: html`${crumbs(name)}327+		body: html`${crumbs(name, issue.status === "closed")}327328 <h1 class="file-page-title">${issue.title} <span class="muted">#${issue.number}</span></h1>328329 <p class="issue-meta mono">329330 <span class="issue-symbol issue-symbol--${issue.status}">@@ -334,6 +335,10 @@334335 ${threadEntries(ctx.kepr, issue.id).map((entry) =>335336 	post(`entry-${entry.id}`, entry.author === "operator", entry.created_at, entry.body),336337 )}338+${339+	token === null &&340+	html`<p class="muted">To add a reply, use the private follow-up link you received when filing this issue.</p>`341+}337342 ${notice.error && html`<p class="issues-error mono" role="alert">${notice.error}</p>`}338343 ${339344 	token !== null &&

view file

tests/issues.test.ts+6 −1
@@ -217,13 +217,17 @@217217 		expect(thread).toContain('<span class="issue-author">Ada</span>');218218 		expect(thread).not.toContain("[email protected]");219219 		expect(thread).not.toContain('name="token"');220+		expect(thread).toContain('href="/app/issues">back to issues</a>');221+		expect(thread).toContain("private follow-up link you received when filing");220222 	});221223 222224 	test("the token opens the follow-up form, without a referrer or cache; a wrong one does not", async () => {223225 		const response = await app.request(`/app/issues/1?token=${token}`);224226 		expect(response.headers.get("referrer-policy")).toBe("no-referrer");225227 		expect(response.headers.get("cache-control")).toBe("no-store");226-		expect(await response.text()).toContain('name="token"');228+		const ownerPage = await response.text();229+		expect(ownerPage).toContain('name="token"');230+		expect(ownerPage).not.toContain("private follow-up link you received when filing");227231 		const wrong = await page(`/app/issues/1?token=${"0".repeat(64)}`);228232 		expect(wrong).not.toContain('name="token"');229233 		expect(wrong).toContain("does not match");@@ -293,6 +297,7 @@293297 		expect(thread).toContain('<span class="issue-badge">operator</span>');294298 		expect(thread).toContain("Fixed, thanks.");295299 		expect(thread).toContain("◈ closed");300+		expect(thread).toContain('href="/app/issues?status=closed">back to issues</a>');296301 		expect(thread).toContain(` · fixed in <a class="history-id" href="/app/saves/${save}">`);297302 		expect(await page("/app/issues")).not.toContain('href="/app/issues/1"');298303 		expect(await page("/app/issues?status=closed")).toContain('href="/app/issues/1"');

view file