detached a1f8e85

feat(web): make the wide mode a global w shortcut, set before the first paint, and tighten the merge request header

commit
a1f8e85bb4d541294e1ab39619856cc323b9ab05
author
brice · · 2026-10-19 15:13 UTC
parent
fcc7703
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
10 files · +22 -17
M internal/adapters/web/diff.templ +2 -2
@@ -131,8 +131,8 @@ templ diffViewMenu(p DiffView) {
131131 </details>
132132 }
133133
134-// wideButton gives the diff the whole width of the screen: the rail goes to
135-// the right edge and the code takes the rest. ermite.js keeps the choice.
134+// wideButton turns the wide mode (w, on every page) on or off: the rail goes
135+// to the right edge and the code takes the rest. ermite.js keeps the choice.
136136 templ wideButton() {
137137 <button type="button" class="btn" data-js data-fire="wide" data-wide-toggle aria-pressed="false" aria-keyshortcuts="w" title="use the whole width of the screen">
138138 Wide<kbd aria-hidden="true">w</kbd>
@@ -131,8 +131,8 @@ templ diffViewMenu(p DiffView) {
131 </details>131 </details>
132 }132 }
133 133
134-// wideButton gives the diff the whole width of the screen: the rail goes to134+// wideButton turns the wide mode (w, on every page) on or off: the rail goes
135-// the right edge and the code takes the rest. ermite.js keeps the choice.135+// to the right edge and the code takes the rest. ermite.js keeps the choice.
136 templ wideButton() {136 templ wideButton() {
137 <button type="button" class="btn" data-js data-fire="wide" data-wide-toggle aria-pressed="false" aria-keyshortcuts="w" title="use the whole width of the screen">137 <button type="button" class="btn" data-js data-fire="wide" data-wide-toggle aria-pressed="false" aria-keyshortcuts="w" title="use the whole width of the screen">
138 Wide<kbd aria-hidden="true">w</kbd>138 Wide<kbd aria-hidden="true">w</kbd>
M internal/adapters/web/diff_templ.go +2 -2 generated file: collapsed
M internal/adapters/web/overlays.templ +1 -1
@@ -28,9 +28,9 @@ templ OverlayHelp(mr bool) {
2828 <div><dt><kbd>b</kbd></dt><dd>{ "switch" } branch or tag</dd></div>
2929 <div><dt><kbd>s</kbd> <kbd>t</kbd> <kbd>c</kbd> <kbd>r</kbd> <kbd>m</kbd> <kbd>i</kbd></dt><dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd></div>
3030 <div><dt><kbd>g h</kbd></dt><dd>home</dd></div>
31+ <div><dt><kbd>w</kbd></dt><dd>wide mode: use the whole width of the screen, or not (remembered)</dd></div>
3132 <div><dt><kbd>y</kbd></dt><dd>copy a permalink pinned to the commit</dd></div>
3233 <div><dt><kbd>d</kbd> <kbd>u</kbd> <kbd>x</kbd></dt><dd>diff: split / unified / collapse or expand all</dd></div>
33- <div><dt><kbd>w</kbd></dt><dd>diff: use the whole width of the screen, or not</dd></div>
3434 <div><dt><kbd>e</kbd> <kbd>E</kbd></dt><dd>diff: the whole file in view / every file, with the lines the hunks hide</dd></div>
3535 <div><dt><kbd>n</kbd></dt><dd>merge request: next note in the diff</dd></div>
3636 if mr {
@@ -28,9 +28,9 @@ templ OverlayHelp(mr bool) {
28 <div><dt><kbd>b</kbd></dt><dd>{ "switch" } branch or tag</dd></div>28 <div><dt><kbd>b</kbd></dt><dd>{ "switch" } branch or tag</dd></div>
29 <div><dt><kbd>s</kbd> <kbd>t</kbd> <kbd>c</kbd> <kbd>r</kbd> <kbd>m</kbd> <kbd>i</kbd></dt><dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd></div>29 <div><dt><kbd>s</kbd> <kbd>t</kbd> <kbd>c</kbd> <kbd>r</kbd> <kbd>m</kbd> <kbd>i</kbd></dt><dd>tabs: summary, files, commits, branches &amp; tags, merge requests, issues</dd></div>
30 <div><dt><kbd>g h</kbd></dt><dd>home</dd></div>30 <div><dt><kbd>g h</kbd></dt><dd>home</dd></div>
31+ <div><dt><kbd>w</kbd></dt><dd>wide mode: use the whole width of the screen, or not (remembered)</dd></div>
31 <div><dt><kbd>y</kbd></dt><dd>copy a permalink pinned to the commit</dd></div>32 <div><dt><kbd>y</kbd></dt><dd>copy a permalink pinned to the commit</dd></div>
32 <div><dt><kbd>d</kbd> <kbd>u</kbd> <kbd>x</kbd></dt><dd>diff: split / unified / collapse or expand all</dd></div>33 <div><dt><kbd>d</kbd> <kbd>u</kbd> <kbd>x</kbd></dt><dd>diff: split / unified / collapse or expand all</dd></div>
33- <div><dt><kbd>w</kbd></dt><dd>diff: use the whole width of the screen, or not</dd></div>
34 <div><dt><kbd>e</kbd> <kbd>E</kbd></dt><dd>diff: the whole file in view / every file, with the lines the hunks hide</dd></div>34 <div><dt><kbd>e</kbd> <kbd>E</kbd></dt><dd>diff: the whole file in view / every file, with the lines the hunks hide</dd></div>
35 <div><dt><kbd>n</kbd></dt><dd>merge request: next note in the diff</dd></div>35 <div><dt><kbd>n</kbd></dt><dd>merge request: next note in the diff</dd></div>
36 if mr {36 if mr {
M internal/adapters/web/overlays_templ.go +1 -1 generated file: collapsed
M internal/adapters/web/shell.templ +1 -1
@@ -25,7 +25,7 @@ templ Shell(l Layout, rail templ.Component) {
2525 <script src={ l.Assets.HTMX } defer></script>
2626 <script src={ l.Assets.Live } defer></script>
2727 <script src={ l.Assets.SSE } defer></script>
28- <script src={ l.Assets.Store } defer></script>
28+ <script src={ l.Assets.Store }></script>
2929 <script src={ l.Assets.App } defer></script>
3030 <script src={ l.Assets.Files } defer></script>
3131 <script src={ l.Assets.Passkey } defer></script>
@@ -25,7 +25,7 @@ templ Shell(l Layout, rail templ.Component) {
25 <script src={ l.Assets.HTMX } defer></script>25 <script src={ l.Assets.HTMX } defer></script>
26 <script src={ l.Assets.Live } defer></script>26 <script src={ l.Assets.Live } defer></script>
27 <script src={ l.Assets.SSE } defer></script>27 <script src={ l.Assets.SSE } defer></script>
28- <script src={ l.Assets.Store } defer></script>28+ <script src={ l.Assets.Store }></script>
29 <script src={ l.Assets.App } defer></script>29 <script src={ l.Assets.App } defer></script>
30 <script src={ l.Assets.Files } defer></script>30 <script src={ l.Assets.Files } defer></script>
31 <script src={ l.Assets.Passkey } defer></script>31 <script src={ l.Assets.Passkey } defer></script>
M internal/adapters/web/shell_templ.go +1 -1 generated file: collapsed
M internal/adapters/web/static/css/20-shell.css +3 -3
@@ -133,9 +133,9 @@
133133 align-content: start;
134134 }
135135 .work.has-rail { grid-template-columns: minmax(0, 1fr) var(--rail); }
136- /* the wide diff (w, ermiteWide): a page with a diff takes the whole width
137- of the screen; the rail goes to the right edge, the code takes the rest */
138- html.wide body:has(#diffs) :is(.work, .hdr-top, .tabs, .foot) { max-width: none; }
136+ /* the wide mode (w, ermiteWide): every page takes the whole width of the
137+ screen; the rail goes to the right edge, the content takes the rest */
138+ html.wide :is(.work, .hdr-top, .tabs, .foot) { max-width: none; }
139139 .rail { display: flex; flex-direction: column; gap: var(--s4); min-width: 0; }
140140 .work-lede { grid-column: 1 / -1; margin-bottom: calc(-1 * var(--s2)); }
141141 main > * + * { margin-top: var(--s4); }
@@ -133,9 +133,9 @@
133 align-content: start;133 align-content: start;
134 }134 }
135 .work.has-rail { grid-template-columns: minmax(0, 1fr) var(--rail); }135 .work.has-rail { grid-template-columns: minmax(0, 1fr) var(--rail); }
136- /* the wide diff (w, ermiteWide): a page with a diff takes the whole width136+ /* the wide mode (w, ermiteWide): every page takes the whole width of the
137- of the screen; the rail goes to the right edge, the code takes the rest */137+ screen; the rail goes to the right edge, the content takes the rest */
138- html.wide body:has(#diffs) :is(.work, .hdr-top, .tabs, .foot) { max-width: none; }138+ html.wide :is(.work, .hdr-top, .tabs, .foot) { max-width: none; }
139 .rail { display: flex; flex-direction: column; gap: var(--s4); min-width: 0; }139 .rail { display: flex; flex-direction: column; gap: var(--s4); min-width: 0; }
140 .work-lede { grid-column: 1 / -1; margin-bottom: calc(-1 * var(--s2)); }140 .work-lede { grid-column: 1 / -1; margin-bottom: calc(-1 * var(--s2)); }
141 main > * + * { margin-top: var(--s4); }141 main > * + * { margin-top: var(--s4); }
M internal/adapters/web/static/css/60-mr.css +1 -1
@@ -4,7 +4,7 @@
44 /* one card: the header (title, facts) a shade lighter, the description below */
55 .mr-card {
66 background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--r); overflow: hidden;
7- & > .mr-h { background: var(--bg-raised); border: 0; border-radius: 0; }
7+ & > .mr-h { background: var(--bg-raised); border: 0; border-radius: 0; padding-bottom: var(--s3); }
88 & > .mr-desc.prose { border: 0; border-top: 1px solid var(--border); border-radius: 0; }
99 }
1010
@@ -4,7 +4,7 @@
4 /* one card: the header (title, facts) a shade lighter, the description below */4 /* one card: the header (title, facts) a shade lighter, the description below */
5 .mr-card {5 .mr-card {
6 background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--r); overflow: hidden;6 background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--r); overflow: hidden;
7- & > .mr-h { background: var(--bg-raised); border: 0; border-radius: 0; }7+ & > .mr-h { background: var(--bg-raised); border: 0; border-radius: 0; padding-bottom: var(--s3); }
8 & > .mr-desc.prose { border: 0; border-top: 1px solid var(--border); border-radius: 0; }8 & > .mr-desc.prose { border: 0; border-top: 1px solid var(--border); border-radius: 0; }
9 }9 }
10 10
M internal/adapters/web/static/js/ermite.js +5 -5
@@ -14,7 +14,7 @@
1414 // 7. The service worker (ermiteInstall).
1515 // 8. Chip lists kept on one line, with a … chip (ermiteFit).
1616 // 9. The layout of the home page, remembered in the browser (ermiteLayout).
17-// 10. The wide diff: the page takes the whole width (ermiteWide).
17+// 10. The wide mode: every page takes the whole width (ermiteWide).
1818 //
1919 // Preferences are kept by ermiteStore (store.js, loaded first).
2020 (() => {
@@ -554,13 +554,13 @@
554554 return { get: pref.get, set, apply, values };
555555 })();
556556
557- // The wide diff: on the pages with a diff, the work area takes the whole
558- // width of the screen, the rail at the right edge and the code in the rest.
559- // The choice is kept by ermiteStore ("diff-wide": '1' when wide) and shown
557+ // The wide mode (w, on every page): the work area takes the whole width of
558+ // the screen, the rail at the right edge and the content in the rest.
559+ // The choice is kept by ermiteStore ("wide": '1' when wide) and shown
560560 // as html.wide, which the styles follow, and as aria-pressed on the
561561 // buttons [data-wide-toggle]. Another tab's change applies here at once.
562562 window.ermiteWide = (() => {
563- const pref = window.ermiteStore.pref('diff-wide', { values: ['1', ''], fallback: '' });
563+ const pref = window.ermiteStore.pref('wide', { values: ['1', ''], fallback: '' });
564564 function apply() {
565565 const wide = pref.get() === '1';
566566 document.documentElement.classList.toggle?.('wide', wide);
@@ -14,7 +14,7 @@
14 // 7. The service worker (ermiteInstall).14 // 7. The service worker (ermiteInstall).
15 // 8. Chip lists kept on one line, with a … chip (ermiteFit).15 // 8. Chip lists kept on one line, with a … chip (ermiteFit).
16 // 9. The layout of the home page, remembered in the browser (ermiteLayout).16 // 9. The layout of the home page, remembered in the browser (ermiteLayout).
17-// 10. The wide diff: the page takes the whole width (ermiteWide).17+// 10. The wide mode: every page takes the whole width (ermiteWide).
18 //18 //
19 // Preferences are kept by ermiteStore (store.js, loaded first).19 // Preferences are kept by ermiteStore (store.js, loaded first).
20 (() => {20 (() => {
@@ -554,13 +554,13 @@
554 return { get: pref.get, set, apply, values };554 return { get: pref.get, set, apply, values };
555 })();555 })();
556 556
557- // The wide diff: on the pages with a diff, the work area takes the whole557+ // The wide mode (w, on every page): the work area takes the whole width of
558- // width of the screen, the rail at the right edge and the code in the rest.558+ // the screen, the rail at the right edge and the content in the rest.
559- // The choice is kept by ermiteStore ("diff-wide": '1' when wide) and shown559+ // The choice is kept by ermiteStore ("wide": '1' when wide) and shown
560 // as html.wide, which the styles follow, and as aria-pressed on the560 // as html.wide, which the styles follow, and as aria-pressed on the
561 // buttons [data-wide-toggle]. Another tab's change applies here at once.561 // buttons [data-wide-toggle]. Another tab's change applies here at once.
562 window.ermiteWide = (() => {562 window.ermiteWide = (() => {
563- const pref = window.ermiteStore.pref('diff-wide', { values: ['1', ''], fallback: '' });563+ const pref = window.ermiteStore.pref('wide', { values: ['1', ''], fallback: '' });
564 function apply() {564 function apply() {
565 const wide = pref.get() === '1';565 const wide = pref.get() === '1';
566 document.documentElement.classList.toggle?.('wide', wide);566 document.documentElement.classList.toggle?.('wide', wide);
M internal/adapters/web/static/js/store.js +5 -0
@@ -74,3 +74,8 @@ window.ermiteStore = (() => {
7474
7575 return { get, set, remove, watch, pref };
7676 })();
77+
78+// The wide mode (ermiteWide in ermite.js) is set here, before the first paint:
79+// shell.templ loads this file without defer, so a wide page never draws narrow
80+// first.
81+if (window.ermiteStore.get('wide') === '1') document?.documentElement?.classList?.add('wide');
@@ -74,3 +74,8 @@ window.ermiteStore = (() => {
74 74
75 return { get, set, remove, watch, pref };75 return { get, set, remove, watch, pref };
76 })();76 })();
77+
78+// The wide mode (ermiteWide in ermite.js) is set here, before the first paint:
79+// shell.templ loads this file without defer, so a wide page never draws narrow
80+// first.
81+if (window.ermiteStore.get('wide') === '1') document?.documentElement?.classList?.add('wide');

Keyboard shortcuts

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

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

Branches 2

Tags 42

↑↓ moveenter openesc close