detached 08e6388

feat(web): show the latest tag in its own summary rail block

commit
08e63884b6d8316c4c508c604d5fcdfdd75f48bc
author
brice · · 2026-10-11 12:46 UTC
parent
5e5d8fb
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
37 files · +500 -297
M cmd/ermite/testdata/web/blob-binary.html +4 -4
@@ -209,7 +209,7 @@
209209 <kbd>u</kbd>
210210 <kbd>x</kbd>
211211 </dt>
212-<dd>diff: split / unified / collapse all</dd>
212+<dd>diff: split / unified / collapse or expand all</dd>
213213 </div>
214214 <div>
215215 <dt>
@@ -383,9 +383,9 @@
383383 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
384384 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
385385 };
386- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
387- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
388- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
386+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
387+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
388+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
389389 k-permalink from:body -> {
390390 let b = document.body.dataset;
391391 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -209,7 +209,7 @@
209 <kbd>u</kbd>209 <kbd>u</kbd>
210 <kbd>x</kbd>210 <kbd>x</kbd>
211 </dt>211 </dt>
212-<dd>diff: split / unified / collapse all</dd>212+<dd>diff: split / unified / collapse or expand all</dd>
213 </div>213 </div>
214 <div>214 <div>
215 <dt>215 <dt>
@@ -383,9 +383,9 @@
383 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);383 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
384 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })384 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
385 };385 };
386- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');386+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
387- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');387+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
388- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);388+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
389 k-permalink from:body -> {389 k-permalink from:body -> {
390 let b = document.body.dataset;390 let b = document.body.dataset;
391 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }391 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/blob-code.html +4 -4
@@ -279,7 +279,7 @@
279279 <kbd>u</kbd>
280280 <kbd>x</kbd>
281281 </dt>
282-<dd>diff: split / unified / collapse all</dd>
282+<dd>diff: split / unified / collapse or expand all</dd>
283283 </div>
284284 <div>
285285 <dt>
@@ -453,9 +453,9 @@
453453 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
454454 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
455455 };
456- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
457- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
458- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
456+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
457+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
458+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
459459 k-permalink from:body -> {
460460 let b = document.body.dataset;
461461 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -279,7 +279,7 @@
279 <kbd>u</kbd>279 <kbd>u</kbd>
280 <kbd>x</kbd>280 <kbd>x</kbd>
281 </dt>281 </dt>
282-<dd>diff: split / unified / collapse all</dd>282+<dd>diff: split / unified / collapse or expand all</dd>
283 </div>283 </div>
284 <div>284 <div>
285 <dt>285 <dt>
@@ -453,9 +453,9 @@
453 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);453 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
454 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })454 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
455 };455 };
456- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');456+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
457- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');457+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
458- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);458+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
459 k-permalink from:body -> {459 k-permalink from:body -> {
460 let b = document.body.dataset;460 let b = document.body.dataset;
461 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }461 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/blob-empty.html +4 -4
@@ -209,7 +209,7 @@
209209 <kbd>u</kbd>
210210 <kbd>x</kbd>
211211 </dt>
212-<dd>diff: split / unified / collapse all</dd>
212+<dd>diff: split / unified / collapse or expand all</dd>
213213 </div>
214214 <div>
215215 <dt>
@@ -383,9 +383,9 @@
383383 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
384384 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
385385 };
386- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
387- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
388- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
386+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
387+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
388+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
389389 k-permalink from:body -> {
390390 let b = document.body.dataset;
391391 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -209,7 +209,7 @@
209 <kbd>u</kbd>209 <kbd>u</kbd>
210 <kbd>x</kbd>210 <kbd>x</kbd>
211 </dt>211 </dt>
212-<dd>diff: split / unified / collapse all</dd>212+<dd>diff: split / unified / collapse or expand all</dd>
213 </div>213 </div>
214 <div>214 <div>
215 <dt>215 <dt>
@@ -383,9 +383,9 @@
383 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);383 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
384 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })384 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
385 };385 };
386- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');386+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
387- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');387+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
388- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);388+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
389 k-permalink from:body -> {389 k-permalink from:body -> {
390 let b = document.body.dataset;390 let b = document.body.dataset;
391 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }391 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/blob-image.html +4 -4
@@ -211,7 +211,7 @@
211211 <kbd>u</kbd>
212212 <kbd>x</kbd>
213213 </dt>
214-<dd>diff: split / unified / collapse all</dd>
214+<dd>diff: split / unified / collapse or expand all</dd>
215215 </div>
216216 <div>
217217 <dt>
@@ -385,9 +385,9 @@
385385 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
386386 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
387387 };
388- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
389- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
390- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
388+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
389+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
390+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
391391 k-permalink from:body -> {
392392 let b = document.body.dataset;
393393 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -211,7 +211,7 @@
211 <kbd>u</kbd>211 <kbd>u</kbd>
212 <kbd>x</kbd>212 <kbd>x</kbd>
213 </dt>213 </dt>
214-<dd>diff: split / unified / collapse all</dd>214+<dd>diff: split / unified / collapse or expand all</dd>
215 </div>215 </div>
216 <div>216 <div>
217 <dt>217 <dt>
@@ -385,9 +385,9 @@
385 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);385 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
386 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })386 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
387 };387 };
388- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');388+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
389- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');389+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
390- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);390+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
391 k-permalink from:body -> {391 k-permalink from:body -> {
392 let b = document.body.dataset;392 let b = document.body.dataset;
393 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }393 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/blob-markdown.html +4 -4
@@ -276,7 +276,7 @@
276276 <kbd>u</kbd>
277277 <kbd>x</kbd>
278278 </dt>
279-<dd>diff: split / unified / collapse all</dd>
279+<dd>diff: split / unified / collapse or expand all</dd>
280280 </div>
281281 <div>
282282 <dt>
@@ -450,9 +450,9 @@
450450 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
451451 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
452452 };
453- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
454- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
455- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
453+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
454+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
455+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
456456 k-permalink from:body -> {
457457 let b = document.body.dataset;
458458 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -276,7 +276,7 @@
276 <kbd>u</kbd>276 <kbd>u</kbd>
277 <kbd>x</kbd>277 <kbd>x</kbd>
278 </dt>278 </dt>
279-<dd>diff: split / unified / collapse all</dd>279+<dd>diff: split / unified / collapse or expand all</dd>
280 </div>280 </div>
281 <div>281 <div>
282 <dt>282 <dt>
@@ -450,9 +450,9 @@
450 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);450 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
451 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })451 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
452 };452 };
453- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');453+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
454- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');454+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
455- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);455+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
456 k-permalink from:body -> {456 k-permalink from:body -> {
457 let b = document.body.dataset;457 let b = document.body.dataset;
458 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }458 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/blob-svg.html +4 -4
@@ -234,7 +234,7 @@
234234 <kbd>u</kbd>
235235 <kbd>x</kbd>
236236 </dt>
237-<dd>diff: split / unified / collapse all</dd>
237+<dd>diff: split / unified / collapse or expand all</dd>
238238 </div>
239239 <div>
240240 <dt>
@@ -408,9 +408,9 @@
408408 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
409409 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
410410 };
411- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
412- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
413- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
411+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
412+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
413+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
414414 k-permalink from:body -> {
415415 let b = document.body.dataset;
416416 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -234,7 +234,7 @@
234 <kbd>u</kbd>234 <kbd>u</kbd>
235 <kbd>x</kbd>235 <kbd>x</kbd>
236 </dt>236 </dt>
237-<dd>diff: split / unified / collapse all</dd>237+<dd>diff: split / unified / collapse or expand all</dd>
238 </div>238 </div>
239 <div>239 <div>
240 <dt>240 <dt>
@@ -408,9 +408,9 @@
408 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);408 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
409 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })409 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
410 };410 };
411- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');411+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
412- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');412+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
413- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);413+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
414 k-permalink from:body -> {414 k-permalink from:body -> {
415 let b = document.body.dataset;415 let b = document.body.dataset;
416 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }416 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/blob-text.html +4 -4
@@ -229,7 +229,7 @@
229229 <kbd>u</kbd>
230230 <kbd>x</kbd>
231231 </dt>
232-<dd>diff: split / unified / collapse all</dd>
232+<dd>diff: split / unified / collapse or expand all</dd>
233233 </div>
234234 <div>
235235 <dt>
@@ -403,9 +403,9 @@
403403 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
404404 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
405405 };
406- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
407- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
408- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
406+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
407+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
408+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
409409 k-permalink from:body -> {
410410 let b = document.body.dataset;
411411 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -229,7 +229,7 @@
229 <kbd>u</kbd>229 <kbd>u</kbd>
230 <kbd>x</kbd>230 <kbd>x</kbd>
231 </dt>231 </dt>
232-<dd>diff: split / unified / collapse all</dd>232+<dd>diff: split / unified / collapse or expand all</dd>
233 </div>233 </div>
234 <div>234 <div>
235 <dt>235 <dt>
@@ -403,9 +403,9 @@
403 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);403 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
404 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })404 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
405 };405 };
406- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');406+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
407- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');407+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
408- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);408+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
409 k-permalink from:body -> {409 k-permalink from:body -> {
410 let b = document.body.dataset;410 let b = document.body.dataset;
411 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }411 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/blob-too-large.html +4 -4
@@ -209,7 +209,7 @@
209209 <kbd>u</kbd>
210210 <kbd>x</kbd>
211211 </dt>
212-<dd>diff: split / unified / collapse all</dd>
212+<dd>diff: split / unified / collapse or expand all</dd>
213213 </div>
214214 <div>
215215 <dt>
@@ -383,9 +383,9 @@
383383 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
384384 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
385385 };
386- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
387- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
388- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
386+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
387+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
388+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
389389 k-permalink from:body -> {
390390 let b = document.body.dataset;
391391 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -209,7 +209,7 @@
209 <kbd>u</kbd>209 <kbd>u</kbd>
210 <kbd>x</kbd>210 <kbd>x</kbd>
211 </dt>211 </dt>
212-<dd>diff: split / unified / collapse all</dd>212+<dd>diff: split / unified / collapse or expand all</dd>
213 </div>213 </div>
214 <div>214 <div>
215 <dt>215 <dt>
@@ -383,9 +383,9 @@
383 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);383 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
384 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })384 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
385 };385 };
386- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');386+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
387- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');387+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
388- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);388+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
389 k-permalink from:body -> {389 k-permalink from:body -> {
390 let b = document.body.dataset;390 let b = document.body.dataset;
391 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }391 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/blob-trojan.html +4 -4
@@ -276,7 +276,7 @@
276276 <kbd>u</kbd>
277277 <kbd>x</kbd>
278278 </dt>
279-<dd>diff: split / unified / collapse all</dd>
279+<dd>diff: split / unified / collapse or expand all</dd>
280280 </div>
281281 <div>
282282 <dt>
@@ -450,9 +450,9 @@
450450 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
451451 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
452452 };
453- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
454- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
455- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
453+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
454+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
455+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
456456 k-permalink from:body -> {
457457 let b = document.body.dataset;
458458 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -276,7 +276,7 @@
276 <kbd>u</kbd>276 <kbd>u</kbd>
277 <kbd>x</kbd>277 <kbd>x</kbd>
278 </dt>278 </dt>
279-<dd>diff: split / unified / collapse all</dd>279+<dd>diff: split / unified / collapse or expand all</dd>
280 </div>280 </div>
281 <div>281 <div>
282 <dt>282 <dt>
@@ -450,9 +450,9 @@
450 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);450 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
451 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })451 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
452 };452 };
453- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');453+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
454- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');454+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
455- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);455+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
456 k-permalink from:body -> {456 k-permalink from:body -> {
457 let b = document.body.dataset;457 let b = document.body.dataset;
458 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }458 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/commit-big.html +7 -5
@@ -109,7 +109,9 @@
109109 </a>
110110 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>
111111 </a>
112-<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">Collapse all<kbd aria-hidden="true">x</kbd>
112+<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">
113+<span hx-live="this.textContent = q('[data-file][open]').count || !q('[data-file]').count ? 'Collapse all' : 'Expand all'">Collapse all</span>
114+<kbd aria-hidden="true">x</kbd>
113115 </button>
114116 </div>
115117 <p class="notice">this diff is incomplete: 1 file is too large to show. the totals above count everything; clone the repository to read the rest.</p>
@@ -244,7 +246,7 @@
244246 <kbd>u</kbd>
245247 <kbd>x</kbd>
246248 </dt>
247-<dd>diff: split / unified / collapse all</dd>
249+<dd>diff: split / unified / collapse or expand all</dd>
248250 </div>
249251 <div>
250252 <dt>
@@ -418,9 +420,9 @@
418420 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
419421 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
420422 };
421- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
422- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
423- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
423+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
424+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
425+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
424426 k-permalink from:body -> {
425427 let b = document.body.dataset;
426428 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -109,7 +109,9 @@
109 </a>109 </a>
110 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>110 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>
111 </a>111 </a>
112-<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">Collapse all<kbd aria-hidden="true">x</kbd>112+<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">
113+<span hx-live="this.textContent = q('[data-file][open]').count || !q('[data-file]').count ? 'Collapse all' : 'Expand all'">Collapse all</span>
114+<kbd aria-hidden="true">x</kbd>
113 </button>115 </button>
114 </div>116 </div>
115 <p class="notice">this diff is incomplete: 1 file is too large to show. the totals above count everything; clone the repository to read the rest.</p>117 <p class="notice">this diff is incomplete: 1 file is too large to show. the totals above count everything; clone the repository to read the rest.</p>
@@ -244,7 +246,7 @@
244 <kbd>u</kbd>246 <kbd>u</kbd>
245 <kbd>x</kbd>247 <kbd>x</kbd>
246 </dt>248 </dt>
247-<dd>diff: split / unified / collapse all</dd>249+<dd>diff: split / unified / collapse or expand all</dd>
248 </div>250 </div>
249 <div>251 <div>
250 <dt>252 <dt>
@@ -418,9 +420,9 @@
418 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);420 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
419 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })421 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
420 };422 };
421- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');423+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
422- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');424+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
423- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);425+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
424 k-permalink from:body -> {426 k-permalink from:body -> {
425 let b = document.body.dataset;427 let b = document.body.dataset;
426 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }428 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/commit-binary.html +7 -5
@@ -105,7 +105,9 @@
105105 </a>
106106 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>
107107 </a>
108-<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">Collapse all<kbd aria-hidden="true">x</kbd>
108+<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">
109+<span hx-live="this.textContent = q('[data-file][open]').count || !q('[data-file]').count ? 'Collapse all' : 'Expand all'">Collapse all</span>
110+<kbd aria-hidden="true">x</kbd>
109111 </button>
110112 </div>
111113 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
@@ -239,7 +241,7 @@
239241 <kbd>u</kbd>
240242 <kbd>x</kbd>
241243 </dt>
242-<dd>diff: split / unified / collapse all</dd>
244+<dd>diff: split / unified / collapse or expand all</dd>
243245 </div>
244246 <div>
245247 <dt>
@@ -413,9 +415,9 @@
413415 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
414416 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
415417 };
416- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
417- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
418- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
418+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
419+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
420+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
419421 k-permalink from:body -> {
420422 let b = document.body.dataset;
421423 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -105,7 +105,9 @@
105 </a>105 </a>
106 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>106 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>
107 </a>107 </a>
108-<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">Collapse all<kbd aria-hidden="true">x</kbd>108+<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">
109+<span hx-live="this.textContent = q('[data-file][open]').count || !q('[data-file]').count ? 'Collapse all' : 'Expand all'">Collapse all</span>
110+<kbd aria-hidden="true">x</kbd>
109 </button>111 </button>
110 </div>112 </div>
111 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">113 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
@@ -239,7 +241,7 @@
239 <kbd>u</kbd>241 <kbd>u</kbd>
240 <kbd>x</kbd>242 <kbd>x</kbd>
241 </dt>243 </dt>
242-<dd>diff: split / unified / collapse all</dd>244+<dd>diff: split / unified / collapse or expand all</dd>
243 </div>245 </div>
244 <div>246 <div>
245 <dt>247 <dt>
@@ -413,9 +415,9 @@
413 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);415 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
414 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })416 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
415 };417 };
416- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');418+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
417- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');419+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
418- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);420+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
419 k-permalink from:body -> {421 k-permalink from:body -> {
420 let b = document.body.dataset;422 let b = document.body.dataset;
421 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }423 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/commit-merge.html +7 -5
@@ -104,7 +104,9 @@
104104 </a>
105105 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>
106106 </a>
107-<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">Collapse all<kbd aria-hidden="true">x</kbd>
107+<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">
108+<span hx-live="this.textContent = q('[data-file][open]').count || !q('[data-file]').count ? 'Collapse all' : 'Expand all'">Collapse all</span>
109+<kbd aria-hidden="true">x</kbd>
108110 </button>
109111 </div>
110112 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
@@ -278,7 +280,7 @@
278280 <kbd>u</kbd>
279281 <kbd>x</kbd>
280282 </dt>
281-<dd>diff: split / unified / collapse all</dd>
283+<dd>diff: split / unified / collapse or expand all</dd>
282284 </div>
283285 <div>
284286 <dt>
@@ -452,9 +454,9 @@
452454 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
453455 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
454456 };
455- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
456- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
457- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
457+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
458+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
459+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
458460 k-permalink from:body -> {
459461 let b = document.body.dataset;
460462 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -104,7 +104,9 @@
104 </a>104 </a>
105 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>105 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>
106 </a>106 </a>
107-<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">Collapse all<kbd aria-hidden="true">x</kbd>107+<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">
108+<span hx-live="this.textContent = q('[data-file][open]').count || !q('[data-file]').count ? 'Collapse all' : 'Expand all'">Collapse all</span>
109+<kbd aria-hidden="true">x</kbd>
108 </button>110 </button>
109 </div>111 </div>
110 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">112 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
@@ -278,7 +280,7 @@
278 <kbd>u</kbd>280 <kbd>u</kbd>
279 <kbd>x</kbd>281 <kbd>x</kbd>
280 </dt>282 </dt>
281-<dd>diff: split / unified / collapse all</dd>283+<dd>diff: split / unified / collapse or expand all</dd>
282 </div>284 </div>
283 <div>285 <div>
284 <dt>286 <dt>
@@ -452,9 +454,9 @@
452 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);454 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
453 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })455 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
454 };456 };
455- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');457+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
456- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');458+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
457- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);459+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
458 k-permalink from:body -> {460 k-permalink from:body -> {
459 let b = document.body.dataset;461 let b = document.body.dataset;
460 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }462 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/commit-modify-split.html +7 -5
@@ -111,7 +111,9 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
111111 </a>
112112 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" aria-current="true" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>
113113 </a>
114-<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">Collapse all<kbd aria-hidden="true">x</kbd>
114+<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">
115+<span hx-live="this.textContent = q('[data-file][open]').count || !q('[data-file]').count ? 'Collapse all' : 'Expand all'">Collapse all</span>
116+<kbd aria-hidden="true">x</kbd>
115117 </button>
116118 </div>
117119 <div id="diffs" data-diff="split">
@@ -399,7 +401,7 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
399401 <kbd>u</kbd>
400402 <kbd>x</kbd>
401403 </dt>
402-<dd>diff: split / unified / collapse all</dd>
404+<dd>diff: split / unified / collapse or expand all</dd>
403405 </div>
404406 <div>
405407 <dt>
@@ -573,9 +575,9 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
573575 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
574576 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
575577 };
576- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
577- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
578- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
578+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
579+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
580+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
579581 k-permalink from:body -> {
580582 let b = document.body.dataset;
581583 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -111,7 +111,9 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
111 </a>111 </a>
112 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" aria-current="true" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>112 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" aria-current="true" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>
113 </a>113 </a>
114-<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">Collapse all<kbd aria-hidden="true">x</kbd>114+<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">
115+<span hx-live="this.textContent = q('[data-file][open]').count || !q('[data-file]').count ? 'Collapse all' : 'Expand all'">Collapse all</span>
116+<kbd aria-hidden="true">x</kbd>
115 </button>117 </button>
116 </div>118 </div>
117 <div id="diffs" data-diff="split">119 <div id="diffs" data-diff="split">
@@ -399,7 +401,7 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
399 <kbd>u</kbd>401 <kbd>u</kbd>
400 <kbd>x</kbd>402 <kbd>x</kbd>
401 </dt>403 </dt>
402-<dd>diff: split / unified / collapse all</dd>404+<dd>diff: split / unified / collapse or expand all</dd>
403 </div>405 </div>
404 <div>406 <div>
405 <dt>407 <dt>
@@ -573,9 +575,9 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
573 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);575 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
574 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })576 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
575 };577 };
576- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');578+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
577- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');579+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
578- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);580+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
579 k-permalink from:body -> {581 k-permalink from:body -> {
580 let b = document.body.dataset;582 let b = document.body.dataset;
581 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }583 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/commit-modify.html +7 -5
@@ -111,7 +111,9 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
111111 </a>
112112 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>
113113 </a>
114-<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">Collapse all<kbd aria-hidden="true">x</kbd>
114+<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">
115+<span hx-live="this.textContent = q('[data-file][open]').count || !q('[data-file]').count ? 'Collapse all' : 'Expand all'">Collapse all</span>
116+<kbd aria-hidden="true">x</kbd>
115117 </button>
116118 </div>
117119 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
@@ -399,7 +401,7 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
399401 <kbd>u</kbd>
400402 <kbd>x</kbd>
401403 </dt>
402-<dd>diff: split / unified / collapse all</dd>
404+<dd>diff: split / unified / collapse or expand all</dd>
403405 </div>
404406 <div>
405407 <dt>
@@ -573,9 +575,9 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
573575 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
574576 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
575577 };
576- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
577- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
578- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
578+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
579+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
580+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
579581 k-permalink from:body -> {
580582 let b = document.body.dataset;
581583 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -111,7 +111,9 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
111 </a>111 </a>
112 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>112 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>
113 </a>113 </a>
114-<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">Collapse all<kbd aria-hidden="true">x</kbd>114+<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">
115+<span hx-live="this.textContent = q('[data-file][open]').count || !q('[data-file]').count ? 'Collapse all' : 'Expand all'">Collapse all</span>
116+<kbd aria-hidden="true">x</kbd>
115 </button>117 </button>
116 </div>118 </div>
117 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">119 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
@@ -399,7 +401,7 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
399 <kbd>u</kbd>401 <kbd>u</kbd>
400 <kbd>x</kbd>402 <kbd>x</kbd>
401 </dt>403 </dt>
402-<dd>diff: split / unified / collapse all</dd>404+<dd>diff: split / unified / collapse or expand all</dd>
403 </div>405 </div>
404 <div>406 <div>
405 <dt>407 <dt>
@@ -573,9 +575,9 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
573 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);575 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
574 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })576 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
575 };577 };
576- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');578+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
577- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');579+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
578- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);580+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
579 k-permalink from:body -> {581 k-permalink from:body -> {
580 let b = document.body.dataset;582 let b = document.body.dataset;
581 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }583 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/commit-rename.html +7 -5
@@ -105,7 +105,9 @@
105105 </a>
106106 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>
107107 </a>
108-<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">Collapse all<kbd aria-hidden="true">x</kbd>
108+<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">
109+<span hx-live="this.textContent = q('[data-file][open]').count || !q('[data-file]').count ? 'Collapse all' : 'Expand all'">Collapse all</span>
110+<kbd aria-hidden="true">x</kbd>
109111 </button>
110112 </div>
111113 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
@@ -304,7 +306,7 @@
304306 <kbd>u</kbd>
305307 <kbd>x</kbd>
306308 </dt>
307-<dd>diff: split / unified / collapse all</dd>
309+<dd>diff: split / unified / collapse or expand all</dd>
308310 </div>
309311 <div>
310312 <dt>
@@ -478,9 +480,9 @@
478480 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
479481 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
480482 };
481- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
482- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
483- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
483+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
484+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
485+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
484486 k-permalink from:body -> {
485487 let b = document.body.dataset;
486488 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -105,7 +105,9 @@
105 </a>105 </a>
106 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>106 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>
107 </a>107 </a>
108-<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">Collapse all<kbd aria-hidden="true">x</kbd>108+<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">
109+<span hx-live="this.textContent = q('[data-file][open]').count || !q('[data-file]').count ? 'Collapse all' : 'Expand all'">Collapse all</span>
110+<kbd aria-hidden="true">x</kbd>
109 </button>111 </button>
110 </div>112 </div>
111 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">113 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
@@ -304,7 +306,7 @@
304 <kbd>u</kbd>306 <kbd>u</kbd>
305 <kbd>x</kbd>307 <kbd>x</kbd>
306 </dt>308 </dt>
307-<dd>diff: split / unified / collapse all</dd>309+<dd>diff: split / unified / collapse or expand all</dd>
308 </div>310 </div>
309 <div>311 <div>
310 <dt>312 <dt>
@@ -478,9 +480,9 @@
478 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);480 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
479 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })481 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
480 };482 };
481- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');483+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
482- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');484+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
483- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);485+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
484 k-permalink from:body -> {486 k-permalink from:body -> {
485 let b = document.body.dataset;487 let b = document.body.dataset;
486 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }488 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/commit-root.html +7 -5
@@ -103,7 +103,9 @@
103103 </a>
104104 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>
105105 </a>
106-<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">Collapse all<kbd aria-hidden="true">x</kbd>
106+<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">
107+<span hx-live="this.textContent = q('[data-file][open]').count || !q('[data-file]').count ? 'Collapse all' : 'Expand all'">Collapse all</span>
108+<kbd aria-hidden="true">x</kbd>
107109 </button>
108110 </div>
109111 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
@@ -620,7 +622,7 @@
620622 <kbd>u</kbd>
621623 <kbd>x</kbd>
622624 </dt>
623-<dd>diff: split / unified / collapse all</dd>
625+<dd>diff: split / unified / collapse or expand all</dd>
624626 </div>
625627 <div>
626628 <dt>
@@ -794,9 +796,9 @@
794796 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
795797 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
796798 };
797- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
798- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
799- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
799+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
800+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
801+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
800802 k-permalink from:body -> {
801803 let b = document.body.dataset;
802804 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -103,7 +103,9 @@
103 </a>103 </a>
104 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>104 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>
105 </a>105 </a>
106-<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">Collapse all<kbd aria-hidden="true">x</kbd>106+<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">
107+<span hx-live="this.textContent = q('[data-file][open]').count || !q('[data-file]').count ? 'Collapse all' : 'Expand all'">Collapse all</span>
108+<kbd aria-hidden="true">x</kbd>
107 </button>109 </button>
108 </div>110 </div>
109 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">111 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
@@ -620,7 +622,7 @@
620 <kbd>u</kbd>622 <kbd>u</kbd>
621 <kbd>x</kbd>623 <kbd>x</kbd>
622 </dt>624 </dt>
623-<dd>diff: split / unified / collapse all</dd>625+<dd>diff: split / unified / collapse or expand all</dd>
624 </div>626 </div>
625 <div>627 <div>
626 <dt>628 <dt>
@@ -794,9 +796,9 @@
794 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);796 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
795 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })797 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
796 };798 };
797- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');799+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
798- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');800+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
799- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);801+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
800 k-permalink from:body -> {802 k-permalink from:body -> {
801 let b = document.body.dataset;803 let b = document.body.dataset;
802 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }804 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/commit-trojan.html +7 -5
@@ -106,7 +106,9 @@
106106 </a>
107107 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>
108108 </a>
109-<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">Collapse all<kbd aria-hidden="true">x</kbd>
109+<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">
110+<span hx-live="this.textContent = q('[data-file][open]').count || !q('[data-file]').count ? 'Collapse all' : 'Expand all'">Collapse all</span>
111+<kbd aria-hidden="true">x</kbd>
110112 </button>
111113 </div>
112114 <p class="notice">this diff holds invisible characters (direction controls, zero-width characters). They are shown as <span class="inv">&lt;U+XXXX&gt;</span> markers, never applied.</p>
@@ -413,7 +415,7 @@
413415 <kbd>u</kbd>
414416 <kbd>x</kbd>
415417 </dt>
416-<dd>diff: split / unified / collapse all</dd>
418+<dd>diff: split / unified / collapse or expand all</dd>
417419 </div>
418420 <div>
419421 <dt>
@@ -587,9 +589,9 @@
587589 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
588590 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
589591 };
590- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
591- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
592- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
592+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
593+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
594+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
593595 k-permalink from:body -> {
594596 let b = document.body.dataset;
595597 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -106,7 +106,9 @@
106 </a>106 </a>
107 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>107 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))" :aria-current="q(&#39;#diffs&#39;).attr[&#39;data-diff&#39;] === &#39;split&#39; ? &#39;true&#39; : false">Split<kbd aria-hidden="true">d</kbd>
108 </a>108 </a>
109-<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">Collapse all<kbd aria-hidden="true">x</kbd>109+<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">
110+<span hx-live="this.textContent = q('[data-file][open]').count || !q('[data-file]').count ? 'Collapse all' : 'Expand all'">Collapse all</span>
111+<kbd aria-hidden="true">x</kbd>
110 </button>112 </button>
111 </div>113 </div>
112 <p class="notice">this diff holds invisible characters (direction controls, zero-width characters). They are shown as <span class="inv">&lt;U+XXXX&gt;</span> markers, never applied.</p>114 <p class="notice">this diff holds invisible characters (direction controls, zero-width characters). They are shown as <span class="inv">&lt;U+XXXX&gt;</span> markers, never applied.</p>
@@ -413,7 +415,7 @@
413 <kbd>u</kbd>415 <kbd>u</kbd>
414 <kbd>x</kbd>416 <kbd>x</kbd>
415 </dt>417 </dt>
416-<dd>diff: split / unified / collapse all</dd>418+<dd>diff: split / unified / collapse or expand all</dd>
417 </div>419 </div>
418 <div>420 <div>
419 <dt>421 <dt>
@@ -587,9 +589,9 @@
587 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);589 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
588 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })590 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
589 };591 };
590- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');592+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
591- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');593+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
592- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);594+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
593 k-permalink from:body -> {595 k-permalink from:body -> {
594 let b = document.body.dataset;596 let b = document.body.dataset;
595 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }597 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/commits-page2.html +4 -4
@@ -238,7 +238,7 @@
238238 <kbd>u</kbd>
239239 <kbd>x</kbd>
240240 </dt>
241-<dd>diff: split / unified / collapse all</dd>
241+<dd>diff: split / unified / collapse or expand all</dd>
242242 </div>
243243 <div>
244244 <dt>
@@ -398,9 +398,9 @@
398398 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
399399 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
400400 };
401- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
402- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
403- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
401+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
402+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
403+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
404404 k-permalink from:body -> {
405405 let b = document.body.dataset;
406406 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -238,7 +238,7 @@
238 <kbd>u</kbd>238 <kbd>u</kbd>
239 <kbd>x</kbd>239 <kbd>x</kbd>
240 </dt>240 </dt>
241-<dd>diff: split / unified / collapse all</dd>241+<dd>diff: split / unified / collapse or expand all</dd>
242 </div>242 </div>
243 <div>243 <div>
244 <dt>244 <dt>
@@ -398,9 +398,9 @@
398 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);398 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
399 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })399 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
400 };400 };
401- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');401+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
402- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');402+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
403- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);403+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
404 k-permalink from:body -> {404 k-permalink from:body -> {
405 let b = document.body.dataset;405 let b = document.body.dataset;
406 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }406 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/commits-path.html +4 -4
@@ -253,7 +253,7 @@
253253 <kbd>u</kbd>
254254 <kbd>x</kbd>
255255 </dt>
256-<dd>diff: split / unified / collapse all</dd>
256+<dd>diff: split / unified / collapse or expand all</dd>
257257 </div>
258258 <div>
259259 <dt>
@@ -413,9 +413,9 @@
413413 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
414414 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
415415 };
416- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
417- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
418- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
416+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
417+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
418+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
419419 k-permalink from:body -> {
420420 let b = document.body.dataset;
421421 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -253,7 +253,7 @@
253 <kbd>u</kbd>253 <kbd>u</kbd>
254 <kbd>x</kbd>254 <kbd>x</kbd>
255 </dt>255 </dt>
256-<dd>diff: split / unified / collapse all</dd>256+<dd>diff: split / unified / collapse or expand all</dd>
257 </div>257 </div>
258 <div>258 <div>
259 <dt>259 <dt>
@@ -413,9 +413,9 @@
413 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);413 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
414 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })414 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
415 };415 };
416- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');416+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
417- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');417+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
418- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);418+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
419 k-permalink from:body -> {419 k-permalink from:body -> {
420 let b = document.body.dataset;420 let b = document.body.dataset;
421 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }421 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/commits-root.html +4 -4
@@ -1040,7 +1040,7 @@
10401040 <kbd>u</kbd>
10411041 <kbd>x</kbd>
10421042 </dt>
1043-<dd>diff: split / unified / collapse all</dd>
1043+<dd>diff: split / unified / collapse or expand all</dd>
10441044 </div>
10451045 <div>
10461046 <dt>
@@ -1200,9 +1200,9 @@
12001200 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
12011201 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
12021202 };
1203- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
1204- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
1205- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
1203+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
1204+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
1205+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
12061206 k-permalink from:body -> {
12071207 let b = document.body.dataset;
12081208 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -1040,7 +1040,7 @@
1040 <kbd>u</kbd>1040 <kbd>u</kbd>
1041 <kbd>x</kbd>1041 <kbd>x</kbd>
1042 </dt>1042 </dt>
1043-<dd>diff: split / unified / collapse all</dd>1043+<dd>diff: split / unified / collapse or expand all</dd>
1044 </div>1044 </div>
1045 <div>1045 <div>
1046 <dt>1046 <dt>
@@ -1200,9 +1200,9 @@
1200 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);1200 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
1201 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })1201 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
1202 };1202 };
1203- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');1203+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
1204- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');1204+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
1205- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);1205+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
1206 k-permalink from:body -> {1206 k-permalink from:body -> {
1207 let b = document.body.dataset;1207 let b = document.body.dataset;
1208 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }1208 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/commits-small.html +4 -4
@@ -385,7 +385,7 @@
385385 <kbd>u</kbd>
386386 <kbd>x</kbd>
387387 </dt>
388-<dd>diff: split / unified / collapse all</dd>
388+<dd>diff: split / unified / collapse or expand all</dd>
389389 </div>
390390 <div>
391391 <dt>
@@ -565,9 +565,9 @@
565565 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
566566 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
567567 };
568- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
569- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
570- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
568+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
569+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
570+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
571571 k-permalink from:body -> {
572572 let b = document.body.dataset;
573573 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -385,7 +385,7 @@
385 <kbd>u</kbd>385 <kbd>u</kbd>
386 <kbd>x</kbd>386 <kbd>x</kbd>
387 </dt>387 </dt>
388-<dd>diff: split / unified / collapse all</dd>388+<dd>diff: split / unified / collapse or expand all</dd>
389 </div>389 </div>
390 <div>390 <div>
391 <dt>391 <dt>
@@ -565,9 +565,9 @@
565 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);565 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
566 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })566 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
567 };567 };
568- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');568+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
569- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');569+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
570- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);570+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
571 k-permalink from:body -> {571 k-permalink from:body -> {
572 let b = document.body.dataset;572 let b = document.body.dataset;
573 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }573 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/empty.html +4 -4
@@ -162,7 +162,7 @@
162162 <kbd>u</kbd>
163163 <kbd>x</kbd>
164164 </dt>
165-<dd>diff: split / unified / collapse all</dd>
165+<dd>diff: split / unified / collapse or expand all</dd>
166166 </div>
167167 <div>
168168 <dt>
@@ -259,9 +259,9 @@
259259 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
260260 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
261261 };
262- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
263- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
264- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
262+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
263+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
264+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
265265 k-permalink from:body -> {
266266 let b = document.body.dataset;
267267 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -162,7 +162,7 @@
162 <kbd>u</kbd>162 <kbd>u</kbd>
163 <kbd>x</kbd>163 <kbd>x</kbd>
164 </dt>164 </dt>
165-<dd>diff: split / unified / collapse all</dd>165+<dd>diff: split / unified / collapse or expand all</dd>
166 </div>166 </div>
167 <div>167 <div>
168 <dt>168 <dt>
@@ -259,9 +259,9 @@
259 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);259 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
260 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })260 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
261 };261 };
262- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');262+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
263- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');263+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
264- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);264+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
265 k-permalink from:body -> {265 k-permalink from:body -> {
266 let b = document.body.dataset;266 let b = document.body.dataset;
267 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }267 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/home.html +4 -4
@@ -192,7 +192,7 @@
192192 <kbd>u</kbd>
193193 <kbd>x</kbd>
194194 </dt>
195-<dd>diff: split / unified / collapse all</dd>
195+<dd>diff: split / unified / collapse or expand all</dd>
196196 </div>
197197 <div>
198198 <dt>
@@ -295,9 +295,9 @@
295295 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
296296 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
297297 };
298- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
299- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
300- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
298+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
299+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
300+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
301301 k-permalink from:body -> {
302302 let b = document.body.dataset;
303303 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -192,7 +192,7 @@
192 <kbd>u</kbd>192 <kbd>u</kbd>
193 <kbd>x</kbd>193 <kbd>x</kbd>
194 </dt>194 </dt>
195-<dd>diff: split / unified / collapse all</dd>195+<dd>diff: split / unified / collapse or expand all</dd>
196 </div>196 </div>
197 <div>197 <div>
198 <dt>198 <dt>
@@ -295,9 +295,9 @@
295 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);295 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
296 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })296 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
297 };297 };
298- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');298+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
299- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');299+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
300- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);300+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
301 k-permalink from:body -> {301 k-permalink from:body -> {
302 let b = document.body.dataset;302 let b = document.body.dataset;
303 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }303 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/not-found-page.html +4 -4
@@ -151,7 +151,7 @@
151151 <kbd>u</kbd>
152152 <kbd>x</kbd>
153153 </dt>
154-<dd>diff: split / unified / collapse all</dd>
154+<dd>diff: split / unified / collapse or expand all</dd>
155155 </div>
156156 <div>
157157 <dt>
@@ -242,9 +242,9 @@
242242 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
243243 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
244244 };
245- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
246- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
247- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
245+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
246+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
247+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
248248 k-permalink from:body -> {
249249 let b = document.body.dataset;
250250 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -151,7 +151,7 @@
151 <kbd>u</kbd>151 <kbd>u</kbd>
152 <kbd>x</kbd>152 <kbd>x</kbd>
153 </dt>153 </dt>
154-<dd>diff: split / unified / collapse all</dd>154+<dd>diff: split / unified / collapse or expand all</dd>
155 </div>155 </div>
156 <div>156 <div>
157 <dt>157 <dt>
@@ -242,9 +242,9 @@
242 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);242 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
243 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })243 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
244 };244 };
245- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');245+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
246- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');246+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
247- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);247+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
248 k-permalink from:body -> {248 k-permalink from:body -> {
249 let b = document.body.dataset;249 let b = document.body.dataset;
250 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }250 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/not-found.html +4 -4
@@ -151,7 +151,7 @@
151151 <kbd>u</kbd>
152152 <kbd>x</kbd>
153153 </dt>
154-<dd>diff: split / unified / collapse all</dd>
154+<dd>diff: split / unified / collapse or expand all</dd>
155155 </div>
156156 <div>
157157 <dt>
@@ -242,9 +242,9 @@
242242 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
243243 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
244244 };
245- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
246- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
247- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
245+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
246+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
247+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
248248 k-permalink from:body -> {
249249 let b = document.body.dataset;
250250 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -151,7 +151,7 @@
151 <kbd>u</kbd>151 <kbd>u</kbd>
152 <kbd>x</kbd>152 <kbd>x</kbd>
153 </dt>153 </dt>
154-<dd>diff: split / unified / collapse all</dd>154+<dd>diff: split / unified / collapse or expand all</dd>
155 </div>155 </div>
156 <div>156 <div>
157 <dt>157 <dt>
@@ -242,9 +242,9 @@
242 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);242 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
243 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })243 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
244 };244 };
245- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');245+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
246- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');246+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
247- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);247+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
248 k-permalink from:body -> {248 k-permalink from:body -> {
249 let b = document.body.dataset;249 let b = document.body.dataset;
250 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }250 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/refs.html +4 -4
@@ -276,7 +276,7 @@
276276 <kbd>u</kbd>
277277 <kbd>x</kbd>
278278 </dt>
279-<dd>diff: split / unified / collapse all</dd>
279+<dd>diff: split / unified / collapse or expand all</dd>
280280 </div>
281281 <div>
282282 <dt>
@@ -466,9 +466,9 @@
466466 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
467467 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
468468 };
469- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
470- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
471- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
469+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
470+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
471+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
472472 k-permalink from:body -> {
473473 let b = document.body.dataset;
474474 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -276,7 +276,7 @@
276 <kbd>u</kbd>276 <kbd>u</kbd>
277 <kbd>x</kbd>277 <kbd>x</kbd>
278 </dt>278 </dt>
279-<dd>diff: split / unified / collapse all</dd>279+<dd>diff: split / unified / collapse or expand all</dd>
280 </div>280 </div>
281 <div>281 <div>
282 <dt>282 <dt>
@@ -466,9 +466,9 @@
466 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);466 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
467 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })467 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
468 };468 };
469- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');469+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
470- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');470+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
471- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);471+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
472 k-permalink from:body -> {472 k-permalink from:body -> {
473 let b = document.body.dataset;473 let b = document.body.dataset;
474 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }474 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/summary.html +17 -6
@@ -127,14 +127,25 @@
127127 </dd>
128128 <dt>branch</dt>
129129 <dd>main · 2 branches</dd>
130-<dt>tag</dt>
131-<dd>v0.2.0 · AGO · 2 tags</dd>
132130 <dt>created</dt>
133131 <dd>
134132 <time datetime="STAMP">STAMP</time>
135133 </dd>
136134 </dl>
137135 </section>
136+<section aria-labelledby="r-tag">
137+<h2 id="r-tag">Tags <span class="count">2</span>
138+<a href="/demo/refs">View all</a>
139+</h2>
140+<div class="latest-tag">
141+<a class="tag-name" href="/demo/tree/v0.2.0">
142+<span class="ico i-tag" aria-hidden="true">
143+</span>v0.2.0</a>
144+<span class="pill">latest</span>
145+<time class="dim" datetime="STAMP" title="STAMP">AGO</time>
146+<p class="tag-sub dim">add main.go</p>
147+</div>
148+</section>
138149 <section aria-labelledby="r-recent">
139150 <h2 id="r-recent">Recent commits <a href="/demo/commits/main">View all</a>
140151 </h2>
@@ -281,7 +292,7 @@
281292 <kbd>u</kbd>
282293 <kbd>x</kbd>
283294 </dt>
284-<dd>diff: split / unified / collapse all</dd>
295+<dd>diff: split / unified / collapse or expand all</dd>
285296 </div>
286297 <div>
287298 <dt>
@@ -471,9 +482,9 @@
471482 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
472483 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
473484 };
474- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
475- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
476- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
485+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
486+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
487+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
477488 k-permalink from:body -> {
478489 let b = document.body.dataset;
479490 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -127,14 +127,25 @@
127 </dd>127 </dd>
128 <dt>branch</dt>128 <dt>branch</dt>
129 <dd>main · 2 branches</dd>129 <dd>main · 2 branches</dd>
130-<dt>tag</dt>
131-<dd>v0.2.0 · AGO · 2 tags</dd>
132 <dt>created</dt>130 <dt>created</dt>
133 <dd>131 <dd>
134 <time datetime="STAMP">STAMP</time>132 <time datetime="STAMP">STAMP</time>
135 </dd>133 </dd>
136 </dl>134 </dl>
137 </section>135 </section>
136+<section aria-labelledby="r-tag">
137+<h2 id="r-tag">Tags <span class="count">2</span>
138+<a href="/demo/refs">View all</a>
139+</h2>
140+<div class="latest-tag">
141+<a class="tag-name" href="/demo/tree/v0.2.0">
142+<span class="ico i-tag" aria-hidden="true">
143+</span>v0.2.0</a>
144+<span class="pill">latest</span>
145+<time class="dim" datetime="STAMP" title="STAMP">AGO</time>
146+<p class="tag-sub dim">add main.go</p>
147+</div>
148+</section>
138 <section aria-labelledby="r-recent">149 <section aria-labelledby="r-recent">
139 <h2 id="r-recent">Recent commits <a href="/demo/commits/main">View all</a>150 <h2 id="r-recent">Recent commits <a href="/demo/commits/main">View all</a>
140 </h2>151 </h2>
@@ -281,7 +292,7 @@
281 <kbd>u</kbd>292 <kbd>u</kbd>
282 <kbd>x</kbd>293 <kbd>x</kbd>
283 </dt>294 </dt>
284-<dd>diff: split / unified / collapse all</dd>295+<dd>diff: split / unified / collapse or expand all</dd>
285 </div>296 </div>
286 <div>297 <div>
287 <dt>298 <dt>
@@ -471,9 +482,9 @@
471 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);482 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
472 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })483 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
473 };484 };
474- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');485+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
475- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');486+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
476- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);487+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
477 k-permalink from:body -> {488 k-permalink from:body -> {
478 let b = document.body.dataset;489 let b = document.body.dataset;
479 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }490 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/tree-nested.html +4 -4
@@ -221,7 +221,7 @@
221221 <kbd>u</kbd>
222222 <kbd>x</kbd>
223223 </dt>
224-<dd>diff: split / unified / collapse all</dd>
224+<dd>diff: split / unified / collapse or expand all</dd>
225225 </div>
226226 <div>
227227 <dt>
@@ -401,9 +401,9 @@
401401 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
402402 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
403403 };
404- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
405- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
406- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
404+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
405+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
406+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
407407 k-permalink from:body -> {
408408 let b = document.body.dataset;
409409 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -221,7 +221,7 @@
221 <kbd>u</kbd>221 <kbd>u</kbd>
222 <kbd>x</kbd>222 <kbd>x</kbd>
223 </dt>223 </dt>
224-<dd>diff: split / unified / collapse all</dd>224+<dd>diff: split / unified / collapse or expand all</dd>
225 </div>225 </div>
226 <div>226 <div>
227 <dt>227 <dt>
@@ -401,9 +401,9 @@
401 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);401 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
402 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })402 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
403 };403 };
404- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');404+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
405- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');405+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
406- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);406+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
407 k-permalink from:body -> {407 k-permalink from:body -> {
408 let b = document.body.dataset;408 let b = document.body.dataset;
409 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }409 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/tree-root.html +4 -4
@@ -448,7 +448,7 @@
448448 <kbd>u</kbd>
449449 <kbd>x</kbd>
450450 </dt>
451-<dd>diff: split / unified / collapse all</dd>
451+<dd>diff: split / unified / collapse or expand all</dd>
452452 </div>
453453 <div>
454454 <dt>
@@ -700,9 +700,9 @@
700700 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
701701 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
702702 };
703- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
704- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
705- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
703+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
704+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
705+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
706706 k-permalink from:body -> {
707707 let b = document.body.dataset;
708708 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -448,7 +448,7 @@
448 <kbd>u</kbd>448 <kbd>u</kbd>
449 <kbd>x</kbd>449 <kbd>x</kbd>
450 </dt>450 </dt>
451-<dd>diff: split / unified / collapse all</dd>451+<dd>diff: split / unified / collapse or expand all</dd>
452 </div>452 </div>
453 <div>453 <div>
454 <dt>454 <dt>
@@ -700,9 +700,9 @@
700 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);700 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
701 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })701 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
702 };702 };
703- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');703+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
704- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');704+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
705- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);705+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
706 k-permalink from:body -> {706 k-permalink from:body -> {
707 let b = document.body.dataset;707 let b = document.body.dataset;
708 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }708 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/tree-slash-ref.html +4 -4
@@ -218,7 +218,7 @@
218218 <kbd>u</kbd>
219219 <kbd>x</kbd>
220220 </dt>
221-<dd>diff: split / unified / collapse all</dd>
221+<dd>diff: split / unified / collapse or expand all</dd>
222222 </div>
223223 <div>
224224 <dt>
@@ -398,9 +398,9 @@
398398 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
399399 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
400400 };
401- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
402- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
403- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
401+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
402+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
403+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
404404 k-permalink from:body -> {
405405 let b = document.body.dataset;
406406 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -218,7 +218,7 @@
218 <kbd>u</kbd>218 <kbd>u</kbd>
219 <kbd>x</kbd>219 <kbd>x</kbd>
220 </dt>220 </dt>
221-<dd>diff: split / unified / collapse all</dd>221+<dd>diff: split / unified / collapse or expand all</dd>
222 </div>222 </div>
223 <div>223 <div>
224 <dt>224 <dt>
@@ -398,9 +398,9 @@
398 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);398 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
399 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })399 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
400 };400 };
401- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');401+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
402- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');402+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
403- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);403+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
404 k-permalink from:body -> {404 k-permalink from:body -> {
405 let b = document.body.dataset;405 let b = document.body.dataset;
406 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }406 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M cmd/ermite/testdata/web/tree-subdir.html +4 -4
@@ -243,7 +243,7 @@
243243 <kbd>u</kbd>
244244 <kbd>x</kbd>
245245 </dt>
246-<dd>diff: split / unified / collapse all</dd>
246+<dd>diff: split / unified / collapse or expand all</dd>
247247 </div>
248248 <div>
249249 <dt>
@@ -429,9 +429,9 @@
429429 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
430430 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
431431 };
432- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
433- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
434- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
432+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
433+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
434+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
435435 k-permalink from:body -> {
436436 let b = document.body.dataset;
437437 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
@@ -243,7 +243,7 @@
243 <kbd>u</kbd>243 <kbd>u</kbd>
244 <kbd>x</kbd>244 <kbd>x</kbd>
245 </dt>245 </dt>
246-<dd>diff: split / unified / collapse all</dd>246+<dd>diff: split / unified / collapse or expand all</dd>
247 </div>247 </div>
248 <div>248 <div>
249 <dt>249 <dt>
@@ -429,9 +429,9 @@
429 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);429 let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);
430 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })430 let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })
431 };431 };
432- k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');432+ k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);
433- k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');433+ k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);
434- k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);434+ k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };
435 k-permalink from:body -> {435 k-permalink from:body -> {
436 let b = document.body.dataset;436 let b = document.body.dataset;
437 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }437 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
M internal/adapters/web/pages.templ +15 -0
@@ -59,6 +59,21 @@ templ summaryRail(p SummaryPage) {
5959 </section>
6060 <section aria-labelledby="r-facts"><h2 id="r-facts">About</h2>@Facts(p.Facts)
6161 </section>
62+ if p.Latest != nil {
63+ <section aria-labelledby="r-tag">
64+ <h2 id="r-tag">Tags <span class="count">{ itoa(p.Latest.Count) }</span> <a href={ templ.SafeURL(p.Latest.RefsHref) }>View all</a></h2>
65+ if p.Latest.Name != "" {
66+ <div class="latest-tag">
67+ <a class="tag-name" href={ templ.SafeURL(p.Latest.Href) }><span class="ico i-tag" aria-hidden="true"></span>{ p.Latest.Name }</a>
68+ <span class="pill">latest</span>
69+ <time class="dim" datetime={ p.Latest.WhenAt } title={ p.Latest.WhenAt }>{ p.Latest.When }</time>
70+ if p.Latest.Subject != "" {
71+ <p class="tag-sub dim">{ p.Latest.Subject }</p>
72+ }
73+ </div>
74+ }
75+ </section>
76+ }
6277 if len(p.Recent) > 0 {
6378 <section aria-labelledby="r-recent"><h2 id="r-recent">Recent commits <a href={ templ.SafeURL(p.AllHref) }>View all</a></h2>@CommitRows(p.Recent)
6479 </section>
@@ -59,6 +59,21 @@ templ summaryRail(p SummaryPage) {
59 </section>59 </section>
60 <section aria-labelledby="r-facts"><h2 id="r-facts">About</h2>@Facts(p.Facts)60 <section aria-labelledby="r-facts"><h2 id="r-facts">About</h2>@Facts(p.Facts)
61 </section>61 </section>
62+ if p.Latest != nil {
63+ <section aria-labelledby="r-tag">
64+ <h2 id="r-tag">Tags <span class="count">{ itoa(p.Latest.Count) }</span> <a href={ templ.SafeURL(p.Latest.RefsHref) }>View all</a></h2>
65+ if p.Latest.Name != "" {
66+ <div class="latest-tag">
67+ <a class="tag-name" href={ templ.SafeURL(p.Latest.Href) }><span class="ico i-tag" aria-hidden="true"></span>{ p.Latest.Name }</a>
68+ <span class="pill">latest</span>
69+ <time class="dim" datetime={ p.Latest.WhenAt } title={ p.Latest.WhenAt }>{ p.Latest.When }</time>
70+ if p.Latest.Subject != "" {
71+ <p class="tag-sub dim">{ p.Latest.Subject }</p>
72+ }
73+ </div>
74+ }
75+ </section>
76+ }
62 if len(p.Recent) > 0 {77 if len(p.Recent) > 0 {
63 <section aria-labelledby="r-recent"><h2 id="r-recent">Recent commits <a href={ templ.SafeURL(p.AllHref) }>View all</a></h2>@CommitRows(p.Recent)78 <section aria-labelledby="r-recent"><h2 id="r-recent">Recent commits <a href={ templ.SafeURL(p.AllHref) }>View all</a></h2>@CommitRows(p.Recent)
64 </section>79 </section>
M internal/adapters/web/pages_templ.go +281 -151
@@ -256,21 +256,151 @@ func summaryRail(p SummaryPage) templ.Component {
256256 if templ_7745c5c3_Err != nil {
257257 return templ_7745c5c3_Err
258258 }
259- if len(p.Recent) > 0 {
260- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<section aria-labelledby=\"r-recent\"><h2 id=\"r-recent\">Recent commits <a href=\"")
259+ if p.Latest != nil {
260+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<section aria-labelledby=\"r-tag\"><h2 id=\"r-tag\">Tags <span class=\"count\">")
261261 if templ_7745c5c3_Err != nil {
262262 return templ_7745c5c3_Err
263263 }
264- var templ_7745c5c3_Var12 templ.SafeURL
265- templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.AllHref))
264+ var templ_7745c5c3_Var12 string
265+ templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Latest.Count))
266266 if templ_7745c5c3_Err != nil {
267- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 63, Col: 105}
267+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 64, Col: 65}
268268 }
269269 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
270270 if templ_7745c5c3_Err != nil {
271271 return templ_7745c5c3_Err
272272 }
273- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\">View all</a></h2>")
273+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</span> <a href=\"")
274+ if templ_7745c5c3_Err != nil {
275+ return templ_7745c5c3_Err
276+ }
277+ var templ_7745c5c3_Var13 templ.SafeURL
278+ templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Latest.RefsHref))
279+ if templ_7745c5c3_Err != nil {
280+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 64, Col: 117}
281+ }
282+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
283+ if templ_7745c5c3_Err != nil {
284+ return templ_7745c5c3_Err
285+ }
286+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\">View all</a></h2>")
287+ if templ_7745c5c3_Err != nil {
288+ return templ_7745c5c3_Err
289+ }
290+ if p.Latest.Name != "" {
291+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<div class=\"latest-tag\"><a class=\"tag-name\" href=\"")
292+ if templ_7745c5c3_Err != nil {
293+ return templ_7745c5c3_Err
294+ }
295+ var templ_7745c5c3_Var14 templ.SafeURL
296+ templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Latest.Href))
297+ if templ_7745c5c3_Err != nil {
298+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 67, Col: 60}
299+ }
300+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
301+ if templ_7745c5c3_Err != nil {
302+ return templ_7745c5c3_Err
303+ }
304+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\"><span class=\"ico i-tag\" aria-hidden=\"true\"></span>")
305+ if templ_7745c5c3_Err != nil {
306+ return templ_7745c5c3_Err
307+ }
308+ var templ_7745c5c3_Var15 string
309+ templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(p.Latest.Name)
310+ if templ_7745c5c3_Err != nil {
311+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 67, Col: 128}
312+ }
313+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
314+ if templ_7745c5c3_Err != nil {
315+ return templ_7745c5c3_Err
316+ }
317+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</a> <span class=\"pill\">latest</span> <time class=\"dim\" datetime=\"")
318+ if templ_7745c5c3_Err != nil {
319+ return templ_7745c5c3_Err
320+ }
321+ var templ_7745c5c3_Var16 string
322+ templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Latest.WhenAt)
323+ if templ_7745c5c3_Err != nil {
324+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 69, Col: 49}
325+ }
326+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var16)
327+ if templ_7745c5c3_Err != nil {
328+ return templ_7745c5c3_Err
329+ }
330+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\" title=\"")
331+ if templ_7745c5c3_Err != nil {
332+ return templ_7745c5c3_Err
333+ }
334+ var templ_7745c5c3_Var17 string
335+ templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Latest.WhenAt)
336+ if templ_7745c5c3_Err != nil {
337+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 69, Col: 75}
338+ }
339+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17)
340+ if templ_7745c5c3_Err != nil {
341+ return templ_7745c5c3_Err
342+ }
343+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\">")
344+ if templ_7745c5c3_Err != nil {
345+ return templ_7745c5c3_Err
346+ }
347+ var templ_7745c5c3_Var18 string
348+ templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(p.Latest.When)
349+ if templ_7745c5c3_Err != nil {
350+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 69, Col: 93}
351+ }
352+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
353+ if templ_7745c5c3_Err != nil {
354+ return templ_7745c5c3_Err
355+ }
356+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</time> ")
357+ if templ_7745c5c3_Err != nil {
358+ return templ_7745c5c3_Err
359+ }
360+ if p.Latest.Subject != "" {
361+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<p class=\"tag-sub dim\">")
362+ if templ_7745c5c3_Err != nil {
363+ return templ_7745c5c3_Err
364+ }
365+ var templ_7745c5c3_Var19 string
366+ templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(p.Latest.Subject)
367+ if templ_7745c5c3_Err != nil {
368+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 71, Col: 47}
369+ }
370+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
371+ if templ_7745c5c3_Err != nil {
372+ return templ_7745c5c3_Err
373+ }
374+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</p>")
375+ if templ_7745c5c3_Err != nil {
376+ return templ_7745c5c3_Err
377+ }
378+ }
379+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</div>")
380+ if templ_7745c5c3_Err != nil {
381+ return templ_7745c5c3_Err
382+ }
383+ }
384+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</section>")
385+ if templ_7745c5c3_Err != nil {
386+ return templ_7745c5c3_Err
387+ }
388+ }
389+ if len(p.Recent) > 0 {
390+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<section aria-labelledby=\"r-recent\"><h2 id=\"r-recent\">Recent commits <a href=\"")
391+ if templ_7745c5c3_Err != nil {
392+ return templ_7745c5c3_Err
393+ }
394+ var templ_7745c5c3_Var20 templ.SafeURL
395+ templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.AllHref))
396+ if templ_7745c5c3_Err != nil {
397+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 78, Col: 105}
398+ }
399+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
400+ if templ_7745c5c3_Err != nil {
401+ return templ_7745c5c3_Err
402+ }
403+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "\">View all</a></h2>")
274404 if templ_7745c5c3_Err != nil {
275405 return templ_7745c5c3_Err
276406 }
@@ -278,13 +408,13 @@ func summaryRail(p SummaryPage) templ.Component {
278408 if templ_7745c5c3_Err != nil {
279409 return templ_7745c5c3_Err
280410 }
281- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</section>")
411+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</section>")
282412 if templ_7745c5c3_Err != nil {
283413 return templ_7745c5c3_Err
284414 }
285415 }
286416 if len(p.Outline) > 0 && p.Readme != nil {
287- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<section aria-labelledby=\"r-outline\"><h2 id=\"r-outline\">On this page</h2>")
417+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<section aria-labelledby=\"r-outline\"><h2 id=\"r-outline\">On this page</h2>")
288418 if templ_7745c5c3_Err != nil {
289419 return templ_7745c5c3_Err
290420 }
@@ -292,7 +422,7 @@ func summaryRail(p SummaryPage) templ.Component {
292422 if templ_7745c5c3_Err != nil {
293423 return templ_7745c5c3_Err
294424 }
295- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</section>")
425+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "</section>")
296426 if templ_7745c5c3_Err != nil {
297427 return templ_7745c5c3_Err
298428 }
@@ -318,12 +448,12 @@ func Refs(l Layout, p RefsPage) templ.Component {
318448 }()
319449 }
320450 ctx = templ.InitializeContext(ctx)
321- templ_7745c5c3_Var13 := templ.GetChildren(ctx)
322- if templ_7745c5c3_Var13 == nil {
323- templ_7745c5c3_Var13 = templ.NopComponent
451+ templ_7745c5c3_Var21 := templ.GetChildren(ctx)
452+ if templ_7745c5c3_Var21 == nil {
453+ templ_7745c5c3_Var21 = templ.NopComponent
324454 }
325455 ctx = templ.ClearChildren(ctx)
326- templ_7745c5c3_Var14 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
456+ templ_7745c5c3_Var22 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
327457 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
328458 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
329459 if !templ_7745c5c3_IsBuffer {
@@ -335,7 +465,7 @@ func Refs(l Layout, p RefsPage) templ.Component {
335465 }()
336466 }
337467 ctx = templ.InitializeContext(ctx)
338- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<div class=\"filter\"><label for=\"ref-filter\" class=\"sr\">filter</label> <input id=\"ref-filter\" type=\"search\" data-filter placeholder=\"Filter branches and tags…\" autocomplete=\"off\" hx-on:input=\"let t = this.value.toLowerCase(); q('#ref-rows > tr').arr().forEach(r => r.hidden = !r.textContent.toLowerCase().includes(t));\n let first = q('#ref-rows > tr:not([hidden])').arr()[0]; first && q(first).take('data-cursor', '#ref-rows > tr')\"></div><table class=\"list refs\" aria-label=\"Branches and tags\"><thead><tr><th class=\"cur\"></th><th class=\"c-kind\">kind</th><th class=\"c-name\">name</th><th class=\"c-sha\">commit</th><th class=\"c-sub\">subject</th><th class=\"c-when\">date</th></tr></thead>")
468+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<div class=\"filter\"><label for=\"ref-filter\" class=\"sr\">filter</label> <input id=\"ref-filter\" type=\"search\" data-filter placeholder=\"Filter branches and tags…\" autocomplete=\"off\" hx-on:input=\"let t = this.value.toLowerCase(); q('#ref-rows > tr').arr().forEach(r => r.hidden = !r.textContent.toLowerCase().includes(t));\n let first = q('#ref-rows > tr:not([hidden])').arr()[0]; first && q(first).take('data-cursor', '#ref-rows > tr')\"></div><table class=\"list refs\" aria-label=\"Branches and tags\"><thead><tr><th class=\"cur\"></th><th class=\"c-kind\">kind</th><th class=\"c-name\">name</th><th class=\"c-sha\">commit</th><th class=\"c-sub\">subject</th><th class=\"c-when\">date</th></tr></thead>")
339469 if templ_7745c5c3_Err != nil {
340470 return templ_7745c5c3_Err
341471 }
@@ -343,32 +473,32 @@ func Refs(l Layout, p RefsPage) templ.Component {
343473 if templ_7745c5c3_Err != nil {
344474 return templ_7745c5c3_Err
345475 }
346- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</table>")
476+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</table>")
347477 if templ_7745c5c3_Err != nil {
348478 return templ_7745c5c3_Err
349479 }
350480 if p.Note != "" {
351- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<p class=\"note\">")
481+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<p class=\"note\">")
352482 if templ_7745c5c3_Err != nil {
353483 return templ_7745c5c3_Err
354484 }
355- var templ_7745c5c3_Var15 string
356- templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(p.Note)
485+ var templ_7745c5c3_Var23 string
486+ templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(p.Note)
357487 if templ_7745c5c3_Err != nil {
358- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 94, Col: 27}
488+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 109, Col: 27}
359489 }
360- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
490+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
361491 if templ_7745c5c3_Err != nil {
362492 return templ_7745c5c3_Err
363493 }
364- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</p>")
494+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "</p>")
365495 if templ_7745c5c3_Err != nil {
366496 return templ_7745c5c3_Err
367497 }
368498 }
369499 return nil
370500 })
371- templ_7745c5c3_Err = Shell(l, refsRail(p)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var14), templ_7745c5c3_Buffer)
501+ templ_7745c5c3_Err = Shell(l, refsRail(p)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var22), templ_7745c5c3_Buffer)
372502 if templ_7745c5c3_Err != nil {
373503 return templ_7745c5c3_Err
374504 }
@@ -392,179 +522,179 @@ func refRow(kind string, r RefRow) templ.Component {
392522 }()
393523 }
394524 ctx = templ.InitializeContext(ctx)
395- templ_7745c5c3_Var16 := templ.GetChildren(ctx)
396- if templ_7745c5c3_Var16 == nil {
397- templ_7745c5c3_Var16 = templ.NopComponent
525+ templ_7745c5c3_Var24 := templ.GetChildren(ctx)
526+ if templ_7745c5c3_Var24 == nil {
527+ templ_7745c5c3_Var24 = templ.NopComponent
398528 }
399529 ctx = templ.ClearChildren(ctx)
400- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<tr")
530+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "<tr")
401531 if templ_7745c5c3_Err != nil {
402532 return templ_7745c5c3_Err
403533 }
404534 if r.Cursor {
405- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, " data-cursor")
535+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, " data-cursor")
406536 if templ_7745c5c3_Err != nil {
407537 return templ_7745c5c3_Err
408538 }
409539 }
410- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "><td class=\"cur\" aria-hidden=\"true\"></td><td class=\"c-kind mute\">")
540+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "><td class=\"cur\" aria-hidden=\"true\"></td><td class=\"c-kind mute\">")
411541 if templ_7745c5c3_Err != nil {
412542 return templ_7745c5c3_Err
413543 }
414- var templ_7745c5c3_Var17 string
415- templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(kind)
544+ var templ_7745c5c3_Var25 string
545+ templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(kind)
416546 if templ_7745c5c3_Err != nil {
417- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 102, Col: 32}
547+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 117, Col: 32}
418548 }
419- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
549+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
420550 if templ_7745c5c3_Err != nil {
421551 return templ_7745c5c3_Err
422552 }
423- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</td><td class=\"c-name\"><a href=\"")
553+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</td><td class=\"c-name\"><a href=\"")
424554 if templ_7745c5c3_Err != nil {
425555 return templ_7745c5c3_Err
426556 }
427- var templ_7745c5c3_Var18 templ.SafeURL
428- templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Href))
557+ var templ_7745c5c3_Var26 templ.SafeURL
558+ templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Href))
429559 if templ_7745c5c3_Err != nil {
430- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 104, Col: 34}
560+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 119, Col: 34}
431561 }
432- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
562+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
433563 if templ_7745c5c3_Err != nil {
434564 return templ_7745c5c3_Err
435565 }
436- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\">")
566+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "\">")
437567 if templ_7745c5c3_Err != nil {
438568 return templ_7745c5c3_Err
439569 }
440- var templ_7745c5c3_Var19 string
441- templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
570+ var templ_7745c5c3_Var27 string
571+ templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
442572 if templ_7745c5c3_Err != nil {
443- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 104, Col: 45}
573+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 119, Col: 45}
444574 }
445- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
575+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
446576 if templ_7745c5c3_Err != nil {
447577 return templ_7745c5c3_Err
448578 }
449- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</a> ")
579+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "</a> ")
450580 if templ_7745c5c3_Err != nil {
451581 return templ_7745c5c3_Err
452582 }
453583 if r.Default {
454- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<span class=\"pill\">default</span>")
584+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "<span class=\"pill\">default</span>")
455585 if templ_7745c5c3_Err != nil {
456586 return templ_7745c5c3_Err
457587 }
458588 }
459- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</td><td class=\"c-sha\">")
589+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "</td><td class=\"c-sha\">")
460590 if templ_7745c5c3_Err != nil {
461591 return templ_7745c5c3_Err
462592 }
463593 if r.CHref != "" {
464- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<a href=\"")
594+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "<a href=\"")
465595 if templ_7745c5c3_Err != nil {
466596 return templ_7745c5c3_Err
467597 }
468- var templ_7745c5c3_Var20 templ.SafeURL
469- templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.CHref))
598+ var templ_7745c5c3_Var28 templ.SafeURL
599+ templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.CHref))
470600 if templ_7745c5c3_Err != nil {
471- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 111, Col: 36}
601+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 126, Col: 36}
472602 }
473- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
603+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
474604 if templ_7745c5c3_Err != nil {
475605 return templ_7745c5c3_Err
476606 }
477- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "\">")
607+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "\">")
478608 if templ_7745c5c3_Err != nil {
479609 return templ_7745c5c3_Err
480610 }
481- var templ_7745c5c3_Var21 string
482- templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short)
611+ var templ_7745c5c3_Var29 string
612+ templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short)
483613 if templ_7745c5c3_Err != nil {
484- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 111, Col: 48}
614+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 126, Col: 48}
485615 }
486- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
616+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
487617 if templ_7745c5c3_Err != nil {
488618 return templ_7745c5c3_Err
489619 }
490- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</a>")
620+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</a>")
491621 if templ_7745c5c3_Err != nil {
492622 return templ_7745c5c3_Err
493623 }
494624 } else {
495- var templ_7745c5c3_Var22 string
496- templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short)
625+ var templ_7745c5c3_Var30 string
626+ templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short)
497627 if templ_7745c5c3_Err != nil {
498- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 113, Col: 13}
628+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 128, Col: 13}
499629 }
500- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
630+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
501631 if templ_7745c5c3_Err != nil {
502632 return templ_7745c5c3_Err
503633 }
504634 }
505- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</td><td class=\"c-sub\">")
635+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</td><td class=\"c-sub\">")
506636 if templ_7745c5c3_Err != nil {
507637 return templ_7745c5c3_Err
508638 }
509- var templ_7745c5c3_Var23 string
510- templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(r.Subject)
639+ var templ_7745c5c3_Var31 string
640+ templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(r.Subject)
511641 if templ_7745c5c3_Err != nil {
512- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 116, Col: 31}
642+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 131, Col: 31}
513643 }
514- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
644+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
515645 if templ_7745c5c3_Err != nil {
516646 return templ_7745c5c3_Err
517647 }
518- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "</td><td class=\"c-when dim\">")
648+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "</td><td class=\"c-when dim\">")
519649 if templ_7745c5c3_Err != nil {
520650 return templ_7745c5c3_Err
521651 }
522652 if r.WhenAt != "" {
523- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "<time datetime=\"")
653+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<time datetime=\"")
524654 if templ_7745c5c3_Err != nil {
525655 return templ_7745c5c3_Err
526656 }
527- var templ_7745c5c3_Var24 string
528- templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt)
657+ var templ_7745c5c3_Var32 string
658+ templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt)
529659 if templ_7745c5c3_Err != nil {
530- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 119, Col: 29}
660+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 134, Col: 29}
531661 }
532- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var24)
662+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var32)
533663 if templ_7745c5c3_Err != nil {
534664 return templ_7745c5c3_Err
535665 }
536- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "\" title=\"")
666+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "\" title=\"")
537667 if templ_7745c5c3_Err != nil {
538668 return templ_7745c5c3_Err
539669 }
540- var templ_7745c5c3_Var25 string
541- templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt)
670+ var templ_7745c5c3_Var33 string
671+ templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt)
542672 if templ_7745c5c3_Err != nil {
543- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 119, Col: 48}
673+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 134, Col: 48}
544674 }
545- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var25)
675+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var33)
546676 if templ_7745c5c3_Err != nil {
547677 return templ_7745c5c3_Err
548678 }
549- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "\">")
679+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\">")
550680 if templ_7745c5c3_Err != nil {
551681 return templ_7745c5c3_Err
552682 }
553- var templ_7745c5c3_Var26 string
554- templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(r.When)
683+ var templ_7745c5c3_Var34 string
684+ templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(r.When)
555685 if templ_7745c5c3_Err != nil {
556- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 119, Col: 59}
686+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 134, Col: 59}
557687 }
558- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
688+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
559689 if templ_7745c5c3_Err != nil {
560690 return templ_7745c5c3_Err
561691 }
562- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</time>")
692+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "</time>")
563693 if templ_7745c5c3_Err != nil {
564694 return templ_7745c5c3_Err
565695 }
566696 }
567- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</td></tr>")
697+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "</td></tr>")
568698 if templ_7745c5c3_Err != nil {
569699 return templ_7745c5c3_Err
570700 }
@@ -588,12 +718,12 @@ func refsRail(p RefsPage) templ.Component {
588718 }()
589719 }
590720 ctx = templ.InitializeContext(ctx)
591- templ_7745c5c3_Var27 := templ.GetChildren(ctx)
592- if templ_7745c5c3_Var27 == nil {
593- templ_7745c5c3_Var27 = templ.NopComponent
721+ templ_7745c5c3_Var35 := templ.GetChildren(ctx)
722+ if templ_7745c5c3_Var35 == nil {
723+ templ_7745c5c3_Var35 = templ.NopComponent
594724 }
595725 ctx = templ.ClearChildren(ctx)
596- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<section aria-labelledby=\"r-refs\"><h2 id=\"r-refs\">Overview</h2>")
726+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "<section aria-labelledby=\"r-refs\"><h2 id=\"r-refs\">Overview</h2>")
597727 if templ_7745c5c3_Err != nil {
598728 return templ_7745c5c3_Err
599729 }
@@ -601,7 +731,7 @@ func refsRail(p RefsPage) templ.Component {
601731 if templ_7745c5c3_Err != nil {
602732 return templ_7745c5c3_Err
603733 }
604- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "</section>")
734+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</section>")
605735 if templ_7745c5c3_Err != nil {
606736 return templ_7745c5c3_Err
607737 }
@@ -626,12 +756,12 @@ func State(l Layout, p StatePage) templ.Component {
626756 }()
627757 }
628758 ctx = templ.InitializeContext(ctx)
629- templ_7745c5c3_Var28 := templ.GetChildren(ctx)
630- if templ_7745c5c3_Var28 == nil {
631- templ_7745c5c3_Var28 = templ.NopComponent
759+ templ_7745c5c3_Var36 := templ.GetChildren(ctx)
760+ if templ_7745c5c3_Var36 == nil {
761+ templ_7745c5c3_Var36 = templ.NopComponent
632762 }
633763 ctx = templ.ClearChildren(ctx)
634- templ_7745c5c3_Var29 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
764+ templ_7745c5c3_Var37 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
635765 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
636766 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
637767 if !templ_7745c5c3_IsBuffer {
@@ -643,51 +773,51 @@ func State(l Layout, p StatePage) templ.Component {
643773 }()
644774 }
645775 ctx = templ.InitializeContext(ctx)
646- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "<section class=\"state\" aria-labelledby=\"state-title\"><p class=\"eyebrow\">")
776+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "<section class=\"state\" aria-labelledby=\"state-title\"><p class=\"eyebrow\">")
647777 if templ_7745c5c3_Err != nil {
648778 return templ_7745c5c3_Err
649779 }
650- var templ_7745c5c3_Var30 string
651- templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(p.Code)
780+ var templ_7745c5c3_Var38 string
781+ templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(p.Code)
652782 if templ_7745c5c3_Err != nil {
653- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 136, Col: 30}
783+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 151, Col: 30}
654784 }
655- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
785+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))
656786 if templ_7745c5c3_Err != nil {
657787 return templ_7745c5c3_Err
658788 }
659- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "</p><h1 id=\"state-title\">")
789+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</p><h1 id=\"state-title\">")
660790 if templ_7745c5c3_Err != nil {
661791 return templ_7745c5c3_Err
662792 }
663- var templ_7745c5c3_Var31 string
664- templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)
793+ var templ_7745c5c3_Var39 string
794+ templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)
665795 if templ_7745c5c3_Err != nil {
666- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 137, Col: 33}
796+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 152, Col: 33}
667797 }
668- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
798+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39))
669799 if templ_7745c5c3_Err != nil {
670800 return templ_7745c5c3_Err
671801 }
672- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "</h1>")
802+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "</h1>")
673803 if templ_7745c5c3_Err != nil {
674804 return templ_7745c5c3_Err
675805 }
676806 for _, line := range p.Lines {
677- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "<p class=\"dim\">")
807+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "<p class=\"dim\">")
678808 if templ_7745c5c3_Err != nil {
679809 return templ_7745c5c3_Err
680810 }
681- var templ_7745c5c3_Var32 string
682- templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(line)
811+ var templ_7745c5c3_Var40 string
812+ templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(line)
683813 if templ_7745c5c3_Err != nil {
684- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 139, Col: 25}
814+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 154, Col: 25}
685815 }
686- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
816+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40))
687817 if templ_7745c5c3_Err != nil {
688818 return templ_7745c5c3_Err
689819 }
690- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</p>")
820+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "</p>")
691821 if templ_7745c5c3_Err != nil {
692822 return templ_7745c5c3_Err
693823 }
@@ -698,49 +828,49 @@ func State(l Layout, p StatePage) templ.Component {
698828 return templ_7745c5c3_Err
699829 }
700830 }
701- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "<p class=\"state-back\">")
831+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "<p class=\"state-back\">")
702832 if templ_7745c5c3_Err != nil {
703833 return templ_7745c5c3_Err
704834 }
705835 for _, b := range p.Back {
706- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<a href=\"")
836+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "<a href=\"")
707837 if templ_7745c5c3_Err != nil {
708838 return templ_7745c5c3_Err
709839 }
710- var templ_7745c5c3_Var33 templ.SafeURL
711- templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(b.Href))
840+ var templ_7745c5c3_Var41 templ.SafeURL
841+ templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(b.Href))
712842 if templ_7745c5c3_Err != nil {
713- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 146, Col: 36}
843+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 161, Col: 36}
714844 }
715- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
845+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41))
716846 if templ_7745c5c3_Err != nil {
717847 return templ_7745c5c3_Err
718848 }
719- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "\">")
849+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "\">")
720850 if templ_7745c5c3_Err != nil {
721851 return templ_7745c5c3_Err
722852 }
723- var templ_7745c5c3_Var34 string
724- templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(b.Label)
853+ var templ_7745c5c3_Var42 string
854+ templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(b.Label)
725855 if templ_7745c5c3_Err != nil {
726- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 146, Col: 48}
856+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 161, Col: 48}
727857 }
728- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
858+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42))
729859 if templ_7745c5c3_Err != nil {
730860 return templ_7745c5c3_Err
731861 }
732- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "</a>")
862+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "</a>")
733863 if templ_7745c5c3_Err != nil {
734864 return templ_7745c5c3_Err
735865 }
736866 }
737- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "</p></section>")
867+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</p></section>")
738868 if templ_7745c5c3_Err != nil {
739869 return templ_7745c5c3_Err
740870 }
741871 return nil
742872 })
743- templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var29), templ_7745c5c3_Buffer)
873+ templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var37), templ_7745c5c3_Buffer)
744874 if templ_7745c5c3_Err != nil {
745875 return templ_7745c5c3_Err
746876 }
@@ -766,12 +896,12 @@ func Login(l Layout, p LoginPage) templ.Component {
766896 }()
767897 }
768898 ctx = templ.InitializeContext(ctx)
769- templ_7745c5c3_Var35 := templ.GetChildren(ctx)
770- if templ_7745c5c3_Var35 == nil {
771- templ_7745c5c3_Var35 = templ.NopComponent
899+ templ_7745c5c3_Var43 := templ.GetChildren(ctx)
900+ if templ_7745c5c3_Var43 == nil {
901+ templ_7745c5c3_Var43 = templ.NopComponent
772902 }
773903 ctx = templ.ClearChildren(ctx)
774- templ_7745c5c3_Var36 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
904+ templ_7745c5c3_Var44 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
775905 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
776906 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
777907 if !templ_7745c5c3_IsBuffer {
@@ -783,75 +913,75 @@ func Login(l Layout, p LoginPage) templ.Component {
783913 }()
784914 }
785915 ctx = templ.InitializeContext(ctx)
786- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "<section class=\"state\" aria-labelledby=\"state-title\"><h1 id=\"state-title\">")
916+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<section class=\"state\" aria-labelledby=\"state-title\"><h1 id=\"state-title\">")
787917 if templ_7745c5c3_Err != nil {
788918 return templ_7745c5c3_Err
789919 }
790- var templ_7745c5c3_Var37 string
791- templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)
920+ var templ_7745c5c3_Var45 string
921+ templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)
792922 if templ_7745c5c3_Err != nil {
793- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 158, Col: 33}
923+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 173, Col: 33}
794924 }
795- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37))
925+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45))
796926 if templ_7745c5c3_Err != nil {
797927 return templ_7745c5c3_Err
798928 }
799- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "</h1>")
929+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "</h1>")
800930 if templ_7745c5c3_Err != nil {
801931 return templ_7745c5c3_Err
802932 }
803933 for _, line := range p.Lines {
804- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "<p class=\"dim\">")
934+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "<p class=\"dim\">")
805935 if templ_7745c5c3_Err != nil {
806936 return templ_7745c5c3_Err
807937 }
808- var templ_7745c5c3_Var38 string
809- templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(line)
938+ var templ_7745c5c3_Var46 string
939+ templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(line)
810940 if templ_7745c5c3_Err != nil {
811- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 160, Col: 25}
941+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 175, Col: 25}
812942 }
813- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))
943+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46))
814944 if templ_7745c5c3_Err != nil {
815945 return templ_7745c5c3_Err
816946 }
817- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "</p>")
947+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "</p>")
818948 if templ_7745c5c3_Err != nil {
819949 return templ_7745c5c3_Err
820950 }
821951 }
822- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "<form class=\"state-act\" method=\"post\" action=\"")
952+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "<form class=\"state-act\" method=\"post\" action=\"")
823953 if templ_7745c5c3_Err != nil {
824954 return templ_7745c5c3_Err
825955 }
826- var templ_7745c5c3_Var39 templ.SafeURL
827- templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Action))
956+ var templ_7745c5c3_Var47 templ.SafeURL
957+ templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Action))
828958 if templ_7745c5c3_Err != nil {
829- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 162, Col: 73}
959+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 177, Col: 73}
830960 }
831- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39))
961+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47))
832962 if templ_7745c5c3_Err != nil {
833963 return templ_7745c5c3_Err
834964 }
835- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "\"><button type=\"submit\" class=\"btn primary\">")
965+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "\"><button type=\"submit\" class=\"btn primary\">")
836966 if templ_7745c5c3_Err != nil {
837967 return templ_7745c5c3_Err
838968 }
839- var templ_7745c5c3_Var40 string
840- templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button)
969+ var templ_7745c5c3_Var48 string
970+ templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button)
841971 if templ_7745c5c3_Err != nil {
842- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 163, Col: 56}
972+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 178, Col: 56}
843973 }
844- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40))
974+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48))
845975 if templ_7745c5c3_Err != nil {
846976 return templ_7745c5c3_Err
847977 }
848- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "</button></form><p class=\"state-back\"><a href=\"/\">Back to home</a></p></section>")
978+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "</button></form><p class=\"state-back\"><a href=\"/\">Back to home</a></p></section>")
849979 if templ_7745c5c3_Err != nil {
850980 return templ_7745c5c3_Err
851981 }
852982 return nil
853983 })
854- templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var36), templ_7745c5c3_Buffer)
984+ templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var44), templ_7745c5c3_Buffer)
855985 if templ_7745c5c3_Err != nil {
856986 return templ_7745c5c3_Err
857987 }
@@ -256,21 +256,151 @@ func summaryRail(p SummaryPage) templ.Component {
256 if templ_7745c5c3_Err != nil {256 if templ_7745c5c3_Err != nil {
257 return templ_7745c5c3_Err257 return templ_7745c5c3_Err
258 }258 }
259- if len(p.Recent) > 0 {259+ if p.Latest != nil {
260- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<section aria-labelledby=\"r-recent\"><h2 id=\"r-recent\">Recent commits <a href=\"")260+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<section aria-labelledby=\"r-tag\"><h2 id=\"r-tag\">Tags <span class=\"count\">")
261 if templ_7745c5c3_Err != nil {261 if templ_7745c5c3_Err != nil {
262 return templ_7745c5c3_Err262 return templ_7745c5c3_Err
263 }263 }
264- var templ_7745c5c3_Var12 templ.SafeURL264+ var templ_7745c5c3_Var12 string
265- templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.AllHref))265+ templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Latest.Count))
266 if templ_7745c5c3_Err != nil {266 if templ_7745c5c3_Err != nil {
267- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 63, Col: 105}267+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 64, Col: 65}
268 }268 }
269 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))269 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
270 if templ_7745c5c3_Err != nil {270 if templ_7745c5c3_Err != nil {
271 return templ_7745c5c3_Err271 return templ_7745c5c3_Err
272 }272 }
273- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\">View all</a></h2>")273+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</span> <a href=\"")
274+ if templ_7745c5c3_Err != nil {
275+ return templ_7745c5c3_Err
276+ }
277+ var templ_7745c5c3_Var13 templ.SafeURL
278+ templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Latest.RefsHref))
279+ if templ_7745c5c3_Err != nil {
280+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 64, Col: 117}
281+ }
282+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
283+ if templ_7745c5c3_Err != nil {
284+ return templ_7745c5c3_Err
285+ }
286+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\">View all</a></h2>")
287+ if templ_7745c5c3_Err != nil {
288+ return templ_7745c5c3_Err
289+ }
290+ if p.Latest.Name != "" {
291+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<div class=\"latest-tag\"><a class=\"tag-name\" href=\"")
292+ if templ_7745c5c3_Err != nil {
293+ return templ_7745c5c3_Err
294+ }
295+ var templ_7745c5c3_Var14 templ.SafeURL
296+ templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Latest.Href))
297+ if templ_7745c5c3_Err != nil {
298+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 67, Col: 60}
299+ }
300+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
301+ if templ_7745c5c3_Err != nil {
302+ return templ_7745c5c3_Err
303+ }
304+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\"><span class=\"ico i-tag\" aria-hidden=\"true\"></span>")
305+ if templ_7745c5c3_Err != nil {
306+ return templ_7745c5c3_Err
307+ }
308+ var templ_7745c5c3_Var15 string
309+ templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(p.Latest.Name)
310+ if templ_7745c5c3_Err != nil {
311+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 67, Col: 128}
312+ }
313+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
314+ if templ_7745c5c3_Err != nil {
315+ return templ_7745c5c3_Err
316+ }
317+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</a> <span class=\"pill\">latest</span> <time class=\"dim\" datetime=\"")
318+ if templ_7745c5c3_Err != nil {
319+ return templ_7745c5c3_Err
320+ }
321+ var templ_7745c5c3_Var16 string
322+ templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Latest.WhenAt)
323+ if templ_7745c5c3_Err != nil {
324+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 69, Col: 49}
325+ }
326+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var16)
327+ if templ_7745c5c3_Err != nil {
328+ return templ_7745c5c3_Err
329+ }
330+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\" title=\"")
331+ if templ_7745c5c3_Err != nil {
332+ return templ_7745c5c3_Err
333+ }
334+ var templ_7745c5c3_Var17 string
335+ templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Latest.WhenAt)
336+ if templ_7745c5c3_Err != nil {
337+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 69, Col: 75}
338+ }
339+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17)
340+ if templ_7745c5c3_Err != nil {
341+ return templ_7745c5c3_Err
342+ }
343+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\">")
344+ if templ_7745c5c3_Err != nil {
345+ return templ_7745c5c3_Err
346+ }
347+ var templ_7745c5c3_Var18 string
348+ templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(p.Latest.When)
349+ if templ_7745c5c3_Err != nil {
350+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 69, Col: 93}
351+ }
352+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
353+ if templ_7745c5c3_Err != nil {
354+ return templ_7745c5c3_Err
355+ }
356+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</time> ")
357+ if templ_7745c5c3_Err != nil {
358+ return templ_7745c5c3_Err
359+ }
360+ if p.Latest.Subject != "" {
361+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<p class=\"tag-sub dim\">")
362+ if templ_7745c5c3_Err != nil {
363+ return templ_7745c5c3_Err
364+ }
365+ var templ_7745c5c3_Var19 string
366+ templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(p.Latest.Subject)
367+ if templ_7745c5c3_Err != nil {
368+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 71, Col: 47}
369+ }
370+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
371+ if templ_7745c5c3_Err != nil {
372+ return templ_7745c5c3_Err
373+ }
374+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</p>")
375+ if templ_7745c5c3_Err != nil {
376+ return templ_7745c5c3_Err
377+ }
378+ }
379+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</div>")
380+ if templ_7745c5c3_Err != nil {
381+ return templ_7745c5c3_Err
382+ }
383+ }
384+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</section>")
385+ if templ_7745c5c3_Err != nil {
386+ return templ_7745c5c3_Err
387+ }
388+ }
389+ if len(p.Recent) > 0 {
390+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<section aria-labelledby=\"r-recent\"><h2 id=\"r-recent\">Recent commits <a href=\"")
391+ if templ_7745c5c3_Err != nil {
392+ return templ_7745c5c3_Err
393+ }
394+ var templ_7745c5c3_Var20 templ.SafeURL
395+ templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.AllHref))
396+ if templ_7745c5c3_Err != nil {
397+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 78, Col: 105}
398+ }
399+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
400+ if templ_7745c5c3_Err != nil {
401+ return templ_7745c5c3_Err
402+ }
403+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "\">View all</a></h2>")
274 if templ_7745c5c3_Err != nil {404 if templ_7745c5c3_Err != nil {
275 return templ_7745c5c3_Err405 return templ_7745c5c3_Err
276 }406 }
@@ -278,13 +408,13 @@ func summaryRail(p SummaryPage) templ.Component {
278 if templ_7745c5c3_Err != nil {408 if templ_7745c5c3_Err != nil {
279 return templ_7745c5c3_Err409 return templ_7745c5c3_Err
280 }410 }
281- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</section>")411+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</section>")
282 if templ_7745c5c3_Err != nil {412 if templ_7745c5c3_Err != nil {
283 return templ_7745c5c3_Err413 return templ_7745c5c3_Err
284 }414 }
285 }415 }
286 if len(p.Outline) > 0 && p.Readme != nil {416 if len(p.Outline) > 0 && p.Readme != nil {
287- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<section aria-labelledby=\"r-outline\"><h2 id=\"r-outline\">On this page</h2>")417+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<section aria-labelledby=\"r-outline\"><h2 id=\"r-outline\">On this page</h2>")
288 if templ_7745c5c3_Err != nil {418 if templ_7745c5c3_Err != nil {
289 return templ_7745c5c3_Err419 return templ_7745c5c3_Err
290 }420 }
@@ -292,7 +422,7 @@ func summaryRail(p SummaryPage) templ.Component {
292 if templ_7745c5c3_Err != nil {422 if templ_7745c5c3_Err != nil {
293 return templ_7745c5c3_Err423 return templ_7745c5c3_Err
294 }424 }
295- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</section>")425+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "</section>")
296 if templ_7745c5c3_Err != nil {426 if templ_7745c5c3_Err != nil {
297 return templ_7745c5c3_Err427 return templ_7745c5c3_Err
298 }428 }
@@ -318,12 +448,12 @@ func Refs(l Layout, p RefsPage) templ.Component {
318 }()448 }()
319 }449 }
320 ctx = templ.InitializeContext(ctx)450 ctx = templ.InitializeContext(ctx)
321- templ_7745c5c3_Var13 := templ.GetChildren(ctx)451+ templ_7745c5c3_Var21 := templ.GetChildren(ctx)
322- if templ_7745c5c3_Var13 == nil {452+ if templ_7745c5c3_Var21 == nil {
323- templ_7745c5c3_Var13 = templ.NopComponent453+ templ_7745c5c3_Var21 = templ.NopComponent
324 }454 }
325 ctx = templ.ClearChildren(ctx)455 ctx = templ.ClearChildren(ctx)
326- templ_7745c5c3_Var14 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {456+ templ_7745c5c3_Var22 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
327 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context457 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
328 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)458 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
329 if !templ_7745c5c3_IsBuffer {459 if !templ_7745c5c3_IsBuffer {
@@ -335,7 +465,7 @@ func Refs(l Layout, p RefsPage) templ.Component {
335 }()465 }()
336 }466 }
337 ctx = templ.InitializeContext(ctx)467 ctx = templ.InitializeContext(ctx)
338- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<div class=\"filter\"><label for=\"ref-filter\" class=\"sr\">filter</label> <input id=\"ref-filter\" type=\"search\" data-filter placeholder=\"Filter branches and tags…\" autocomplete=\"off\" hx-on:input=\"let t = this.value.toLowerCase(); q('#ref-rows > tr').arr().forEach(r => r.hidden = !r.textContent.toLowerCase().includes(t));\n let first = q('#ref-rows > tr:not([hidden])').arr()[0]; first && q(first).take('data-cursor', '#ref-rows > tr')\"></div><table class=\"list refs\" aria-label=\"Branches and tags\"><thead><tr><th class=\"cur\"></th><th class=\"c-kind\">kind</th><th class=\"c-name\">name</th><th class=\"c-sha\">commit</th><th class=\"c-sub\">subject</th><th class=\"c-when\">date</th></tr></thead>")468+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<div class=\"filter\"><label for=\"ref-filter\" class=\"sr\">filter</label> <input id=\"ref-filter\" type=\"search\" data-filter placeholder=\"Filter branches and tags…\" autocomplete=\"off\" hx-on:input=\"let t = this.value.toLowerCase(); q('#ref-rows > tr').arr().forEach(r => r.hidden = !r.textContent.toLowerCase().includes(t));\n let first = q('#ref-rows > tr:not([hidden])').arr()[0]; first && q(first).take('data-cursor', '#ref-rows > tr')\"></div><table class=\"list refs\" aria-label=\"Branches and tags\"><thead><tr><th class=\"cur\"></th><th class=\"c-kind\">kind</th><th class=\"c-name\">name</th><th class=\"c-sha\">commit</th><th class=\"c-sub\">subject</th><th class=\"c-when\">date</th></tr></thead>")
339 if templ_7745c5c3_Err != nil {469 if templ_7745c5c3_Err != nil {
340 return templ_7745c5c3_Err470 return templ_7745c5c3_Err
341 }471 }
@@ -343,32 +473,32 @@ func Refs(l Layout, p RefsPage) templ.Component {
343 if templ_7745c5c3_Err != nil {473 if templ_7745c5c3_Err != nil {
344 return templ_7745c5c3_Err474 return templ_7745c5c3_Err
345 }475 }
346- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</table>")476+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</table>")
347 if templ_7745c5c3_Err != nil {477 if templ_7745c5c3_Err != nil {
348 return templ_7745c5c3_Err478 return templ_7745c5c3_Err
349 }479 }
350 if p.Note != "" {480 if p.Note != "" {
351- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<p class=\"note\">")481+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<p class=\"note\">")
352 if templ_7745c5c3_Err != nil {482 if templ_7745c5c3_Err != nil {
353 return templ_7745c5c3_Err483 return templ_7745c5c3_Err
354 }484 }
355- var templ_7745c5c3_Var15 string485+ var templ_7745c5c3_Var23 string
356- templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(p.Note)486+ templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(p.Note)
357 if templ_7745c5c3_Err != nil {487 if templ_7745c5c3_Err != nil {
358- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 94, Col: 27}488+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 109, Col: 27}
359 }489 }
360- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))490+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
361 if templ_7745c5c3_Err != nil {491 if templ_7745c5c3_Err != nil {
362 return templ_7745c5c3_Err492 return templ_7745c5c3_Err
363 }493 }
364- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</p>")494+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "</p>")
365 if templ_7745c5c3_Err != nil {495 if templ_7745c5c3_Err != nil {
366 return templ_7745c5c3_Err496 return templ_7745c5c3_Err
367 }497 }
368 }498 }
369 return nil499 return nil
370 })500 })
371- templ_7745c5c3_Err = Shell(l, refsRail(p)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var14), templ_7745c5c3_Buffer)501+ templ_7745c5c3_Err = Shell(l, refsRail(p)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var22), templ_7745c5c3_Buffer)
372 if templ_7745c5c3_Err != nil {502 if templ_7745c5c3_Err != nil {
373 return templ_7745c5c3_Err503 return templ_7745c5c3_Err
374 }504 }
@@ -392,179 +522,179 @@ func refRow(kind string, r RefRow) templ.Component {
392 }()522 }()
393 }523 }
394 ctx = templ.InitializeContext(ctx)524 ctx = templ.InitializeContext(ctx)
395- templ_7745c5c3_Var16 := templ.GetChildren(ctx)525+ templ_7745c5c3_Var24 := templ.GetChildren(ctx)
396- if templ_7745c5c3_Var16 == nil {526+ if templ_7745c5c3_Var24 == nil {
397- templ_7745c5c3_Var16 = templ.NopComponent527+ templ_7745c5c3_Var24 = templ.NopComponent
398 }528 }
399 ctx = templ.ClearChildren(ctx)529 ctx = templ.ClearChildren(ctx)
400- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<tr")530+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "<tr")
401 if templ_7745c5c3_Err != nil {531 if templ_7745c5c3_Err != nil {
402 return templ_7745c5c3_Err532 return templ_7745c5c3_Err
403 }533 }
404 if r.Cursor {534 if r.Cursor {
405- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, " data-cursor")535+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, " data-cursor")
406 if templ_7745c5c3_Err != nil {536 if templ_7745c5c3_Err != nil {
407 return templ_7745c5c3_Err537 return templ_7745c5c3_Err
408 }538 }
409 }539 }
410- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "><td class=\"cur\" aria-hidden=\"true\"></td><td class=\"c-kind mute\">")540+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "><td class=\"cur\" aria-hidden=\"true\"></td><td class=\"c-kind mute\">")
411 if templ_7745c5c3_Err != nil {541 if templ_7745c5c3_Err != nil {
412 return templ_7745c5c3_Err542 return templ_7745c5c3_Err
413 }543 }
414- var templ_7745c5c3_Var17 string544+ var templ_7745c5c3_Var25 string
415- templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(kind)545+ templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(kind)
416 if templ_7745c5c3_Err != nil {546 if templ_7745c5c3_Err != nil {
417- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 102, Col: 32}547+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 117, Col: 32}
418 }548 }
419- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))549+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
420 if templ_7745c5c3_Err != nil {550 if templ_7745c5c3_Err != nil {
421 return templ_7745c5c3_Err551 return templ_7745c5c3_Err
422 }552 }
423- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</td><td class=\"c-name\"><a href=\"")553+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</td><td class=\"c-name\"><a href=\"")
424 if templ_7745c5c3_Err != nil {554 if templ_7745c5c3_Err != nil {
425 return templ_7745c5c3_Err555 return templ_7745c5c3_Err
426 }556 }
427- var templ_7745c5c3_Var18 templ.SafeURL557+ var templ_7745c5c3_Var26 templ.SafeURL
428- templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Href))558+ templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Href))
429 if templ_7745c5c3_Err != nil {559 if templ_7745c5c3_Err != nil {
430- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 104, Col: 34}560+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 119, Col: 34}
431 }561 }
432- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))562+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
433 if templ_7745c5c3_Err != nil {563 if templ_7745c5c3_Err != nil {
434 return templ_7745c5c3_Err564 return templ_7745c5c3_Err
435 }565 }
436- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\">")566+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "\">")
437 if templ_7745c5c3_Err != nil {567 if templ_7745c5c3_Err != nil {
438 return templ_7745c5c3_Err568 return templ_7745c5c3_Err
439 }569 }
440- var templ_7745c5c3_Var19 string570+ var templ_7745c5c3_Var27 string
441- templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)571+ templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
442 if templ_7745c5c3_Err != nil {572 if templ_7745c5c3_Err != nil {
443- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 104, Col: 45}573+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 119, Col: 45}
444 }574 }
445- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))575+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
446 if templ_7745c5c3_Err != nil {576 if templ_7745c5c3_Err != nil {
447 return templ_7745c5c3_Err577 return templ_7745c5c3_Err
448 }578 }
449- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</a> ")579+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "</a> ")
450 if templ_7745c5c3_Err != nil {580 if templ_7745c5c3_Err != nil {
451 return templ_7745c5c3_Err581 return templ_7745c5c3_Err
452 }582 }
453 if r.Default {583 if r.Default {
454- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<span class=\"pill\">default</span>")584+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "<span class=\"pill\">default</span>")
455 if templ_7745c5c3_Err != nil {585 if templ_7745c5c3_Err != nil {
456 return templ_7745c5c3_Err586 return templ_7745c5c3_Err
457 }587 }
458 }588 }
459- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</td><td class=\"c-sha\">")589+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "</td><td class=\"c-sha\">")
460 if templ_7745c5c3_Err != nil {590 if templ_7745c5c3_Err != nil {
461 return templ_7745c5c3_Err591 return templ_7745c5c3_Err
462 }592 }
463 if r.CHref != "" {593 if r.CHref != "" {
464- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<a href=\"")594+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "<a href=\"")
465 if templ_7745c5c3_Err != nil {595 if templ_7745c5c3_Err != nil {
466 return templ_7745c5c3_Err596 return templ_7745c5c3_Err
467 }597 }
468- var templ_7745c5c3_Var20 templ.SafeURL598+ var templ_7745c5c3_Var28 templ.SafeURL
469- templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.CHref))599+ templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.CHref))
470 if templ_7745c5c3_Err != nil {600 if templ_7745c5c3_Err != nil {
471- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 111, Col: 36}601+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 126, Col: 36}
472 }602 }
473- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))603+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
474 if templ_7745c5c3_Err != nil {604 if templ_7745c5c3_Err != nil {
475 return templ_7745c5c3_Err605 return templ_7745c5c3_Err
476 }606 }
477- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "\">")607+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "\">")
478 if templ_7745c5c3_Err != nil {608 if templ_7745c5c3_Err != nil {
479 return templ_7745c5c3_Err609 return templ_7745c5c3_Err
480 }610 }
481- var templ_7745c5c3_Var21 string611+ var templ_7745c5c3_Var29 string
482- templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short)612+ templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short)
483 if templ_7745c5c3_Err != nil {613 if templ_7745c5c3_Err != nil {
484- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 111, Col: 48}614+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 126, Col: 48}
485 }615 }
486- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))616+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
487 if templ_7745c5c3_Err != nil {617 if templ_7745c5c3_Err != nil {
488 return templ_7745c5c3_Err618 return templ_7745c5c3_Err
489 }619 }
490- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</a>")620+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</a>")
491 if templ_7745c5c3_Err != nil {621 if templ_7745c5c3_Err != nil {
492 return templ_7745c5c3_Err622 return templ_7745c5c3_Err
493 }623 }
494 } else {624 } else {
495- var templ_7745c5c3_Var22 string625+ var templ_7745c5c3_Var30 string
496- templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short)626+ templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short)
497 if templ_7745c5c3_Err != nil {627 if templ_7745c5c3_Err != nil {
498- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 113, Col: 13}628+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 128, Col: 13}
499 }629 }
500- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))630+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
501 if templ_7745c5c3_Err != nil {631 if templ_7745c5c3_Err != nil {
502 return templ_7745c5c3_Err632 return templ_7745c5c3_Err
503 }633 }
504 }634 }
505- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</td><td class=\"c-sub\">")635+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</td><td class=\"c-sub\">")
506 if templ_7745c5c3_Err != nil {636 if templ_7745c5c3_Err != nil {
507 return templ_7745c5c3_Err637 return templ_7745c5c3_Err
508 }638 }
509- var templ_7745c5c3_Var23 string639+ var templ_7745c5c3_Var31 string
510- templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(r.Subject)640+ templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(r.Subject)
511 if templ_7745c5c3_Err != nil {641 if templ_7745c5c3_Err != nil {
512- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 116, Col: 31}642+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 131, Col: 31}
513 }643 }
514- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))644+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
515 if templ_7745c5c3_Err != nil {645 if templ_7745c5c3_Err != nil {
516 return templ_7745c5c3_Err646 return templ_7745c5c3_Err
517 }647 }
518- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "</td><td class=\"c-when dim\">")648+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "</td><td class=\"c-when dim\">")
519 if templ_7745c5c3_Err != nil {649 if templ_7745c5c3_Err != nil {
520 return templ_7745c5c3_Err650 return templ_7745c5c3_Err
521 }651 }
522 if r.WhenAt != "" {652 if r.WhenAt != "" {
523- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "<time datetime=\"")653+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<time datetime=\"")
524 if templ_7745c5c3_Err != nil {654 if templ_7745c5c3_Err != nil {
525 return templ_7745c5c3_Err655 return templ_7745c5c3_Err
526 }656 }
527- var templ_7745c5c3_Var24 string657+ var templ_7745c5c3_Var32 string
528- templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt)658+ templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt)
529 if templ_7745c5c3_Err != nil {659 if templ_7745c5c3_Err != nil {
530- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 119, Col: 29}660+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 134, Col: 29}
531 }661 }
532- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var24)662+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var32)
533 if templ_7745c5c3_Err != nil {663 if templ_7745c5c3_Err != nil {
534 return templ_7745c5c3_Err664 return templ_7745c5c3_Err
535 }665 }
536- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "\" title=\"")666+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "\" title=\"")
537 if templ_7745c5c3_Err != nil {667 if templ_7745c5c3_Err != nil {
538 return templ_7745c5c3_Err668 return templ_7745c5c3_Err
539 }669 }
540- var templ_7745c5c3_Var25 string670+ var templ_7745c5c3_Var33 string
541- templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt)671+ templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt)
542 if templ_7745c5c3_Err != nil {672 if templ_7745c5c3_Err != nil {
543- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 119, Col: 48}673+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 134, Col: 48}
544 }674 }
545- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var25)675+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var33)
546 if templ_7745c5c3_Err != nil {676 if templ_7745c5c3_Err != nil {
547 return templ_7745c5c3_Err677 return templ_7745c5c3_Err
548 }678 }
549- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "\">")679+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\">")
550 if templ_7745c5c3_Err != nil {680 if templ_7745c5c3_Err != nil {
551 return templ_7745c5c3_Err681 return templ_7745c5c3_Err
552 }682 }
553- var templ_7745c5c3_Var26 string683+ var templ_7745c5c3_Var34 string
554- templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(r.When)684+ templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(r.When)
555 if templ_7745c5c3_Err != nil {685 if templ_7745c5c3_Err != nil {
556- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 119, Col: 59}686+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 134, Col: 59}
557 }687 }
558- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))688+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
559 if templ_7745c5c3_Err != nil {689 if templ_7745c5c3_Err != nil {
560 return templ_7745c5c3_Err690 return templ_7745c5c3_Err
561 }691 }
562- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</time>")692+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "</time>")
563 if templ_7745c5c3_Err != nil {693 if templ_7745c5c3_Err != nil {
564 return templ_7745c5c3_Err694 return templ_7745c5c3_Err
565 }695 }
566 }696 }
567- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</td></tr>")697+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "</td></tr>")
568 if templ_7745c5c3_Err != nil {698 if templ_7745c5c3_Err != nil {
569 return templ_7745c5c3_Err699 return templ_7745c5c3_Err
570 }700 }
@@ -588,12 +718,12 @@ func refsRail(p RefsPage) templ.Component {
588 }()718 }()
589 }719 }
590 ctx = templ.InitializeContext(ctx)720 ctx = templ.InitializeContext(ctx)
591- templ_7745c5c3_Var27 := templ.GetChildren(ctx)721+ templ_7745c5c3_Var35 := templ.GetChildren(ctx)
592- if templ_7745c5c3_Var27 == nil {722+ if templ_7745c5c3_Var35 == nil {
593- templ_7745c5c3_Var27 = templ.NopComponent723+ templ_7745c5c3_Var35 = templ.NopComponent
594 }724 }
595 ctx = templ.ClearChildren(ctx)725 ctx = templ.ClearChildren(ctx)
596- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<section aria-labelledby=\"r-refs\"><h2 id=\"r-refs\">Overview</h2>")726+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "<section aria-labelledby=\"r-refs\"><h2 id=\"r-refs\">Overview</h2>")
597 if templ_7745c5c3_Err != nil {727 if templ_7745c5c3_Err != nil {
598 return templ_7745c5c3_Err728 return templ_7745c5c3_Err
599 }729 }
@@ -601,7 +731,7 @@ func refsRail(p RefsPage) templ.Component {
601 if templ_7745c5c3_Err != nil {731 if templ_7745c5c3_Err != nil {
602 return templ_7745c5c3_Err732 return templ_7745c5c3_Err
603 }733 }
604- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "</section>")734+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</section>")
605 if templ_7745c5c3_Err != nil {735 if templ_7745c5c3_Err != nil {
606 return templ_7745c5c3_Err736 return templ_7745c5c3_Err
607 }737 }
@@ -626,12 +756,12 @@ func State(l Layout, p StatePage) templ.Component {
626 }()756 }()
627 }757 }
628 ctx = templ.InitializeContext(ctx)758 ctx = templ.InitializeContext(ctx)
629- templ_7745c5c3_Var28 := templ.GetChildren(ctx)759+ templ_7745c5c3_Var36 := templ.GetChildren(ctx)
630- if templ_7745c5c3_Var28 == nil {760+ if templ_7745c5c3_Var36 == nil {
631- templ_7745c5c3_Var28 = templ.NopComponent761+ templ_7745c5c3_Var36 = templ.NopComponent
632 }762 }
633 ctx = templ.ClearChildren(ctx)763 ctx = templ.ClearChildren(ctx)
634- templ_7745c5c3_Var29 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {764+ templ_7745c5c3_Var37 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
635 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context765 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
636 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)766 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
637 if !templ_7745c5c3_IsBuffer {767 if !templ_7745c5c3_IsBuffer {
@@ -643,51 +773,51 @@ func State(l Layout, p StatePage) templ.Component {
643 }()773 }()
644 }774 }
645 ctx = templ.InitializeContext(ctx)775 ctx = templ.InitializeContext(ctx)
646- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "<section class=\"state\" aria-labelledby=\"state-title\"><p class=\"eyebrow\">")776+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "<section class=\"state\" aria-labelledby=\"state-title\"><p class=\"eyebrow\">")
647 if templ_7745c5c3_Err != nil {777 if templ_7745c5c3_Err != nil {
648 return templ_7745c5c3_Err778 return templ_7745c5c3_Err
649 }779 }
650- var templ_7745c5c3_Var30 string780+ var templ_7745c5c3_Var38 string
651- templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(p.Code)781+ templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(p.Code)
652 if templ_7745c5c3_Err != nil {782 if templ_7745c5c3_Err != nil {
653- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 136, Col: 30}783+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 151, Col: 30}
654 }784 }
655- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))785+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))
656 if templ_7745c5c3_Err != nil {786 if templ_7745c5c3_Err != nil {
657 return templ_7745c5c3_Err787 return templ_7745c5c3_Err
658 }788 }
659- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "</p><h1 id=\"state-title\">")789+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</p><h1 id=\"state-title\">")
660 if templ_7745c5c3_Err != nil {790 if templ_7745c5c3_Err != nil {
661 return templ_7745c5c3_Err791 return templ_7745c5c3_Err
662 }792 }
663- var templ_7745c5c3_Var31 string793+ var templ_7745c5c3_Var39 string
664- templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)794+ templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)
665 if templ_7745c5c3_Err != nil {795 if templ_7745c5c3_Err != nil {
666- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 137, Col: 33}796+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 152, Col: 33}
667 }797 }
668- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))798+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39))
669 if templ_7745c5c3_Err != nil {799 if templ_7745c5c3_Err != nil {
670 return templ_7745c5c3_Err800 return templ_7745c5c3_Err
671 }801 }
672- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "</h1>")802+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "</h1>")
673 if templ_7745c5c3_Err != nil {803 if templ_7745c5c3_Err != nil {
674 return templ_7745c5c3_Err804 return templ_7745c5c3_Err
675 }805 }
676 for _, line := range p.Lines {806 for _, line := range p.Lines {
677- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "<p class=\"dim\">")807+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "<p class=\"dim\">")
678 if templ_7745c5c3_Err != nil {808 if templ_7745c5c3_Err != nil {
679 return templ_7745c5c3_Err809 return templ_7745c5c3_Err
680 }810 }
681- var templ_7745c5c3_Var32 string811+ var templ_7745c5c3_Var40 string
682- templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(line)812+ templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(line)
683 if templ_7745c5c3_Err != nil {813 if templ_7745c5c3_Err != nil {
684- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 139, Col: 25}814+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 154, Col: 25}
685 }815 }
686- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))816+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40))
687 if templ_7745c5c3_Err != nil {817 if templ_7745c5c3_Err != nil {
688 return templ_7745c5c3_Err818 return templ_7745c5c3_Err
689 }819 }
690- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</p>")820+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "</p>")
691 if templ_7745c5c3_Err != nil {821 if templ_7745c5c3_Err != nil {
692 return templ_7745c5c3_Err822 return templ_7745c5c3_Err
693 }823 }
@@ -698,49 +828,49 @@ func State(l Layout, p StatePage) templ.Component {
698 return templ_7745c5c3_Err828 return templ_7745c5c3_Err
699 }829 }
700 }830 }
701- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "<p class=\"state-back\">")831+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "<p class=\"state-back\">")
702 if templ_7745c5c3_Err != nil {832 if templ_7745c5c3_Err != nil {
703 return templ_7745c5c3_Err833 return templ_7745c5c3_Err
704 }834 }
705 for _, b := range p.Back {835 for _, b := range p.Back {
706- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<a href=\"")836+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "<a href=\"")
707 if templ_7745c5c3_Err != nil {837 if templ_7745c5c3_Err != nil {
708 return templ_7745c5c3_Err838 return templ_7745c5c3_Err
709 }839 }
710- var templ_7745c5c3_Var33 templ.SafeURL840+ var templ_7745c5c3_Var41 templ.SafeURL
711- templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(b.Href))841+ templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(b.Href))
712 if templ_7745c5c3_Err != nil {842 if templ_7745c5c3_Err != nil {
713- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 146, Col: 36}843+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 161, Col: 36}
714 }844 }
715- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))845+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41))
716 if templ_7745c5c3_Err != nil {846 if templ_7745c5c3_Err != nil {
717 return templ_7745c5c3_Err847 return templ_7745c5c3_Err
718 }848 }
719- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "\">")849+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "\">")
720 if templ_7745c5c3_Err != nil {850 if templ_7745c5c3_Err != nil {
721 return templ_7745c5c3_Err851 return templ_7745c5c3_Err
722 }852 }
723- var templ_7745c5c3_Var34 string853+ var templ_7745c5c3_Var42 string
724- templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(b.Label)854+ templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(b.Label)
725 if templ_7745c5c3_Err != nil {855 if templ_7745c5c3_Err != nil {
726- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 146, Col: 48}856+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 161, Col: 48}
727 }857 }
728- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))858+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42))
729 if templ_7745c5c3_Err != nil {859 if templ_7745c5c3_Err != nil {
730 return templ_7745c5c3_Err860 return templ_7745c5c3_Err
731 }861 }
732- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "</a>")862+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "</a>")
733 if templ_7745c5c3_Err != nil {863 if templ_7745c5c3_Err != nil {
734 return templ_7745c5c3_Err864 return templ_7745c5c3_Err
735 }865 }
736 }866 }
737- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "</p></section>")867+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</p></section>")
738 if templ_7745c5c3_Err != nil {868 if templ_7745c5c3_Err != nil {
739 return templ_7745c5c3_Err869 return templ_7745c5c3_Err
740 }870 }
741 return nil871 return nil
742 })872 })
743- templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var29), templ_7745c5c3_Buffer)873+ templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var37), templ_7745c5c3_Buffer)
744 if templ_7745c5c3_Err != nil {874 if templ_7745c5c3_Err != nil {
745 return templ_7745c5c3_Err875 return templ_7745c5c3_Err
746 }876 }
@@ -766,12 +896,12 @@ func Login(l Layout, p LoginPage) templ.Component {
766 }()896 }()
767 }897 }
768 ctx = templ.InitializeContext(ctx)898 ctx = templ.InitializeContext(ctx)
769- templ_7745c5c3_Var35 := templ.GetChildren(ctx)899+ templ_7745c5c3_Var43 := templ.GetChildren(ctx)
770- if templ_7745c5c3_Var35 == nil {900+ if templ_7745c5c3_Var43 == nil {
771- templ_7745c5c3_Var35 = templ.NopComponent901+ templ_7745c5c3_Var43 = templ.NopComponent
772 }902 }
773 ctx = templ.ClearChildren(ctx)903 ctx = templ.ClearChildren(ctx)
774- templ_7745c5c3_Var36 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {904+ templ_7745c5c3_Var44 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
775 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context905 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
776 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)906 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
777 if !templ_7745c5c3_IsBuffer {907 if !templ_7745c5c3_IsBuffer {
@@ -783,75 +913,75 @@ func Login(l Layout, p LoginPage) templ.Component {
783 }()913 }()
784 }914 }
785 ctx = templ.InitializeContext(ctx)915 ctx = templ.InitializeContext(ctx)
786- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "<section class=\"state\" aria-labelledby=\"state-title\"><h1 id=\"state-title\">")916+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<section class=\"state\" aria-labelledby=\"state-title\"><h1 id=\"state-title\">")
787 if templ_7745c5c3_Err != nil {917 if templ_7745c5c3_Err != nil {
788 return templ_7745c5c3_Err918 return templ_7745c5c3_Err
789 }919 }
790- var templ_7745c5c3_Var37 string920+ var templ_7745c5c3_Var45 string
791- templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)921+ templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)
792 if templ_7745c5c3_Err != nil {922 if templ_7745c5c3_Err != nil {
793- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 158, Col: 33}923+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 173, Col: 33}
794 }924 }
795- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37))925+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45))
796 if templ_7745c5c3_Err != nil {926 if templ_7745c5c3_Err != nil {
797 return templ_7745c5c3_Err927 return templ_7745c5c3_Err
798 }928 }
799- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "</h1>")929+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "</h1>")
800 if templ_7745c5c3_Err != nil {930 if templ_7745c5c3_Err != nil {
801 return templ_7745c5c3_Err931 return templ_7745c5c3_Err
802 }932 }
803 for _, line := range p.Lines {933 for _, line := range p.Lines {
804- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "<p class=\"dim\">")934+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "<p class=\"dim\">")
805 if templ_7745c5c3_Err != nil {935 if templ_7745c5c3_Err != nil {
806 return templ_7745c5c3_Err936 return templ_7745c5c3_Err
807 }937 }
808- var templ_7745c5c3_Var38 string938+ var templ_7745c5c3_Var46 string
809- templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(line)939+ templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(line)
810 if templ_7745c5c3_Err != nil {940 if templ_7745c5c3_Err != nil {
811- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 160, Col: 25}941+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 175, Col: 25}
812 }942 }
813- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))943+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46))
814 if templ_7745c5c3_Err != nil {944 if templ_7745c5c3_Err != nil {
815 return templ_7745c5c3_Err945 return templ_7745c5c3_Err
816 }946 }
817- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "</p>")947+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "</p>")
818 if templ_7745c5c3_Err != nil {948 if templ_7745c5c3_Err != nil {
819 return templ_7745c5c3_Err949 return templ_7745c5c3_Err
820 }950 }
821 }951 }
822- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "<form class=\"state-act\" method=\"post\" action=\"")952+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "<form class=\"state-act\" method=\"post\" action=\"")
823 if templ_7745c5c3_Err != nil {953 if templ_7745c5c3_Err != nil {
824 return templ_7745c5c3_Err954 return templ_7745c5c3_Err
825 }955 }
826- var templ_7745c5c3_Var39 templ.SafeURL956+ var templ_7745c5c3_Var47 templ.SafeURL
827- templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Action))957+ templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Action))
828 if templ_7745c5c3_Err != nil {958 if templ_7745c5c3_Err != nil {
829- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 162, Col: 73}959+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 177, Col: 73}
830 }960 }
831- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39))961+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47))
832 if templ_7745c5c3_Err != nil {962 if templ_7745c5c3_Err != nil {
833 return templ_7745c5c3_Err963 return templ_7745c5c3_Err
834 }964 }
835- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "\"><button type=\"submit\" class=\"btn primary\">")965+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "\"><button type=\"submit\" class=\"btn primary\">")
836 if templ_7745c5c3_Err != nil {966 if templ_7745c5c3_Err != nil {
837 return templ_7745c5c3_Err967 return templ_7745c5c3_Err
838 }968 }
839- var templ_7745c5c3_Var40 string969+ var templ_7745c5c3_Var48 string
840- templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button)970+ templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button)
841 if templ_7745c5c3_Err != nil {971 if templ_7745c5c3_Err != nil {
842- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 163, Col: 56}972+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 178, Col: 56}
843 }973 }
844- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40))974+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48))
845 if templ_7745c5c3_Err != nil {975 if templ_7745c5c3_Err != nil {
846 return templ_7745c5c3_Err976 return templ_7745c5c3_Err
847 }977 }
848- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "</button></form><p class=\"state-back\"><a href=\"/\">Back to home</a></p></section>")978+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "</button></form><p class=\"state-back\"><a href=\"/\">Back to home</a></p></section>")
849 if templ_7745c5c3_Err != nil {979 if templ_7745c5c3_Err != nil {
850 return templ_7745c5c3_Err980 return templ_7745c5c3_Err
851 }981 }
852 return nil982 return nil
853 })983 })
854- templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var36), templ_7745c5c3_Buffer)984+ templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var44), templ_7745c5c3_Buffer)
855 if templ_7745c5c3_Err != nil {985 if templ_7745c5c3_Err != nil {
856 return templ_7745c5c3_Err986 return templ_7745c5c3_Err
857 }987 }
M internal/adapters/web/static/css/20-shell.css +11 -0
@@ -20,6 +20,7 @@
2020 .i-branch { --i: url(icons/branch.svg); }
2121 .i-search { --i: url(icons/search.svg); }
2222 .i-file { --i: url(icons/file.svg); }
23+ .i-tag { --i: url(icons/tag.svg); }
2324
2425 .pill {
2526 display: inline-flex; align-items: center; height: 20px; padding: 0 var(--s2); margin-left: var(--s1);
@@ -123,6 +124,16 @@
123124 .rail section > :not(h2) { margin: var(--s3) var(--s4); }
124125 .rail section > .list { margin: var(--s2) 0; }
125126 .rail section > p + p { margin-top: calc(-1 * var(--s2)); }
127+ .rail h2 .count + a { margin-left: auto; }
128+
129+ /* newest tag in the summary rail */
130+ .latest-tag {
131+ display: flex; flex-wrap: wrap; align-items: center; gap: var(--s1) var(--s2); font-size: var(--fs-s);
132+ & .tag-name { display: inline-flex; align-items: center; gap: 6px; font: 700 var(--fs) / 20px var(--mono); color: var(--ink-hi); text-decoration: none; }
133+ & .tag-name:hover { text-decoration: underline; }
134+ & time { margin-left: auto; }
135+ & .tag-sub { flex-basis: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
136+ }
126137
127138 .kv {
128139 display: grid; grid-template-columns: max-content minmax(0, 1fr); column-gap: var(--s4); row-gap: var(--s1);
@@ -20,6 +20,7 @@
20 .i-branch { --i: url(icons/branch.svg); }20 .i-branch { --i: url(icons/branch.svg); }
21 .i-search { --i: url(icons/search.svg); }21 .i-search { --i: url(icons/search.svg); }
22 .i-file { --i: url(icons/file.svg); }22 .i-file { --i: url(icons/file.svg); }
23+ .i-tag { --i: url(icons/tag.svg); }
23 24
24 .pill {25 .pill {
25 display: inline-flex; align-items: center; height: 20px; padding: 0 var(--s2); margin-left: var(--s1);26 display: inline-flex; align-items: center; height: 20px; padding: 0 var(--s2); margin-left: var(--s1);
@@ -123,6 +124,16 @@
123 .rail section > :not(h2) { margin: var(--s3) var(--s4); }124 .rail section > :not(h2) { margin: var(--s3) var(--s4); }
124 .rail section > .list { margin: var(--s2) 0; }125 .rail section > .list { margin: var(--s2) 0; }
125 .rail section > p + p { margin-top: calc(-1 * var(--s2)); }126 .rail section > p + p { margin-top: calc(-1 * var(--s2)); }
127+ .rail h2 .count + a { margin-left: auto; }
128+
129+ /* newest tag in the summary rail */
130+ .latest-tag {
131+ display: flex; flex-wrap: wrap; align-items: center; gap: var(--s1) var(--s2); font-size: var(--fs-s);
132+ & .tag-name { display: inline-flex; align-items: center; gap: 6px; font: 700 var(--fs) / 20px var(--mono); color: var(--ink-hi); text-decoration: none; }
133+ & .tag-name:hover { text-decoration: underline; }
134+ & time { margin-left: auto; }
135+ & .tag-sub { flex-basis: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
136+ }
126 137
127 .kv {138 .kv {
128 display: grid; grid-template-columns: max-content minmax(0, 1fr); column-gap: var(--s4); row-gap: var(--s1);139 display: grid; grid-template-columns: max-content minmax(0, 1fr); column-gap: var(--s4); row-gap: var(--s1);
A internal/adapters/web/static/icons/tag.svg +1 -0
@@ -0,0 +1,1 @@
1+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="#000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 2.5v5l6.5 6.5 5-5L7 2.5z"/><circle cx="5" cy="5.5" r="1"/></svg>
@@ -0,0 +1,1 @@
1+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="#000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 2.5v5l6.5 6.5 5-5L7 2.5z"/><circle cx="5" cy="5.5" r="1"/></svg>
M internal/adapters/web/summary.go +20 -12
@@ -61,7 +61,8 @@ func (s *Server) summary(ctx context.Context, r *http.Request) (templ.Component,
6161 p.Recent = append(p.Recent, row)
6262 }
6363
64- p.Facts = s.facts(ctx, d, now)
64+ p.Facts = s.facts(d, now)
65+ p.Latest = s.latest(ctx, d)
6566
6667 if rd, err := s.readme(ctx, d, ref); err != nil {
6768 return nil, 0, err
@@ -78,24 +79,31 @@ func (s *Server) summary(ctx context.Context, r *http.Request) (templ.Component,
7879 }
7980
8081 // maxTagDates is the most tags whose commit dates are read to name the
81-// latest one; with more, the fact shows the count only.
82+// latest one; with more, the rail shows the count only.
8283 const maxTagDates = 20
8384
85+// latest is the newest tag of the repository for the rail, nil without tags.
86+func (s *Server) latest(ctx context.Context, d *repoData) *LatestTag {
87+ n := len(d.tags)
88+ if n == 0 {
89+ return nil
90+ }
91+ lt := &LatestTag{Count: n, RefsHref: repoHref(d.name(), "refs")}
92+ if n <= maxTagDates {
93+ if name, c := latestTag(d.tags, s.commitsOf(ctx, d.name(), d.tags)); name != "" {
94+ lt.Name, lt.Href = name, repoHref(d.name(), "tree", name)
95+ lt.Subject, lt.When, lt.WhenAt = c.subject, c.when, c.whenAt
96+ }
97+ }
98+ return lt
99+}
100+
84101 // facts are the key/value rows of the rail.
85-func (s *Server) facts(ctx context.Context, d *repoData, now time.Time) []Fact {
102+func (s *Server) facts(d *repoData, now time.Time) []Fact {
86103 fs := []Fact{
87104 lastPushFact(d.repo, now),
88105 {Key: "branch", Val: d.head.GetShortName() + " · " + plural(len(d.branches), "branch", "branches")},
89106 }
90- if n := len(d.tags); n > 0 {
91- val := plural(n, "tag", "tags")
92- if n <= maxTagDates {
93- if name, c := latestTag(d.tags, s.commitsOf(ctx, d.name(), d.tags)); name != "" {
94- val = name + " · " + c.when + " · " + val
95- }
96- }
97- fs = append(fs, Fact{Key: "tag", Val: val})
98- }
99107 if c := d.repo.GetCreatedAt(); c.IsValid() {
100108 fs = append(fs, Fact{Key: "created", Val: absolute(c.AsTime()), Time: rfc3339(c.AsTime())})
101109 }
@@ -61,7 +61,8 @@ func (s *Server) summary(ctx context.Context, r *http.Request) (templ.Component,
61 p.Recent = append(p.Recent, row)61 p.Recent = append(p.Recent, row)
62 }62 }
63 63
64- p.Facts = s.facts(ctx, d, now)64+ p.Facts = s.facts(d, now)
65+ p.Latest = s.latest(ctx, d)
65 66
66 if rd, err := s.readme(ctx, d, ref); err != nil {67 if rd, err := s.readme(ctx, d, ref); err != nil {
67 return nil, 0, err68 return nil, 0, err
@@ -78,24 +79,31 @@ func (s *Server) summary(ctx context.Context, r *http.Request) (templ.Component,
78 }79 }
79 80
80 // maxTagDates is the most tags whose commit dates are read to name the81 // maxTagDates is the most tags whose commit dates are read to name the
81-// latest one; with more, the fact shows the count only.82+// latest one; with more, the rail shows the count only.
82 const maxTagDates = 2083 const maxTagDates = 20
83 84
85+// latest is the newest tag of the repository for the rail, nil without tags.
86+func (s *Server) latest(ctx context.Context, d *repoData) *LatestTag {
87+ n := len(d.tags)
88+ if n == 0 {
89+ return nil
90+ }
91+ lt := &LatestTag{Count: n, RefsHref: repoHref(d.name(), "refs")}
92+ if n <= maxTagDates {
93+ if name, c := latestTag(d.tags, s.commitsOf(ctx, d.name(), d.tags)); name != "" {
94+ lt.Name, lt.Href = name, repoHref(d.name(), "tree", name)
95+ lt.Subject, lt.When, lt.WhenAt = c.subject, c.when, c.whenAt
96+ }
97+ }
98+ return lt
99+}
100+
84 // facts are the key/value rows of the rail.101 // facts are the key/value rows of the rail.
85-func (s *Server) facts(ctx context.Context, d *repoData, now time.Time) []Fact {102+func (s *Server) facts(d *repoData, now time.Time) []Fact {
86 fs := []Fact{103 fs := []Fact{
87 lastPushFact(d.repo, now),104 lastPushFact(d.repo, now),
88 {Key: "branch", Val: d.head.GetShortName() + " · " + plural(len(d.branches), "branch", "branches")},105 {Key: "branch", Val: d.head.GetShortName() + " · " + plural(len(d.branches), "branch", "branches")},
89 }106 }
90- if n := len(d.tags); n > 0 {
91- val := plural(n, "tag", "tags")
92- if n <= maxTagDates {
93- if name, c := latestTag(d.tags, s.commitsOf(ctx, d.name(), d.tags)); name != "" {
94- val = name + " · " + c.when + " · " + val
95- }
96- }
97- fs = append(fs, Fact{Key: "tag", Val: val})
98- }
99 if c := d.repo.GetCreatedAt(); c.IsValid() {107 if c := d.repo.GetCreatedAt(); c.IsValid() {
100 fs = append(fs, Fact{Key: "created", Val: absolute(c.AsTime()), Time: rfc3339(c.AsTime())})108 fs = append(fs, Fact{Key: "created", Val: absolute(c.AsTime()), Time: rfc3339(c.AsTime())})
101 }109 }
M internal/adapters/web/view.go +11 -0
@@ -141,6 +141,17 @@ type SummaryPage struct {
141141 Readme *Readme
142142 Outline []Heading
143143 AllHref string
144+ // Latest is the newest tag, nil when the repository has none.
145+ Latest *LatestTag
146+}
147+
148+// LatestTag is the newest tag of a repository, shown in the summary rail.
149+// Name is empty when there are too many tags to date them; Count and
150+// RefsHref still lead to the full list.
151+type LatestTag struct {
152+ Name, Href, Subject, When, WhenAt string
153+ Count int
154+ RefsHref string
144155 }
145156
146157 // RefRow is a branch or a tag in the refs table.
@@ -141,6 +141,17 @@ type SummaryPage struct {
141 Readme *Readme141 Readme *Readme
142 Outline []Heading142 Outline []Heading
143 AllHref string143 AllHref string
144+ // Latest is the newest tag, nil when the repository has none.
145+ Latest *LatestTag
146+}
147+
148+// LatestTag is the newest tag of a repository, shown in the summary rail.
149+// Name is empty when there are too many tags to date them; Count and
150+// RefsHref still lead to the full list.
151+type LatestTag struct {
152+ Name, Href, Subject, When, WhenAt string
153+ Count int
154+ RefsHref string
144 }155 }
145 156
146 // RefRow is a branch or a tag in the refs table.157 // RefRow is a branch or a tag in the refs table.

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 43

↑↓ moveenter openesc close