detached 32e9fe7

fix(web): match loaded lines to a selection by file key, keep the focus on a closing menu, close menus with escape on every page and restore the hunk header contrast

commit
32e9fe75d911f4768e499992c6eb8716e5562f14
author
brice · · 2026-10-19 15:52 UTC
parent
4277264
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
13 files · +267 -244
M internal/adapters/web/diff.templ +1 -1
@@ -221,7 +221,7 @@ templ DiffFileView(f DiffFile, p DiffView) {
221221 <details
222222 class={ "dfile", "lnw-" + itoa(f.Digits) }
223223 id={ f.ID }
224- data-file
224+ data-file={ f.Key }
225225 if f.Open {
226226 open
227227 }
@@ -221,7 +221,7 @@ templ DiffFileView(f DiffFile, p DiffView) {
221 <details221 <details
222 class={ "dfile", "lnw-" + itoa(f.Digits) }222 class={ "dfile", "lnw-" + itoa(f.Digits) }
223 id={ f.ID }223 id={ f.ID }
224- data-file224+ data-file={ f.Key }
225 if f.Open {225 if f.Open {
226 open226 open
227 }227 }
M internal/adapters/web/diff_templ.go +229 -216 generated file: collapsed
M internal/adapters/web/static/css/50-code.css +1 -1
@@ -82,7 +82,7 @@
8282 /* the hunk headers and the hidden lines stay quiet: a faint band between
8383 the code, so that the eye keeps reading */
8484 & .hunk td, & .gap td { background: color-mix(in srgb, var(--paper-alt) 35%, var(--paper)); }
85- & .hunk td { color: color-mix(in srgb, var(--ink-mute) 70%, transparent); padding: 0 1ch; }
85+ & .hunk td { color: var(--ink-mute); padding: 0 1ch; }
8686 /* a run of hidden lines: the whole row loads them */
8787 & .gap td { padding: 0; }
8888 & .gap button {
@@ -82,7 +82,7 @@
82 /* the hunk headers and the hidden lines stay quiet: a faint band between82 /* the hunk headers and the hidden lines stay quiet: a faint band between
83 the code, so that the eye keeps reading */83 the code, so that the eye keeps reading */
84 & .hunk td, & .gap td { background: color-mix(in srgb, var(--paper-alt) 35%, var(--paper)); }84 & .hunk td, & .gap td { background: color-mix(in srgb, var(--paper-alt) 35%, var(--paper)); }
85- & .hunk td { color: color-mix(in srgb, var(--ink-mute) 70%, transparent); padding: 0 1ch; }85+ & .hunk td { color: var(--ink-mute); padding: 0 1ch; }
86 /* a run of hidden lines: the whole row loads them */86 /* a run of hidden lines: the whole row loads them */
87 & .gap td { padding: 0; }87 & .gap td { padding: 0; }
88 & .gap button {88 & .gap button {
M internal/adapters/web/static/js/ermite.js +20 -4
@@ -407,7 +407,8 @@
407407 // The loaded rows are context lines, the same on both sides.
408408 function selectLoaded(file, rows) {
409409 const m = /^(new|old):(?:(\d+)-)?(\d+):(.+)$/.exec(new URLSearchParams(location.search).get('on') || '');
410- if (!m || file.querySelector('summary .p')?.textContent !== m[4]) return;
410+ // data-file is the path comments name (the new path of a rename), as ?on=
411+ if (!m || file.dataset.file !== m[4]) return;
411412 const end = Number(m[3]);
412413 const start = m[2] ? Number(m[2]) : end;
413414 for (const r of rows) {
@@ -593,6 +594,21 @@
593594 return { toggle, apply };
594595 })();
595596
597+ // The menus of the bars (<details class="menu">: the diff view, the merge
598+ // menu): closing one hands the focus back to its button when it was inside,
599+ // so that a keyboard user keeps their place.
600+ function closeMenu(d) {
601+ const had = d.contains(document.activeElement);
602+ d.open = false;
603+ if (had) $('summary', d)?.focus();
604+ }
605+ // closeMenus closes every open menu (escape); true when one was open.
606+ function closeMenus() {
607+ const open = $$('details.menu[open]');
608+ for (const d of open) closeMenu(d);
609+ return open.length > 0;
610+ }
611+
596612 // 1. Keys.
597613 const single = { j: 'j', k: 'k', G: 'bottom', '/': 'filter', '?': 'help', b: 'refs', f: 'cmd', ':': 'cmd', y: 'permalink',
598614 s: 's', t: 't', c: 'c', r: 'r', m: 'm', i: 'i', n: 'next', h: 'left', l: 'right', d: 'split', u: 'unified', x: 'collapse', w: 'wide', e: 'expand', E: 'expandAll', Enter: 'open', Escape: 'esc',
@@ -611,7 +627,7 @@
611627 const mr = window.ermiteMR;
612628 if (e.key === 'Escape') {
613629 pending = 0;
614- if (mr?.closeMenus() || mr?.leaveFiles()) return;
630+ if (closeMenus() || mr?.leaveFiles()) return;
615631 if (typing) e.target.blur();
616632 return ermite.fire('esc');
617633 }
@@ -651,9 +667,9 @@
651667 if (!el || e.button || e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;
652668 e.preventDefault();
653669 ermite.fire(el.dataset.fire);
654- // A choice in a menu closes it.
670+ // A choice in a menu closes it; the focus goes back to its button.
655671 const menu = el.closest('details.menu');
656- if (menu) menu.open = false;
672+ if (menu) closeMenu(menu);
657673 });
658674 // A click outside an open menu closes it.
659675 addEventListener('click', (e) => {
@@ -407,7 +407,8 @@
407 // The loaded rows are context lines, the same on both sides.407 // The loaded rows are context lines, the same on both sides.
408 function selectLoaded(file, rows) {408 function selectLoaded(file, rows) {
409 const m = /^(new|old):(?:(\d+)-)?(\d+):(.+)$/.exec(new URLSearchParams(location.search).get('on') || '');409 const m = /^(new|old):(?:(\d+)-)?(\d+):(.+)$/.exec(new URLSearchParams(location.search).get('on') || '');
410- if (!m || file.querySelector('summary .p')?.textContent !== m[4]) return;410+ // data-file is the path comments name (the new path of a rename), as ?on=
411+ if (!m || file.dataset.file !== m[4]) return;
411 const end = Number(m[3]);412 const end = Number(m[3]);
412 const start = m[2] ? Number(m[2]) : end;413 const start = m[2] ? Number(m[2]) : end;
413 for (const r of rows) {414 for (const r of rows) {
@@ -593,6 +594,21 @@
593 return { toggle, apply };594 return { toggle, apply };
594 })();595 })();
595 596
597+ // The menus of the bars (<details class="menu">: the diff view, the merge
598+ // menu): closing one hands the focus back to its button when it was inside,
599+ // so that a keyboard user keeps their place.
600+ function closeMenu(d) {
601+ const had = d.contains(document.activeElement);
602+ d.open = false;
603+ if (had) $('summary', d)?.focus();
604+ }
605+ // closeMenus closes every open menu (escape); true when one was open.
606+ function closeMenus() {
607+ const open = $$('details.menu[open]');
608+ for (const d of open) closeMenu(d);
609+ return open.length > 0;
610+ }
611+
596 // 1. Keys.612 // 1. Keys.
597 const single = { j: 'j', k: 'k', G: 'bottom', '/': 'filter', '?': 'help', b: 'refs', f: 'cmd', ':': 'cmd', y: 'permalink',613 const single = { j: 'j', k: 'k', G: 'bottom', '/': 'filter', '?': 'help', b: 'refs', f: 'cmd', ':': 'cmd', y: 'permalink',
598 s: 's', t: 't', c: 'c', r: 'r', m: 'm', i: 'i', n: 'next', h: 'left', l: 'right', d: 'split', u: 'unified', x: 'collapse', w: 'wide', e: 'expand', E: 'expandAll', Enter: 'open', Escape: 'esc',614 s: 's', t: 't', c: 'c', r: 'r', m: 'm', i: 'i', n: 'next', h: 'left', l: 'right', d: 'split', u: 'unified', x: 'collapse', w: 'wide', e: 'expand', E: 'expandAll', Enter: 'open', Escape: 'esc',
@@ -611,7 +627,7 @@
611 const mr = window.ermiteMR;627 const mr = window.ermiteMR;
612 if (e.key === 'Escape') {628 if (e.key === 'Escape') {
613 pending = 0;629 pending = 0;
614- if (mr?.closeMenus() || mr?.leaveFiles()) return;630+ if (closeMenus() || mr?.leaveFiles()) return;
615 if (typing) e.target.blur();631 if (typing) e.target.blur();
616 return ermite.fire('esc');632 return ermite.fire('esc');
617 }633 }
@@ -651,9 +667,9 @@
651 if (!el || e.button || e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;667 if (!el || e.button || e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;
652 e.preventDefault();668 e.preventDefault();
653 ermite.fire(el.dataset.fire);669 ermite.fire(el.dataset.fire);
654- // A choice in a menu closes it.670+ // A choice in a menu closes it; the focus goes back to its button.
655 const menu = el.closest('details.menu');671 const menu = el.closest('details.menu');
656- if (menu) menu.open = false;672+ if (menu) closeMenu(menu);
657 });673 });
658 // A click outside an open menu closes it.674 // A click outside an open menu closes it.
659 addEventListener('click', (e) => {675 addEventListener('click', (e) => {
M internal/adapters/web/static/js/mr.js +1 -7
@@ -196,12 +196,6 @@ window.ermiteMR = (() => {
196196 $('[data-default]', m)?.focus();
197197 return true;
198198 }
199- function closeMenus() {
200- const open = $$('details.menu[open]');
201- for (const d of open) d.open = false;
202- if (open.length) $('summary', open[0])?.focus();
203- return open.length > 0;
204- }
205199 // menuKey handles a key while the merge menu is open; true when it used it.
206200 function menuKey(e) {
207201 const m = menu();
@@ -313,7 +307,7 @@ window.ermiteMR = (() => {
313307 });
314308 }
315309
316- return { core, start, load, reveal, openMerge, closeMenus, menuKey, markRead, focusFiles, stepFiles, leaveFiles, foldFiles, openFile };
310+ return { core, start, load, reveal, openMerge, menuKey, markRead, focusFiles, stepFiles, leaveFiles, foldFiles, openFile };
317311 })();
318312
319313 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => window.ermiteMR.start());
@@ -196,12 +196,6 @@ window.ermiteMR = (() => {
196 $('[data-default]', m)?.focus();196 $('[data-default]', m)?.focus();
197 return true;197 return true;
198 }198 }
199- function closeMenus() {
200- const open = $$('details.menu[open]');
201- for (const d of open) d.open = false;
202- if (open.length) $('summary', open[0])?.focus();
203- return open.length > 0;
204- }
205 // menuKey handles a key while the merge menu is open; true when it used it.199 // menuKey handles a key while the merge menu is open; true when it used it.
206 function menuKey(e) {200 function menuKey(e) {
207 const m = menu();201 const m = menu();
@@ -313,7 +307,7 @@ window.ermiteMR = (() => {
313 });307 });
314 }308 }
315 309
316- return { core, start, load, reveal, openMerge, closeMenus, menuKey, markRead, focusFiles, stepFiles, leaveFiles, foldFiles, openFile };310+ return { core, start, load, reveal, openMerge, menuKey, markRead, focusFiles, stepFiles, leaveFiles, foldFiles, openFile };
317 })();311 })();
318 312
319 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => window.ermiteMR.start());313 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => window.ermiteMR.start());
M internal/app/testdata/web/commit-big.html +1 -1
@@ -143,7 +143,7 @@
143143 </div>
144144 <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>
145145 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
146-<details class="dfile lnw-2" id="f1" data-file>
146+<details class="dfile lnw-2" id="f1" data-file="big.txt">
147147 <summary>
148148 <span class="st">A</span>
149149 <span class="p">big.txt</span>
@@ -143,7 +143,7 @@
143 </div>143 </div>
144 <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>144 <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>
145 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">145 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
146-<details class="dfile lnw-2" id="f1" data-file>146+<details class="dfile lnw-2" id="f1" data-file="big.txt">
147 <summary>147 <summary>
148 <span class="st">A</span>148 <span class="st">A</span>
149 <span class="p">big.txt</span>149 <span class="p">big.txt</span>
M internal/app/testdata/web/commit-binary.html +1 -1
@@ -138,7 +138,7 @@
138138 </details>
139139 </div>
140140 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
141-<details class="dfile lnw-2" id="f1" data-file>
141+<details class="dfile lnw-2" id="f1" data-file="logo.bin">
142142 <summary>
143143 <span class="st">M</span>
144144 <span class="p">logo.bin</span>
@@ -138,7 +138,7 @@
138 </details>138 </details>
139 </div>139 </div>
140 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">140 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
141-<details class="dfile lnw-2" id="f1" data-file>141+<details class="dfile lnw-2" id="f1" data-file="logo.bin">
142 <summary>142 <summary>
143 <span class="st">M</span>143 <span class="st">M</span>
144 <span class="p">logo.bin</span>144 <span class="p">logo.bin</span>
M internal/app/testdata/web/commit-merge.html +1 -1
@@ -137,7 +137,7 @@
137137 </details>
138138 </div>
139139 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
140-<details class="dfile lnw-2" id="f1" data-file open>
140+<details class="dfile lnw-2" id="f1" data-file="side.txt" open>
141141 <summary>
142142 <span class="st">A</span>
143143 <span class="p">side.txt</span>
@@ -137,7 +137,7 @@
137 </details>137 </details>
138 </div>138 </div>
139 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">139 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
140-<details class="dfile lnw-2" id="f1" data-file open>140+<details class="dfile lnw-2" id="f1" data-file="side.txt" open>
141 <summary>141 <summary>
142 <span class="st">A</span>142 <span class="st">A</span>
143 <span class="p">side.txt</span>143 <span class="p">side.txt</span>
M internal/app/testdata/web/commit-modify-split.html +2 -2
@@ -144,7 +144,7 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
144144 </details>
145145 </div>
146146 <div id="diffs" data-diff="split">
147-<details class="dfile lnw-2" id="f1" data-file open>
147+<details class="dfile lnw-2" id="f1" data-file="a.txt" open>
148148 <summary>
149149 <span class="st">M</span>
150150 <span class="p">a.txt</span>
@@ -284,7 +284,7 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
284284 </table>
285285 </div>
286286 </details>
287-<details class="dfile lnw-2" id="f2" data-file>
287+<details class="dfile lnw-2" id="f2" data-file="run.sh">
288288 <summary>
289289 <span class="st">M</span>
290290 <span class="p">run.sh</span>
@@ -144,7 +144,7 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
144 </details>144 </details>
145 </div>145 </div>
146 <div id="diffs" data-diff="split">146 <div id="diffs" data-diff="split">
147-<details class="dfile lnw-2" id="f1" data-file open>147+<details class="dfile lnw-2" id="f1" data-file="a.txt" open>
148 <summary>148 <summary>
149 <span class="st">M</span>149 <span class="st">M</span>
150 <span class="p">a.txt</span>150 <span class="p">a.txt</span>
@@ -284,7 +284,7 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
284 </table>284 </table>
285 </div>285 </div>
286 </details>286 </details>
287-<details class="dfile lnw-2" id="f2" data-file>287+<details class="dfile lnw-2" id="f2" data-file="run.sh">
288 <summary>288 <summary>
289 <span class="st">M</span>289 <span class="st">M</span>
290 <span class="p">run.sh</span>290 <span class="p">run.sh</span>
M internal/app/testdata/web/commit-modify.html +2 -2
@@ -144,7 +144,7 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
144144 </details>
145145 </div>
146146 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
147-<details class="dfile lnw-2" id="f1" data-file open>
147+<details class="dfile lnw-2" id="f1" data-file="a.txt" open>
148148 <summary>
149149 <span class="st">M</span>
150150 <span class="p">a.txt</span>
@@ -284,7 +284,7 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
284284 </table>
285285 </div>
286286 </details>
287-<details class="dfile lnw-2" id="f2" data-file>
287+<details class="dfile lnw-2" id="f2" data-file="run.sh">
288288 <summary>
289289 <span class="st">M</span>
290290 <span class="p">run.sh</span>
@@ -144,7 +144,7 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
144 </details>144 </details>
145 </div>145 </div>
146 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">146 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
147-<details class="dfile lnw-2" id="f1" data-file open>147+<details class="dfile lnw-2" id="f1" data-file="a.txt" open>
148 <summary>148 <summary>
149 <span class="st">M</span>149 <span class="st">M</span>
150 <span class="p">a.txt</span>150 <span class="p">a.txt</span>
@@ -284,7 +284,7 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
284 </table>284 </table>
285 </div>285 </div>
286 </details>286 </details>
287-<details class="dfile lnw-2" id="f2" data-file>287+<details class="dfile lnw-2" id="f2" data-file="run.sh">
288 <summary>288 <summary>
289 <span class="st">M</span>289 <span class="st">M</span>
290 <span class="p">run.sh</span>290 <span class="p">run.sh</span>
M internal/app/testdata/web/commit-rename.html +2 -2
@@ -138,7 +138,7 @@
138138 </details>
139139 </div>
140140 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
141-<details class="dfile lnw-2" id="f1" data-file>
141+<details class="dfile lnw-2" id="f1" data-file="docs-old.txt">
142142 <summary>
143143 <span class="st">R</span>
144144 <span class="p">old.txt → docs-old.txt</span>
@@ -148,7 +148,7 @@
148148 </summary>
149149 <p class="note pad">renamed, 100% similar</p>
150150 </details>
151-<details class="dfile lnw-2" id="f2" data-file open>
151+<details class="dfile lnw-2" id="f2" data-file="gone.txt" open>
152152 <summary>
153153 <span class="st">D</span>
154154 <span class="p">gone.txt</span>
@@ -138,7 +138,7 @@
138 </details>138 </details>
139 </div>139 </div>
140 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">140 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
141-<details class="dfile lnw-2" id="f1" data-file>141+<details class="dfile lnw-2" id="f1" data-file="docs-old.txt">
142 <summary>142 <summary>
143 <span class="st">R</span>143 <span class="st">R</span>
144 <span class="p">old.txt → docs-old.txt</span>144 <span class="p">old.txt → docs-old.txt</span>
@@ -148,7 +148,7 @@
148 </summary>148 </summary>
149 <p class="note pad">renamed, 100% similar</p>149 <p class="note pad">renamed, 100% similar</p>
150 </details>150 </details>
151-<details class="dfile lnw-2" id="f2" data-file open>151+<details class="dfile lnw-2" id="f2" data-file="gone.txt" open>
152 <summary>152 <summary>
153 <span class="st">D</span>153 <span class="st">D</span>
154 <span class="p">gone.txt</span>154 <span class="p">gone.txt</span>
M internal/app/testdata/web/commit-root.html +5 -5
@@ -136,7 +136,7 @@
136136 </details>
137137 </div>
138138 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
139-<details class="dfile lnw-2" id="f1" data-file open>
139+<details class="dfile lnw-2" id="f1" data-file="a.txt" open>
140140 <summary>
141141 <span class="st">A</span>
142142 <span class="p">a.txt</span>
@@ -258,7 +258,7 @@
258258 </table>
259259 </div>
260260 </details>
261-<details class="dfile lnw-2" id="f2" data-file open>
261+<details class="dfile lnw-2" id="f2" data-file="gone.txt" open>
262262 <summary>
263263 <span class="st">A</span>
264264 <span class="p">gone.txt</span>
@@ -308,7 +308,7 @@
308308 </table>
309309 </div>
310310 </details>
311-<details class="dfile lnw-2" id="f3" data-file>
311+<details class="dfile lnw-2" id="f3" data-file="logo.bin">
312312 <summary>
313313 <span class="st">A</span>
314314 <span class="p">logo.bin</span>
@@ -318,7 +318,7 @@
318318 </summary>
319319 <p class="note pad">binary file: no text diff</p>
320320 </details>
321-<details class="dfile lnw-2" id="f4" data-file open>
321+<details class="dfile lnw-2" id="f4" data-file="old.txt" open>
322322 <summary>
323323 <span class="st">A</span>
324324 <span class="p">old.txt</span>
@@ -404,7 +404,7 @@
404404 </table>
405405 </div>
406406 </details>
407-<details class="dfile lnw-2" id="f5" data-file open>
407+<details class="dfile lnw-2" id="f5" data-file="run.sh" open>
408408 <summary>
409409 <span class="st">A</span>
410410 <span class="p">run.sh</span>
@@ -136,7 +136,7 @@
136 </details>136 </details>
137 </div>137 </div>
138 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">138 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
139-<details class="dfile lnw-2" id="f1" data-file open>139+<details class="dfile lnw-2" id="f1" data-file="a.txt" open>
140 <summary>140 <summary>
141 <span class="st">A</span>141 <span class="st">A</span>
142 <span class="p">a.txt</span>142 <span class="p">a.txt</span>
@@ -258,7 +258,7 @@
258 </table>258 </table>
259 </div>259 </div>
260 </details>260 </details>
261-<details class="dfile lnw-2" id="f2" data-file open>261+<details class="dfile lnw-2" id="f2" data-file="gone.txt" open>
262 <summary>262 <summary>
263 <span class="st">A</span>263 <span class="st">A</span>
264 <span class="p">gone.txt</span>264 <span class="p">gone.txt</span>
@@ -308,7 +308,7 @@
308 </table>308 </table>
309 </div>309 </div>
310 </details>310 </details>
311-<details class="dfile lnw-2" id="f3" data-file>311+<details class="dfile lnw-2" id="f3" data-file="logo.bin">
312 <summary>312 <summary>
313 <span class="st">A</span>313 <span class="st">A</span>
314 <span class="p">logo.bin</span>314 <span class="p">logo.bin</span>
@@ -318,7 +318,7 @@
318 </summary>318 </summary>
319 <p class="note pad">binary file: no text diff</p>319 <p class="note pad">binary file: no text diff</p>
320 </details>320 </details>
321-<details class="dfile lnw-2" id="f4" data-file open>321+<details class="dfile lnw-2" id="f4" data-file="old.txt" open>
322 <summary>322 <summary>
323 <span class="st">A</span>323 <span class="st">A</span>
324 <span class="p">old.txt</span>324 <span class="p">old.txt</span>
@@ -404,7 +404,7 @@
404 </table>404 </table>
405 </div>405 </div>
406 </details>406 </details>
407-<details class="dfile lnw-2" id="f5" data-file open>407+<details class="dfile lnw-2" id="f5" data-file="run.sh" open>
408 <summary>408 <summary>
409 <span class="st">A</span>409 <span class="st">A</span>
410 <span class="p">run.sh</span>410 <span class="p">run.sh</span>
M internal/app/testdata/web/commit-trojan.html +1 -1
@@ -140,7 +140,7 @@
140140 </div>
141141 <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>
142142 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
143-<details class="dfile lnw-2" id="f1" data-file open>
143+<details class="dfile lnw-2" id="f1" data-file="evil.go" open>
144144 <summary>
145145 <span class="st">A</span>
146146 <span class="p">evil.go</span>
@@ -140,7 +140,7 @@
140 </div>140 </div>
141 <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>141 <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>
142 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">142 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
143-<details class="dfile lnw-2" id="f1" data-file open>143+<details class="dfile lnw-2" id="f1" data-file="evil.go" open>
144 <summary>144 <summary>
145 <span class="st">A</span>145 <span class="st">A</span>
146 <span class="p">evil.go</span>146 <span class="p">evil.go</span>

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 2

Tags 45

↑↓ moveenter openesc close