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
↓ kepr-c337f277.tar.gzatlas at this save
files changed4 files
package.json+1 −1public/js/kepr.js+5 −3src/issues/pages.ts+8 −3tests/issues.test.ts+6 −1package.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": {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;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 &&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"');