detached 5e16168

#30 feat(web): release card, latest release in About, g-sequence shortcuts

state
merged · 7c1886f · squash
branch
feat/release-view-ux → main
opened
bnema ·
updated
CommitSubjectAuthorDateSignatureStatus
5e16168fix(releases): resolve the latest release from one snapshot and keep the index cheap to maintainbriceGPG Verified
879e0cfperf(releases): index the latest release per repositorybriceGPG Verified
3b03a2etest(app): update web goldens for g sequence shortcutsbriceGPG Verified
eb30609fix(web): drop sequence aria-keyshortcuts and use fake timers in keyboard testsbriceGPG Verified
a8c4b10test(web): cover g sequences in the jstest suitebriceGPG Verified
c3ae830feat(web): navigate sections with g sequences and fix uppercase action keysbriceGPG Verified
e08572cfeat(web): show releases as one card with prose notes and the latest release in AboutbriceGPG Verified

Checks 4

73 files · +2058 -1284
4/4
M README.md +1 -0
M docs/issues.md +1 -1
A docs/keyboard-shortcuts.md +30 -0
M docs/merge-requests.md +1 -1
M internal/adapters/kvstore/issues_test.go +1 -1
M internal/adapters/kvstore/releases.go +28 -2
A internal/adapters/kvstore/releases_index.go +67 -0
A internal/adapters/kvstore/releases_index_test.go +97 -0
M internal/adapters/kvstore/store.go +6 -1
M internal/adapters/web/fragments.go +1 -1
M internal/adapters/web/fragments_test.go +3 -3
M internal/adapters/web/issues_test.go +3 -3
A internal/adapters/web/jstest/keyboard.test.mjs +76 -0
D internal/adapters/web/jstest/keys.test.mjs +0 -29
M internal/adapters/web/keys.templ +3 -2
M internal/adapters/web/keys_templ.go +1 -1
A internal/adapters/web/keys_test.go +26 -0
M internal/adapters/web/mr.templ +1 -1
M internal/adapters/web/mr_templ.go +1 -1
M internal/adapters/web/mr_test.go +1 -1
M internal/adapters/web/overlays.templ +2 -2
M internal/adapters/web/overlays_templ.go +2 -2
M internal/adapters/web/pages.templ +8 -0
M internal/adapters/web/pages_templ.go +371 -261
M internal/adapters/web/releases.templ +16 -9
M internal/adapters/web/releases_templ.go +32 -28
M internal/adapters/web/releases_test.go +12 -2
M internal/adapters/web/repo.go +4 -4
M internal/adapters/web/shell.templ +2 -3
M internal/adapters/web/shell_templ.go +48 -61
M internal/adapters/web/static/js/ermite.js +13 -4
M internal/adapters/web/static/js/mr.js +2 -2
M internal/adapters/web/summary.go +21 -0
A internal/adapters/web/summary_releases_test.go +49 -0
M internal/adapters/web/view.go +7 -1
M internal/app/testdata/web/blob-binary.html +33 -30
M internal/app/testdata/web/blob-code.html +33 -30
M internal/app/testdata/web/blob-empty.html +33 -30
M internal/app/testdata/web/blob-image.html +33 -30
M internal/app/testdata/web/blob-markdown.html +33 -30
M internal/app/testdata/web/blob-svg.html +33 -30
M internal/app/testdata/web/blob-text.html +33 -30
M internal/app/testdata/web/blob-too-large.html +33 -30
M internal/app/testdata/web/blob-trojan.html +33 -30
M internal/app/testdata/web/commit-big.html +33 -30
M internal/app/testdata/web/commit-binary.html +33 -30
M internal/app/testdata/web/commit-merge.html +33 -30
M internal/app/testdata/web/commit-modify-split.html +33 -30
M internal/app/testdata/web/commit-modify.html +33 -30
M internal/app/testdata/web/commit-rename.html +33 -30
M internal/app/testdata/web/commit-root.html +33 -30
@@ -72,31 +72,31 @@
7272 <nav class="tabs" aria-label="Sections">
7373 <ul>
7474 <li>
75-<a href="/diffs" data-key="s" aria-keyshortcuts="s">Summary <kbd aria-hidden="true">s</kbd>
75+<a href="/diffs" data-key="g s">Summary <kbd aria-hidden="true">g s</kbd>
7676 </a>
7777 </li>
7878 <li>
79-<a href="/diffs/tree/main" data-key="t" aria-keyshortcuts="t">Files <kbd aria-hidden="true">t</kbd>
79+<a href="/diffs/tree/main" data-key="g f">Files <kbd aria-hidden="true">g f</kbd>
8080 </a>
8181 </li>
8282 <li>
83-<a href="/diffs/commits/main" data-key="c" aria-keyshortcuts="c" aria-current="page">Commits <kbd aria-hidden="true">c</kbd>
83+<a href="/diffs/commits/main" data-key="g c" aria-current="page">Commits <kbd aria-hidden="true">g c</kbd>
8484 </a>
8585 </li>
8686 <li>
87-<a href="/diffs/refs" data-key="r" aria-keyshortcuts="r">Branches &amp; tags <kbd aria-hidden="true">r</kbd>
87+<a href="/diffs/refs" data-key="g b">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
8888 </a>
8989 </li>
9090 <li>
91-<a href="/diffs/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>
91+<a href="/diffs/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
9292 </a>
9393 </li>
9494 <li>
95-<a href="/diffs/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>
95+<a href="/diffs/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
9696 </a>
9797 </li>
9898 <li>
99-<a href="/diffs/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>
99+<a href="/diffs/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
100100 </a>
101101 </li>
102102 </ul>
@@ -659,14 +659,16 @@
659659 </div>
660660 <div>
661661 <dt>
662-<kbd>s</kbd>
663-<kbd>t</kbd>
664-<kbd>c</kbd>
665-<kbd>r</kbd>
666-<kbd>m</kbd>
667-<kbd>i</kbd>
662+<kbd>g s</kbd>
663+<kbd>g f</kbd>
664+<kbd>g c</kbd>
665+<kbd>g b</kbd>
666+<kbd>g m</kbd>
667+<kbd>g i</kbd>
668+<kbd>g r</kbd>
669+<kbd>g a</kbd>
668670 </dt>
669-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>
671+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
670672 </div>
671673 <div>
672674 <dt>
@@ -765,45 +767,45 @@
765767 <span class="k">go</span>
766768 </a>
767769 </li>
768-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
770+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
769771 <a role="presentation" data-href="/diffs">
770-<span class="lbl">summary</span>
772+<span class="lbl">summary (g s)</span>
771773 <span class="k">section</span>
772774 </a>
773775 </li>
774-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
776+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
775777 <a role="presentation" data-href="/diffs/tree/main">
776-<span class="lbl">files</span>
778+<span class="lbl">files (g f)</span>
777779 <span class="k">section</span>
778780 </a>
779781 </li>
780-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
782+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
781783 <a role="presentation" data-href="/diffs/commits/main">
782-<span class="lbl">commits</span>
784+<span class="lbl">commits (g c)</span>
783785 <span class="k">section</span>
784786 </a>
785787 </li>
786-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
788+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
787789 <a role="presentation" data-href="/diffs/refs">
788-<span class="lbl">branches &amp; tags</span>
790+<span class="lbl">branches &amp; tags (g b)</span>
789791 <span class="k">section</span>
790792 </a>
791793 </li>
792-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
794+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
793795 <a role="presentation" data-href="/diffs/merge-requests">
794-<span class="lbl">merge requests</span>
796+<span class="lbl">merge requests (g m)</span>
795797 <span class="k">section</span>
796798 </a>
797799 </li>
798-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
800+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
799801 <a role="presentation" data-href="/diffs/releases">
800-<span class="lbl">releases</span>
802+<span class="lbl">releases (g r)</span>
801803 <span class="k">section</span>
802804 </a>
803805 </li>
804-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
806+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
805807 <a role="presentation" data-href="/diffs/actions">
806-<span class="lbl">actions</span>
808+<span class="lbl">actions (g a)</span>
807809 <span class="k">section</span>
808810 </a>
809811 </li>
@@ -856,7 +858,9 @@
856858 k-filter from:body -> ermite.focusFilter();
857859 k-gh from:body -> ermite.go('.brand');
858860 k-s from:body -> ermite.tab('s');
859- k-t from:body -> ermite.tab('t');
861+ k-f from:body -> ermite.tab('f');
862+ k-b from:body -> ermite.tab('b');
863+ k-a from:body -> ermite.tab('a');
860864 k-c from:body -> ermite.tab('c');
861865 k-r from:body -> ermite.tab('r');
862866 k-m from:body -> ermite.tab('m');
@@ -872,7 +876,6 @@
872876 k-permalink from:body -> ermite.permalink();
873877 k-merge from:body -> window.ermiteMR?.openMerge();
874878 k-read from:body -> window.ermiteMR?.markRead();
875- k-gf from:body -> window.ermiteMR?.focusFiles();
876879 k-keys from:body -> ermite.toggleKeys()">
877880 </div>
878881 </body>
@@ -72,31 +72,31 @@
72 <nav class="tabs" aria-label="Sections">72 <nav class="tabs" aria-label="Sections">
73 <ul>73 <ul>
74 <li>74 <li>
75-<a href="/diffs" data-key="s" aria-keyshortcuts="s">Summary <kbd aria-hidden="true">s</kbd>75+<a href="/diffs" data-key="g s">Summary <kbd aria-hidden="true">g s</kbd>
76 </a>76 </a>
77 </li>77 </li>
78 <li>78 <li>
79-<a href="/diffs/tree/main" data-key="t" aria-keyshortcuts="t">Files <kbd aria-hidden="true">t</kbd>79+<a href="/diffs/tree/main" data-key="g f">Files <kbd aria-hidden="true">g f</kbd>
80 </a>80 </a>
81 </li>81 </li>
82 <li>82 <li>
83-<a href="/diffs/commits/main" data-key="c" aria-keyshortcuts="c" aria-current="page">Commits <kbd aria-hidden="true">c</kbd>83+<a href="/diffs/commits/main" data-key="g c" aria-current="page">Commits <kbd aria-hidden="true">g c</kbd>
84 </a>84 </a>
85 </li>85 </li>
86 <li>86 <li>
87-<a href="/diffs/refs" data-key="r" aria-keyshortcuts="r">Branches &amp; tags <kbd aria-hidden="true">r</kbd>87+<a href="/diffs/refs" data-key="g b">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
88 </a>88 </a>
89 </li>89 </li>
90 <li>90 <li>
91-<a href="/diffs/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>91+<a href="/diffs/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
92 </a>92 </a>
93 </li>93 </li>
94 <li>94 <li>
95-<a href="/diffs/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>95+<a href="/diffs/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
96 </a>96 </a>
97 </li>97 </li>
98 <li>98 <li>
99-<a href="/diffs/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>99+<a href="/diffs/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
100 </a>100 </a>
101 </li>101 </li>
102 </ul>102 </ul>
@@ -659,14 +659,16 @@
659 </div>659 </div>
660 <div>660 <div>
661 <dt>661 <dt>
662-<kbd>s</kbd>662+<kbd>g s</kbd>
663-<kbd>t</kbd>663+<kbd>g f</kbd>
664-<kbd>c</kbd>664+<kbd>g c</kbd>
665-<kbd>r</kbd>665+<kbd>g b</kbd>
666-<kbd>m</kbd>666+<kbd>g m</kbd>
667-<kbd>i</kbd>667+<kbd>g i</kbd>
668+<kbd>g r</kbd>
669+<kbd>g a</kbd>
668 </dt>670 </dt>
669-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>671+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
670 </div>672 </div>
671 <div>673 <div>
672 <dt>674 <dt>
@@ -765,45 +767,45 @@
765 <span class="k">go</span>767 <span class="k">go</span>
766 </a>768 </a>
767 </li>769 </li>
768-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">770+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
769 <a role="presentation" data-href="/diffs">771 <a role="presentation" data-href="/diffs">
770-<span class="lbl">summary</span>772+<span class="lbl">summary (g s)</span>
771 <span class="k">section</span>773 <span class="k">section</span>
772 </a>774 </a>
773 </li>775 </li>
774-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">776+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
775 <a role="presentation" data-href="/diffs/tree/main">777 <a role="presentation" data-href="/diffs/tree/main">
776-<span class="lbl">files</span>778+<span class="lbl">files (g f)</span>
777 <span class="k">section</span>779 <span class="k">section</span>
778 </a>780 </a>
779 </li>781 </li>
780-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">782+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
781 <a role="presentation" data-href="/diffs/commits/main">783 <a role="presentation" data-href="/diffs/commits/main">
782-<span class="lbl">commits</span>784+<span class="lbl">commits (g c)</span>
783 <span class="k">section</span>785 <span class="k">section</span>
784 </a>786 </a>
785 </li>787 </li>
786-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">788+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
787 <a role="presentation" data-href="/diffs/refs">789 <a role="presentation" data-href="/diffs/refs">
788-<span class="lbl">branches &amp; tags</span>790+<span class="lbl">branches &amp; tags (g b)</span>
789 <span class="k">section</span>791 <span class="k">section</span>
790 </a>792 </a>
791 </li>793 </li>
792-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">794+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
793 <a role="presentation" data-href="/diffs/merge-requests">795 <a role="presentation" data-href="/diffs/merge-requests">
794-<span class="lbl">merge requests</span>796+<span class="lbl">merge requests (g m)</span>
795 <span class="k">section</span>797 <span class="k">section</span>
796 </a>798 </a>
797 </li>799 </li>
798-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">800+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
799 <a role="presentation" data-href="/diffs/releases">801 <a role="presentation" data-href="/diffs/releases">
800-<span class="lbl">releases</span>802+<span class="lbl">releases (g r)</span>
801 <span class="k">section</span>803 <span class="k">section</span>
802 </a>804 </a>
803 </li>805 </li>
804-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">806+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
805 <a role="presentation" data-href="/diffs/actions">807 <a role="presentation" data-href="/diffs/actions">
806-<span class="lbl">actions</span>808+<span class="lbl">actions (g a)</span>
807 <span class="k">section</span>809 <span class="k">section</span>
808 </a>810 </a>
809 </li>811 </li>
@@ -856,7 +858,9 @@
856 k-filter from:body -> ermite.focusFilter();858 k-filter from:body -> ermite.focusFilter();
857 k-gh from:body -> ermite.go('.brand');859 k-gh from:body -> ermite.go('.brand');
858 k-s from:body -> ermite.tab('s');860 k-s from:body -> ermite.tab('s');
859- k-t from:body -> ermite.tab('t');861+ k-f from:body -> ermite.tab('f');
862+ k-b from:body -> ermite.tab('b');
863+ k-a from:body -> ermite.tab('a');
860 k-c from:body -> ermite.tab('c');864 k-c from:body -> ermite.tab('c');
861 k-r from:body -> ermite.tab('r');865 k-r from:body -> ermite.tab('r');
862 k-m from:body -> ermite.tab('m');866 k-m from:body -> ermite.tab('m');
@@ -872,7 +876,6 @@
872 k-permalink from:body -> ermite.permalink();876 k-permalink from:body -> ermite.permalink();
873 k-merge from:body -> window.ermiteMR?.openMerge();877 k-merge from:body -> window.ermiteMR?.openMerge();
874 k-read from:body -> window.ermiteMR?.markRead();878 k-read from:body -> window.ermiteMR?.markRead();
875- k-gf from:body -> window.ermiteMR?.focusFiles();
876 k-keys from:body -> ermite.toggleKeys()">879 k-keys from:body -> ermite.toggleKeys()">
877 </div>880 </div>
878 </body>881 </body>
M internal/app/testdata/web/commit-trojan.html +33 -30
@@ -72,31 +72,31 @@
7272 <nav class="tabs" aria-label="Sections">
7373 <ul>
7474 <li>
75-<a href="/diffs" data-key="s" aria-keyshortcuts="s">Summary <kbd aria-hidden="true">s</kbd>
75+<a href="/diffs" data-key="g s">Summary <kbd aria-hidden="true">g s</kbd>
7676 </a>
7777 </li>
7878 <li>
79-<a href="/diffs/tree/main" data-key="t" aria-keyshortcuts="t">Files <kbd aria-hidden="true">t</kbd>
79+<a href="/diffs/tree/main" data-key="g f">Files <kbd aria-hidden="true">g f</kbd>
8080 </a>
8181 </li>
8282 <li>
83-<a href="/diffs/commits/main" data-key="c" aria-keyshortcuts="c" aria-current="page">Commits <kbd aria-hidden="true">c</kbd>
83+<a href="/diffs/commits/main" data-key="g c" aria-current="page">Commits <kbd aria-hidden="true">g c</kbd>
8484 </a>
8585 </li>
8686 <li>
87-<a href="/diffs/refs" data-key="r" aria-keyshortcuts="r">Branches &amp; tags <kbd aria-hidden="true">r</kbd>
87+<a href="/diffs/refs" data-key="g b">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
8888 </a>
8989 </li>
9090 <li>
91-<a href="/diffs/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>
91+<a href="/diffs/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
9292 </a>
9393 </li>
9494 <li>
95-<a href="/diffs/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>
95+<a href="/diffs/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
9696 </a>
9797 </li>
9898 <li>
99-<a href="/diffs/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>
99+<a href="/diffs/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
100100 </a>
101101 </li>
102102 </ul>
@@ -488,14 +488,16 @@
488488 </div>
489489 <div>
490490 <dt>
491-<kbd>s</kbd>
492-<kbd>t</kbd>
493-<kbd>c</kbd>
494-<kbd>r</kbd>
495-<kbd>m</kbd>
496-<kbd>i</kbd>
491+<kbd>g s</kbd>
492+<kbd>g f</kbd>
493+<kbd>g c</kbd>
494+<kbd>g b</kbd>
495+<kbd>g m</kbd>
496+<kbd>g i</kbd>
497+<kbd>g r</kbd>
498+<kbd>g a</kbd>
497499 </dt>
498-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>
500+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
499501 </div>
500502 <div>
501503 <dt>
@@ -594,45 +596,45 @@
594596 <span class="k">go</span>
595597 </a>
596598 </li>
597-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
599+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
598600 <a role="presentation" data-href="/diffs">
599-<span class="lbl">summary</span>
601+<span class="lbl">summary (g s)</span>
600602 <span class="k">section</span>
601603 </a>
602604 </li>
603-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
605+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
604606 <a role="presentation" data-href="/diffs/tree/main">
605-<span class="lbl">files</span>
607+<span class="lbl">files (g f)</span>
606608 <span class="k">section</span>
607609 </a>
608610 </li>
609-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
611+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
610612 <a role="presentation" data-href="/diffs/commits/main">
611-<span class="lbl">commits</span>
613+<span class="lbl">commits (g c)</span>
612614 <span class="k">section</span>
613615 </a>
614616 </li>
615-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
617+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
616618 <a role="presentation" data-href="/diffs/refs">
617-<span class="lbl">branches &amp; tags</span>
619+<span class="lbl">branches &amp; tags (g b)</span>
618620 <span class="k">section</span>
619621 </a>
620622 </li>
621-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
623+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
622624 <a role="presentation" data-href="/diffs/merge-requests">
623-<span class="lbl">merge requests</span>
625+<span class="lbl">merge requests (g m)</span>
624626 <span class="k">section</span>
625627 </a>
626628 </li>
627-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
629+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
628630 <a role="presentation" data-href="/diffs/releases">
629-<span class="lbl">releases</span>
631+<span class="lbl">releases (g r)</span>
630632 <span class="k">section</span>
631633 </a>
632634 </li>
633-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
635+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
634636 <a role="presentation" data-href="/diffs/actions">
635-<span class="lbl">actions</span>
637+<span class="lbl">actions (g a)</span>
636638 <span class="k">section</span>
637639 </a>
638640 </li>
@@ -685,7 +687,9 @@
685687 k-filter from:body -> ermite.focusFilter();
686688 k-gh from:body -> ermite.go('.brand');
687689 k-s from:body -> ermite.tab('s');
688- k-t from:body -> ermite.tab('t');
690+ k-f from:body -> ermite.tab('f');
691+ k-b from:body -> ermite.tab('b');
692+ k-a from:body -> ermite.tab('a');
689693 k-c from:body -> ermite.tab('c');
690694 k-r from:body -> ermite.tab('r');
691695 k-m from:body -> ermite.tab('m');
@@ -701,7 +705,6 @@
701705 k-permalink from:body -> ermite.permalink();
702706 k-merge from:body -> window.ermiteMR?.openMerge();
703707 k-read from:body -> window.ermiteMR?.markRead();
704- k-gf from:body -> window.ermiteMR?.focusFiles();
705708 k-keys from:body -> ermite.toggleKeys()">
706709 </div>
707710 </body>
@@ -72,31 +72,31 @@
72 <nav class="tabs" aria-label="Sections">72 <nav class="tabs" aria-label="Sections">
73 <ul>73 <ul>
74 <li>74 <li>
75-<a href="/diffs" data-key="s" aria-keyshortcuts="s">Summary <kbd aria-hidden="true">s</kbd>75+<a href="/diffs" data-key="g s">Summary <kbd aria-hidden="true">g s</kbd>
76 </a>76 </a>
77 </li>77 </li>
78 <li>78 <li>
79-<a href="/diffs/tree/main" data-key="t" aria-keyshortcuts="t">Files <kbd aria-hidden="true">t</kbd>79+<a href="/diffs/tree/main" data-key="g f">Files <kbd aria-hidden="true">g f</kbd>
80 </a>80 </a>
81 </li>81 </li>
82 <li>82 <li>
83-<a href="/diffs/commits/main" data-key="c" aria-keyshortcuts="c" aria-current="page">Commits <kbd aria-hidden="true">c</kbd>83+<a href="/diffs/commits/main" data-key="g c" aria-current="page">Commits <kbd aria-hidden="true">g c</kbd>
84 </a>84 </a>
85 </li>85 </li>
86 <li>86 <li>
87-<a href="/diffs/refs" data-key="r" aria-keyshortcuts="r">Branches &amp; tags <kbd aria-hidden="true">r</kbd>87+<a href="/diffs/refs" data-key="g b">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
88 </a>88 </a>
89 </li>89 </li>
90 <li>90 <li>
91-<a href="/diffs/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>91+<a href="/diffs/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
92 </a>92 </a>
93 </li>93 </li>
94 <li>94 <li>
95-<a href="/diffs/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>95+<a href="/diffs/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
96 </a>96 </a>
97 </li>97 </li>
98 <li>98 <li>
99-<a href="/diffs/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>99+<a href="/diffs/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
100 </a>100 </a>
101 </li>101 </li>
102 </ul>102 </ul>
@@ -488,14 +488,16 @@
488 </div>488 </div>
489 <div>489 <div>
490 <dt>490 <dt>
491-<kbd>s</kbd>491+<kbd>g s</kbd>
492-<kbd>t</kbd>492+<kbd>g f</kbd>
493-<kbd>c</kbd>493+<kbd>g c</kbd>
494-<kbd>r</kbd>494+<kbd>g b</kbd>
495-<kbd>m</kbd>495+<kbd>g m</kbd>
496-<kbd>i</kbd>496+<kbd>g i</kbd>
497+<kbd>g r</kbd>
498+<kbd>g a</kbd>
497 </dt>499 </dt>
498-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>500+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
499 </div>501 </div>
500 <div>502 <div>
501 <dt>503 <dt>
@@ -594,45 +596,45 @@
594 <span class="k">go</span>596 <span class="k">go</span>
595 </a>597 </a>
596 </li>598 </li>
597-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">599+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
598 <a role="presentation" data-href="/diffs">600 <a role="presentation" data-href="/diffs">
599-<span class="lbl">summary</span>601+<span class="lbl">summary (g s)</span>
600 <span class="k">section</span>602 <span class="k">section</span>
601 </a>603 </a>
602 </li>604 </li>
603-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">605+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
604 <a role="presentation" data-href="/diffs/tree/main">606 <a role="presentation" data-href="/diffs/tree/main">
605-<span class="lbl">files</span>607+<span class="lbl">files (g f)</span>
606 <span class="k">section</span>608 <span class="k">section</span>
607 </a>609 </a>
608 </li>610 </li>
609-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">611+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
610 <a role="presentation" data-href="/diffs/commits/main">612 <a role="presentation" data-href="/diffs/commits/main">
611-<span class="lbl">commits</span>613+<span class="lbl">commits (g c)</span>
612 <span class="k">section</span>614 <span class="k">section</span>
613 </a>615 </a>
614 </li>616 </li>
615-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">617+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
616 <a role="presentation" data-href="/diffs/refs">618 <a role="presentation" data-href="/diffs/refs">
617-<span class="lbl">branches &amp; tags</span>619+<span class="lbl">branches &amp; tags (g b)</span>
618 <span class="k">section</span>620 <span class="k">section</span>
619 </a>621 </a>
620 </li>622 </li>
621-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">623+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
622 <a role="presentation" data-href="/diffs/merge-requests">624 <a role="presentation" data-href="/diffs/merge-requests">
623-<span class="lbl">merge requests</span>625+<span class="lbl">merge requests (g m)</span>
624 <span class="k">section</span>626 <span class="k">section</span>
625 </a>627 </a>
626 </li>628 </li>
627-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">629+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
628 <a role="presentation" data-href="/diffs/releases">630 <a role="presentation" data-href="/diffs/releases">
629-<span class="lbl">releases</span>631+<span class="lbl">releases (g r)</span>
630 <span class="k">section</span>632 <span class="k">section</span>
631 </a>633 </a>
632 </li>634 </li>
633-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">635+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
634 <a role="presentation" data-href="/diffs/actions">636 <a role="presentation" data-href="/diffs/actions">
635-<span class="lbl">actions</span>637+<span class="lbl">actions (g a)</span>
636 <span class="k">section</span>638 <span class="k">section</span>
637 </a>639 </a>
638 </li>640 </li>
@@ -685,7 +687,9 @@
685 k-filter from:body -> ermite.focusFilter();687 k-filter from:body -> ermite.focusFilter();
686 k-gh from:body -> ermite.go('.brand');688 k-gh from:body -> ermite.go('.brand');
687 k-s from:body -> ermite.tab('s');689 k-s from:body -> ermite.tab('s');
688- k-t from:body -> ermite.tab('t');690+ k-f from:body -> ermite.tab('f');
691+ k-b from:body -> ermite.tab('b');
692+ k-a from:body -> ermite.tab('a');
689 k-c from:body -> ermite.tab('c');693 k-c from:body -> ermite.tab('c');
690 k-r from:body -> ermite.tab('r');694 k-r from:body -> ermite.tab('r');
691 k-m from:body -> ermite.tab('m');695 k-m from:body -> ermite.tab('m');
@@ -701,7 +705,6 @@
701 k-permalink from:body -> ermite.permalink();705 k-permalink from:body -> ermite.permalink();
702 k-merge from:body -> window.ermiteMR?.openMerge();706 k-merge from:body -> window.ermiteMR?.openMerge();
703 k-read from:body -> window.ermiteMR?.markRead();707 k-read from:body -> window.ermiteMR?.markRead();
704- k-gf from:body -> window.ermiteMR?.focusFiles();
705 k-keys from:body -> ermite.toggleKeys()">708 k-keys from:body -> ermite.toggleKeys()">
706 </div>709 </div>
707 </body>710 </body>
M internal/app/testdata/web/commits-page2.html +33 -30
@@ -70,31 +70,31 @@
7070 <nav class="tabs" aria-label="Sections">
7171 <ul>
7272 <li>
73-<a href="/long" data-key="s" aria-keyshortcuts="s">Summary <kbd aria-hidden="true">s</kbd>
73+<a href="/long" data-key="g s">Summary <kbd aria-hidden="true">g s</kbd>
7474 </a>
7575 </li>
7676 <li>
77-<a href="/long/tree/main" data-key="t" aria-keyshortcuts="t">Files <kbd aria-hidden="true">t</kbd>
77+<a href="/long/tree/main" data-key="g f">Files <kbd aria-hidden="true">g f</kbd>
7878 </a>
7979 </li>
8080 <li>
81-<a href="/long/commits/main" data-key="c" aria-keyshortcuts="c" aria-current="page">Commits <kbd aria-hidden="true">c</kbd>
81+<a href="/long/commits/main" data-key="g c" aria-current="page">Commits <kbd aria-hidden="true">g c</kbd>
8282 </a>
8383 </li>
8484 <li>
85-<a href="/long/refs" data-key="r" aria-keyshortcuts="r">Branches &amp; tags <kbd aria-hidden="true">r</kbd>
85+<a href="/long/refs" data-key="g b">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
8686 </a>
8787 </li>
8888 <li>
89-<a href="/long/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>
89+<a href="/long/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
9090 </a>
9191 </li>
9292 <li>
93-<a href="/long/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>
93+<a href="/long/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
9494 </a>
9595 </li>
9696 <li>
97-<a href="/long/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>
97+<a href="/long/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
9898 </a>
9999 </li>
100100 </ul>
@@ -255,14 +255,16 @@
255255 </div>
256256 <div>
257257 <dt>
258-<kbd>s</kbd>
259-<kbd>t</kbd>
260-<kbd>c</kbd>
261-<kbd>r</kbd>
262-<kbd>m</kbd>
263-<kbd>i</kbd>
258+<kbd>g s</kbd>
259+<kbd>g f</kbd>
260+<kbd>g c</kbd>
261+<kbd>g b</kbd>
262+<kbd>g m</kbd>
263+<kbd>g i</kbd>
264+<kbd>g r</kbd>
265+<kbd>g a</kbd>
264266 </dt>
265-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>
267+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
266268 </div>
267269 <div>
268270 <dt>
@@ -353,45 +355,45 @@
353355 <span class="k">go</span>
354356 </a>
355357 </li>
356-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
358+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
357359 <a role="presentation" data-href="/long">
358-<span class="lbl">summary</span>
360+<span class="lbl">summary (g s)</span>
359361 <span class="k">section</span>
360362 </a>
361363 </li>
362-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
364+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
363365 <a role="presentation" data-href="/long/tree/main">
364-<span class="lbl">files</span>
366+<span class="lbl">files (g f)</span>
365367 <span class="k">section</span>
366368 </a>
367369 </li>
368-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
370+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
369371 <a role="presentation" data-href="/long/commits/main">
370-<span class="lbl">commits</span>
372+<span class="lbl">commits (g c)</span>
371373 <span class="k">section</span>
372374 </a>
373375 </li>
374-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
376+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
375377 <a role="presentation" data-href="/long/refs">
376-<span class="lbl">branches &amp; tags</span>
378+<span class="lbl">branches &amp; tags (g b)</span>
377379 <span class="k">section</span>
378380 </a>
379381 </li>
380-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
382+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
381383 <a role="presentation" data-href="/long/merge-requests">
382-<span class="lbl">merge requests</span>
384+<span class="lbl">merge requests (g m)</span>
383385 <span class="k">section</span>
384386 </a>
385387 </li>
386-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
388+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
387389 <a role="presentation" data-href="/long/releases">
388-<span class="lbl">releases</span>
390+<span class="lbl">releases (g r)</span>
389391 <span class="k">section</span>
390392 </a>
391393 </li>
392-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
394+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
393395 <a role="presentation" data-href="/long/actions">
394-<span class="lbl">actions</span>
396+<span class="lbl">actions (g a)</span>
395397 <span class="k">section</span>
396398 </a>
397399 </li>
@@ -438,7 +440,9 @@
438440 k-filter from:body -> ermite.focusFilter();
439441 k-gh from:body -> ermite.go('.brand');
440442 k-s from:body -> ermite.tab('s');
441- k-t from:body -> ermite.tab('t');
443+ k-f from:body -> ermite.tab('f');
444+ k-b from:body -> ermite.tab('b');
445+ k-a from:body -> ermite.tab('a');
442446 k-c from:body -> ermite.tab('c');
443447 k-r from:body -> ermite.tab('r');
444448 k-m from:body -> ermite.tab('m');
@@ -454,7 +458,6 @@
454458 k-permalink from:body -> ermite.permalink();
455459 k-merge from:body -> window.ermiteMR?.openMerge();
456460 k-read from:body -> window.ermiteMR?.markRead();
457- k-gf from:body -> window.ermiteMR?.focusFiles();
458461 k-keys from:body -> ermite.toggleKeys()">
459462 </div>
460463 </body>
@@ -70,31 +70,31 @@
70 <nav class="tabs" aria-label="Sections">70 <nav class="tabs" aria-label="Sections">
71 <ul>71 <ul>
72 <li>72 <li>
73-<a href="/long" data-key="s" aria-keyshortcuts="s">Summary <kbd aria-hidden="true">s</kbd>73+<a href="/long" data-key="g s">Summary <kbd aria-hidden="true">g s</kbd>
74 </a>74 </a>
75 </li>75 </li>
76 <li>76 <li>
77-<a href="/long/tree/main" data-key="t" aria-keyshortcuts="t">Files <kbd aria-hidden="true">t</kbd>77+<a href="/long/tree/main" data-key="g f">Files <kbd aria-hidden="true">g f</kbd>
78 </a>78 </a>
79 </li>79 </li>
80 <li>80 <li>
81-<a href="/long/commits/main" data-key="c" aria-keyshortcuts="c" aria-current="page">Commits <kbd aria-hidden="true">c</kbd>81+<a href="/long/commits/main" data-key="g c" aria-current="page">Commits <kbd aria-hidden="true">g c</kbd>
82 </a>82 </a>
83 </li>83 </li>
84 <li>84 <li>
85-<a href="/long/refs" data-key="r" aria-keyshortcuts="r">Branches &amp; tags <kbd aria-hidden="true">r</kbd>85+<a href="/long/refs" data-key="g b">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
86 </a>86 </a>
87 </li>87 </li>
88 <li>88 <li>
89-<a href="/long/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>89+<a href="/long/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
90 </a>90 </a>
91 </li>91 </li>
92 <li>92 <li>
93-<a href="/long/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>93+<a href="/long/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
94 </a>94 </a>
95 </li>95 </li>
96 <li>96 <li>
97-<a href="/long/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>97+<a href="/long/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
98 </a>98 </a>
99 </li>99 </li>
100 </ul>100 </ul>
@@ -255,14 +255,16 @@
255 </div>255 </div>
256 <div>256 <div>
257 <dt>257 <dt>
258-<kbd>s</kbd>258+<kbd>g s</kbd>
259-<kbd>t</kbd>259+<kbd>g f</kbd>
260-<kbd>c</kbd>260+<kbd>g c</kbd>
261-<kbd>r</kbd>261+<kbd>g b</kbd>
262-<kbd>m</kbd>262+<kbd>g m</kbd>
263-<kbd>i</kbd>263+<kbd>g i</kbd>
264+<kbd>g r</kbd>
265+<kbd>g a</kbd>
264 </dt>266 </dt>
265-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>267+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
266 </div>268 </div>
267 <div>269 <div>
268 <dt>270 <dt>
@@ -353,45 +355,45 @@
353 <span class="k">go</span>355 <span class="k">go</span>
354 </a>356 </a>
355 </li>357 </li>
356-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">358+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
357 <a role="presentation" data-href="/long">359 <a role="presentation" data-href="/long">
358-<span class="lbl">summary</span>360+<span class="lbl">summary (g s)</span>
359 <span class="k">section</span>361 <span class="k">section</span>
360 </a>362 </a>
361 </li>363 </li>
362-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">364+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
363 <a role="presentation" data-href="/long/tree/main">365 <a role="presentation" data-href="/long/tree/main">
364-<span class="lbl">files</span>366+<span class="lbl">files (g f)</span>
365 <span class="k">section</span>367 <span class="k">section</span>
366 </a>368 </a>
367 </li>369 </li>
368-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">370+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
369 <a role="presentation" data-href="/long/commits/main">371 <a role="presentation" data-href="/long/commits/main">
370-<span class="lbl">commits</span>372+<span class="lbl">commits (g c)</span>
371 <span class="k">section</span>373 <span class="k">section</span>
372 </a>374 </a>
373 </li>375 </li>
374-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">376+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
375 <a role="presentation" data-href="/long/refs">377 <a role="presentation" data-href="/long/refs">
376-<span class="lbl">branches &amp; tags</span>378+<span class="lbl">branches &amp; tags (g b)</span>
377 <span class="k">section</span>379 <span class="k">section</span>
378 </a>380 </a>
379 </li>381 </li>
380-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">382+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
381 <a role="presentation" data-href="/long/merge-requests">383 <a role="presentation" data-href="/long/merge-requests">
382-<span class="lbl">merge requests</span>384+<span class="lbl">merge requests (g m)</span>
383 <span class="k">section</span>385 <span class="k">section</span>
384 </a>386 </a>
385 </li>387 </li>
386-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">388+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
387 <a role="presentation" data-href="/long/releases">389 <a role="presentation" data-href="/long/releases">
388-<span class="lbl">releases</span>390+<span class="lbl">releases (g r)</span>
389 <span class="k">section</span>391 <span class="k">section</span>
390 </a>392 </a>
391 </li>393 </li>
392-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">394+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
393 <a role="presentation" data-href="/long/actions">395 <a role="presentation" data-href="/long/actions">
394-<span class="lbl">actions</span>396+<span class="lbl">actions (g a)</span>
395 <span class="k">section</span>397 <span class="k">section</span>
396 </a>398 </a>
397 </li>399 </li>
@@ -438,7 +440,9 @@
438 k-filter from:body -> ermite.focusFilter();440 k-filter from:body -> ermite.focusFilter();
439 k-gh from:body -> ermite.go('.brand');441 k-gh from:body -> ermite.go('.brand');
440 k-s from:body -> ermite.tab('s');442 k-s from:body -> ermite.tab('s');
441- k-t from:body -> ermite.tab('t');443+ k-f from:body -> ermite.tab('f');
444+ k-b from:body -> ermite.tab('b');
445+ k-a from:body -> ermite.tab('a');
442 k-c from:body -> ermite.tab('c');446 k-c from:body -> ermite.tab('c');
443 k-r from:body -> ermite.tab('r');447 k-r from:body -> ermite.tab('r');
444 k-m from:body -> ermite.tab('m');448 k-m from:body -> ermite.tab('m');
@@ -454,7 +458,6 @@
454 k-permalink from:body -> ermite.permalink();458 k-permalink from:body -> ermite.permalink();
455 k-merge from:body -> window.ermiteMR?.openMerge();459 k-merge from:body -> window.ermiteMR?.openMerge();
456 k-read from:body -> window.ermiteMR?.markRead();460 k-read from:body -> window.ermiteMR?.markRead();
457- k-gf from:body -> window.ermiteMR?.focusFiles();
458 k-keys from:body -> ermite.toggleKeys()">461 k-keys from:body -> ermite.toggleKeys()">
459 </div>462 </div>
460 </body>463 </body>
M internal/app/testdata/web/commits-path.html +33 -30
@@ -73,31 +73,31 @@
7373 <nav class="tabs" aria-label="Sections">
7474 <ul>
7575 <li>
76-<a href="/long" data-key="s" aria-keyshortcuts="s">Summary <kbd aria-hidden="true">s</kbd>
76+<a href="/long" data-key="g s">Summary <kbd aria-hidden="true">g s</kbd>
7777 </a>
7878 </li>
7979 <li>
80-<a href="/long/tree/main" data-key="t" aria-keyshortcuts="t">Files <kbd aria-hidden="true">t</kbd>
80+<a href="/long/tree/main" data-key="g f">Files <kbd aria-hidden="true">g f</kbd>
8181 </a>
8282 </li>
8383 <li>
84-<a href="/long/commits/main" data-key="c" aria-keyshortcuts="c" aria-current="page">Commits <kbd aria-hidden="true">c</kbd>
84+<a href="/long/commits/main" data-key="g c" aria-current="page">Commits <kbd aria-hidden="true">g c</kbd>
8585 </a>
8686 </li>
8787 <li>
88-<a href="/long/refs" data-key="r" aria-keyshortcuts="r">Branches &amp; tags <kbd aria-hidden="true">r</kbd>
88+<a href="/long/refs" data-key="g b">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
8989 </a>
9090 </li>
9191 <li>
92-<a href="/long/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>
92+<a href="/long/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
9393 </a>
9494 </li>
9595 <li>
96-<a href="/long/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>
96+<a href="/long/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
9797 </a>
9898 </li>
9999 <li>
100-<a href="/long/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>
100+<a href="/long/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
101101 </a>
102102 </li>
103103 </ul>
@@ -270,14 +270,16 @@
270270 </div>
271271 <div>
272272 <dt>
273-<kbd>s</kbd>
274-<kbd>t</kbd>
275-<kbd>c</kbd>
276-<kbd>r</kbd>
277-<kbd>m</kbd>
278-<kbd>i</kbd>
273+<kbd>g s</kbd>
274+<kbd>g f</kbd>
275+<kbd>g c</kbd>
276+<kbd>g b</kbd>
277+<kbd>g m</kbd>
278+<kbd>g i</kbd>
279+<kbd>g r</kbd>
280+<kbd>g a</kbd>
279281 </dt>
280-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>
282+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
281283 </div>
282284 <div>
283285 <dt>
@@ -368,45 +370,45 @@
368370 <span class="k">go</span>
369371 </a>
370372 </li>
371-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
373+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
372374 <a role="presentation" data-href="/long">
373-<span class="lbl">summary</span>
375+<span class="lbl">summary (g s)</span>
374376 <span class="k">section</span>
375377 </a>
376378 </li>
377-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
379+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
378380 <a role="presentation" data-href="/long/tree/main">
379-<span class="lbl">files</span>
381+<span class="lbl">files (g f)</span>
380382 <span class="k">section</span>
381383 </a>
382384 </li>
383-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
385+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
384386 <a role="presentation" data-href="/long/commits/main">
385-<span class="lbl">commits</span>
387+<span class="lbl">commits (g c)</span>
386388 <span class="k">section</span>
387389 </a>
388390 </li>
389-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
391+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
390392 <a role="presentation" data-href="/long/refs">
391-<span class="lbl">branches &amp; tags</span>
393+<span class="lbl">branches &amp; tags (g b)</span>
392394 <span class="k">section</span>
393395 </a>
394396 </li>
395-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
397+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
396398 <a role="presentation" data-href="/long/merge-requests">
397-<span class="lbl">merge requests</span>
399+<span class="lbl">merge requests (g m)</span>
398400 <span class="k">section</span>
399401 </a>
400402 </li>
401-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
403+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
402404 <a role="presentation" data-href="/long/releases">
403-<span class="lbl">releases</span>
405+<span class="lbl">releases (g r)</span>
404406 <span class="k">section</span>
405407 </a>
406408 </li>
407-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
409+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
408410 <a role="presentation" data-href="/long/actions">
409-<span class="lbl">actions</span>
411+<span class="lbl">actions (g a)</span>
410412 <span class="k">section</span>
411413 </a>
412414 </li>
@@ -453,7 +455,9 @@
453455 k-filter from:body -> ermite.focusFilter();
454456 k-gh from:body -> ermite.go('.brand');
455457 k-s from:body -> ermite.tab('s');
456- k-t from:body -> ermite.tab('t');
458+ k-f from:body -> ermite.tab('f');
459+ k-b from:body -> ermite.tab('b');
460+ k-a from:body -> ermite.tab('a');
457461 k-c from:body -> ermite.tab('c');
458462 k-r from:body -> ermite.tab('r');
459463 k-m from:body -> ermite.tab('m');
@@ -469,7 +473,6 @@
469473 k-permalink from:body -> ermite.permalink();
470474 k-merge from:body -> window.ermiteMR?.openMerge();
471475 k-read from:body -> window.ermiteMR?.markRead();
472- k-gf from:body -> window.ermiteMR?.focusFiles();
473476 k-keys from:body -> ermite.toggleKeys()">
474477 </div>
475478 </body>
@@ -73,31 +73,31 @@
73 <nav class="tabs" aria-label="Sections">73 <nav class="tabs" aria-label="Sections">
74 <ul>74 <ul>
75 <li>75 <li>
76-<a href="/long" data-key="s" aria-keyshortcuts="s">Summary <kbd aria-hidden="true">s</kbd>76+<a href="/long" data-key="g s">Summary <kbd aria-hidden="true">g s</kbd>
77 </a>77 </a>
78 </li>78 </li>
79 <li>79 <li>
80-<a href="/long/tree/main" data-key="t" aria-keyshortcuts="t">Files <kbd aria-hidden="true">t</kbd>80+<a href="/long/tree/main" data-key="g f">Files <kbd aria-hidden="true">g f</kbd>
81 </a>81 </a>
82 </li>82 </li>
83 <li>83 <li>
84-<a href="/long/commits/main" data-key="c" aria-keyshortcuts="c" aria-current="page">Commits <kbd aria-hidden="true">c</kbd>84+<a href="/long/commits/main" data-key="g c" aria-current="page">Commits <kbd aria-hidden="true">g c</kbd>
85 </a>85 </a>
86 </li>86 </li>
87 <li>87 <li>
88-<a href="/long/refs" data-key="r" aria-keyshortcuts="r">Branches &amp; tags <kbd aria-hidden="true">r</kbd>88+<a href="/long/refs" data-key="g b">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
89 </a>89 </a>
90 </li>90 </li>
91 <li>91 <li>
92-<a href="/long/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>92+<a href="/long/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
93 </a>93 </a>
94 </li>94 </li>
95 <li>95 <li>
96-<a href="/long/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>96+<a href="/long/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
97 </a>97 </a>
98 </li>98 </li>
99 <li>99 <li>
100-<a href="/long/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>100+<a href="/long/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
101 </a>101 </a>
102 </li>102 </li>
103 </ul>103 </ul>
@@ -270,14 +270,16 @@
270 </div>270 </div>
271 <div>271 <div>
272 <dt>272 <dt>
273-<kbd>s</kbd>273+<kbd>g s</kbd>
274-<kbd>t</kbd>274+<kbd>g f</kbd>
275-<kbd>c</kbd>275+<kbd>g c</kbd>
276-<kbd>r</kbd>276+<kbd>g b</kbd>
277-<kbd>m</kbd>277+<kbd>g m</kbd>
278-<kbd>i</kbd>278+<kbd>g i</kbd>
279+<kbd>g r</kbd>
280+<kbd>g a</kbd>
279 </dt>281 </dt>
280-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>282+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
281 </div>283 </div>
282 <div>284 <div>
283 <dt>285 <dt>
@@ -368,45 +370,45 @@
368 <span class="k">go</span>370 <span class="k">go</span>
369 </a>371 </a>
370 </li>372 </li>
371-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">373+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
372 <a role="presentation" data-href="/long">374 <a role="presentation" data-href="/long">
373-<span class="lbl">summary</span>375+<span class="lbl">summary (g s)</span>
374 <span class="k">section</span>376 <span class="k">section</span>
375 </a>377 </a>
376 </li>378 </li>
377-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">379+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
378 <a role="presentation" data-href="/long/tree/main">380 <a role="presentation" data-href="/long/tree/main">
379-<span class="lbl">files</span>381+<span class="lbl">files (g f)</span>
380 <span class="k">section</span>382 <span class="k">section</span>
381 </a>383 </a>
382 </li>384 </li>
383-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">385+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
384 <a role="presentation" data-href="/long/commits/main">386 <a role="presentation" data-href="/long/commits/main">
385-<span class="lbl">commits</span>387+<span class="lbl">commits (g c)</span>
386 <span class="k">section</span>388 <span class="k">section</span>
387 </a>389 </a>
388 </li>390 </li>
389-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">391+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
390 <a role="presentation" data-href="/long/refs">392 <a role="presentation" data-href="/long/refs">
391-<span class="lbl">branches &amp; tags</span>393+<span class="lbl">branches &amp; tags (g b)</span>
392 <span class="k">section</span>394 <span class="k">section</span>
393 </a>395 </a>
394 </li>396 </li>
395-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">397+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
396 <a role="presentation" data-href="/long/merge-requests">398 <a role="presentation" data-href="/long/merge-requests">
397-<span class="lbl">merge requests</span>399+<span class="lbl">merge requests (g m)</span>
398 <span class="k">section</span>400 <span class="k">section</span>
399 </a>401 </a>
400 </li>402 </li>
401-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">403+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
402 <a role="presentation" data-href="/long/releases">404 <a role="presentation" data-href="/long/releases">
403-<span class="lbl">releases</span>405+<span class="lbl">releases (g r)</span>
404 <span class="k">section</span>406 <span class="k">section</span>
405 </a>407 </a>
406 </li>408 </li>
407-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">409+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
408 <a role="presentation" data-href="/long/actions">410 <a role="presentation" data-href="/long/actions">
409-<span class="lbl">actions</span>411+<span class="lbl">actions (g a)</span>
410 <span class="k">section</span>412 <span class="k">section</span>
411 </a>413 </a>
412 </li>414 </li>
@@ -453,7 +455,9 @@
453 k-filter from:body -> ermite.focusFilter();455 k-filter from:body -> ermite.focusFilter();
454 k-gh from:body -> ermite.go('.brand');456 k-gh from:body -> ermite.go('.brand');
455 k-s from:body -> ermite.tab('s');457 k-s from:body -> ermite.tab('s');
456- k-t from:body -> ermite.tab('t');458+ k-f from:body -> ermite.tab('f');
459+ k-b from:body -> ermite.tab('b');
460+ k-a from:body -> ermite.tab('a');
457 k-c from:body -> ermite.tab('c');461 k-c from:body -> ermite.tab('c');
458 k-r from:body -> ermite.tab('r');462 k-r from:body -> ermite.tab('r');
459 k-m from:body -> ermite.tab('m');463 k-m from:body -> ermite.tab('m');
@@ -469,7 +473,6 @@
469 k-permalink from:body -> ermite.permalink();473 k-permalink from:body -> ermite.permalink();
470 k-merge from:body -> window.ermiteMR?.openMerge();474 k-merge from:body -> window.ermiteMR?.openMerge();
471 k-read from:body -> window.ermiteMR?.markRead();475 k-read from:body -> window.ermiteMR?.markRead();
472- k-gf from:body -> window.ermiteMR?.focusFiles();
473 k-keys from:body -> ermite.toggleKeys()">476 k-keys from:body -> ermite.toggleKeys()">
474 </div>477 </div>
475 </body>478 </body>
M internal/app/testdata/web/commits-root.html +33 -30
@@ -70,31 +70,31 @@
7070 <nav class="tabs" aria-label="Sections">
7171 <ul>
7272 <li>
73-<a href="/long" data-key="s" aria-keyshortcuts="s">Summary <kbd aria-hidden="true">s</kbd>
73+<a href="/long" data-key="g s">Summary <kbd aria-hidden="true">g s</kbd>
7474 </a>
7575 </li>
7676 <li>
77-<a href="/long/tree/main" data-key="t" aria-keyshortcuts="t">Files <kbd aria-hidden="true">t</kbd>
77+<a href="/long/tree/main" data-key="g f">Files <kbd aria-hidden="true">g f</kbd>
7878 </a>
7979 </li>
8080 <li>
81-<a href="/long/commits/main" data-key="c" aria-keyshortcuts="c" aria-current="page">Commits <kbd aria-hidden="true">c</kbd>
81+<a href="/long/commits/main" data-key="g c" aria-current="page">Commits <kbd aria-hidden="true">g c</kbd>
8282 </a>
8383 </li>
8484 <li>
85-<a href="/long/refs" data-key="r" aria-keyshortcuts="r">Branches &amp; tags <kbd aria-hidden="true">r</kbd>
85+<a href="/long/refs" data-key="g b">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
8686 </a>
8787 </li>
8888 <li>
89-<a href="/long/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>
89+<a href="/long/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
9090 </a>
9191 </li>
9292 <li>
93-<a href="/long/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>
93+<a href="/long/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
9494 </a>
9595 </li>
9696 <li>
97-<a href="/long/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>
97+<a href="/long/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
9898 </a>
9999 </li>
100100 </ul>
@@ -1060,14 +1060,16 @@
10601060 </div>
10611061 <div>
10621062 <dt>
1063-<kbd>s</kbd>
1064-<kbd>t</kbd>
1065-<kbd>c</kbd>
1066-<kbd>r</kbd>
1067-<kbd>m</kbd>
1068-<kbd>i</kbd>
1063+<kbd>g s</kbd>
1064+<kbd>g f</kbd>
1065+<kbd>g c</kbd>
1066+<kbd>g b</kbd>
1067+<kbd>g m</kbd>
1068+<kbd>g i</kbd>
1069+<kbd>g r</kbd>
1070+<kbd>g a</kbd>
10691071 </dt>
1070-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>
1072+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
10711073 </div>
10721074 <div>
10731075 <dt>
@@ -1158,45 +1160,45 @@
11581160 <span class="k">go</span>
11591161 </a>
11601162 </li>
1161-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
1163+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
11621164 <a role="presentation" data-href="/long">
1163-<span class="lbl">summary</span>
1165+<span class="lbl">summary (g s)</span>
11641166 <span class="k">section</span>
11651167 </a>
11661168 </li>
1167-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
1169+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
11681170 <a role="presentation" data-href="/long/tree/main">
1169-<span class="lbl">files</span>
1171+<span class="lbl">files (g f)</span>
11701172 <span class="k">section</span>
11711173 </a>
11721174 </li>
1173-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
1175+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
11741176 <a role="presentation" data-href="/long/commits/main">
1175-<span class="lbl">commits</span>
1177+<span class="lbl">commits (g c)</span>
11761178 <span class="k">section</span>
11771179 </a>
11781180 </li>
1179-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
1181+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
11801182 <a role="presentation" data-href="/long/refs">
1181-<span class="lbl">branches &amp; tags</span>
1183+<span class="lbl">branches &amp; tags (g b)</span>
11821184 <span class="k">section</span>
11831185 </a>
11841186 </li>
1185-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
1187+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
11861188 <a role="presentation" data-href="/long/merge-requests">
1187-<span class="lbl">merge requests</span>
1189+<span class="lbl">merge requests (g m)</span>
11881190 <span class="k">section</span>
11891191 </a>
11901192 </li>
1191-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
1193+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
11921194 <a role="presentation" data-href="/long/releases">
1193-<span class="lbl">releases</span>
1195+<span class="lbl">releases (g r)</span>
11941196 <span class="k">section</span>
11951197 </a>
11961198 </li>
1197-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
1199+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
11981200 <a role="presentation" data-href="/long/actions">
1199-<span class="lbl">actions</span>
1201+<span class="lbl">actions (g a)</span>
12001202 <span class="k">section</span>
12011203 </a>
12021204 </li>
@@ -1243,7 +1245,9 @@
12431245 k-filter from:body -> ermite.focusFilter();
12441246 k-gh from:body -> ermite.go('.brand');
12451247 k-s from:body -> ermite.tab('s');
1246- k-t from:body -> ermite.tab('t');
1248+ k-f from:body -> ermite.tab('f');
1249+ k-b from:body -> ermite.tab('b');
1250+ k-a from:body -> ermite.tab('a');
12471251 k-c from:body -> ermite.tab('c');
12481252 k-r from:body -> ermite.tab('r');
12491253 k-m from:body -> ermite.tab('m');
@@ -1259,7 +1263,6 @@
12591263 k-permalink from:body -> ermite.permalink();
12601264 k-merge from:body -> window.ermiteMR?.openMerge();
12611265 k-read from:body -> window.ermiteMR?.markRead();
1262- k-gf from:body -> window.ermiteMR?.focusFiles();
12631266 k-keys from:body -> ermite.toggleKeys()">
12641267 </div>
12651268 </body>
@@ -70,31 +70,31 @@
70 <nav class="tabs" aria-label="Sections">70 <nav class="tabs" aria-label="Sections">
71 <ul>71 <ul>
72 <li>72 <li>
73-<a href="/long" data-key="s" aria-keyshortcuts="s">Summary <kbd aria-hidden="true">s</kbd>73+<a href="/long" data-key="g s">Summary <kbd aria-hidden="true">g s</kbd>
74 </a>74 </a>
75 </li>75 </li>
76 <li>76 <li>
77-<a href="/long/tree/main" data-key="t" aria-keyshortcuts="t">Files <kbd aria-hidden="true">t</kbd>77+<a href="/long/tree/main" data-key="g f">Files <kbd aria-hidden="true">g f</kbd>
78 </a>78 </a>
79 </li>79 </li>
80 <li>80 <li>
81-<a href="/long/commits/main" data-key="c" aria-keyshortcuts="c" aria-current="page">Commits <kbd aria-hidden="true">c</kbd>81+<a href="/long/commits/main" data-key="g c" aria-current="page">Commits <kbd aria-hidden="true">g c</kbd>
82 </a>82 </a>
83 </li>83 </li>
84 <li>84 <li>
85-<a href="/long/refs" data-key="r" aria-keyshortcuts="r">Branches &amp; tags <kbd aria-hidden="true">r</kbd>85+<a href="/long/refs" data-key="g b">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
86 </a>86 </a>
87 </li>87 </li>
88 <li>88 <li>
89-<a href="/long/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>89+<a href="/long/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
90 </a>90 </a>
91 </li>91 </li>
92 <li>92 <li>
93-<a href="/long/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>93+<a href="/long/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
94 </a>94 </a>
95 </li>95 </li>
96 <li>96 <li>
97-<a href="/long/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>97+<a href="/long/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
98 </a>98 </a>
99 </li>99 </li>
100 </ul>100 </ul>
@@ -1060,14 +1060,16 @@
1060 </div>1060 </div>
1061 <div>1061 <div>
1062 <dt>1062 <dt>
1063-<kbd>s</kbd>1063+<kbd>g s</kbd>
1064-<kbd>t</kbd>1064+<kbd>g f</kbd>
1065-<kbd>c</kbd>1065+<kbd>g c</kbd>
1066-<kbd>r</kbd>1066+<kbd>g b</kbd>
1067-<kbd>m</kbd>1067+<kbd>g m</kbd>
1068-<kbd>i</kbd>1068+<kbd>g i</kbd>
1069+<kbd>g r</kbd>
1070+<kbd>g a</kbd>
1069 </dt>1071 </dt>
1070-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>1072+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
1071 </div>1073 </div>
1072 <div>1074 <div>
1073 <dt>1075 <dt>
@@ -1158,45 +1160,45 @@
1158 <span class="k">go</span>1160 <span class="k">go</span>
1159 </a>1161 </a>
1160 </li>1162 </li>
1161-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">1163+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
1162 <a role="presentation" data-href="/long">1164 <a role="presentation" data-href="/long">
1163-<span class="lbl">summary</span>1165+<span class="lbl">summary (g s)</span>
1164 <span class="k">section</span>1166 <span class="k">section</span>
1165 </a>1167 </a>
1166 </li>1168 </li>
1167-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">1169+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
1168 <a role="presentation" data-href="/long/tree/main">1170 <a role="presentation" data-href="/long/tree/main">
1169-<span class="lbl">files</span>1171+<span class="lbl">files (g f)</span>
1170 <span class="k">section</span>1172 <span class="k">section</span>
1171 </a>1173 </a>
1172 </li>1174 </li>
1173-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">1175+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
1174 <a role="presentation" data-href="/long/commits/main">1176 <a role="presentation" data-href="/long/commits/main">
1175-<span class="lbl">commits</span>1177+<span class="lbl">commits (g c)</span>
1176 <span class="k">section</span>1178 <span class="k">section</span>
1177 </a>1179 </a>
1178 </li>1180 </li>
1179-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">1181+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
1180 <a role="presentation" data-href="/long/refs">1182 <a role="presentation" data-href="/long/refs">
1181-<span class="lbl">branches &amp; tags</span>1183+<span class="lbl">branches &amp; tags (g b)</span>
1182 <span class="k">section</span>1184 <span class="k">section</span>
1183 </a>1185 </a>
1184 </li>1186 </li>
1185-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">1187+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
1186 <a role="presentation" data-href="/long/merge-requests">1188 <a role="presentation" data-href="/long/merge-requests">
1187-<span class="lbl">merge requests</span>1189+<span class="lbl">merge requests (g m)</span>
1188 <span class="k">section</span>1190 <span class="k">section</span>
1189 </a>1191 </a>
1190 </li>1192 </li>
1191-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">1193+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
1192 <a role="presentation" data-href="/long/releases">1194 <a role="presentation" data-href="/long/releases">
1193-<span class="lbl">releases</span>1195+<span class="lbl">releases (g r)</span>
1194 <span class="k">section</span>1196 <span class="k">section</span>
1195 </a>1197 </a>
1196 </li>1198 </li>
1197-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">1199+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
1198 <a role="presentation" data-href="/long/actions">1200 <a role="presentation" data-href="/long/actions">
1199-<span class="lbl">actions</span>1201+<span class="lbl">actions (g a)</span>
1200 <span class="k">section</span>1202 <span class="k">section</span>
1201 </a>1203 </a>
1202 </li>1204 </li>
@@ -1243,7 +1245,9 @@
1243 k-filter from:body -> ermite.focusFilter();1245 k-filter from:body -> ermite.focusFilter();
1244 k-gh from:body -> ermite.go('.brand');1246 k-gh from:body -> ermite.go('.brand');
1245 k-s from:body -> ermite.tab('s');1247 k-s from:body -> ermite.tab('s');
1246- k-t from:body -> ermite.tab('t');1248+ k-f from:body -> ermite.tab('f');
1249+ k-b from:body -> ermite.tab('b');
1250+ k-a from:body -> ermite.tab('a');
1247 k-c from:body -> ermite.tab('c');1251 k-c from:body -> ermite.tab('c');
1248 k-r from:body -> ermite.tab('r');1252 k-r from:body -> ermite.tab('r');
1249 k-m from:body -> ermite.tab('m');1253 k-m from:body -> ermite.tab('m');
@@ -1259,7 +1263,6 @@
1259 k-permalink from:body -> ermite.permalink();1263 k-permalink from:body -> ermite.permalink();
1260 k-merge from:body -> window.ermiteMR?.openMerge();1264 k-merge from:body -> window.ermiteMR?.openMerge();
1261 k-read from:body -> window.ermiteMR?.markRead();1265 k-read from:body -> window.ermiteMR?.markRead();
1262- k-gf from:body -> window.ermiteMR?.focusFiles();
1263 k-keys from:body -> ermite.toggleKeys()">1266 k-keys from:body -> ermite.toggleKeys()">
1264 </div>1267 </div>
1265 </body>1268 </body>
M internal/app/testdata/web/commits-small.html +33 -30
@@ -70,31 +70,31 @@
7070 <nav class="tabs" aria-label="Sections">
7171 <ul>
7272 <li>
73-<a href="/diffs" data-key="s" aria-keyshortcuts="s">Summary <kbd aria-hidden="true">s</kbd>
73+<a href="/diffs" data-key="g s">Summary <kbd aria-hidden="true">g s</kbd>
7474 </a>
7575 </li>
7676 <li>
77-<a href="/diffs/tree/main" data-key="t" aria-keyshortcuts="t">Files <kbd aria-hidden="true">t</kbd>
77+<a href="/diffs/tree/main" data-key="g f">Files <kbd aria-hidden="true">g f</kbd>
7878 </a>
7979 </li>
8080 <li>
81-<a href="/diffs/commits/main" data-key="c" aria-keyshortcuts="c" aria-current="page">Commits <kbd aria-hidden="true">c</kbd>
81+<a href="/diffs/commits/main" data-key="g c" aria-current="page">Commits <kbd aria-hidden="true">g c</kbd>
8282 </a>
8383 </li>
8484 <li>
85-<a href="/diffs/refs" data-key="r" aria-keyshortcuts="r">Branches &amp; tags <kbd aria-hidden="true">r</kbd>
85+<a href="/diffs/refs" data-key="g b">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
8686 </a>
8787 </li>
8888 <li>
89-<a href="/diffs/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>
89+<a href="/diffs/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
9090 </a>
9191 </li>
9292 <li>
93-<a href="/diffs/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>
93+<a href="/diffs/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
9494 </a>
9595 </li>
9696 <li>
97-<a href="/diffs/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>
97+<a href="/diffs/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
9898 </a>
9999 </li>
100100 </ul>
@@ -405,14 +405,16 @@
405405 </div>
406406 <div>
407407 <dt>
408-<kbd>s</kbd>
409-<kbd>t</kbd>
410-<kbd>c</kbd>
411-<kbd>r</kbd>
412-<kbd>m</kbd>
413-<kbd>i</kbd>
408+<kbd>g s</kbd>
409+<kbd>g f</kbd>
410+<kbd>g c</kbd>
411+<kbd>g b</kbd>
412+<kbd>g m</kbd>
413+<kbd>g i</kbd>
414+<kbd>g r</kbd>
415+<kbd>g a</kbd>
414416 </dt>
415-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>
417+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
416418 </div>
417419 <div>
418420 <dt>
@@ -511,45 +513,45 @@
511513 <span class="k">go</span>
512514 </a>
513515 </li>
514-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
516+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
515517 <a role="presentation" data-href="/diffs">
516-<span class="lbl">summary</span>
518+<span class="lbl">summary (g s)</span>
517519 <span class="k">section</span>
518520 </a>
519521 </li>
520-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
522+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
521523 <a role="presentation" data-href="/diffs/tree/main">
522-<span class="lbl">files</span>
524+<span class="lbl">files (g f)</span>
523525 <span class="k">section</span>
524526 </a>
525527 </li>
526-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
528+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
527529 <a role="presentation" data-href="/diffs/commits/main">
528-<span class="lbl">commits</span>
530+<span class="lbl">commits (g c)</span>
529531 <span class="k">section</span>
530532 </a>
531533 </li>
532-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
534+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
533535 <a role="presentation" data-href="/diffs/refs">
534-<span class="lbl">branches &amp; tags</span>
536+<span class="lbl">branches &amp; tags (g b)</span>
535537 <span class="k">section</span>
536538 </a>
537539 </li>
538-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
540+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
539541 <a role="presentation" data-href="/diffs/merge-requests">
540-<span class="lbl">merge requests</span>
542+<span class="lbl">merge requests (g m)</span>
541543 <span class="k">section</span>
542544 </a>
543545 </li>
544-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
546+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
545547 <a role="presentation" data-href="/diffs/releases">
546-<span class="lbl">releases</span>
548+<span class="lbl">releases (g r)</span>
547549 <span class="k">section</span>
548550 </a>
549551 </li>
550-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
552+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
551553 <a role="presentation" data-href="/diffs/actions">
552-<span class="lbl">actions</span>
554+<span class="lbl">actions (g a)</span>
553555 <span class="k">section</span>
554556 </a>
555557 </li>
@@ -608,7 +610,9 @@
608610 k-filter from:body -> ermite.focusFilter();
609611 k-gh from:body -> ermite.go('.brand');
610612 k-s from:body -> ermite.tab('s');
611- k-t from:body -> ermite.tab('t');
613+ k-f from:body -> ermite.tab('f');
614+ k-b from:body -> ermite.tab('b');
615+ k-a from:body -> ermite.tab('a');
612616 k-c from:body -> ermite.tab('c');
613617 k-r from:body -> ermite.tab('r');
614618 k-m from:body -> ermite.tab('m');
@@ -624,7 +628,6 @@
624628 k-permalink from:body -> ermite.permalink();
625629 k-merge from:body -> window.ermiteMR?.openMerge();
626630 k-read from:body -> window.ermiteMR?.markRead();
627- k-gf from:body -> window.ermiteMR?.focusFiles();
628631 k-keys from:body -> ermite.toggleKeys()">
629632 </div>
630633 </body>
@@ -70,31 +70,31 @@
70 <nav class="tabs" aria-label="Sections">70 <nav class="tabs" aria-label="Sections">
71 <ul>71 <ul>
72 <li>72 <li>
73-<a href="/diffs" data-key="s" aria-keyshortcuts="s">Summary <kbd aria-hidden="true">s</kbd>73+<a href="/diffs" data-key="g s">Summary <kbd aria-hidden="true">g s</kbd>
74 </a>74 </a>
75 </li>75 </li>
76 <li>76 <li>
77-<a href="/diffs/tree/main" data-key="t" aria-keyshortcuts="t">Files <kbd aria-hidden="true">t</kbd>77+<a href="/diffs/tree/main" data-key="g f">Files <kbd aria-hidden="true">g f</kbd>
78 </a>78 </a>
79 </li>79 </li>
80 <li>80 <li>
81-<a href="/diffs/commits/main" data-key="c" aria-keyshortcuts="c" aria-current="page">Commits <kbd aria-hidden="true">c</kbd>81+<a href="/diffs/commits/main" data-key="g c" aria-current="page">Commits <kbd aria-hidden="true">g c</kbd>
82 </a>82 </a>
83 </li>83 </li>
84 <li>84 <li>
85-<a href="/diffs/refs" data-key="r" aria-keyshortcuts="r">Branches &amp; tags <kbd aria-hidden="true">r</kbd>85+<a href="/diffs/refs" data-key="g b">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
86 </a>86 </a>
87 </li>87 </li>
88 <li>88 <li>
89-<a href="/diffs/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>89+<a href="/diffs/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
90 </a>90 </a>
91 </li>91 </li>
92 <li>92 <li>
93-<a href="/diffs/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>93+<a href="/diffs/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
94 </a>94 </a>
95 </li>95 </li>
96 <li>96 <li>
97-<a href="/diffs/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>97+<a href="/diffs/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
98 </a>98 </a>
99 </li>99 </li>
100 </ul>100 </ul>
@@ -405,14 +405,16 @@
405 </div>405 </div>
406 <div>406 <div>
407 <dt>407 <dt>
408-<kbd>s</kbd>408+<kbd>g s</kbd>
409-<kbd>t</kbd>409+<kbd>g f</kbd>
410-<kbd>c</kbd>410+<kbd>g c</kbd>
411-<kbd>r</kbd>411+<kbd>g b</kbd>
412-<kbd>m</kbd>412+<kbd>g m</kbd>
413-<kbd>i</kbd>413+<kbd>g i</kbd>
414+<kbd>g r</kbd>
415+<kbd>g a</kbd>
414 </dt>416 </dt>
415-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>417+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
416 </div>418 </div>
417 <div>419 <div>
418 <dt>420 <dt>
@@ -511,45 +513,45 @@
511 <span class="k">go</span>513 <span class="k">go</span>
512 </a>514 </a>
513 </li>515 </li>
514-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">516+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
515 <a role="presentation" data-href="/diffs">517 <a role="presentation" data-href="/diffs">
516-<span class="lbl">summary</span>518+<span class="lbl">summary (g s)</span>
517 <span class="k">section</span>519 <span class="k">section</span>
518 </a>520 </a>
519 </li>521 </li>
520-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">522+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
521 <a role="presentation" data-href="/diffs/tree/main">523 <a role="presentation" data-href="/diffs/tree/main">
522-<span class="lbl">files</span>524+<span class="lbl">files (g f)</span>
523 <span class="k">section</span>525 <span class="k">section</span>
524 </a>526 </a>
525 </li>527 </li>
526-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">528+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
527 <a role="presentation" data-href="/diffs/commits/main">529 <a role="presentation" data-href="/diffs/commits/main">
528-<span class="lbl">commits</span>530+<span class="lbl">commits (g c)</span>
529 <span class="k">section</span>531 <span class="k">section</span>
530 </a>532 </a>
531 </li>533 </li>
532-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">534+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
533 <a role="presentation" data-href="/diffs/refs">535 <a role="presentation" data-href="/diffs/refs">
534-<span class="lbl">branches &amp; tags</span>536+<span class="lbl">branches &amp; tags (g b)</span>
535 <span class="k">section</span>537 <span class="k">section</span>
536 </a>538 </a>
537 </li>539 </li>
538-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">540+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
539 <a role="presentation" data-href="/diffs/merge-requests">541 <a role="presentation" data-href="/diffs/merge-requests">
540-<span class="lbl">merge requests</span>542+<span class="lbl">merge requests (g m)</span>
541 <span class="k">section</span>543 <span class="k">section</span>
542 </a>544 </a>
543 </li>545 </li>
544-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">546+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
545 <a role="presentation" data-href="/diffs/releases">547 <a role="presentation" data-href="/diffs/releases">
546-<span class="lbl">releases</span>548+<span class="lbl">releases (g r)</span>
547 <span class="k">section</span>549 <span class="k">section</span>
548 </a>550 </a>
549 </li>551 </li>
550-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">552+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
551 <a role="presentation" data-href="/diffs/actions">553 <a role="presentation" data-href="/diffs/actions">
552-<span class="lbl">actions</span>554+<span class="lbl">actions (g a)</span>
553 <span class="k">section</span>555 <span class="k">section</span>
554 </a>556 </a>
555 </li>557 </li>
@@ -608,7 +610,9 @@
608 k-filter from:body -> ermite.focusFilter();610 k-filter from:body -> ermite.focusFilter();
609 k-gh from:body -> ermite.go('.brand');611 k-gh from:body -> ermite.go('.brand');
610 k-s from:body -> ermite.tab('s');612 k-s from:body -> ermite.tab('s');
611- k-t from:body -> ermite.tab('t');613+ k-f from:body -> ermite.tab('f');
614+ k-b from:body -> ermite.tab('b');
615+ k-a from:body -> ermite.tab('a');
612 k-c from:body -> ermite.tab('c');616 k-c from:body -> ermite.tab('c');
613 k-r from:body -> ermite.tab('r');617 k-r from:body -> ermite.tab('r');
614 k-m from:body -> ermite.tab('m');618 k-m from:body -> ermite.tab('m');
@@ -624,7 +628,6 @@
624 k-permalink from:body -> ermite.permalink();628 k-permalink from:body -> ermite.permalink();
625 k-merge from:body -> window.ermiteMR?.openMerge();629 k-merge from:body -> window.ermiteMR?.openMerge();
626 k-read from:body -> window.ermiteMR?.markRead();630 k-read from:body -> window.ermiteMR?.markRead();
627- k-gf from:body -> window.ermiteMR?.focusFiles();
628 k-keys from:body -> ermite.toggleKeys()">631 k-keys from:body -> ermite.toggleKeys()">
629 </div>632 </div>
630 </body>633 </body>
M internal/app/testdata/web/empty.html +12 -9
@@ -173,14 +173,16 @@
173173 </div>
174174 <div>
175175 <dt>
176-<kbd>s</kbd>
177-<kbd>t</kbd>
178-<kbd>c</kbd>
179-<kbd>r</kbd>
180-<kbd>m</kbd>
181-<kbd>i</kbd>
176+<kbd>g s</kbd>
177+<kbd>g f</kbd>
178+<kbd>g c</kbd>
179+<kbd>g b</kbd>
180+<kbd>g m</kbd>
181+<kbd>g i</kbd>
182+<kbd>g r</kbd>
183+<kbd>g a</kbd>
182184 </dt>
183-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>
185+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
184186 </div>
185187 <div>
186188 <dt>
@@ -281,7 +283,9 @@
281283 k-filter from:body -> ermite.focusFilter();
282284 k-gh from:body -> ermite.go('.brand');
283285 k-s from:body -> ermite.tab('s');
284- k-t from:body -> ermite.tab('t');
286+ k-f from:body -> ermite.tab('f');
287+ k-b from:body -> ermite.tab('b');
288+ k-a from:body -> ermite.tab('a');
285289 k-c from:body -> ermite.tab('c');
286290 k-r from:body -> ermite.tab('r');
287291 k-m from:body -> ermite.tab('m');
@@ -297,7 +301,6 @@
297301 k-permalink from:body -> ermite.permalink();
298302 k-merge from:body -> window.ermiteMR?.openMerge();
299303 k-read from:body -> window.ermiteMR?.markRead();
300- k-gf from:body -> window.ermiteMR?.focusFiles();
301304 k-keys from:body -> ermite.toggleKeys()">
302305 </div>
303306 </body>
@@ -173,14 +173,16 @@
173 </div>173 </div>
174 <div>174 <div>
175 <dt>175 <dt>
176-<kbd>s</kbd>176+<kbd>g s</kbd>
177-<kbd>t</kbd>177+<kbd>g f</kbd>
178-<kbd>c</kbd>178+<kbd>g c</kbd>
179-<kbd>r</kbd>179+<kbd>g b</kbd>
180-<kbd>m</kbd>180+<kbd>g m</kbd>
181-<kbd>i</kbd>181+<kbd>g i</kbd>
182+<kbd>g r</kbd>
183+<kbd>g a</kbd>
182 </dt>184 </dt>
183-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>185+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
184 </div>186 </div>
185 <div>187 <div>
186 <dt>188 <dt>
@@ -281,7 +283,9 @@
281 k-filter from:body -> ermite.focusFilter();283 k-filter from:body -> ermite.focusFilter();
282 k-gh from:body -> ermite.go('.brand');284 k-gh from:body -> ermite.go('.brand');
283 k-s from:body -> ermite.tab('s');285 k-s from:body -> ermite.tab('s');
284- k-t from:body -> ermite.tab('t');286+ k-f from:body -> ermite.tab('f');
287+ k-b from:body -> ermite.tab('b');
288+ k-a from:body -> ermite.tab('a');
285 k-c from:body -> ermite.tab('c');289 k-c from:body -> ermite.tab('c');
286 k-r from:body -> ermite.tab('r');290 k-r from:body -> ermite.tab('r');
287 k-m from:body -> ermite.tab('m');291 k-m from:body -> ermite.tab('m');
@@ -297,7 +301,6 @@
297 k-permalink from:body -> ermite.permalink();301 k-permalink from:body -> ermite.permalink();
298 k-merge from:body -> window.ermiteMR?.openMerge();302 k-merge from:body -> window.ermiteMR?.openMerge();
299 k-read from:body -> window.ermiteMR?.markRead();303 k-read from:body -> window.ermiteMR?.markRead();
300- k-gf from:body -> window.ermiteMR?.focusFiles();
301 k-keys from:body -> ermite.toggleKeys()">304 k-keys from:body -> ermite.toggleKeys()">
302 </div>305 </div>
303 </body>306 </body>
M internal/app/testdata/web/home.html +12 -9
@@ -277,14 +277,16 @@
277277 </div>
278278 <div>
279279 <dt>
280-<kbd>s</kbd>
281-<kbd>t</kbd>
282-<kbd>c</kbd>
283-<kbd>r</kbd>
284-<kbd>m</kbd>
285-<kbd>i</kbd>
280+<kbd>g s</kbd>
281+<kbd>g f</kbd>
282+<kbd>g c</kbd>
283+<kbd>g b</kbd>
284+<kbd>g m</kbd>
285+<kbd>g i</kbd>
286+<kbd>g r</kbd>
287+<kbd>g a</kbd>
286288 </dt>
287-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>
289+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
288290 </div>
289291 <div>
290292 <dt>
@@ -391,7 +393,9 @@
391393 k-filter from:body -> ermite.focusFilter();
392394 k-gh from:body -> ermite.go('.brand');
393395 k-s from:body -> ermite.tab('s');
394- k-t from:body -> ermite.tab('t');
396+ k-f from:body -> ermite.tab('f');
397+ k-b from:body -> ermite.tab('b');
398+ k-a from:body -> ermite.tab('a');
395399 k-c from:body -> ermite.tab('c');
396400 k-r from:body -> ermite.tab('r');
397401 k-m from:body -> ermite.tab('m');
@@ -407,7 +411,6 @@
407411 k-permalink from:body -> ermite.permalink();
408412 k-merge from:body -> window.ermiteMR?.openMerge();
409413 k-read from:body -> window.ermiteMR?.markRead();
410- k-gf from:body -> window.ermiteMR?.focusFiles();
411414 k-keys from:body -> ermite.toggleKeys()">
412415 </div>
413416 </body>
@@ -277,14 +277,16 @@
277 </div>277 </div>
278 <div>278 <div>
279 <dt>279 <dt>
280-<kbd>s</kbd>280+<kbd>g s</kbd>
281-<kbd>t</kbd>281+<kbd>g f</kbd>
282-<kbd>c</kbd>282+<kbd>g c</kbd>
283-<kbd>r</kbd>283+<kbd>g b</kbd>
284-<kbd>m</kbd>284+<kbd>g m</kbd>
285-<kbd>i</kbd>285+<kbd>g i</kbd>
286+<kbd>g r</kbd>
287+<kbd>g a</kbd>
286 </dt>288 </dt>
287-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>289+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
288 </div>290 </div>
289 <div>291 <div>
290 <dt>292 <dt>
@@ -391,7 +393,9 @@
391 k-filter from:body -> ermite.focusFilter();393 k-filter from:body -> ermite.focusFilter();
392 k-gh from:body -> ermite.go('.brand');394 k-gh from:body -> ermite.go('.brand');
393 k-s from:body -> ermite.tab('s');395 k-s from:body -> ermite.tab('s');
394- k-t from:body -> ermite.tab('t');396+ k-f from:body -> ermite.tab('f');
397+ k-b from:body -> ermite.tab('b');
398+ k-a from:body -> ermite.tab('a');
395 k-c from:body -> ermite.tab('c');399 k-c from:body -> ermite.tab('c');
396 k-r from:body -> ermite.tab('r');400 k-r from:body -> ermite.tab('r');
397 k-m from:body -> ermite.tab('m');401 k-m from:body -> ermite.tab('m');
@@ -407,7 +411,6 @@
407 k-permalink from:body -> ermite.permalink();411 k-permalink from:body -> ermite.permalink();
408 k-merge from:body -> window.ermiteMR?.openMerge();412 k-merge from:body -> window.ermiteMR?.openMerge();
409 k-read from:body -> window.ermiteMR?.markRead();413 k-read from:body -> window.ermiteMR?.markRead();
410- k-gf from:body -> window.ermiteMR?.focusFiles();
411 k-keys from:body -> ermite.toggleKeys()">414 k-keys from:body -> ermite.toggleKeys()">
412 </div>415 </div>
413 </body>416 </body>
M internal/app/testdata/web/not-found-page.html +12 -9
@@ -162,14 +162,16 @@
162162 </div>
163163 <div>
164164 <dt>
165-<kbd>s</kbd>
166-<kbd>t</kbd>
167-<kbd>c</kbd>
168-<kbd>r</kbd>
169-<kbd>m</kbd>
170-<kbd>i</kbd>
165+<kbd>g s</kbd>
166+<kbd>g f</kbd>
167+<kbd>g c</kbd>
168+<kbd>g b</kbd>
169+<kbd>g m</kbd>
170+<kbd>g i</kbd>
171+<kbd>g r</kbd>
172+<kbd>g a</kbd>
171173 </dt>
172-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>
174+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
173175 </div>
174176 <div>
175177 <dt>
@@ -264,7 +266,9 @@
264266 k-filter from:body -> ermite.focusFilter();
265267 k-gh from:body -> ermite.go('.brand');
266268 k-s from:body -> ermite.tab('s');
267- k-t from:body -> ermite.tab('t');
269+ k-f from:body -> ermite.tab('f');
270+ k-b from:body -> ermite.tab('b');
271+ k-a from:body -> ermite.tab('a');
268272 k-c from:body -> ermite.tab('c');
269273 k-r from:body -> ermite.tab('r');
270274 k-m from:body -> ermite.tab('m');
@@ -280,7 +284,6 @@
280284 k-permalink from:body -> ermite.permalink();
281285 k-merge from:body -> window.ermiteMR?.openMerge();
282286 k-read from:body -> window.ermiteMR?.markRead();
283- k-gf from:body -> window.ermiteMR?.focusFiles();
284287 k-keys from:body -> ermite.toggleKeys()">
285288 </div>
286289 </body>
@@ -162,14 +162,16 @@
162 </div>162 </div>
163 <div>163 <div>
164 <dt>164 <dt>
165-<kbd>s</kbd>165+<kbd>g s</kbd>
166-<kbd>t</kbd>166+<kbd>g f</kbd>
167-<kbd>c</kbd>167+<kbd>g c</kbd>
168-<kbd>r</kbd>168+<kbd>g b</kbd>
169-<kbd>m</kbd>169+<kbd>g m</kbd>
170-<kbd>i</kbd>170+<kbd>g i</kbd>
171+<kbd>g r</kbd>
172+<kbd>g a</kbd>
171 </dt>173 </dt>
172-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>174+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
173 </div>175 </div>
174 <div>176 <div>
175 <dt>177 <dt>
@@ -264,7 +266,9 @@
264 k-filter from:body -> ermite.focusFilter();266 k-filter from:body -> ermite.focusFilter();
265 k-gh from:body -> ermite.go('.brand');267 k-gh from:body -> ermite.go('.brand');
266 k-s from:body -> ermite.tab('s');268 k-s from:body -> ermite.tab('s');
267- k-t from:body -> ermite.tab('t');269+ k-f from:body -> ermite.tab('f');
270+ k-b from:body -> ermite.tab('b');
271+ k-a from:body -> ermite.tab('a');
268 k-c from:body -> ermite.tab('c');272 k-c from:body -> ermite.tab('c');
269 k-r from:body -> ermite.tab('r');273 k-r from:body -> ermite.tab('r');
270 k-m from:body -> ermite.tab('m');274 k-m from:body -> ermite.tab('m');
@@ -280,7 +284,6 @@
280 k-permalink from:body -> ermite.permalink();284 k-permalink from:body -> ermite.permalink();
281 k-merge from:body -> window.ermiteMR?.openMerge();285 k-merge from:body -> window.ermiteMR?.openMerge();
282 k-read from:body -> window.ermiteMR?.markRead();286 k-read from:body -> window.ermiteMR?.markRead();
283- k-gf from:body -> window.ermiteMR?.focusFiles();
284 k-keys from:body -> ermite.toggleKeys()">287 k-keys from:body -> ermite.toggleKeys()">
285 </div>288 </div>
286 </body>289 </body>
M internal/app/testdata/web/not-found.html +12 -9
@@ -162,14 +162,16 @@
162162 </div>
163163 <div>
164164 <dt>
165-<kbd>s</kbd>
166-<kbd>t</kbd>
167-<kbd>c</kbd>
168-<kbd>r</kbd>
169-<kbd>m</kbd>
170-<kbd>i</kbd>
165+<kbd>g s</kbd>
166+<kbd>g f</kbd>
167+<kbd>g c</kbd>
168+<kbd>g b</kbd>
169+<kbd>g m</kbd>
170+<kbd>g i</kbd>
171+<kbd>g r</kbd>
172+<kbd>g a</kbd>
171173 </dt>
172-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>
174+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
173175 </div>
174176 <div>
175177 <dt>
@@ -264,7 +266,9 @@
264266 k-filter from:body -> ermite.focusFilter();
265267 k-gh from:body -> ermite.go('.brand');
266268 k-s from:body -> ermite.tab('s');
267- k-t from:body -> ermite.tab('t');
269+ k-f from:body -> ermite.tab('f');
270+ k-b from:body -> ermite.tab('b');
271+ k-a from:body -> ermite.tab('a');
268272 k-c from:body -> ermite.tab('c');
269273 k-r from:body -> ermite.tab('r');
270274 k-m from:body -> ermite.tab('m');
@@ -280,7 +284,6 @@
280284 k-permalink from:body -> ermite.permalink();
281285 k-merge from:body -> window.ermiteMR?.openMerge();
282286 k-read from:body -> window.ermiteMR?.markRead();
283- k-gf from:body -> window.ermiteMR?.focusFiles();
284287 k-keys from:body -> ermite.toggleKeys()">
285288 </div>
286289 </body>
@@ -162,14 +162,16 @@
162 </div>162 </div>
163 <div>163 <div>
164 <dt>164 <dt>
165-<kbd>s</kbd>165+<kbd>g s</kbd>
166-<kbd>t</kbd>166+<kbd>g f</kbd>
167-<kbd>c</kbd>167+<kbd>g c</kbd>
168-<kbd>r</kbd>168+<kbd>g b</kbd>
169-<kbd>m</kbd>169+<kbd>g m</kbd>
170-<kbd>i</kbd>170+<kbd>g i</kbd>
171+<kbd>g r</kbd>
172+<kbd>g a</kbd>
171 </dt>173 </dt>
172-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>174+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
173 </div>175 </div>
174 <div>176 <div>
175 <dt>177 <dt>
@@ -264,7 +266,9 @@
264 k-filter from:body -> ermite.focusFilter();266 k-filter from:body -> ermite.focusFilter();
265 k-gh from:body -> ermite.go('.brand');267 k-gh from:body -> ermite.go('.brand');
266 k-s from:body -> ermite.tab('s');268 k-s from:body -> ermite.tab('s');
267- k-t from:body -> ermite.tab('t');269+ k-f from:body -> ermite.tab('f');
270+ k-b from:body -> ermite.tab('b');
271+ k-a from:body -> ermite.tab('a');
268 k-c from:body -> ermite.tab('c');272 k-c from:body -> ermite.tab('c');
269 k-r from:body -> ermite.tab('r');273 k-r from:body -> ermite.tab('r');
270 k-m from:body -> ermite.tab('m');274 k-m from:body -> ermite.tab('m');
@@ -280,7 +284,6 @@
280 k-permalink from:body -> ermite.permalink();284 k-permalink from:body -> ermite.permalink();
281 k-merge from:body -> window.ermiteMR?.openMerge();285 k-merge from:body -> window.ermiteMR?.openMerge();
282 k-read from:body -> window.ermiteMR?.markRead();286 k-read from:body -> window.ermiteMR?.markRead();
283- k-gf from:body -> window.ermiteMR?.focusFiles();
284 k-keys from:body -> ermite.toggleKeys()">287 k-keys from:body -> ermite.toggleKeys()">
285 </div>288 </div>
286 </body>289 </body>
M internal/app/testdata/web/refs.html +33 -30
@@ -67,31 +67,31 @@
6767 <nav class="tabs" aria-label="Sections">
6868 <ul>
6969 <li>
70-<a href="/demo" data-key="s" aria-keyshortcuts="s">Summary <kbd aria-hidden="true">s</kbd>
70+<a href="/demo" data-key="g s">Summary <kbd aria-hidden="true">g s</kbd>
7171 </a>
7272 </li>
7373 <li>
74-<a href="/demo/tree/main" data-key="t" aria-keyshortcuts="t">Files <kbd aria-hidden="true">t</kbd>
74+<a href="/demo/tree/main" data-key="g f">Files <kbd aria-hidden="true">g f</kbd>
7575 </a>
7676 </li>
7777 <li>
78-<a href="/demo/commits/main" data-key="c" aria-keyshortcuts="c">Commits <kbd aria-hidden="true">c</kbd>
78+<a href="/demo/commits/main" data-key="g c">Commits <kbd aria-hidden="true">g c</kbd>
7979 </a>
8080 </li>
8181 <li>
82-<a href="/demo/refs" data-key="r" aria-keyshortcuts="r" aria-current="page">Branches &amp; tags <kbd aria-hidden="true">r</kbd>
82+<a href="/demo/refs" data-key="g b" aria-current="page">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
8383 </a>
8484 </li>
8585 <li>
86-<a href="/demo/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>
86+<a href="/demo/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
8787 </a>
8888 </li>
8989 <li>
90-<a href="/demo/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>
90+<a href="/demo/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
9191 </a>
9292 </li>
9393 <li>
94-<a href="/demo/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>
94+<a href="/demo/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
9595 </a>
9696 </li>
9797 </ul>
@@ -287,14 +287,16 @@
287287 </div>
288288 <div>
289289 <dt>
290-<kbd>s</kbd>
291-<kbd>t</kbd>
292-<kbd>c</kbd>
293-<kbd>r</kbd>
294-<kbd>m</kbd>
295-<kbd>i</kbd>
290+<kbd>g s</kbd>
291+<kbd>g f</kbd>
292+<kbd>g c</kbd>
293+<kbd>g b</kbd>
294+<kbd>g m</kbd>
295+<kbd>g i</kbd>
296+<kbd>g r</kbd>
297+<kbd>g a</kbd>
296298 </dt>
297-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>
299+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
298300 </div>
299301 <div>
300302 <dt>
@@ -397,45 +399,45 @@
397399 <span class="k">go</span>
398400 </a>
399401 </li>
400-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
402+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
401403 <a role="presentation" data-href="/demo">
402-<span class="lbl">summary</span>
404+<span class="lbl">summary (g s)</span>
403405 <span class="k">section</span>
404406 </a>
405407 </li>
406-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
408+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
407409 <a role="presentation" data-href="/demo/tree/main">
408-<span class="lbl">files</span>
410+<span class="lbl">files (g f)</span>
409411 <span class="k">section</span>
410412 </a>
411413 </li>
412-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
414+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
413415 <a role="presentation" data-href="/demo/commits/main">
414-<span class="lbl">commits</span>
416+<span class="lbl">commits (g c)</span>
415417 <span class="k">section</span>
416418 </a>
417419 </li>
418-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
420+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
419421 <a role="presentation" data-href="/demo/refs">
420-<span class="lbl">branches &amp; tags</span>
422+<span class="lbl">branches &amp; tags (g b)</span>
421423 <span class="k">section</span>
422424 </a>
423425 </li>
424-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
426+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
425427 <a role="presentation" data-href="/demo/merge-requests">
426-<span class="lbl">merge requests</span>
428+<span class="lbl">merge requests (g m)</span>
427429 <span class="k">section</span>
428430 </a>
429431 </li>
430-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
432+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
431433 <a role="presentation" data-href="/demo/releases">
432-<span class="lbl">releases</span>
434+<span class="lbl">releases (g r)</span>
433435 <span class="k">section</span>
434436 </a>
435437 </li>
436-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
438+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
437439 <a role="presentation" data-href="/demo/actions">
438-<span class="lbl">actions</span>
440+<span class="lbl">actions (g a)</span>
439441 <span class="k">section</span>
440442 </a>
441443 </li>
@@ -500,7 +502,9 @@
500502 k-filter from:body -> ermite.focusFilter();
501503 k-gh from:body -> ermite.go('.brand');
502504 k-s from:body -> ermite.tab('s');
503- k-t from:body -> ermite.tab('t');
505+ k-f from:body -> ermite.tab('f');
506+ k-b from:body -> ermite.tab('b');
507+ k-a from:body -> ermite.tab('a');
504508 k-c from:body -> ermite.tab('c');
505509 k-r from:body -> ermite.tab('r');
506510 k-m from:body -> ermite.tab('m');
@@ -516,7 +520,6 @@
516520 k-permalink from:body -> ermite.permalink();
517521 k-merge from:body -> window.ermiteMR?.openMerge();
518522 k-read from:body -> window.ermiteMR?.markRead();
519- k-gf from:body -> window.ermiteMR?.focusFiles();
520523 k-keys from:body -> ermite.toggleKeys()">
521524 </div>
522525 </body>
@@ -67,31 +67,31 @@
67 <nav class="tabs" aria-label="Sections">67 <nav class="tabs" aria-label="Sections">
68 <ul>68 <ul>
69 <li>69 <li>
70-<a href="/demo" data-key="s" aria-keyshortcuts="s">Summary <kbd aria-hidden="true">s</kbd>70+<a href="/demo" data-key="g s">Summary <kbd aria-hidden="true">g s</kbd>
71 </a>71 </a>
72 </li>72 </li>
73 <li>73 <li>
74-<a href="/demo/tree/main" data-key="t" aria-keyshortcuts="t">Files <kbd aria-hidden="true">t</kbd>74+<a href="/demo/tree/main" data-key="g f">Files <kbd aria-hidden="true">g f</kbd>
75 </a>75 </a>
76 </li>76 </li>
77 <li>77 <li>
78-<a href="/demo/commits/main" data-key="c" aria-keyshortcuts="c">Commits <kbd aria-hidden="true">c</kbd>78+<a href="/demo/commits/main" data-key="g c">Commits <kbd aria-hidden="true">g c</kbd>
79 </a>79 </a>
80 </li>80 </li>
81 <li>81 <li>
82-<a href="/demo/refs" data-key="r" aria-keyshortcuts="r" aria-current="page">Branches &amp; tags <kbd aria-hidden="true">r</kbd>82+<a href="/demo/refs" data-key="g b" aria-current="page">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
83 </a>83 </a>
84 </li>84 </li>
85 <li>85 <li>
86-<a href="/demo/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>86+<a href="/demo/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
87 </a>87 </a>
88 </li>88 </li>
89 <li>89 <li>
90-<a href="/demo/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>90+<a href="/demo/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
91 </a>91 </a>
92 </li>92 </li>
93 <li>93 <li>
94-<a href="/demo/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>94+<a href="/demo/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
95 </a>95 </a>
96 </li>96 </li>
97 </ul>97 </ul>
@@ -287,14 +287,16 @@
287 </div>287 </div>
288 <div>288 <div>
289 <dt>289 <dt>
290-<kbd>s</kbd>290+<kbd>g s</kbd>
291-<kbd>t</kbd>291+<kbd>g f</kbd>
292-<kbd>c</kbd>292+<kbd>g c</kbd>
293-<kbd>r</kbd>293+<kbd>g b</kbd>
294-<kbd>m</kbd>294+<kbd>g m</kbd>
295-<kbd>i</kbd>295+<kbd>g i</kbd>
296+<kbd>g r</kbd>
297+<kbd>g a</kbd>
296 </dt>298 </dt>
297-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>299+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
298 </div>300 </div>
299 <div>301 <div>
300 <dt>302 <dt>
@@ -397,45 +399,45 @@
397 <span class="k">go</span>399 <span class="k">go</span>
398 </a>400 </a>
399 </li>401 </li>
400-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">402+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
401 <a role="presentation" data-href="/demo">403 <a role="presentation" data-href="/demo">
402-<span class="lbl">summary</span>404+<span class="lbl">summary (g s)</span>
403 <span class="k">section</span>405 <span class="k">section</span>
404 </a>406 </a>
405 </li>407 </li>
406-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">408+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
407 <a role="presentation" data-href="/demo/tree/main">409 <a role="presentation" data-href="/demo/tree/main">
408-<span class="lbl">files</span>410+<span class="lbl">files (g f)</span>
409 <span class="k">section</span>411 <span class="k">section</span>
410 </a>412 </a>
411 </li>413 </li>
412-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">414+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
413 <a role="presentation" data-href="/demo/commits/main">415 <a role="presentation" data-href="/demo/commits/main">
414-<span class="lbl">commits</span>416+<span class="lbl">commits (g c)</span>
415 <span class="k">section</span>417 <span class="k">section</span>
416 </a>418 </a>
417 </li>419 </li>
418-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">420+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
419 <a role="presentation" data-href="/demo/refs">421 <a role="presentation" data-href="/demo/refs">
420-<span class="lbl">branches &amp; tags</span>422+<span class="lbl">branches &amp; tags (g b)</span>
421 <span class="k">section</span>423 <span class="k">section</span>
422 </a>424 </a>
423 </li>425 </li>
424-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">426+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
425 <a role="presentation" data-href="/demo/merge-requests">427 <a role="presentation" data-href="/demo/merge-requests">
426-<span class="lbl">merge requests</span>428+<span class="lbl">merge requests (g m)</span>
427 <span class="k">section</span>429 <span class="k">section</span>
428 </a>430 </a>
429 </li>431 </li>
430-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">432+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
431 <a role="presentation" data-href="/demo/releases">433 <a role="presentation" data-href="/demo/releases">
432-<span class="lbl">releases</span>434+<span class="lbl">releases (g r)</span>
433 <span class="k">section</span>435 <span class="k">section</span>
434 </a>436 </a>
435 </li>437 </li>
436-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">438+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
437 <a role="presentation" data-href="/demo/actions">439 <a role="presentation" data-href="/demo/actions">
438-<span class="lbl">actions</span>440+<span class="lbl">actions (g a)</span>
439 <span class="k">section</span>441 <span class="k">section</span>
440 </a>442 </a>
441 </li>443 </li>
@@ -500,7 +502,9 @@
500 k-filter from:body -> ermite.focusFilter();502 k-filter from:body -> ermite.focusFilter();
501 k-gh from:body -> ermite.go('.brand');503 k-gh from:body -> ermite.go('.brand');
502 k-s from:body -> ermite.tab('s');504 k-s from:body -> ermite.tab('s');
503- k-t from:body -> ermite.tab('t');505+ k-f from:body -> ermite.tab('f');
506+ k-b from:body -> ermite.tab('b');
507+ k-a from:body -> ermite.tab('a');
504 k-c from:body -> ermite.tab('c');508 k-c from:body -> ermite.tab('c');
505 k-r from:body -> ermite.tab('r');509 k-r from:body -> ermite.tab('r');
506 k-m from:body -> ermite.tab('m');510 k-m from:body -> ermite.tab('m');
@@ -516,7 +520,6 @@
516 k-permalink from:body -> ermite.permalink();520 k-permalink from:body -> ermite.permalink();
517 k-merge from:body -> window.ermiteMR?.openMerge();521 k-merge from:body -> window.ermiteMR?.openMerge();
518 k-read from:body -> window.ermiteMR?.markRead();522 k-read from:body -> window.ermiteMR?.markRead();
519- k-gf from:body -> window.ermiteMR?.focusFiles();
520 k-keys from:body -> ermite.toggleKeys()">523 k-keys from:body -> ermite.toggleKeys()">
521 </div>524 </div>
522 </body>525 </body>
M internal/app/testdata/web/summary.html +33 -30
@@ -64,31 +64,31 @@
6464 <nav class="tabs" aria-label="Sections">
6565 <ul>
6666 <li>
67-<a href="/demo" data-key="s" aria-keyshortcuts="s" aria-current="page">Summary <kbd aria-hidden="true">s</kbd>
67+<a href="/demo" data-key="g s" aria-current="page">Summary <kbd aria-hidden="true">g s</kbd>
6868 </a>
6969 </li>
7070 <li>
71-<a href="/demo/tree/main" data-key="t" aria-keyshortcuts="t">Files <kbd aria-hidden="true">t</kbd>
71+<a href="/demo/tree/main" data-key="g f">Files <kbd aria-hidden="true">g f</kbd>
7272 </a>
7373 </li>
7474 <li>
75-<a href="/demo/commits/main" data-key="c" aria-keyshortcuts="c">Commits <kbd aria-hidden="true">c</kbd>
75+<a href="/demo/commits/main" data-key="g c">Commits <kbd aria-hidden="true">g c</kbd>
7676 </a>
7777 </li>
7878 <li>
79-<a href="/demo/refs" data-key="r" aria-keyshortcuts="r">Branches &amp; tags <kbd aria-hidden="true">r</kbd>
79+<a href="/demo/refs" data-key="g b">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
8080 </a>
8181 </li>
8282 <li>
83-<a href="/demo/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>
83+<a href="/demo/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
8484 </a>
8585 </li>
8686 <li>
87-<a href="/demo/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>
87+<a href="/demo/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
8888 </a>
8989 </li>
9090 <li>
91-<a href="/demo/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>
91+<a href="/demo/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
9292 </a>
9393 </li>
9494 </ul>
@@ -313,14 +313,16 @@
313313 </div>
314314 <div>
315315 <dt>
316-<kbd>s</kbd>
317-<kbd>t</kbd>
318-<kbd>c</kbd>
319-<kbd>r</kbd>
320-<kbd>m</kbd>
321-<kbd>i</kbd>
316+<kbd>g s</kbd>
317+<kbd>g f</kbd>
318+<kbd>g c</kbd>
319+<kbd>g b</kbd>
320+<kbd>g m</kbd>
321+<kbd>g i</kbd>
322+<kbd>g r</kbd>
323+<kbd>g a</kbd>
322324 </dt>
323-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>
325+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
324326 </div>
325327 <div>
326328 <dt>
@@ -423,45 +425,45 @@
423425 <span class="k">go</span>
424426 </a>
425427 </li>
426-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
428+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
427429 <a role="presentation" data-href="/demo">
428-<span class="lbl">summary</span>
430+<span class="lbl">summary (g s)</span>
429431 <span class="k">section</span>
430432 </a>
431433 </li>
432-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
434+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
433435 <a role="presentation" data-href="/demo/tree/main">
434-<span class="lbl">files</span>
436+<span class="lbl">files (g f)</span>
435437 <span class="k">section</span>
436438 </a>
437439 </li>
438-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
440+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
439441 <a role="presentation" data-href="/demo/commits/main">
440-<span class="lbl">commits</span>
442+<span class="lbl">commits (g c)</span>
441443 <span class="k">section</span>
442444 </a>
443445 </li>
444-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
446+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
445447 <a role="presentation" data-href="/demo/refs">
446-<span class="lbl">branches &amp; tags</span>
448+<span class="lbl">branches &amp; tags (g b)</span>
447449 <span class="k">section</span>
448450 </a>
449451 </li>
450-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
452+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
451453 <a role="presentation" data-href="/demo/merge-requests">
452-<span class="lbl">merge requests</span>
454+<span class="lbl">merge requests (g m)</span>
453455 <span class="k">section</span>
454456 </a>
455457 </li>
456-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
458+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
457459 <a role="presentation" data-href="/demo/releases">
458-<span class="lbl">releases</span>
460+<span class="lbl">releases (g r)</span>
459461 <span class="k">section</span>
460462 </a>
461463 </li>
462-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
464+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
463465 <a role="presentation" data-href="/demo/actions">
464-<span class="lbl">actions</span>
466+<span class="lbl">actions (g a)</span>
465467 <span class="k">section</span>
466468 </a>
467469 </li>
@@ -526,7 +528,9 @@
526528 k-filter from:body -> ermite.focusFilter();
527529 k-gh from:body -> ermite.go('.brand');
528530 k-s from:body -> ermite.tab('s');
529- k-t from:body -> ermite.tab('t');
531+ k-f from:body -> ermite.tab('f');
532+ k-b from:body -> ermite.tab('b');
533+ k-a from:body -> ermite.tab('a');
530534 k-c from:body -> ermite.tab('c');
531535 k-r from:body -> ermite.tab('r');
532536 k-m from:body -> ermite.tab('m');
@@ -542,7 +546,6 @@
542546 k-permalink from:body -> ermite.permalink();
543547 k-merge from:body -> window.ermiteMR?.openMerge();
544548 k-read from:body -> window.ermiteMR?.markRead();
545- k-gf from:body -> window.ermiteMR?.focusFiles();
546549 k-keys from:body -> ermite.toggleKeys()">
547550 </div>
548551 </body>
@@ -64,31 +64,31 @@
64 <nav class="tabs" aria-label="Sections">64 <nav class="tabs" aria-label="Sections">
65 <ul>65 <ul>
66 <li>66 <li>
67-<a href="/demo" data-key="s" aria-keyshortcuts="s" aria-current="page">Summary <kbd aria-hidden="true">s</kbd>67+<a href="/demo" data-key="g s" aria-current="page">Summary <kbd aria-hidden="true">g s</kbd>
68 </a>68 </a>
69 </li>69 </li>
70 <li>70 <li>
71-<a href="/demo/tree/main" data-key="t" aria-keyshortcuts="t">Files <kbd aria-hidden="true">t</kbd>71+<a href="/demo/tree/main" data-key="g f">Files <kbd aria-hidden="true">g f</kbd>
72 </a>72 </a>
73 </li>73 </li>
74 <li>74 <li>
75-<a href="/demo/commits/main" data-key="c" aria-keyshortcuts="c">Commits <kbd aria-hidden="true">c</kbd>75+<a href="/demo/commits/main" data-key="g c">Commits <kbd aria-hidden="true">g c</kbd>
76 </a>76 </a>
77 </li>77 </li>
78 <li>78 <li>
79-<a href="/demo/refs" data-key="r" aria-keyshortcuts="r">Branches &amp; tags <kbd aria-hidden="true">r</kbd>79+<a href="/demo/refs" data-key="g b">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
80 </a>80 </a>
81 </li>81 </li>
82 <li>82 <li>
83-<a href="/demo/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>83+<a href="/demo/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
84 </a>84 </a>
85 </li>85 </li>
86 <li>86 <li>
87-<a href="/demo/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>87+<a href="/demo/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
88 </a>88 </a>
89 </li>89 </li>
90 <li>90 <li>
91-<a href="/demo/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>91+<a href="/demo/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
92 </a>92 </a>
93 </li>93 </li>
94 </ul>94 </ul>
@@ -313,14 +313,16 @@
313 </div>313 </div>
314 <div>314 <div>
315 <dt>315 <dt>
316-<kbd>s</kbd>316+<kbd>g s</kbd>
317-<kbd>t</kbd>317+<kbd>g f</kbd>
318-<kbd>c</kbd>318+<kbd>g c</kbd>
319-<kbd>r</kbd>319+<kbd>g b</kbd>
320-<kbd>m</kbd>320+<kbd>g m</kbd>
321-<kbd>i</kbd>321+<kbd>g i</kbd>
322+<kbd>g r</kbd>
323+<kbd>g a</kbd>
322 </dt>324 </dt>
323-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>325+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
324 </div>326 </div>
325 <div>327 <div>
326 <dt>328 <dt>
@@ -423,45 +425,45 @@
423 <span class="k">go</span>425 <span class="k">go</span>
424 </a>426 </a>
425 </li>427 </li>
426-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">428+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
427 <a role="presentation" data-href="/demo">429 <a role="presentation" data-href="/demo">
428-<span class="lbl">summary</span>430+<span class="lbl">summary (g s)</span>
429 <span class="k">section</span>431 <span class="k">section</span>
430 </a>432 </a>
431 </li>433 </li>
432-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">434+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
433 <a role="presentation" data-href="/demo/tree/main">435 <a role="presentation" data-href="/demo/tree/main">
434-<span class="lbl">files</span>436+<span class="lbl">files (g f)</span>
435 <span class="k">section</span>437 <span class="k">section</span>
436 </a>438 </a>
437 </li>439 </li>
438-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">440+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
439 <a role="presentation" data-href="/demo/commits/main">441 <a role="presentation" data-href="/demo/commits/main">
440-<span class="lbl">commits</span>442+<span class="lbl">commits (g c)</span>
441 <span class="k">section</span>443 <span class="k">section</span>
442 </a>444 </a>
443 </li>445 </li>
444-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">446+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
445 <a role="presentation" data-href="/demo/refs">447 <a role="presentation" data-href="/demo/refs">
446-<span class="lbl">branches &amp; tags</span>448+<span class="lbl">branches &amp; tags (g b)</span>
447 <span class="k">section</span>449 <span class="k">section</span>
448 </a>450 </a>
449 </li>451 </li>
450-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">452+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
451 <a role="presentation" data-href="/demo/merge-requests">453 <a role="presentation" data-href="/demo/merge-requests">
452-<span class="lbl">merge requests</span>454+<span class="lbl">merge requests (g m)</span>
453 <span class="k">section</span>455 <span class="k">section</span>
454 </a>456 </a>
455 </li>457 </li>
456-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">458+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
457 <a role="presentation" data-href="/demo/releases">459 <a role="presentation" data-href="/demo/releases">
458-<span class="lbl">releases</span>460+<span class="lbl">releases (g r)</span>
459 <span class="k">section</span>461 <span class="k">section</span>
460 </a>462 </a>
461 </li>463 </li>
462-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">464+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
463 <a role="presentation" data-href="/demo/actions">465 <a role="presentation" data-href="/demo/actions">
464-<span class="lbl">actions</span>466+<span class="lbl">actions (g a)</span>
465 <span class="k">section</span>467 <span class="k">section</span>
466 </a>468 </a>
467 </li>469 </li>
@@ -526,7 +528,9 @@
526 k-filter from:body -> ermite.focusFilter();528 k-filter from:body -> ermite.focusFilter();
527 k-gh from:body -> ermite.go('.brand');529 k-gh from:body -> ermite.go('.brand');
528 k-s from:body -> ermite.tab('s');530 k-s from:body -> ermite.tab('s');
529- k-t from:body -> ermite.tab('t');531+ k-f from:body -> ermite.tab('f');
532+ k-b from:body -> ermite.tab('b');
533+ k-a from:body -> ermite.tab('a');
530 k-c from:body -> ermite.tab('c');534 k-c from:body -> ermite.tab('c');
531 k-r from:body -> ermite.tab('r');535 k-r from:body -> ermite.tab('r');
532 k-m from:body -> ermite.tab('m');536 k-m from:body -> ermite.tab('m');
@@ -542,7 +546,6 @@
542 k-permalink from:body -> ermite.permalink();546 k-permalink from:body -> ermite.permalink();
543 k-merge from:body -> window.ermiteMR?.openMerge();547 k-merge from:body -> window.ermiteMR?.openMerge();
544 k-read from:body -> window.ermiteMR?.markRead();548 k-read from:body -> window.ermiteMR?.markRead();
545- k-gf from:body -> window.ermiteMR?.focusFiles();
546 k-keys from:body -> ermite.toggleKeys()">549 k-keys from:body -> ermite.toggleKeys()">
547 </div>550 </div>
548 </body>551 </body>
M internal/app/testdata/web/tree-nested.html +33 -30
@@ -76,31 +76,31 @@
7676 <nav class="tabs" aria-label="Sections">
7777 <ul>
7878 <li>
79-<a href="/files" data-key="s" aria-keyshortcuts="s">Summary <kbd aria-hidden="true">s</kbd>
79+<a href="/files" data-key="g s">Summary <kbd aria-hidden="true">g s</kbd>
8080 </a>
8181 </li>
8282 <li>
83-<a href="/files/tree/main" data-key="t" aria-keyshortcuts="t" aria-current="page">Files <kbd aria-hidden="true">t</kbd>
83+<a href="/files/tree/main" data-key="g f" aria-current="page">Files <kbd aria-hidden="true">g f</kbd>
8484 </a>
8585 </li>
8686 <li>
87-<a href="/files/commits/main" data-key="c" aria-keyshortcuts="c">Commits <kbd aria-hidden="true">c</kbd>
87+<a href="/files/commits/main" data-key="g c">Commits <kbd aria-hidden="true">g c</kbd>
8888 </a>
8989 </li>
9090 <li>
91-<a href="/files/refs" data-key="r" aria-keyshortcuts="r">Branches &amp; tags <kbd aria-hidden="true">r</kbd>
91+<a href="/files/refs" data-key="g b">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
9292 </a>
9393 </li>
9494 <li>
95-<a href="/files/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>
95+<a href="/files/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
9696 </a>
9797 </li>
9898 <li>
99-<a href="/files/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>
99+<a href="/files/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
100100 </a>
101101 </li>
102102 <li>
103-<a href="/files/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>
103+<a href="/files/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
104104 </a>
105105 </li>
106106 </ul>
@@ -243,14 +243,16 @@
243243 </div>
244244 <div>
245245 <dt>
246-<kbd>s</kbd>
247-<kbd>t</kbd>
248-<kbd>c</kbd>
249-<kbd>r</kbd>
250-<kbd>m</kbd>
251-<kbd>i</kbd>
246+<kbd>g s</kbd>
247+<kbd>g f</kbd>
248+<kbd>g c</kbd>
249+<kbd>g b</kbd>
250+<kbd>g m</kbd>
251+<kbd>g i</kbd>
252+<kbd>g r</kbd>
253+<kbd>g a</kbd>
252254 </dt>
253-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>
255+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
254256 </div>
255257 <div>
256258 <dt>
@@ -349,45 +351,45 @@
349351 <span class="k">go</span>
350352 </a>
351353 </li>
352-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
354+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
353355 <a role="presentation" data-href="/files">
354-<span class="lbl">summary</span>
356+<span class="lbl">summary (g s)</span>
355357 <span class="k">section</span>
356358 </a>
357359 </li>
358-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
360+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
359361 <a role="presentation" data-href="/files/tree/main">
360-<span class="lbl">files</span>
362+<span class="lbl">files (g f)</span>
361363 <span class="k">section</span>
362364 </a>
363365 </li>
364-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
366+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
365367 <a role="presentation" data-href="/files/commits/main">
366-<span class="lbl">commits</span>
368+<span class="lbl">commits (g c)</span>
367369 <span class="k">section</span>
368370 </a>
369371 </li>
370-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
372+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
371373 <a role="presentation" data-href="/files/refs">
372-<span class="lbl">branches &amp; tags</span>
374+<span class="lbl">branches &amp; tags (g b)</span>
373375 <span class="k">section</span>
374376 </a>
375377 </li>
376-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
378+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
377379 <a role="presentation" data-href="/files/merge-requests">
378-<span class="lbl">merge requests</span>
380+<span class="lbl">merge requests (g m)</span>
379381 <span class="k">section</span>
380382 </a>
381383 </li>
382-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
384+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
383385 <a role="presentation" data-href="/files/releases">
384-<span class="lbl">releases</span>
386+<span class="lbl">releases (g r)</span>
385387 <span class="k">section</span>
386388 </a>
387389 </li>
388-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
390+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
389391 <a role="presentation" data-href="/files/actions">
390-<span class="lbl">actions</span>
392+<span class="lbl">actions (g a)</span>
391393 <span class="k">section</span>
392394 </a>
393395 </li>
@@ -440,7 +442,9 @@
440442 k-filter from:body -> ermite.focusFilter();
441443 k-gh from:body -> ermite.go('.brand');
442444 k-s from:body -> ermite.tab('s');
443- k-t from:body -> ermite.tab('t');
445+ k-f from:body -> ermite.tab('f');
446+ k-b from:body -> ermite.tab('b');
447+ k-a from:body -> ermite.tab('a');
444448 k-c from:body -> ermite.tab('c');
445449 k-r from:body -> ermite.tab('r');
446450 k-m from:body -> ermite.tab('m');
@@ -456,7 +460,6 @@
456460 k-permalink from:body -> ermite.permalink();
457461 k-merge from:body -> window.ermiteMR?.openMerge();
458462 k-read from:body -> window.ermiteMR?.markRead();
459- k-gf from:body -> window.ermiteMR?.focusFiles();
460463 k-keys from:body -> ermite.toggleKeys()">
461464 </div>
462465 </body>
@@ -76,31 +76,31 @@
76 <nav class="tabs" aria-label="Sections">76 <nav class="tabs" aria-label="Sections">
77 <ul>77 <ul>
78 <li>78 <li>
79-<a href="/files" data-key="s" aria-keyshortcuts="s">Summary <kbd aria-hidden="true">s</kbd>79+<a href="/files" data-key="g s">Summary <kbd aria-hidden="true">g s</kbd>
80 </a>80 </a>
81 </li>81 </li>
82 <li>82 <li>
83-<a href="/files/tree/main" data-key="t" aria-keyshortcuts="t" aria-current="page">Files <kbd aria-hidden="true">t</kbd>83+<a href="/files/tree/main" data-key="g f" aria-current="page">Files <kbd aria-hidden="true">g f</kbd>
84 </a>84 </a>
85 </li>85 </li>
86 <li>86 <li>
87-<a href="/files/commits/main" data-key="c" aria-keyshortcuts="c">Commits <kbd aria-hidden="true">c</kbd>87+<a href="/files/commits/main" data-key="g c">Commits <kbd aria-hidden="true">g c</kbd>
88 </a>88 </a>
89 </li>89 </li>
90 <li>90 <li>
91-<a href="/files/refs" data-key="r" aria-keyshortcuts="r">Branches &amp; tags <kbd aria-hidden="true">r</kbd>91+<a href="/files/refs" data-key="g b">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
92 </a>92 </a>
93 </li>93 </li>
94 <li>94 <li>
95-<a href="/files/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>95+<a href="/files/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
96 </a>96 </a>
97 </li>97 </li>
98 <li>98 <li>
99-<a href="/files/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>99+<a href="/files/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
100 </a>100 </a>
101 </li>101 </li>
102 <li>102 <li>
103-<a href="/files/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>103+<a href="/files/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
104 </a>104 </a>
105 </li>105 </li>
106 </ul>106 </ul>
@@ -243,14 +243,16 @@
243 </div>243 </div>
244 <div>244 <div>
245 <dt>245 <dt>
246-<kbd>s</kbd>246+<kbd>g s</kbd>
247-<kbd>t</kbd>247+<kbd>g f</kbd>
248-<kbd>c</kbd>248+<kbd>g c</kbd>
249-<kbd>r</kbd>249+<kbd>g b</kbd>
250-<kbd>m</kbd>250+<kbd>g m</kbd>
251-<kbd>i</kbd>251+<kbd>g i</kbd>
252+<kbd>g r</kbd>
253+<kbd>g a</kbd>
252 </dt>254 </dt>
253-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>255+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
254 </div>256 </div>
255 <div>257 <div>
256 <dt>258 <dt>
@@ -349,45 +351,45 @@
349 <span class="k">go</span>351 <span class="k">go</span>
350 </a>352 </a>
351 </li>353 </li>
352-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">354+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
353 <a role="presentation" data-href="/files">355 <a role="presentation" data-href="/files">
354-<span class="lbl">summary</span>356+<span class="lbl">summary (g s)</span>
355 <span class="k">section</span>357 <span class="k">section</span>
356 </a>358 </a>
357 </li>359 </li>
358-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">360+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
359 <a role="presentation" data-href="/files/tree/main">361 <a role="presentation" data-href="/files/tree/main">
360-<span class="lbl">files</span>362+<span class="lbl">files (g f)</span>
361 <span class="k">section</span>363 <span class="k">section</span>
362 </a>364 </a>
363 </li>365 </li>
364-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">366+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
365 <a role="presentation" data-href="/files/commits/main">367 <a role="presentation" data-href="/files/commits/main">
366-<span class="lbl">commits</span>368+<span class="lbl">commits (g c)</span>
367 <span class="k">section</span>369 <span class="k">section</span>
368 </a>370 </a>
369 </li>371 </li>
370-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">372+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
371 <a role="presentation" data-href="/files/refs">373 <a role="presentation" data-href="/files/refs">
372-<span class="lbl">branches &amp; tags</span>374+<span class="lbl">branches &amp; tags (g b)</span>
373 <span class="k">section</span>375 <span class="k">section</span>
374 </a>376 </a>
375 </li>377 </li>
376-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">378+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
377 <a role="presentation" data-href="/files/merge-requests">379 <a role="presentation" data-href="/files/merge-requests">
378-<span class="lbl">merge requests</span>380+<span class="lbl">merge requests (g m)</span>
379 <span class="k">section</span>381 <span class="k">section</span>
380 </a>382 </a>
381 </li>383 </li>
382-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">384+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
383 <a role="presentation" data-href="/files/releases">385 <a role="presentation" data-href="/files/releases">
384-<span class="lbl">releases</span>386+<span class="lbl">releases (g r)</span>
385 <span class="k">section</span>387 <span class="k">section</span>
386 </a>388 </a>
387 </li>389 </li>
388-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">390+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
389 <a role="presentation" data-href="/files/actions">391 <a role="presentation" data-href="/files/actions">
390-<span class="lbl">actions</span>392+<span class="lbl">actions (g a)</span>
391 <span class="k">section</span>393 <span class="k">section</span>
392 </a>394 </a>
393 </li>395 </li>
@@ -440,7 +442,9 @@
440 k-filter from:body -> ermite.focusFilter();442 k-filter from:body -> ermite.focusFilter();
441 k-gh from:body -> ermite.go('.brand');443 k-gh from:body -> ermite.go('.brand');
442 k-s from:body -> ermite.tab('s');444 k-s from:body -> ermite.tab('s');
443- k-t from:body -> ermite.tab('t');445+ k-f from:body -> ermite.tab('f');
446+ k-b from:body -> ermite.tab('b');
447+ k-a from:body -> ermite.tab('a');
444 k-c from:body -> ermite.tab('c');448 k-c from:body -> ermite.tab('c');
445 k-r from:body -> ermite.tab('r');449 k-r from:body -> ermite.tab('r');
446 k-m from:body -> ermite.tab('m');450 k-m from:body -> ermite.tab('m');
@@ -456,7 +460,6 @@
456 k-permalink from:body -> ermite.permalink();460 k-permalink from:body -> ermite.permalink();
457 k-merge from:body -> window.ermiteMR?.openMerge();461 k-merge from:body -> window.ermiteMR?.openMerge();
458 k-read from:body -> window.ermiteMR?.markRead();462 k-read from:body -> window.ermiteMR?.markRead();
459- k-gf from:body -> window.ermiteMR?.focusFiles();
460 k-keys from:body -> ermite.toggleKeys()">463 k-keys from:body -> ermite.toggleKeys()">
461 </div>464 </div>
462 </body>465 </body>
M internal/app/testdata/web/tree-root.html +33 -30
@@ -70,31 +70,31 @@
7070 <nav class="tabs" aria-label="Sections">
7171 <ul>
7272 <li>
73-<a href="/files" data-key="s" aria-keyshortcuts="s">Summary <kbd aria-hidden="true">s</kbd>
73+<a href="/files" data-key="g s">Summary <kbd aria-hidden="true">g s</kbd>
7474 </a>
7575 </li>
7676 <li>
77-<a href="/files/tree/main" data-key="t" aria-keyshortcuts="t" aria-current="page">Files <kbd aria-hidden="true">t</kbd>
77+<a href="/files/tree/main" data-key="g f" aria-current="page">Files <kbd aria-hidden="true">g f</kbd>
7878 </a>
7979 </li>
8080 <li>
81-<a href="/files/commits/main" data-key="c" aria-keyshortcuts="c">Commits <kbd aria-hidden="true">c</kbd>
81+<a href="/files/commits/main" data-key="g c">Commits <kbd aria-hidden="true">g c</kbd>
8282 </a>
8383 </li>
8484 <li>
85-<a href="/files/refs" data-key="r" aria-keyshortcuts="r">Branches &amp; tags <kbd aria-hidden="true">r</kbd>
85+<a href="/files/refs" data-key="g b">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
8686 </a>
8787 </li>
8888 <li>
89-<a href="/files/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>
89+<a href="/files/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
9090 </a>
9191 </li>
9292 <li>
93-<a href="/files/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>
93+<a href="/files/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
9494 </a>
9595 </li>
9696 <li>
97-<a href="/files/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>
97+<a href="/files/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
9898 </a>
9999 </li>
100100 </ul>
@@ -464,14 +464,16 @@
464464 </div>
465465 <div>
466466 <dt>
467-<kbd>s</kbd>
468-<kbd>t</kbd>
469-<kbd>c</kbd>
470-<kbd>r</kbd>
471-<kbd>m</kbd>
472-<kbd>i</kbd>
467+<kbd>g s</kbd>
468+<kbd>g f</kbd>
469+<kbd>g c</kbd>
470+<kbd>g b</kbd>
471+<kbd>g m</kbd>
472+<kbd>g i</kbd>
473+<kbd>g r</kbd>
474+<kbd>g a</kbd>
473475 </dt>
474-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>
476+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
475477 </div>
476478 <div>
477479 <dt>
@@ -570,45 +572,45 @@
570572 <span class="k">go</span>
571573 </a>
572574 </li>
573-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
575+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
574576 <a role="presentation" data-href="/files">
575-<span class="lbl">summary</span>
577+<span class="lbl">summary (g s)</span>
576578 <span class="k">section</span>
577579 </a>
578580 </li>
579-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
581+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
580582 <a role="presentation" data-href="/files/tree/main">
581-<span class="lbl">files</span>
583+<span class="lbl">files (g f)</span>
582584 <span class="k">section</span>
583585 </a>
584586 </li>
585-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
587+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
586588 <a role="presentation" data-href="/files/commits/main">
587-<span class="lbl">commits</span>
589+<span class="lbl">commits (g c)</span>
588590 <span class="k">section</span>
589591 </a>
590592 </li>
591-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
593+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
592594 <a role="presentation" data-href="/files/refs">
593-<span class="lbl">branches &amp; tags</span>
595+<span class="lbl">branches &amp; tags (g b)</span>
594596 <span class="k">section</span>
595597 </a>
596598 </li>
597-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
599+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
598600 <a role="presentation" data-href="/files/merge-requests">
599-<span class="lbl">merge requests</span>
601+<span class="lbl">merge requests (g m)</span>
600602 <span class="k">section</span>
601603 </a>
602604 </li>
603-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
605+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
604606 <a role="presentation" data-href="/files/releases">
605-<span class="lbl">releases</span>
607+<span class="lbl">releases (g r)</span>
606608 <span class="k">section</span>
607609 </a>
608610 </li>
609-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
611+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
610612 <a role="presentation" data-href="/files/actions">
611-<span class="lbl">actions</span>
613+<span class="lbl">actions (g a)</span>
612614 <span class="k">section</span>
613615 </a>
614616 </li>
@@ -661,7 +663,9 @@
661663 k-filter from:body -> ermite.focusFilter();
662664 k-gh from:body -> ermite.go('.brand');
663665 k-s from:body -> ermite.tab('s');
664- k-t from:body -> ermite.tab('t');
666+ k-f from:body -> ermite.tab('f');
667+ k-b from:body -> ermite.tab('b');
668+ k-a from:body -> ermite.tab('a');
665669 k-c from:body -> ermite.tab('c');
666670 k-r from:body -> ermite.tab('r');
667671 k-m from:body -> ermite.tab('m');
@@ -677,7 +681,6 @@
677681 k-permalink from:body -> ermite.permalink();
678682 k-merge from:body -> window.ermiteMR?.openMerge();
679683 k-read from:body -> window.ermiteMR?.markRead();
680- k-gf from:body -> window.ermiteMR?.focusFiles();
681684 k-keys from:body -> ermite.toggleKeys()">
682685 </div>
683686 </body>
@@ -70,31 +70,31 @@
70 <nav class="tabs" aria-label="Sections">70 <nav class="tabs" aria-label="Sections">
71 <ul>71 <ul>
72 <li>72 <li>
73-<a href="/files" data-key="s" aria-keyshortcuts="s">Summary <kbd aria-hidden="true">s</kbd>73+<a href="/files" data-key="g s">Summary <kbd aria-hidden="true">g s</kbd>
74 </a>74 </a>
75 </li>75 </li>
76 <li>76 <li>
77-<a href="/files/tree/main" data-key="t" aria-keyshortcuts="t" aria-current="page">Files <kbd aria-hidden="true">t</kbd>77+<a href="/files/tree/main" data-key="g f" aria-current="page">Files <kbd aria-hidden="true">g f</kbd>
78 </a>78 </a>
79 </li>79 </li>
80 <li>80 <li>
81-<a href="/files/commits/main" data-key="c" aria-keyshortcuts="c">Commits <kbd aria-hidden="true">c</kbd>81+<a href="/files/commits/main" data-key="g c">Commits <kbd aria-hidden="true">g c</kbd>
82 </a>82 </a>
83 </li>83 </li>
84 <li>84 <li>
85-<a href="/files/refs" data-key="r" aria-keyshortcuts="r">Branches &amp; tags <kbd aria-hidden="true">r</kbd>85+<a href="/files/refs" data-key="g b">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
86 </a>86 </a>
87 </li>87 </li>
88 <li>88 <li>
89-<a href="/files/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>89+<a href="/files/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
90 </a>90 </a>
91 </li>91 </li>
92 <li>92 <li>
93-<a href="/files/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>93+<a href="/files/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
94 </a>94 </a>
95 </li>95 </li>
96 <li>96 <li>
97-<a href="/files/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>97+<a href="/files/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
98 </a>98 </a>
99 </li>99 </li>
100 </ul>100 </ul>
@@ -464,14 +464,16 @@
464 </div>464 </div>
465 <div>465 <div>
466 <dt>466 <dt>
467-<kbd>s</kbd>467+<kbd>g s</kbd>
468-<kbd>t</kbd>468+<kbd>g f</kbd>
469-<kbd>c</kbd>469+<kbd>g c</kbd>
470-<kbd>r</kbd>470+<kbd>g b</kbd>
471-<kbd>m</kbd>471+<kbd>g m</kbd>
472-<kbd>i</kbd>472+<kbd>g i</kbd>
473+<kbd>g r</kbd>
474+<kbd>g a</kbd>
473 </dt>475 </dt>
474-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>476+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
475 </div>477 </div>
476 <div>478 <div>
477 <dt>479 <dt>
@@ -570,45 +572,45 @@
570 <span class="k">go</span>572 <span class="k">go</span>
571 </a>573 </a>
572 </li>574 </li>
573-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">575+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
574 <a role="presentation" data-href="/files">576 <a role="presentation" data-href="/files">
575-<span class="lbl">summary</span>577+<span class="lbl">summary (g s)</span>
576 <span class="k">section</span>578 <span class="k">section</span>
577 </a>579 </a>
578 </li>580 </li>
579-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">581+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
580 <a role="presentation" data-href="/files/tree/main">582 <a role="presentation" data-href="/files/tree/main">
581-<span class="lbl">files</span>583+<span class="lbl">files (g f)</span>
582 <span class="k">section</span>584 <span class="k">section</span>
583 </a>585 </a>
584 </li>586 </li>
585-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">587+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
586 <a role="presentation" data-href="/files/commits/main">588 <a role="presentation" data-href="/files/commits/main">
587-<span class="lbl">commits</span>589+<span class="lbl">commits (g c)</span>
588 <span class="k">section</span>590 <span class="k">section</span>
589 </a>591 </a>
590 </li>592 </li>
591-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">593+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
592 <a role="presentation" data-href="/files/refs">594 <a role="presentation" data-href="/files/refs">
593-<span class="lbl">branches &amp; tags</span>595+<span class="lbl">branches &amp; tags (g b)</span>
594 <span class="k">section</span>596 <span class="k">section</span>
595 </a>597 </a>
596 </li>598 </li>
597-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">599+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
598 <a role="presentation" data-href="/files/merge-requests">600 <a role="presentation" data-href="/files/merge-requests">
599-<span class="lbl">merge requests</span>601+<span class="lbl">merge requests (g m)</span>
600 <span class="k">section</span>602 <span class="k">section</span>
601 </a>603 </a>
602 </li>604 </li>
603-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">605+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
604 <a role="presentation" data-href="/files/releases">606 <a role="presentation" data-href="/files/releases">
605-<span class="lbl">releases</span>607+<span class="lbl">releases (g r)</span>
606 <span class="k">section</span>608 <span class="k">section</span>
607 </a>609 </a>
608 </li>610 </li>
609-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">611+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
610 <a role="presentation" data-href="/files/actions">612 <a role="presentation" data-href="/files/actions">
611-<span class="lbl">actions</span>613+<span class="lbl">actions (g a)</span>
612 <span class="k">section</span>614 <span class="k">section</span>
613 </a>615 </a>
614 </li>616 </li>
@@ -661,7 +663,9 @@
661 k-filter from:body -> ermite.focusFilter();663 k-filter from:body -> ermite.focusFilter();
662 k-gh from:body -> ermite.go('.brand');664 k-gh from:body -> ermite.go('.brand');
663 k-s from:body -> ermite.tab('s');665 k-s from:body -> ermite.tab('s');
664- k-t from:body -> ermite.tab('t');666+ k-f from:body -> ermite.tab('f');
667+ k-b from:body -> ermite.tab('b');
668+ k-a from:body -> ermite.tab('a');
665 k-c from:body -> ermite.tab('c');669 k-c from:body -> ermite.tab('c');
666 k-r from:body -> ermite.tab('r');670 k-r from:body -> ermite.tab('r');
667 k-m from:body -> ermite.tab('m');671 k-m from:body -> ermite.tab('m');
@@ -677,7 +681,6 @@
677 k-permalink from:body -> ermite.permalink();681 k-permalink from:body -> ermite.permalink();
678 k-merge from:body -> window.ermiteMR?.openMerge();682 k-merge from:body -> window.ermiteMR?.openMerge();
679 k-read from:body -> window.ermiteMR?.markRead();683 k-read from:body -> window.ermiteMR?.markRead();
680- k-gf from:body -> window.ermiteMR?.focusFiles();
681 k-keys from:body -> ermite.toggleKeys()">684 k-keys from:body -> ermite.toggleKeys()">
682 </div>685 </div>
683 </body>686 </body>
M internal/app/testdata/web/tree-slash-ref.html +33 -30
@@ -73,31 +73,31 @@
7373 <nav class="tabs" aria-label="Sections">
7474 <ul>
7575 <li>
76-<a href="/files" data-key="s" aria-keyshortcuts="s">Summary <kbd aria-hidden="true">s</kbd>
76+<a href="/files" data-key="g s">Summary <kbd aria-hidden="true">g s</kbd>
7777 </a>
7878 </li>
7979 <li>
80-<a href="/files/tree/main" data-key="t" aria-keyshortcuts="t" aria-current="page">Files <kbd aria-hidden="true">t</kbd>
80+<a href="/files/tree/main" data-key="g f" aria-current="page">Files <kbd aria-hidden="true">g f</kbd>
8181 </a>
8282 </li>
8383 <li>
84-<a href="/files/commits/main" data-key="c" aria-keyshortcuts="c">Commits <kbd aria-hidden="true">c</kbd>
84+<a href="/files/commits/main" data-key="g c">Commits <kbd aria-hidden="true">g c</kbd>
8585 </a>
8686 </li>
8787 <li>
88-<a href="/files/refs" data-key="r" aria-keyshortcuts="r">Branches &amp; tags <kbd aria-hidden="true">r</kbd>
88+<a href="/files/refs" data-key="g b">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
8989 </a>
9090 </li>
9191 <li>
92-<a href="/files/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>
92+<a href="/files/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
9393 </a>
9494 </li>
9595 <li>
96-<a href="/files/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>
96+<a href="/files/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
9797 </a>
9898 </li>
9999 <li>
100-<a href="/files/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>
100+<a href="/files/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
101101 </a>
102102 </li>
103103 </ul>
@@ -240,14 +240,16 @@
240240 </div>
241241 <div>
242242 <dt>
243-<kbd>s</kbd>
244-<kbd>t</kbd>
245-<kbd>c</kbd>
246-<kbd>r</kbd>
247-<kbd>m</kbd>
248-<kbd>i</kbd>
243+<kbd>g s</kbd>
244+<kbd>g f</kbd>
245+<kbd>g c</kbd>
246+<kbd>g b</kbd>
247+<kbd>g m</kbd>
248+<kbd>g i</kbd>
249+<kbd>g r</kbd>
250+<kbd>g a</kbd>
249251 </dt>
250-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>
252+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
251253 </div>
252254 <div>
253255 <dt>
@@ -346,45 +348,45 @@
346348 <span class="k">go</span>
347349 </a>
348350 </li>
349-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
351+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
350352 <a role="presentation" data-href="/files">
351-<span class="lbl">summary</span>
353+<span class="lbl">summary (g s)</span>
352354 <span class="k">section</span>
353355 </a>
354356 </li>
355-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
357+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
356358 <a role="presentation" data-href="/files/tree/main">
357-<span class="lbl">files</span>
359+<span class="lbl">files (g f)</span>
358360 <span class="k">section</span>
359361 </a>
360362 </li>
361-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
363+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
362364 <a role="presentation" data-href="/files/commits/main">
363-<span class="lbl">commits</span>
365+<span class="lbl">commits (g c)</span>
364366 <span class="k">section</span>
365367 </a>
366368 </li>
367-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
369+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
368370 <a role="presentation" data-href="/files/refs">
369-<span class="lbl">branches &amp; tags</span>
371+<span class="lbl">branches &amp; tags (g b)</span>
370372 <span class="k">section</span>
371373 </a>
372374 </li>
373-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
375+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
374376 <a role="presentation" data-href="/files/merge-requests">
375-<span class="lbl">merge requests</span>
377+<span class="lbl">merge requests (g m)</span>
376378 <span class="k">section</span>
377379 </a>
378380 </li>
379-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
381+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
380382 <a role="presentation" data-href="/files/releases">
381-<span class="lbl">releases</span>
383+<span class="lbl">releases (g r)</span>
382384 <span class="k">section</span>
383385 </a>
384386 </li>
385-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
387+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
386388 <a role="presentation" data-href="/files/actions">
387-<span class="lbl">actions</span>
389+<span class="lbl">actions (g a)</span>
388390 <span class="k">section</span>
389391 </a>
390392 </li>
@@ -437,7 +439,9 @@
437439 k-filter from:body -> ermite.focusFilter();
438440 k-gh from:body -> ermite.go('.brand');
439441 k-s from:body -> ermite.tab('s');
440- k-t from:body -> ermite.tab('t');
442+ k-f from:body -> ermite.tab('f');
443+ k-b from:body -> ermite.tab('b');
444+ k-a from:body -> ermite.tab('a');
441445 k-c from:body -> ermite.tab('c');
442446 k-r from:body -> ermite.tab('r');
443447 k-m from:body -> ermite.tab('m');
@@ -453,7 +457,6 @@
453457 k-permalink from:body -> ermite.permalink();
454458 k-merge from:body -> window.ermiteMR?.openMerge();
455459 k-read from:body -> window.ermiteMR?.markRead();
456- k-gf from:body -> window.ermiteMR?.focusFiles();
457460 k-keys from:body -> ermite.toggleKeys()">
458461 </div>
459462 </body>
@@ -73,31 +73,31 @@
73 <nav class="tabs" aria-label="Sections">73 <nav class="tabs" aria-label="Sections">
74 <ul>74 <ul>
75 <li>75 <li>
76-<a href="/files" data-key="s" aria-keyshortcuts="s">Summary <kbd aria-hidden="true">s</kbd>76+<a href="/files" data-key="g s">Summary <kbd aria-hidden="true">g s</kbd>
77 </a>77 </a>
78 </li>78 </li>
79 <li>79 <li>
80-<a href="/files/tree/main" data-key="t" aria-keyshortcuts="t" aria-current="page">Files <kbd aria-hidden="true">t</kbd>80+<a href="/files/tree/main" data-key="g f" aria-current="page">Files <kbd aria-hidden="true">g f</kbd>
81 </a>81 </a>
82 </li>82 </li>
83 <li>83 <li>
84-<a href="/files/commits/main" data-key="c" aria-keyshortcuts="c">Commits <kbd aria-hidden="true">c</kbd>84+<a href="/files/commits/main" data-key="g c">Commits <kbd aria-hidden="true">g c</kbd>
85 </a>85 </a>
86 </li>86 </li>
87 <li>87 <li>
88-<a href="/files/refs" data-key="r" aria-keyshortcuts="r">Branches &amp; tags <kbd aria-hidden="true">r</kbd>88+<a href="/files/refs" data-key="g b">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
89 </a>89 </a>
90 </li>90 </li>
91 <li>91 <li>
92-<a href="/files/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>92+<a href="/files/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
93 </a>93 </a>
94 </li>94 </li>
95 <li>95 <li>
96-<a href="/files/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>96+<a href="/files/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
97 </a>97 </a>
98 </li>98 </li>
99 <li>99 <li>
100-<a href="/files/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>100+<a href="/files/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
101 </a>101 </a>
102 </li>102 </li>
103 </ul>103 </ul>
@@ -240,14 +240,16 @@
240 </div>240 </div>
241 <div>241 <div>
242 <dt>242 <dt>
243-<kbd>s</kbd>243+<kbd>g s</kbd>
244-<kbd>t</kbd>244+<kbd>g f</kbd>
245-<kbd>c</kbd>245+<kbd>g c</kbd>
246-<kbd>r</kbd>246+<kbd>g b</kbd>
247-<kbd>m</kbd>247+<kbd>g m</kbd>
248-<kbd>i</kbd>248+<kbd>g i</kbd>
249+<kbd>g r</kbd>
250+<kbd>g a</kbd>
249 </dt>251 </dt>
250-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>252+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
251 </div>253 </div>
252 <div>254 <div>
253 <dt>255 <dt>
@@ -346,45 +348,45 @@
346 <span class="k">go</span>348 <span class="k">go</span>
347 </a>349 </a>
348 </li>350 </li>
349-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">351+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
350 <a role="presentation" data-href="/files">352 <a role="presentation" data-href="/files">
351-<span class="lbl">summary</span>353+<span class="lbl">summary (g s)</span>
352 <span class="k">section</span>354 <span class="k">section</span>
353 </a>355 </a>
354 </li>356 </li>
355-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">357+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
356 <a role="presentation" data-href="/files/tree/main">358 <a role="presentation" data-href="/files/tree/main">
357-<span class="lbl">files</span>359+<span class="lbl">files (g f)</span>
358 <span class="k">section</span>360 <span class="k">section</span>
359 </a>361 </a>
360 </li>362 </li>
361-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">363+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
362 <a role="presentation" data-href="/files/commits/main">364 <a role="presentation" data-href="/files/commits/main">
363-<span class="lbl">commits</span>365+<span class="lbl">commits (g c)</span>
364 <span class="k">section</span>366 <span class="k">section</span>
365 </a>367 </a>
366 </li>368 </li>
367-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">369+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
368 <a role="presentation" data-href="/files/refs">370 <a role="presentation" data-href="/files/refs">
369-<span class="lbl">branches &amp; tags</span>371+<span class="lbl">branches &amp; tags (g b)</span>
370 <span class="k">section</span>372 <span class="k">section</span>
371 </a>373 </a>
372 </li>374 </li>
373-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">375+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
374 <a role="presentation" data-href="/files/merge-requests">376 <a role="presentation" data-href="/files/merge-requests">
375-<span class="lbl">merge requests</span>377+<span class="lbl">merge requests (g m)</span>
376 <span class="k">section</span>378 <span class="k">section</span>
377 </a>379 </a>
378 </li>380 </li>
379-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">381+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
380 <a role="presentation" data-href="/files/releases">382 <a role="presentation" data-href="/files/releases">
381-<span class="lbl">releases</span>383+<span class="lbl">releases (g r)</span>
382 <span class="k">section</span>384 <span class="k">section</span>
383 </a>385 </a>
384 </li>386 </li>
385-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">387+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
386 <a role="presentation" data-href="/files/actions">388 <a role="presentation" data-href="/files/actions">
387-<span class="lbl">actions</span>389+<span class="lbl">actions (g a)</span>
388 <span class="k">section</span>390 <span class="k">section</span>
389 </a>391 </a>
390 </li>392 </li>
@@ -437,7 +439,9 @@
437 k-filter from:body -> ermite.focusFilter();439 k-filter from:body -> ermite.focusFilter();
438 k-gh from:body -> ermite.go('.brand');440 k-gh from:body -> ermite.go('.brand');
439 k-s from:body -> ermite.tab('s');441 k-s from:body -> ermite.tab('s');
440- k-t from:body -> ermite.tab('t');442+ k-f from:body -> ermite.tab('f');
443+ k-b from:body -> ermite.tab('b');
444+ k-a from:body -> ermite.tab('a');
441 k-c from:body -> ermite.tab('c');445 k-c from:body -> ermite.tab('c');
442 k-r from:body -> ermite.tab('r');446 k-r from:body -> ermite.tab('r');
443 k-m from:body -> ermite.tab('m');447 k-m from:body -> ermite.tab('m');
@@ -453,7 +457,6 @@
453 k-permalink from:body -> ermite.permalink();457 k-permalink from:body -> ermite.permalink();
454 k-merge from:body -> window.ermiteMR?.openMerge();458 k-merge from:body -> window.ermiteMR?.openMerge();
455 k-read from:body -> window.ermiteMR?.markRead();459 k-read from:body -> window.ermiteMR?.markRead();
456- k-gf from:body -> window.ermiteMR?.focusFiles();
457 k-keys from:body -> ermite.toggleKeys()">460 k-keys from:body -> ermite.toggleKeys()">
458 </div>461 </div>
459 </body>462 </body>
M internal/app/testdata/web/tree-subdir.html +33 -30
@@ -73,31 +73,31 @@
7373 <nav class="tabs" aria-label="Sections">
7474 <ul>
7575 <li>
76-<a href="/files" data-key="s" aria-keyshortcuts="s">Summary <kbd aria-hidden="true">s</kbd>
76+<a href="/files" data-key="g s">Summary <kbd aria-hidden="true">g s</kbd>
7777 </a>
7878 </li>
7979 <li>
80-<a href="/files/tree/main" data-key="t" aria-keyshortcuts="t" aria-current="page">Files <kbd aria-hidden="true">t</kbd>
80+<a href="/files/tree/main" data-key="g f" aria-current="page">Files <kbd aria-hidden="true">g f</kbd>
8181 </a>
8282 </li>
8383 <li>
84-<a href="/files/commits/main" data-key="c" aria-keyshortcuts="c">Commits <kbd aria-hidden="true">c</kbd>
84+<a href="/files/commits/main" data-key="g c">Commits <kbd aria-hidden="true">g c</kbd>
8585 </a>
8686 </li>
8787 <li>
88-<a href="/files/refs" data-key="r" aria-keyshortcuts="r">Branches &amp; tags <kbd aria-hidden="true">r</kbd>
88+<a href="/files/refs" data-key="g b">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
8989 </a>
9090 </li>
9191 <li>
92-<a href="/files/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>
92+<a href="/files/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
9393 </a>
9494 </li>
9595 <li>
96-<a href="/files/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>
96+<a href="/files/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
9797 </a>
9898 </li>
9999 <li>
100-<a href="/files/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>
100+<a href="/files/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
101101 </a>
102102 </li>
103103 </ul>
@@ -256,14 +256,16 @@
256256 </div>
257257 <div>
258258 <dt>
259-<kbd>s</kbd>
260-<kbd>t</kbd>
261-<kbd>c</kbd>
262-<kbd>r</kbd>
263-<kbd>m</kbd>
264-<kbd>i</kbd>
259+<kbd>g s</kbd>
260+<kbd>g f</kbd>
261+<kbd>g c</kbd>
262+<kbd>g b</kbd>
263+<kbd>g m</kbd>
264+<kbd>g i</kbd>
265+<kbd>g r</kbd>
266+<kbd>g a</kbd>
265267 </dt>
266-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>
268+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
267269 </div>
268270 <div>
269271 <dt>
@@ -362,45 +364,45 @@
362364 <span class="k">go</span>
363365 </a>
364366 </li>
365-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
367+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
366368 <a role="presentation" data-href="/files">
367-<span class="lbl">summary</span>
369+<span class="lbl">summary (g s)</span>
368370 <span class="k">section</span>
369371 </a>
370372 </li>
371-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
373+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
372374 <a role="presentation" data-href="/files/tree/main">
373-<span class="lbl">files</span>
375+<span class="lbl">files (g f)</span>
374376 <span class="k">section</span>
375377 </a>
376378 </li>
377-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
379+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
378380 <a role="presentation" data-href="/files/commits/main">
379-<span class="lbl">commits</span>
381+<span class="lbl">commits (g c)</span>
380382 <span class="k">section</span>
381383 </a>
382384 </li>
383-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
385+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
384386 <a role="presentation" data-href="/files/refs">
385-<span class="lbl">branches &amp; tags</span>
387+<span class="lbl">branches &amp; tags (g b)</span>
386388 <span class="k">section</span>
387389 </a>
388390 </li>
389-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
391+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
390392 <a role="presentation" data-href="/files/merge-requests">
391-<span class="lbl">merge requests</span>
393+<span class="lbl">merge requests (g m)</span>
392394 <span class="k">section</span>
393395 </a>
394396 </li>
395-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
397+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
396398 <a role="presentation" data-href="/files/releases">
397-<span class="lbl">releases</span>
399+<span class="lbl">releases (g r)</span>
398400 <span class="k">section</span>
399401 </a>
400402 </li>
401-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
403+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
402404 <a role="presentation" data-href="/files/actions">
403-<span class="lbl">actions</span>
405+<span class="lbl">actions (g a)</span>
404406 <span class="k">section</span>
405407 </a>
406408 </li>
@@ -453,7 +455,9 @@
453455 k-filter from:body -> ermite.focusFilter();
454456 k-gh from:body -> ermite.go('.brand');
455457 k-s from:body -> ermite.tab('s');
456- k-t from:body -> ermite.tab('t');
458+ k-f from:body -> ermite.tab('f');
459+ k-b from:body -> ermite.tab('b');
460+ k-a from:body -> ermite.tab('a');
457461 k-c from:body -> ermite.tab('c');
458462 k-r from:body -> ermite.tab('r');
459463 k-m from:body -> ermite.tab('m');
@@ -469,7 +473,6 @@
469473 k-permalink from:body -> ermite.permalink();
470474 k-merge from:body -> window.ermiteMR?.openMerge();
471475 k-read from:body -> window.ermiteMR?.markRead();
472- k-gf from:body -> window.ermiteMR?.focusFiles();
473476 k-keys from:body -> ermite.toggleKeys()">
474477 </div>
475478 </body>
@@ -73,31 +73,31 @@
73 <nav class="tabs" aria-label="Sections">73 <nav class="tabs" aria-label="Sections">
74 <ul>74 <ul>
75 <li>75 <li>
76-<a href="/files" data-key="s" aria-keyshortcuts="s">Summary <kbd aria-hidden="true">s</kbd>76+<a href="/files" data-key="g s">Summary <kbd aria-hidden="true">g s</kbd>
77 </a>77 </a>
78 </li>78 </li>
79 <li>79 <li>
80-<a href="/files/tree/main" data-key="t" aria-keyshortcuts="t" aria-current="page">Files <kbd aria-hidden="true">t</kbd>80+<a href="/files/tree/main" data-key="g f" aria-current="page">Files <kbd aria-hidden="true">g f</kbd>
81 </a>81 </a>
82 </li>82 </li>
83 <li>83 <li>
84-<a href="/files/commits/main" data-key="c" aria-keyshortcuts="c">Commits <kbd aria-hidden="true">c</kbd>84+<a href="/files/commits/main" data-key="g c">Commits <kbd aria-hidden="true">g c</kbd>
85 </a>85 </a>
86 </li>86 </li>
87 <li>87 <li>
88-<a href="/files/refs" data-key="r" aria-keyshortcuts="r">Branches &amp; tags <kbd aria-hidden="true">r</kbd>88+<a href="/files/refs" data-key="g b">Branches &amp; tags <kbd aria-hidden="true">g b</kbd>
89 </a>89 </a>
90 </li>90 </li>
91 <li>91 <li>
92-<a href="/files/merge-requests" data-key="m" aria-keyshortcuts="m">Merge requests <kbd aria-hidden="true">m</kbd>92+<a href="/files/merge-requests" data-key="g m">Merge requests <kbd aria-hidden="true">g m</kbd>
93 </a>93 </a>
94 </li>94 </li>
95 <li>95 <li>
96-<a href="/files/releases" data-key="l" aria-keyshortcuts="l">Releases <kbd aria-hidden="true">l</kbd>96+<a href="/files/releases" data-key="g r">Releases <kbd aria-hidden="true">g r</kbd>
97 </a>97 </a>
98 </li>98 </li>
99 <li>99 <li>
100-<a href="/files/actions" data-key="a" aria-keyshortcuts="a">Actions <kbd aria-hidden="true">a</kbd>100+<a href="/files/actions" data-key="g a">Actions <kbd aria-hidden="true">g a</kbd>
101 </a>101 </a>
102 </li>102 </li>
103 </ul>103 </ul>
@@ -256,14 +256,16 @@
256 </div>256 </div>
257 <div>257 <div>
258 <dt>258 <dt>
259-<kbd>s</kbd>259+<kbd>g s</kbd>
260-<kbd>t</kbd>260+<kbd>g f</kbd>
261-<kbd>c</kbd>261+<kbd>g c</kbd>
262-<kbd>r</kbd>262+<kbd>g b</kbd>
263-<kbd>m</kbd>263+<kbd>g m</kbd>
264-<kbd>i</kbd>264+<kbd>g i</kbd>
265+<kbd>g r</kbd>
266+<kbd>g a</kbd>
265 </dt>267 </dt>
266-<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd>268+<dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues, releases, actions (when available)</dd>
267 </div>269 </div>
268 <div>270 <div>
269 <dt>271 <dt>
@@ -362,45 +364,45 @@
362 <span class="k">go</span>364 <span class="k">go</span>
363 </a>365 </a>
364 </li>366 </li>
365-<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">367+<li id="cmd-o1" role="option" aria-selected="false" data-k="section summary (g s)">
366 <a role="presentation" data-href="/files">368 <a role="presentation" data-href="/files">
367-<span class="lbl">summary</span>369+<span class="lbl">summary (g s)</span>
368 <span class="k">section</span>370 <span class="k">section</span>
369 </a>371 </a>
370 </li>372 </li>
371-<li id="cmd-o2" role="option" aria-selected="false" data-k="section files">373+<li id="cmd-o2" role="option" aria-selected="false" data-k="section files (g f)">
372 <a role="presentation" data-href="/files/tree/main">374 <a role="presentation" data-href="/files/tree/main">
373-<span class="lbl">files</span>375+<span class="lbl">files (g f)</span>
374 <span class="k">section</span>376 <span class="k">section</span>
375 </a>377 </a>
376 </li>378 </li>
377-<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">379+<li id="cmd-o3" role="option" aria-selected="false" data-k="section commits (g c)">
378 <a role="presentation" data-href="/files/commits/main">380 <a role="presentation" data-href="/files/commits/main">
379-<span class="lbl">commits</span>381+<span class="lbl">commits (g c)</span>
380 <span class="k">section</span>382 <span class="k">section</span>
381 </a>383 </a>
382 </li>384 </li>
383-<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">385+<li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags (g b)">
384 <a role="presentation" data-href="/files/refs">386 <a role="presentation" data-href="/files/refs">
385-<span class="lbl">branches &amp; tags</span>387+<span class="lbl">branches &amp; tags (g b)</span>
386 <span class="k">section</span>388 <span class="k">section</span>
387 </a>389 </a>
388 </li>390 </li>
389-<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">391+<li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests (g m)">
390 <a role="presentation" data-href="/files/merge-requests">392 <a role="presentation" data-href="/files/merge-requests">
391-<span class="lbl">merge requests</span>393+<span class="lbl">merge requests (g m)</span>
392 <span class="k">section</span>394 <span class="k">section</span>
393 </a>395 </a>
394 </li>396 </li>
395-<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">397+<li id="cmd-o6" role="option" aria-selected="false" data-k="section releases (g r)">
396 <a role="presentation" data-href="/files/releases">398 <a role="presentation" data-href="/files/releases">
397-<span class="lbl">releases</span>399+<span class="lbl">releases (g r)</span>
398 <span class="k">section</span>400 <span class="k">section</span>
399 </a>401 </a>
400 </li>402 </li>
401-<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">403+<li id="cmd-o7" role="option" aria-selected="false" data-k="section actions (g a)">
402 <a role="presentation" data-href="/files/actions">404 <a role="presentation" data-href="/files/actions">
403-<span class="lbl">actions</span>405+<span class="lbl">actions (g a)</span>
404 <span class="k">section</span>406 <span class="k">section</span>
405 </a>407 </a>
406 </li>408 </li>
@@ -453,7 +455,9 @@
453 k-filter from:body -> ermite.focusFilter();455 k-filter from:body -> ermite.focusFilter();
454 k-gh from:body -> ermite.go('.brand');456 k-gh from:body -> ermite.go('.brand');
455 k-s from:body -> ermite.tab('s');457 k-s from:body -> ermite.tab('s');
456- k-t from:body -> ermite.tab('t');458+ k-f from:body -> ermite.tab('f');
459+ k-b from:body -> ermite.tab('b');
460+ k-a from:body -> ermite.tab('a');
457 k-c from:body -> ermite.tab('c');461 k-c from:body -> ermite.tab('c');
458 k-r from:body -> ermite.tab('r');462 k-r from:body -> ermite.tab('r');
459 k-m from:body -> ermite.tab('m');463 k-m from:body -> ermite.tab('m');
@@ -469,7 +473,6 @@
469 k-permalink from:body -> ermite.permalink();473 k-permalink from:body -> ermite.permalink();
470 k-merge from:body -> window.ermiteMR?.openMerge();474 k-merge from:body -> window.ermiteMR?.openMerge();
471 k-read from:body -> window.ermiteMR?.markRead();475 k-read from:body -> window.ermiteMR?.markRead();
472- k-gf from:body -> window.ermiteMR?.focusFiles();
473 k-keys from:body -> ermite.toggleKeys()">476 k-keys from:body -> ermite.toggleKeys()">
474 </div>477 </div>
475 </body>478 </body>
M internal/app/web_issue_test.go +1 -1
@@ -28,7 +28,7 @@ func TestWebIssuePagesEndToEnd(t *testing.T) {
2828 // Off by default: no page, no tab, for the owner as well.
2929 assert.Equal(t, 404, b.get("/proj/issues", as(token)).status)
3030 assert.Equal(t, 404, b.get("/proj/issues/new", as(token)).status)
31- assert.NotContains(t, b.get("/proj", as(token)).body, `data-key="i"`)
31+ assert.NotContains(t, b.get("/proj", as(token)).body, `data-key="g i"`)
3232
3333 mode := domain.IssuesOwner
3434 _, err := r.forge.Repos.Update(r.ctx, domain.Root(), r.name("proj"), ports.UpdateRepoParams{Issues: &mode})
@@ -28,7 +28,7 @@ func TestWebIssuePagesEndToEnd(t *testing.T) {
28 // Off by default: no page, no tab, for the owner as well.28 // Off by default: no page, no tab, for the owner as well.
29 assert.Equal(t, 404, b.get("/proj/issues", as(token)).status)29 assert.Equal(t, 404, b.get("/proj/issues", as(token)).status)
30 assert.Equal(t, 404, b.get("/proj/issues/new", as(token)).status)30 assert.Equal(t, 404, b.get("/proj/issues/new", as(token)).status)
31- assert.NotContains(t, b.get("/proj", as(token)).body, `data-key="i"`)31+ assert.NotContains(t, b.get("/proj", as(token)).body, `data-key="g i"`)
32 32
33 mode := domain.IssuesOwner33 mode := domain.IssuesOwner
34 _, err := r.forge.Repos.Update(r.ctx, domain.Root(), r.name("proj"), ports.UpdateRepoParams{Issues: &mode})34 _, err := r.forge.Repos.Update(r.ctx, domain.Root(), r.name("proj"), ports.UpdateRepoParams{Issues: &mode})
M internal/app/web_mr_test.go +1 -1
@@ -93,7 +93,7 @@ func TestWebMergeRequestPagesRender(t *testing.T) {
9393 require.Equal(t, 200, list.status)
9494 assert.Contains(t, list.body, "no open merge requests")
9595 assert.Contains(t, list.body, `href="/proj/compare/feat/search"`)
96- assert.Contains(t, list.body, `data-key="m"`, "the section of the nav")
96+ assert.Contains(t, list.body, `data-key="g m"`, "the section of the nav")
9797 assert.NotContains(t, strings.ToLower(list.body), "proposal")
9898
9999 // Open one from the compare page.
@@ -93,7 +93,7 @@ func TestWebMergeRequestPagesRender(t *testing.T) {
93 require.Equal(t, 200, list.status)93 require.Equal(t, 200, list.status)
94 assert.Contains(t, list.body, "no open merge requests")94 assert.Contains(t, list.body, "no open merge requests")
95 assert.Contains(t, list.body, `href="/proj/compare/feat/search"`)95 assert.Contains(t, list.body, `href="/proj/compare/feat/search"`)
96- assert.Contains(t, list.body, `data-key="m"`, "the section of the nav")96+ assert.Contains(t, list.body, `data-key="g m"`, "the section of the nav")
97 assert.NotContains(t, strings.ToLower(list.body), "proposal")97 assert.NotContains(t, strings.ToLower(list.body), "proposal")
98 98
99 // Open one from the compare page.99 // Open one from the compare page.
M internal/ports/mocks/ReleaseStore.go +66 -0
@@ -311,6 +311,72 @@ func (_c *ReleaseStore_GetAssetByName_Call) RunAndReturn(run func(ctx context.Co
311311 return _c
312312 }
313313
314+// GetLatestRelease provides a mock function for the type ReleaseStore
315+func (_mock *ReleaseStore) GetLatestRelease(ctx context.Context, repo domain.RepoName) (domain.Release, error) {
316+ ret := _mock.Called(ctx, repo)
317+
318+ if len(ret) == 0 {
319+ panic("no return value specified for GetLatestRelease")
320+ }
321+
322+ var r0 domain.Release
323+ var r1 error
324+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName) (domain.Release, error)); ok {
325+ return returnFunc(ctx, repo)
326+ }
327+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName) domain.Release); ok {
328+ r0 = returnFunc(ctx, repo)
329+ } else {
330+ r0 = ret.Get(0).(domain.Release)
331+ }
332+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName) error); ok {
333+ r1 = returnFunc(ctx, repo)
334+ } else {
335+ r1 = ret.Error(1)
336+ }
337+ return r0, r1
338+}
339+
340+// ReleaseStore_GetLatestRelease_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestRelease'
341+type ReleaseStore_GetLatestRelease_Call struct {
342+ *mock.Call
343+}
344+
345+// GetLatestRelease is a helper method to define mock.On call
346+// - ctx context.Context
347+// - repo domain.RepoName
348+func (_e *ReleaseStore_Expecter) GetLatestRelease(ctx any, repo any) *ReleaseStore_GetLatestRelease_Call {
349+ return &ReleaseStore_GetLatestRelease_Call{Call: _e.mock.On("GetLatestRelease", ctx, repo)}
350+}
351+
352+func (_c *ReleaseStore_GetLatestRelease_Call) Run(run func(ctx context.Context, repo domain.RepoName)) *ReleaseStore_GetLatestRelease_Call {
353+ _c.Call.Run(func(args mock.Arguments) {
354+ var arg0 context.Context
355+ if args[0] != nil {
356+ arg0 = args[0].(context.Context)
357+ }
358+ var arg1 domain.RepoName
359+ if args[1] != nil {
360+ arg1 = args[1].(domain.RepoName)
361+ }
362+ run(
363+ arg0,
364+ arg1,
365+ )
366+ })
367+ return _c
368+}
369+
370+func (_c *ReleaseStore_GetLatestRelease_Call) Return(release domain.Release, err error) *ReleaseStore_GetLatestRelease_Call {
371+ _c.Call.Return(release, err)
372+ return _c
373+}
374+
375+func (_c *ReleaseStore_GetLatestRelease_Call) RunAndReturn(run func(ctx context.Context, repo domain.RepoName) (domain.Release, error)) *ReleaseStore_GetLatestRelease_Call {
376+ _c.Call.Return(run)
377+ return _c
378+}
379+
314380 // GetRelease provides a mock function for the type ReleaseStore
315381 func (_mock *ReleaseStore) GetRelease(ctx context.Context, repo domain.RepoName, id int64) (domain.Release, error) {
316382 ret := _mock.Called(ctx, repo, id)
@@ -311,6 +311,72 @@ func (_c *ReleaseStore_GetAssetByName_Call) RunAndReturn(run func(ctx context.Co
311 return _c311 return _c
312 }312 }
313 313
314+// GetLatestRelease provides a mock function for the type ReleaseStore
315+func (_mock *ReleaseStore) GetLatestRelease(ctx context.Context, repo domain.RepoName) (domain.Release, error) {
316+ ret := _mock.Called(ctx, repo)
317+
318+ if len(ret) == 0 {
319+ panic("no return value specified for GetLatestRelease")
320+ }
321+
322+ var r0 domain.Release
323+ var r1 error
324+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName) (domain.Release, error)); ok {
325+ return returnFunc(ctx, repo)
326+ }
327+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName) domain.Release); ok {
328+ r0 = returnFunc(ctx, repo)
329+ } else {
330+ r0 = ret.Get(0).(domain.Release)
331+ }
332+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName) error); ok {
333+ r1 = returnFunc(ctx, repo)
334+ } else {
335+ r1 = ret.Error(1)
336+ }
337+ return r0, r1
338+}
339+
340+// ReleaseStore_GetLatestRelease_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestRelease'
341+type ReleaseStore_GetLatestRelease_Call struct {
342+ *mock.Call
343+}
344+
345+// GetLatestRelease is a helper method to define mock.On call
346+// - ctx context.Context
347+// - repo domain.RepoName
348+func (_e *ReleaseStore_Expecter) GetLatestRelease(ctx any, repo any) *ReleaseStore_GetLatestRelease_Call {
349+ return &ReleaseStore_GetLatestRelease_Call{Call: _e.mock.On("GetLatestRelease", ctx, repo)}
350+}
351+
352+func (_c *ReleaseStore_GetLatestRelease_Call) Run(run func(ctx context.Context, repo domain.RepoName)) *ReleaseStore_GetLatestRelease_Call {
353+ _c.Call.Run(func(args mock.Arguments) {
354+ var arg0 context.Context
355+ if args[0] != nil {
356+ arg0 = args[0].(context.Context)
357+ }
358+ var arg1 domain.RepoName
359+ if args[1] != nil {
360+ arg1 = args[1].(domain.RepoName)
361+ }
362+ run(
363+ arg0,
364+ arg1,
365+ )
366+ })
367+ return _c
368+}
369+
370+func (_c *ReleaseStore_GetLatestRelease_Call) Return(release domain.Release, err error) *ReleaseStore_GetLatestRelease_Call {
371+ _c.Call.Return(release, err)
372+ return _c
373+}
374+
375+func (_c *ReleaseStore_GetLatestRelease_Call) RunAndReturn(run func(ctx context.Context, repo domain.RepoName) (domain.Release, error)) *ReleaseStore_GetLatestRelease_Call {
376+ _c.Call.Return(run)
377+ return _c
378+}
379+
314 // GetRelease provides a mock function for the type ReleaseStore380 // GetRelease provides a mock function for the type ReleaseStore
315 func (_mock *ReleaseStore) GetRelease(ctx context.Context, repo domain.RepoName, id int64) (domain.Release, error) {381 func (_mock *ReleaseStore) GetRelease(ctx context.Context, repo domain.RepoName, id int64) (domain.Release, error) {
316 ret := _mock.Called(ctx, repo, id)382 ret := _mock.Called(ctx, repo, id)
M internal/ports/release.go +3 -0
@@ -13,6 +13,8 @@ import (
1313 // Used includes both uploading and uploaded assets.
1414 type ReleaseWrite struct {
1515 Repo domain.RepoName
16+ SetLatest bool // false leaves the latest index unchanged
17+ LatestID int64 // zero means no eligible release when SetLatest
1618 Used int64
1719 UsageVersion uint64
1820 PutReleases, DeleteReleases []domain.Release
@@ -21,6 +23,7 @@ type ReleaseWrite struct {
2123
2224 // ReleaseStore keeps metadata, never contents. *kvstore.Releases implements it.
2325 type ReleaseStore interface {
26+ GetLatestRelease(ctx context.Context, repo domain.RepoName) (domain.Release, error)
2427 NextReleaseID(ctx context.Context) (int64, error)
2528 ReleaseUsage(ctx context.Context, repo domain.RepoName) (used int64, version uint64, err error)
2629 GetRelease(ctx context.Context, repo domain.RepoName, id int64) (domain.Release, error)
@@ -13,6 +13,8 @@ import (
13 // Used includes both uploading and uploaded assets.13 // Used includes both uploading and uploaded assets.
14 type ReleaseWrite struct {14 type ReleaseWrite struct {
15 Repo domain.RepoName15 Repo domain.RepoName
16+ SetLatest bool // false leaves the latest index unchanged
17+ LatestID int64 // zero means no eligible release when SetLatest
16 Used int6418 Used int64
17 UsageVersion uint6419 UsageVersion uint64
18 PutReleases, DeleteReleases []domain.Release20 PutReleases, DeleteReleases []domain.Release
@@ -21,6 +23,7 @@ type ReleaseWrite struct {
21 23
22 // ReleaseStore keeps metadata, never contents. *kvstore.Releases implements it.24 // ReleaseStore keeps metadata, never contents. *kvstore.Releases implements it.
23 type ReleaseStore interface {25 type ReleaseStore interface {
26+ GetLatestRelease(ctx context.Context, repo domain.RepoName) (domain.Release, error)
24 NextReleaseID(ctx context.Context) (int64, error)27 NextReleaseID(ctx context.Context) (int64, error)
25 ReleaseUsage(ctx context.Context, repo domain.RepoName) (used int64, version uint64, err error)28 ReleaseUsage(ctx context.Context, repo domain.RepoName) (used int64, version uint64, err error)
26 GetRelease(ctx context.Context, repo domain.RepoName, id int64) (domain.Release, error)29 GetRelease(ctx context.Context, repo domain.RepoName, id int64) (domain.Release, error)
M internal/usecase/releases.go +25 -9
@@ -92,6 +92,30 @@ func (r *Releases) updateReleases(ctx context.Context, repo domain.RepoName, fn
9292 if err = fn(&w); err != nil {
9393 return err
9494 }
95+ if len(w.PutReleases) > 0 || len(w.DeleteReleases) > 0 {
96+ w.SetLatest = true
97+ all, err := r.store.ListReleases(ctx, repo)
98+ if err != nil {
99+ return err
100+ }
101+ byID := make(map[int64]domain.Release, len(all)+len(w.PutReleases))
102+ for _, rel := range all {
103+ byID[rel.ID] = rel
104+ }
105+ for _, rel := range w.DeleteReleases {
106+ delete(byID, rel.ID)
107+ }
108+ for _, rel := range w.PutReleases {
109+ byID[rel.ID] = rel
110+ }
111+ all = all[:0]
112+ for _, rel := range byID {
113+ all = append(all, rel)
114+ }
115+ if latest, ok := domain.LatestRelease(all); ok {
116+ w.LatestID = latest.ID
117+ }
118+ }
95119 err = r.store.ApplyReleases(ctx, w)
96120 if errors.Is(err, domain.ErrConflict) {
97121 continue
@@ -245,15 +269,7 @@ func (r *Releases) GetRelease(ctx context.Context, p domain.Principal, name doma
245269 case ref.Tag != "":
246270 rel, err = r.store.GetReleaseByTag(ctx, name, ref.Tag)
247271 default:
248- var all []domain.Release
249- all, err = r.store.ListReleases(ctx, name)
250- if err == nil {
251- var ok bool
252- rel, ok = domain.LatestRelease(all)
253- if !ok {
254- err = domain.ErrNotFound
255- }
256- }
272+ rel, err = r.store.GetLatestRelease(ctx, name)
257273 }
258274 if err == nil && rel.Draft && !releaseWriter(p, name) {
259275 err = domain.ErrNotFound
@@ -92,6 +92,30 @@ func (r *Releases) updateReleases(ctx context.Context, repo domain.RepoName, fn
92 if err = fn(&w); err != nil {92 if err = fn(&w); err != nil {
93 return err93 return err
94 }94 }
95+ if len(w.PutReleases) > 0 || len(w.DeleteReleases) > 0 {
96+ w.SetLatest = true
97+ all, err := r.store.ListReleases(ctx, repo)
98+ if err != nil {
99+ return err
100+ }
101+ byID := make(map[int64]domain.Release, len(all)+len(w.PutReleases))
102+ for _, rel := range all {
103+ byID[rel.ID] = rel
104+ }
105+ for _, rel := range w.DeleteReleases {
106+ delete(byID, rel.ID)
107+ }
108+ for _, rel := range w.PutReleases {
109+ byID[rel.ID] = rel
110+ }
111+ all = all[:0]
112+ for _, rel := range byID {
113+ all = append(all, rel)
114+ }
115+ if latest, ok := domain.LatestRelease(all); ok {
116+ w.LatestID = latest.ID
117+ }
118+ }
95 err = r.store.ApplyReleases(ctx, w)119 err = r.store.ApplyReleases(ctx, w)
96 if errors.Is(err, domain.ErrConflict) {120 if errors.Is(err, domain.ErrConflict) {
97 continue121 continue
@@ -245,15 +269,7 @@ func (r *Releases) GetRelease(ctx context.Context, p domain.Principal, name doma
245 case ref.Tag != "":269 case ref.Tag != "":
246 rel, err = r.store.GetReleaseByTag(ctx, name, ref.Tag)270 rel, err = r.store.GetReleaseByTag(ctx, name, ref.Tag)
247 default:271 default:
248- var all []domain.Release272+ rel, err = r.store.GetLatestRelease(ctx, name)
249- all, err = r.store.ListReleases(ctx, name)
250- if err == nil {
251- var ok bool
252- rel, ok = domain.LatestRelease(all)
253- if !ok {
254- err = domain.ErrNotFound
255- }
256- }
257 }273 }
258 if err == nil && rel.Draft && !releaseWriter(p, name) {274 if err == nil && rel.Draft && !releaseWriter(p, name) {
259 err = domain.ErrNotFound275 err = domain.ErrNotFound
M internal/usecase/releases_integration_test.go +59 -0
@@ -263,6 +263,7 @@ func TestReleaseWriteRetriesUsageConflict(t *testing.T) {
263263 reader.EXPECT().ResolveRevision(ctx, repo, rev).Return(domain.ResolvedRevision{}, domain.ErrNotFound)
264264 store.EXPECT().GetReleaseByTag(ctx, repo, "v1").Return(domain.Release{}, domain.ErrNotFound)
265265 store.EXPECT().NextReleaseID(ctx).Return(int64(1), nil)
266+ store.EXPECT().ListReleases(ctx, repo).Return(nil, nil).Twice()
266267 store.EXPECT().ReleaseUsage(ctx, repo).Return(int64(0), uint64(1), nil).Once()
267268 store.EXPECT().ApplyReleases(ctx, mock.MatchedBy(func(w ports.ReleaseWrite) bool { return w.UsageVersion == 1 && len(w.PutReleases) == 1 })).Return(domain.ErrConflict).Once()
268269 store.EXPECT().ReleaseUsage(ctx, repo).Return(int64(0), uint64(2), nil).Once()
@@ -334,3 +335,61 @@ func TestReleaseUpdateTargetNeverMovesExistingTag(t *testing.T) {
334335 require.NoError(t, err)
335336 require.Equal(t, oidA, got.TargetCommitish)
336337 }
338+
339+func TestReleaseLatestIndexTransitions(t *testing.T) {
340+ r := newReleaseRig(t, ports.ReleaseLimits{})
341+ latest := func(id int64) {
342+ t.Helper()
343+ got, err := r.svc.GetRelease(r.ctx, domain.Anonymous(), r.repo, ports.ReleaseRef{Latest: true})
344+ if id == 0 {
345+ require.ErrorIs(t, err, domain.ErrNotFound)
346+ return
347+ }
348+ require.NoError(t, err)
349+ require.Equal(t, id, got.ID)
350+ }
351+ update := func(id int64, p ports.UpdateReleaseParams) {
352+ t.Helper()
353+ _, err := r.svc.UpdateRelease(r.ctx, domain.Root(), r.repo, id, p)
354+ require.NoError(t, err)
355+ }
356+ yes, no := true, false
357+ draft := r.create(t, "draft", true)
358+ latest(0)
359+ first := r.create(t, "v1", false)
360+ latest(first.ID)
361+ update(draft.ID, ports.UpdateReleaseParams{Draft: &no})
362+ latest(first.ID) // equal creation times: higher ID wins
363+ second := r.create(t, "v2", false)
364+ latest(second.ID)
365+ update(second.ID, ports.UpdateReleaseParams{Prerelease: &yes})
366+ latest(first.ID)
367+ update(second.ID, ports.UpdateReleaseParams{Prerelease: &no})
368+ latest(second.ID)
369+ update(first.ID, ports.UpdateReleaseParams{MakeLatest: &yes})
370+ latest(first.ID)
371+ tag := "renamed"
372+ r.resolve(t, tag, nil)
373+ update(first.ID, ports.UpdateReleaseParams{Tag: &tag})
374+ latest(first.ID)
375+ require.NoError(t, r.svc.DeleteRelease(r.ctx, domain.Root(), r.repo, first.ID))
376+ latest(second.ID)
377+ update(second.ID, ports.UpdateReleaseParams{Draft: &yes})
378+ latest(draft.ID)
379+ update(draft.ID, ports.UpdateReleaseParams{Draft: &yes})
380+ latest(0)
381+}
382+
383+// No ListReleases expectation: latest resolution must use only point reads.
384+func TestReleaseLatestUsesIndex(t *testing.T) {
385+ ctx := t.Context()
386+ repo := name(t, "indexed")
387+ store := mocks.NewReleaseStore(t)
388+ meta := mocks.NewRepoMetaStore(t)
389+ meta.EXPECT().Get(ctx, repo).Return(domain.Repo{Name: repo, Visibility: domain.VisibilityPublic}, nil)
390+ store.EXPECT().GetLatestRelease(ctx, repo).Return(domain.Release{ID: 42, Repo: repo}, nil).Once()
391+ svc := usecase.NewReleases(store, nil, meta, nil, nil, nil, nil, nil, ports.ReleaseLimits{})
392+ got, err := svc.GetRelease(ctx, domain.Anonymous(), repo, ports.ReleaseRef{Latest: true})
393+ require.NoError(t, err)
394+ require.EqualValues(t, 42, got.ID)
395+}
@@ -263,6 +263,7 @@ func TestReleaseWriteRetriesUsageConflict(t *testing.T) {
263 reader.EXPECT().ResolveRevision(ctx, repo, rev).Return(domain.ResolvedRevision{}, domain.ErrNotFound)263 reader.EXPECT().ResolveRevision(ctx, repo, rev).Return(domain.ResolvedRevision{}, domain.ErrNotFound)
264 store.EXPECT().GetReleaseByTag(ctx, repo, "v1").Return(domain.Release{}, domain.ErrNotFound)264 store.EXPECT().GetReleaseByTag(ctx, repo, "v1").Return(domain.Release{}, domain.ErrNotFound)
265 store.EXPECT().NextReleaseID(ctx).Return(int64(1), nil)265 store.EXPECT().NextReleaseID(ctx).Return(int64(1), nil)
266+ store.EXPECT().ListReleases(ctx, repo).Return(nil, nil).Twice()
266 store.EXPECT().ReleaseUsage(ctx, repo).Return(int64(0), uint64(1), nil).Once()267 store.EXPECT().ReleaseUsage(ctx, repo).Return(int64(0), uint64(1), nil).Once()
267 store.EXPECT().ApplyReleases(ctx, mock.MatchedBy(func(w ports.ReleaseWrite) bool { return w.UsageVersion == 1 && len(w.PutReleases) == 1 })).Return(domain.ErrConflict).Once()268 store.EXPECT().ApplyReleases(ctx, mock.MatchedBy(func(w ports.ReleaseWrite) bool { return w.UsageVersion == 1 && len(w.PutReleases) == 1 })).Return(domain.ErrConflict).Once()
268 store.EXPECT().ReleaseUsage(ctx, repo).Return(int64(0), uint64(2), nil).Once()269 store.EXPECT().ReleaseUsage(ctx, repo).Return(int64(0), uint64(2), nil).Once()
@@ -334,3 +335,61 @@ func TestReleaseUpdateTargetNeverMovesExistingTag(t *testing.T) {
334 require.NoError(t, err)335 require.NoError(t, err)
335 require.Equal(t, oidA, got.TargetCommitish)336 require.Equal(t, oidA, got.TargetCommitish)
336 }337 }
338+
339+func TestReleaseLatestIndexTransitions(t *testing.T) {
340+ r := newReleaseRig(t, ports.ReleaseLimits{})
341+ latest := func(id int64) {
342+ t.Helper()
343+ got, err := r.svc.GetRelease(r.ctx, domain.Anonymous(), r.repo, ports.ReleaseRef{Latest: true})
344+ if id == 0 {
345+ require.ErrorIs(t, err, domain.ErrNotFound)
346+ return
347+ }
348+ require.NoError(t, err)
349+ require.Equal(t, id, got.ID)
350+ }
351+ update := func(id int64, p ports.UpdateReleaseParams) {
352+ t.Helper()
353+ _, err := r.svc.UpdateRelease(r.ctx, domain.Root(), r.repo, id, p)
354+ require.NoError(t, err)
355+ }
356+ yes, no := true, false
357+ draft := r.create(t, "draft", true)
358+ latest(0)
359+ first := r.create(t, "v1", false)
360+ latest(first.ID)
361+ update(draft.ID, ports.UpdateReleaseParams{Draft: &no})
362+ latest(first.ID) // equal creation times: higher ID wins
363+ second := r.create(t, "v2", false)
364+ latest(second.ID)
365+ update(second.ID, ports.UpdateReleaseParams{Prerelease: &yes})
366+ latest(first.ID)
367+ update(second.ID, ports.UpdateReleaseParams{Prerelease: &no})
368+ latest(second.ID)
369+ update(first.ID, ports.UpdateReleaseParams{MakeLatest: &yes})
370+ latest(first.ID)
371+ tag := "renamed"
372+ r.resolve(t, tag, nil)
373+ update(first.ID, ports.UpdateReleaseParams{Tag: &tag})
374+ latest(first.ID)
375+ require.NoError(t, r.svc.DeleteRelease(r.ctx, domain.Root(), r.repo, first.ID))
376+ latest(second.ID)
377+ update(second.ID, ports.UpdateReleaseParams{Draft: &yes})
378+ latest(draft.ID)
379+ update(draft.ID, ports.UpdateReleaseParams{Draft: &yes})
380+ latest(0)
381+}
382+
383+// No ListReleases expectation: latest resolution must use only point reads.
384+func TestReleaseLatestUsesIndex(t *testing.T) {
385+ ctx := t.Context()
386+ repo := name(t, "indexed")
387+ store := mocks.NewReleaseStore(t)
388+ meta := mocks.NewRepoMetaStore(t)
389+ meta.EXPECT().Get(ctx, repo).Return(domain.Repo{Name: repo, Visibility: domain.VisibilityPublic}, nil)
390+ store.EXPECT().GetLatestRelease(ctx, repo).Return(domain.Release{ID: 42, Repo: repo}, nil).Once()
391+ svc := usecase.NewReleases(store, nil, meta, nil, nil, nil, nil, nil, ports.ReleaseLimits{})
392+ got, err := svc.GetRelease(ctx, domain.Anonymous(), repo, ports.ReleaseRef{Latest: true})
393+ require.NoError(t, err)
394+ require.EqualValues(t, 42, got.ID)
395+}
A internal/usecase/releases_write_test.go +27 -0
@@ -0,0 +1,27 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+package usecase
3+
4+import (
5+ "testing"
6+
7+ "bnema.dev/ermite/internal/domain"
8+ "bnema.dev/ermite/internal/ports"
9+ "bnema.dev/ermite/internal/ports/mocks"
10+ "github.com/stretchr/testify/require"
11+)
12+
13+// No ListReleases expectation: asset-only changes must not scan releases.
14+func TestAssetOnlyWriteDoesNotListReleases(t *testing.T) {
15+ ctx := t.Context()
16+ repo, err := domain.ParseRepoName("assets")
17+ require.NoError(t, err)
18+ store := mocks.NewReleaseStore(t)
19+ asset := domain.ReleaseAsset{ID: 1, Repo: repo}
20+ store.EXPECT().ReleaseUsage(ctx, repo).Return(int64(0), uint64(2), nil).Once()
21+ store.EXPECT().ApplyReleases(ctx, ports.ReleaseWrite{Repo: repo, UsageVersion: 2, PutAssets: []domain.ReleaseAsset{asset}}).Return(nil).Once()
22+ svc := &Releases{store: store}
23+ require.NoError(t, svc.updateReleases(ctx, repo, func(w *ports.ReleaseWrite) error {
24+ w.PutAssets = append(w.PutAssets, asset)
25+ return nil
26+ }))
27+}
@@ -0,0 +1,27 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+package usecase
3+
4+import (
5+ "testing"
6+
7+ "bnema.dev/ermite/internal/domain"
8+ "bnema.dev/ermite/internal/ports"
9+ "bnema.dev/ermite/internal/ports/mocks"
10+ "github.com/stretchr/testify/require"
11+)
12+
13+// No ListReleases expectation: asset-only changes must not scan releases.
14+func TestAssetOnlyWriteDoesNotListReleases(t *testing.T) {
15+ ctx := t.Context()
16+ repo, err := domain.ParseRepoName("assets")
17+ require.NoError(t, err)
18+ store := mocks.NewReleaseStore(t)
19+ asset := domain.ReleaseAsset{ID: 1, Repo: repo}
20+ store.EXPECT().ReleaseUsage(ctx, repo).Return(int64(0), uint64(2), nil).Once()
21+ store.EXPECT().ApplyReleases(ctx, ports.ReleaseWrite{Repo: repo, UsageVersion: 2, PutAssets: []domain.ReleaseAsset{asset}}).Return(nil).Once()
22+ svc := &Releases{store: store}
23+ require.NoError(t, svc.updateReleases(ctx, repo, func(w *ports.ReleaseWrite) error {
24+ w.PutAssets = append(w.PutAssets, asset)
25+ return nil
26+ }))
27+}

Comments0 open / 0

no comments yet.

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
l
merge request: the list of files (j k move, enter opens, esc back)
M
merge request: the merge menu; there 1–3 pick the mode, D deletes the branch, enter merges
R
merge request: mark the head read
esc
close a dialog, leave a field

Branches 2

Tags 41

↑↓ moveenter openesc close