detached 0a9e349

#6 feat(web): review page polish and home page ordered by last activity

state
merged · 4ce8534 · squash
branch
feat/mr-header-card → main
opened
bnema ·
updated

Review page and home page polish, plus "last activity" ordering of the repositories.

Merge request page

  • Title, facts and description in one card; the header a shade lighter than the description. Issues share it.
  • Unified / Split / Collapse all in one view menu (the existing details.menu component); u, d and x still work, escape closes menus on every page and the focus goes back to the menu button.
  • Wide mode on every page with w (listed in ?), kept in localStorage; store.js loads without defer and sets it before the first paint.
  • Hunk headers and hidden-line rows are quieter (background and border), text keeps its contrast.
  • A shift + click range is one block (one border, none between its lines, per side in split); the form title names the whole range (a.go:21-149); lines loaded later from a gap inside the range are selected too.

Home page

  • Inbox button next to the logo, home page only, with a mailbox icon (Lucide) and a darker count badge.
  • Default order is the last activity: push (incl. merge), merge request opened / merged / closed / ready, comment on a merge request, issue opened / approved / closed / reopened / commented. Moderation (submissions, rejections) stays owner-only and does not count.
  • The row of a repository with activity moves to the top live (activity order only; CI updates do not move it).

Storage

  • Repo.ActiveAt (storage proto field 8; falls back to the later of pushed and created).
  • RepoMetaStore.Update is replaced by Mutate: a compare-and-swap on the version read, retried up to 8 times. The previous Update checked the version read at write time, so a read-modify-write could overwrite a concurrent change (e.g. a visibility change).

No change to ermite-api.

Checks: go vet ./..., go test ./..., go test -race on usecase and kvstore, JS tests (160), golden pages refreshed, manual checks on the dev server. Reviewed by Go and JS reviewers; findings fixed.

CommitSubjectAuthorDateSignatureStatus
0a9e349test: run the activity store test outside the usecase package and its home row subtests in parallelbriceGPG Verified
9ca7c68fix(repos): mutate metadata atomically and record approved issue activitybriceGPG Verified
32e9fe7fix(web): match loaded lines to a selection by file key, keep the focus on a closing menu, close menus with escape on every page and restore the hunk header contrastbriceGPG Verified
4277264feat(web): order the home page by last activity and move repositories up livebriceGPG Verified
4fc291efeat(web): move the inbox button of the home page next to the logo, with a mailbox icon and a darker count badgebriceGPG Verified
5f6d2c6test(app): refresh the web golden pages for the view menu, the w shortcut and the store scriptbriceGPG Verified
a772c1bfix(web): a selected line range is one block, its title names the whole range and its hidden lines stay selected once loadedbriceGPG Verified
502935erefactor(web): drop the Wide button of the diff bar, the w shortcut covers itbriceGPG Verified
a1f8e85feat(web): make the wide mode a global w shortcut, set before the first paint, and tighten the merge request headerbriceGPG Verified
fcc7703feat(web): one merge request card, a diff view menu, a wide diff mode and quieter hunk rowsbriceGPG Verified

Checks 4

92 files · +2426 -1392
4/4
M gen/ermite/storage/repo.pb.go +22 -11
M internal/adapters/kvstore/issues_test.go +4 -2
M internal/adapters/kvstore/mergerequests_test.go +4 -2
M internal/adapters/kvstore/repos.go +44 -22
A internal/adapters/kvstore/repos_mutate_test.go +87 -0
M internal/adapters/kvstore/store_test.go +8 -4
M internal/adapters/web/components.templ +2 -2
M internal/adapters/web/components_templ.go +119 -113
M internal/adapters/web/diff.templ +22 -4
M internal/adapters/web/diff_templ.go +523 -437
M internal/adapters/web/events.go +2 -0
M internal/adapters/web/format.go +8 -0
M internal/adapters/web/fragments.go +18 -0
M internal/adapters/web/home.go +24 -14
M internal/adapters/web/home_test.go +50 -1
M internal/adapters/web/issues.templ +24 -22
M internal/adapters/web/issues_templ.go +26 -26
M internal/adapters/web/jstest/a11y.test.mjs +25 -0
M internal/adapters/web/keys.templ +1 -0
M internal/adapters/web/keys_templ.go +1 -1
M internal/adapters/web/mr.go +6 -1
M internal/adapters/web/mr.templ +37 -39
M internal/adapters/web/mr_templ.go +107 -123
M internal/adapters/web/overlays.templ +1 -0
M internal/adapters/web/overlays_templ.go +12 -12
M internal/adapters/web/pages.templ +3 -9
M internal/adapters/web/pages_templ.go +205 -223
M internal/adapters/web/shell.templ +14 -1
M internal/adapters/web/shell_templ.go +158 -116
M internal/adapters/web/static/css/20-shell.css +9 -0
M internal/adapters/web/static/css/50-code.css +7 -4
M internal/adapters/web/static/css/55-diff.css +25 -0
M internal/adapters/web/static/css/60-mr.css +15 -1
A internal/adapters/web/static/icons/mailbox.svg +1 -0
M internal/adapters/web/static/js/ermite.js +70 -3
M internal/adapters/web/static/js/mr.js +1 -11
M internal/adapters/web/static/js/store.js +5 -0
M internal/adapters/web/view.go +6 -4
M internal/app/testdata/web/blob-binary.html +8 -1
M internal/app/testdata/web/blob-code.html +8 -1
M internal/app/testdata/web/blob-empty.html +8 -1
M internal/app/testdata/web/blob-image.html +8 -1
M internal/app/testdata/web/blob-markdown.html +8 -1
M internal/app/testdata/web/blob-svg.html +8 -1
M internal/app/testdata/web/blob-text.html +8 -1
M internal/app/testdata/web/blob-too-large.html +8 -1
M internal/app/testdata/web/blob-trojan.html +8 -1
M internal/app/testdata/web/commit-big.html +16 -2
M internal/app/testdata/web/commit-binary.html +16 -2
M internal/app/testdata/web/commit-merge.html +16 -2
M internal/app/testdata/web/commit-modify-split.html +17 -3
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -127,6 +127,11 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
127127 </span>
128128 <span class="grow">
129129 </span>
130+<details class="menu vmenu">
131+<summary class="btn" aria-label="Diff view">
132+<span hx-live="this.textContent = q('#diffs').attr['data-diff'] === 'split' ? 'Split' : 'Unified'">Split</span>
133+</summary>
134+<div class="menu-pop" role="group" aria-label="Diff view">
130135 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>
131136 </a>
132137 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" aria-current="true" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>
@@ -136,8 +141,10 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
136141 <kbd aria-hidden="true">x</kbd>
137142 </button>
138143 </div>
144+</details>
145+</div>
139146 <div id="diffs" data-diff="split">
140-<details class="dfile lnw-2" id="f1" data-file open>
147+<details class="dfile lnw-2" id="f1" data-file="a.txt" open>
141148 <summary>
142149 <span class="st">M</span>
143150 <span class="p">a.txt</span>
@@ -277,7 +284,7 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
277284 </table>
278285 </div>
279286 </details>
280-<details class="dfile lnw-2" id="f2" data-file>
287+<details class="dfile lnw-2" id="f2" data-file="run.sh">
281288 <summary>
282289 <span class="st">M</span>
283290 <span class="p">run.sh</span>
@@ -438,6 +445,12 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
438445 </div>
439446 <div>
440447 <dt>
448+<kbd>w</kbd>
449+</dt>
450+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
451+</div>
452+<div>
453+<dt>
441454 <kbd>y</kbd>
442455 </dt>
443456 <dd>copy a permalink pinned to the commit</dd>
@@ -615,6 +628,7 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
615628 k-split from:body -> ermite.layout('split');
616629 k-unified from:body -> ermite.layout('unified');
617630 k-collapse from:body -> ermite.collapse();
631+ k-wide from:body -> window.ermiteWide.toggle();
618632 k-expand from:body -> ermite.expandFile();
619633 k-expandAll from:body -> ermite.expandAll();
620634 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -127,6 +127,11 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
127 </span>127 </span>
128 <span class="grow">128 <span class="grow">
129 </span>129 </span>
130+<details class="menu vmenu">
131+<summary class="btn" aria-label="Diff view">
132+<span hx-live="this.textContent = q('#diffs').attr['data-diff'] === 'split' ? 'Split' : 'Unified'">Split</span>
133+</summary>
134+<div class="menu-pop" role="group" aria-label="Diff view">
130 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>135 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>
131 </a>136 </a>
132 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" aria-current="true" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>137 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" aria-current="true" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>
@@ -136,8 +141,10 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
136 <kbd aria-hidden="true">x</kbd>141 <kbd aria-hidden="true">x</kbd>
137 </button>142 </button>
138 </div>143 </div>
144+</details>
145+</div>
139 <div id="diffs" data-diff="split">146 <div id="diffs" data-diff="split">
140-<details class="dfile lnw-2" id="f1" data-file open>147+<details class="dfile lnw-2" id="f1" data-file="a.txt" open>
141 <summary>148 <summary>
142 <span class="st">M</span>149 <span class="st">M</span>
143 <span class="p">a.txt</span>150 <span class="p">a.txt</span>
@@ -277,7 +284,7 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
277 </table>284 </table>
278 </div>285 </div>
279 </details>286 </details>
280-<details class="dfile lnw-2" id="f2" data-file>287+<details class="dfile lnw-2" id="f2" data-file="run.sh">
281 <summary>288 <summary>
282 <span class="st">M</span>289 <span class="st">M</span>
283 <span class="p">run.sh</span>290 <span class="p">run.sh</span>
@@ -438,6 +445,12 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
438 </div>445 </div>
439 <div>446 <div>
440 <dt>447 <dt>
448+<kbd>w</kbd>
449+</dt>
450+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
451+</div>
452+<div>
453+<dt>
441 <kbd>y</kbd>454 <kbd>y</kbd>
442 </dt>455 </dt>
443 <dd>copy a permalink pinned to the commit</dd>456 <dd>copy a permalink pinned to the commit</dd>
@@ -615,6 +628,7 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
615 k-split from:body -> ermite.layout('split');628 k-split from:body -> ermite.layout('split');
616 k-unified from:body -> ermite.layout('unified');629 k-unified from:body -> ermite.layout('unified');
617 k-collapse from:body -> ermite.collapse();630 k-collapse from:body -> ermite.collapse();
631+ k-wide from:body -> window.ermiteWide.toggle();
618 k-expand from:body -> ermite.expandFile();632 k-expand from:body -> ermite.expandFile();
619 k-expandAll from:body -> ermite.expandAll();633 k-expandAll from:body -> ermite.expandAll();
620 k-permalink from:body -> ermite.permalink();634 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/commit-modify.html +17 -3
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -127,6 +127,11 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
127127 </span>
128128 <span class="grow">
129129 </span>
130+<details class="menu vmenu">
131+<summary class="btn" aria-label="Diff view">
132+<span hx-live="this.textContent = q('#diffs').attr['data-diff'] === 'split' ? 'Split' : 'Unified'">Unified</span>
133+</summary>
134+<div class="menu-pop" role="group" aria-label="Diff view">
130135 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" aria-current="true" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>
131136 </a>
132137 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>
@@ -136,8 +141,10 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
136141 <kbd aria-hidden="true">x</kbd>
137142 </button>
138143 </div>
144+</details>
145+</div>
139146 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
140-<details class="dfile lnw-2" id="f1" data-file open>
147+<details class="dfile lnw-2" id="f1" data-file="a.txt" open>
141148 <summary>
142149 <span class="st">M</span>
143150 <span class="p">a.txt</span>
@@ -277,7 +284,7 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
277284 </table>
278285 </div>
279286 </details>
280-<details class="dfile lnw-2" id="f2" data-file>
287+<details class="dfile lnw-2" id="f2" data-file="run.sh">
281288 <summary>
282289 <span class="st">M</span>
283290 <span class="p">run.sh</span>
@@ -438,6 +445,12 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
438445 </div>
439446 <div>
440447 <dt>
448+<kbd>w</kbd>
449+</dt>
450+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
451+</div>
452+<div>
453+<dt>
441454 <kbd>y</kbd>
442455 </dt>
443456 <dd>copy a permalink pinned to the commit</dd>
@@ -615,6 +628,7 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
615628 k-split from:body -> ermite.layout('split');
616629 k-unified from:body -> ermite.layout('unified');
617630 k-collapse from:body -> ermite.collapse();
631+ k-wide from:body -> window.ermiteWide.toggle();
618632 k-expand from:body -> ermite.expandFile();
619633 k-expandAll from:body -> ermite.expandAll();
620634 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -127,6 +127,11 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
127 </span>127 </span>
128 <span class="grow">128 <span class="grow">
129 </span>129 </span>
130+<details class="menu vmenu">
131+<summary class="btn" aria-label="Diff view">
132+<span hx-live="this.textContent = q('#diffs').attr['data-diff'] === 'split' ? 'Split' : 'Unified'">Unified</span>
133+</summary>
134+<div class="menu-pop" role="group" aria-label="Diff view">
130 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" aria-current="true" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>135 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" aria-current="true" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>
131 </a>136 </a>
132 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>137 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>
@@ -136,8 +141,10 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
136 <kbd aria-hidden="true">x</kbd>141 <kbd aria-hidden="true">x</kbd>
137 </button>142 </button>
138 </div>143 </div>
144+</details>
145+</div>
139 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">146 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
140-<details class="dfile lnw-2" id="f1" data-file open>147+<details class="dfile lnw-2" id="f1" data-file="a.txt" open>
141 <summary>148 <summary>
142 <span class="st">M</span>149 <span class="st">M</span>
143 <span class="p">a.txt</span>150 <span class="p">a.txt</span>
@@ -277,7 +284,7 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
277 </table>284 </table>
278 </div>285 </div>
279 </details>286 </details>
280-<details class="dfile lnw-2" id="f2" data-file>287+<details class="dfile lnw-2" id="f2" data-file="run.sh">
281 <summary>288 <summary>
282 <span class="st">M</span>289 <span class="st">M</span>
283 <span class="p">run.sh</span>290 <span class="p">run.sh</span>
@@ -438,6 +445,12 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
438 </div>445 </div>
439 <div>446 <div>
440 <dt>447 <dt>
448+<kbd>w</kbd>
449+</dt>
450+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
451+</div>
452+<div>
453+<dt>
441 <kbd>y</kbd>454 <kbd>y</kbd>
442 </dt>455 </dt>
443 <dd>copy a permalink pinned to the commit</dd>456 <dd>copy a permalink pinned to the commit</dd>
@@ -615,6 +628,7 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
615 k-split from:body -> ermite.layout('split');628 k-split from:body -> ermite.layout('split');
616 k-unified from:body -> ermite.layout('unified');629 k-unified from:body -> ermite.layout('unified');
617 k-collapse from:body -> ermite.collapse();630 k-collapse from:body -> ermite.collapse();
631+ k-wide from:body -> window.ermiteWide.toggle();
618 k-expand from:body -> ermite.expandFile();632 k-expand from:body -> ermite.expandFile();
619 k-expandAll from:body -> ermite.expandAll();633 k-expandAll from:body -> ermite.expandAll();
620 k-permalink from:body -> ermite.permalink();634 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/commit-rename.html +17 -3
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -121,6 +121,11 @@
121121 </span>
122122 <span class="grow">
123123 </span>
124+<details class="menu vmenu">
125+<summary class="btn" aria-label="Diff view">
126+<span hx-live="this.textContent = q('#diffs').attr['data-diff'] === 'split' ? 'Split' : 'Unified'">Unified</span>
127+</summary>
128+<div class="menu-pop" role="group" aria-label="Diff view">
124129 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" aria-current="true" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>
125130 </a>
126131 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>
@@ -130,8 +135,10 @@
130135 <kbd aria-hidden="true">x</kbd>
131136 </button>
132137 </div>
138+</details>
139+</div>
133140 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
134-<details class="dfile lnw-2" id="f1" data-file>
141+<details class="dfile lnw-2" id="f1" data-file="docs-old.txt">
135142 <summary>
136143 <span class="st">R</span>
137144 <span class="p">old.txt → docs-old.txt</span>
@@ -141,7 +148,7 @@
141148 </summary>
142149 <p class="note pad">renamed, 100% similar</p>
143150 </details>
144-<details class="dfile lnw-2" id="f2" data-file open>
151+<details class="dfile lnw-2" id="f2" data-file="gone.txt" open>
145152 <summary>
146153 <span class="st">D</span>
147154 <span class="p">gone.txt</span>
@@ -342,6 +349,12 @@
342349 </div>
343350 <div>
344351 <dt>
352+<kbd>w</kbd>
353+</dt>
354+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
355+</div>
356+<div>
357+<dt>
345358 <kbd>y</kbd>
346359 </dt>
347360 <dd>copy a permalink pinned to the commit</dd>
@@ -519,6 +532,7 @@
519532 k-split from:body -> ermite.layout('split');
520533 k-unified from:body -> ermite.layout('unified');
521534 k-collapse from:body -> ermite.collapse();
535+ k-wide from:body -> window.ermiteWide.toggle();
522536 k-expand from:body -> ermite.expandFile();
523537 k-expandAll from:body -> ermite.expandAll();
524538 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -121,6 +121,11 @@
121 </span>121 </span>
122 <span class="grow">122 <span class="grow">
123 </span>123 </span>
124+<details class="menu vmenu">
125+<summary class="btn" aria-label="Diff view">
126+<span hx-live="this.textContent = q('#diffs').attr['data-diff'] === 'split' ? 'Split' : 'Unified'">Unified</span>
127+</summary>
128+<div class="menu-pop" role="group" aria-label="Diff view">
124 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" aria-current="true" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>129 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" aria-current="true" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>
125 </a>130 </a>
126 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>131 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>
@@ -130,8 +135,10 @@
130 <kbd aria-hidden="true">x</kbd>135 <kbd aria-hidden="true">x</kbd>
131 </button>136 </button>
132 </div>137 </div>
138+</details>
139+</div>
133 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">140 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
134-<details class="dfile lnw-2" id="f1" data-file>141+<details class="dfile lnw-2" id="f1" data-file="docs-old.txt">
135 <summary>142 <summary>
136 <span class="st">R</span>143 <span class="st">R</span>
137 <span class="p">old.txt → docs-old.txt</span>144 <span class="p">old.txt → docs-old.txt</span>
@@ -141,7 +148,7 @@
141 </summary>148 </summary>
142 <p class="note pad">renamed, 100% similar</p>149 <p class="note pad">renamed, 100% similar</p>
143 </details>150 </details>
144-<details class="dfile lnw-2" id="f2" data-file open>151+<details class="dfile lnw-2" id="f2" data-file="gone.txt" open>
145 <summary>152 <summary>
146 <span class="st">D</span>153 <span class="st">D</span>
147 <span class="p">gone.txt</span>154 <span class="p">gone.txt</span>
@@ -342,6 +349,12 @@
342 </div>349 </div>
343 <div>350 <div>
344 <dt>351 <dt>
352+<kbd>w</kbd>
353+</dt>
354+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
355+</div>
356+<div>
357+<dt>
345 <kbd>y</kbd>358 <kbd>y</kbd>
346 </dt>359 </dt>
347 <dd>copy a permalink pinned to the commit</dd>360 <dd>copy a permalink pinned to the commit</dd>
@@ -519,6 +532,7 @@
519 k-split from:body -> ermite.layout('split');532 k-split from:body -> ermite.layout('split');
520 k-unified from:body -> ermite.layout('unified');533 k-unified from:body -> ermite.layout('unified');
521 k-collapse from:body -> ermite.collapse();534 k-collapse from:body -> ermite.collapse();
535+ k-wide from:body -> window.ermiteWide.toggle();
522 k-expand from:body -> ermite.expandFile();536 k-expand from:body -> ermite.expandFile();
523 k-expandAll from:body -> ermite.expandAll();537 k-expandAll from:body -> ermite.expandAll();
524 k-permalink from:body -> ermite.permalink();538 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/commit-root.html +20 -6
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -119,6 +119,11 @@
119119 </span>
120120 <span class="grow">
121121 </span>
122+<details class="menu vmenu">
123+<summary class="btn" aria-label="Diff view">
124+<span hx-live="this.textContent = q('#diffs').attr['data-diff'] === 'split' ? 'Split' : 'Unified'">Unified</span>
125+</summary>
126+<div class="menu-pop" role="group" aria-label="Diff view">
122127 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" aria-current="true" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>
123128 </a>
124129 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>
@@ -128,8 +133,10 @@
128133 <kbd aria-hidden="true">x</kbd>
129134 </button>
130135 </div>
136+</details>
137+</div>
131138 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
132-<details class="dfile lnw-2" id="f1" data-file open>
139+<details class="dfile lnw-2" id="f1" data-file="a.txt" open>
133140 <summary>
134141 <span class="st">A</span>
135142 <span class="p">a.txt</span>
@@ -251,7 +258,7 @@
251258 </table>
252259 </div>
253260 </details>
254-<details class="dfile lnw-2" id="f2" data-file open>
261+<details class="dfile lnw-2" id="f2" data-file="gone.txt" open>
255262 <summary>
256263 <span class="st">A</span>
257264 <span class="p">gone.txt</span>
@@ -301,7 +308,7 @@
301308 </table>
302309 </div>
303310 </details>
304-<details class="dfile lnw-2" id="f3" data-file>
311+<details class="dfile lnw-2" id="f3" data-file="logo.bin">
305312 <summary>
306313 <span class="st">A</span>
307314 <span class="p">logo.bin</span>
@@ -311,7 +318,7 @@
311318 </summary>
312319 <p class="note pad">binary file: no text diff</p>
313320 </details>
314-<details class="dfile lnw-2" id="f4" data-file open>
321+<details class="dfile lnw-2" id="f4" data-file="old.txt" open>
315322 <summary>
316323 <span class="st">A</span>
317324 <span class="p">old.txt</span>
@@ -397,7 +404,7 @@
397404 </table>
398405 </div>
399406 </details>
400-<details class="dfile lnw-2" id="f5" data-file open>
407+<details class="dfile lnw-2" id="f5" data-file="run.sh" open>
401408 <summary>
402409 <span class="st">A</span>
403410 <span class="p">run.sh</span>
@@ -661,6 +668,12 @@
661668 </div>
662669 <div>
663670 <dt>
671+<kbd>w</kbd>
672+</dt>
673+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
674+</div>
675+<div>
676+<dt>
664677 <kbd>y</kbd>
665678 </dt>
666679 <dd>copy a permalink pinned to the commit</dd>
@@ -838,6 +851,7 @@
838851 k-split from:body -> ermite.layout('split');
839852 k-unified from:body -> ermite.layout('unified');
840853 k-collapse from:body -> ermite.collapse();
854+ k-wide from:body -> window.ermiteWide.toggle();
841855 k-expand from:body -> ermite.expandFile();
842856 k-expandAll from:body -> ermite.expandAll();
843857 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -119,6 +119,11 @@
119 </span>119 </span>
120 <span class="grow">120 <span class="grow">
121 </span>121 </span>
122+<details class="menu vmenu">
123+<summary class="btn" aria-label="Diff view">
124+<span hx-live="this.textContent = q('#diffs').attr['data-diff'] === 'split' ? 'Split' : 'Unified'">Unified</span>
125+</summary>
126+<div class="menu-pop" role="group" aria-label="Diff view">
122 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" aria-current="true" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>127 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" aria-current="true" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>
123 </a>128 </a>
124 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>129 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>
@@ -128,8 +133,10 @@
128 <kbd aria-hidden="true">x</kbd>133 <kbd aria-hidden="true">x</kbd>
129 </button>134 </button>
130 </div>135 </div>
136+</details>
137+</div>
131 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">138 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
132-<details class="dfile lnw-2" id="f1" data-file open>139+<details class="dfile lnw-2" id="f1" data-file="a.txt" open>
133 <summary>140 <summary>
134 <span class="st">A</span>141 <span class="st">A</span>
135 <span class="p">a.txt</span>142 <span class="p">a.txt</span>
@@ -251,7 +258,7 @@
251 </table>258 </table>
252 </div>259 </div>
253 </details>260 </details>
254-<details class="dfile lnw-2" id="f2" data-file open>261+<details class="dfile lnw-2" id="f2" data-file="gone.txt" open>
255 <summary>262 <summary>
256 <span class="st">A</span>263 <span class="st">A</span>
257 <span class="p">gone.txt</span>264 <span class="p">gone.txt</span>
@@ -301,7 +308,7 @@
301 </table>308 </table>
302 </div>309 </div>
303 </details>310 </details>
304-<details class="dfile lnw-2" id="f3" data-file>311+<details class="dfile lnw-2" id="f3" data-file="logo.bin">
305 <summary>312 <summary>
306 <span class="st">A</span>313 <span class="st">A</span>
307 <span class="p">logo.bin</span>314 <span class="p">logo.bin</span>
@@ -311,7 +318,7 @@
311 </summary>318 </summary>
312 <p class="note pad">binary file: no text diff</p>319 <p class="note pad">binary file: no text diff</p>
313 </details>320 </details>
314-<details class="dfile lnw-2" id="f4" data-file open>321+<details class="dfile lnw-2" id="f4" data-file="old.txt" open>
315 <summary>322 <summary>
316 <span class="st">A</span>323 <span class="st">A</span>
317 <span class="p">old.txt</span>324 <span class="p">old.txt</span>
@@ -397,7 +404,7 @@
397 </table>404 </table>
398 </div>405 </div>
399 </details>406 </details>
400-<details class="dfile lnw-2" id="f5" data-file open>407+<details class="dfile lnw-2" id="f5" data-file="run.sh" open>
401 <summary>408 <summary>
402 <span class="st">A</span>409 <span class="st">A</span>
403 <span class="p">run.sh</span>410 <span class="p">run.sh</span>
@@ -661,6 +668,12 @@
661 </div>668 </div>
662 <div>669 <div>
663 <dt>670 <dt>
671+<kbd>w</kbd>
672+</dt>
673+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
674+</div>
675+<div>
676+<dt>
664 <kbd>y</kbd>677 <kbd>y</kbd>
665 </dt>678 </dt>
666 <dd>copy a permalink pinned to the commit</dd>679 <dd>copy a permalink pinned to the commit</dd>
@@ -838,6 +851,7 @@
838 k-split from:body -> ermite.layout('split');851 k-split from:body -> ermite.layout('split');
839 k-unified from:body -> ermite.layout('unified');852 k-unified from:body -> ermite.layout('unified');
840 k-collapse from:body -> ermite.collapse();853 k-collapse from:body -> ermite.collapse();
854+ k-wide from:body -> window.ermiteWide.toggle();
841 k-expand from:body -> ermite.expandFile();855 k-expand from:body -> ermite.expandFile();
842 k-expandAll from:body -> ermite.expandAll();856 k-expandAll from:body -> ermite.expandAll();
843 k-permalink from:body -> ermite.permalink();857 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/commit-trojan.html +16 -2
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -122,6 +122,11 @@
122122 </span>
123123 <span class="grow">
124124 </span>
125+<details class="menu vmenu">
126+<summary class="btn" aria-label="Diff view">
127+<span hx-live="this.textContent = q('#diffs').attr['data-diff'] === 'split' ? 'Split' : 'Unified'">Unified</span>
128+</summary>
129+<div class="menu-pop" role="group" aria-label="Diff view">
125130 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" aria-current="true" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>
126131 </a>
127132 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>
@@ -131,9 +136,11 @@
131136 <kbd aria-hidden="true">x</kbd>
132137 </button>
133138 </div>
139+</details>
140+</div>
134141 <p class="notice">this diff holds invisible characters (direction controls, zero-width characters). They are shown as <span class="inv">&lt;U+XXXX&gt;</span> markers, never applied.</p>
135142 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
136-<details class="dfile lnw-2" id="f1" data-file open>
143+<details class="dfile lnw-2" id="f1" data-file="evil.go" open>
137144 <summary>
138145 <span class="st">A</span>
139146 <span class="p">evil.go</span>
@@ -490,6 +497,12 @@
490497 </div>
491498 <div>
492499 <dt>
500+<kbd>w</kbd>
501+</dt>
502+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
503+</div>
504+<div>
505+<dt>
493506 <kbd>y</kbd>
494507 </dt>
495508 <dd>copy a permalink pinned to the commit</dd>
@@ -667,6 +680,7 @@
667680 k-split from:body -> ermite.layout('split');
668681 k-unified from:body -> ermite.layout('unified');
669682 k-collapse from:body -> ermite.collapse();
683+ k-wide from:body -> window.ermiteWide.toggle();
670684 k-expand from:body -> ermite.expandFile();
671685 k-expandAll from:body -> ermite.expandAll();
672686 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -122,6 +122,11 @@
122 </span>122 </span>
123 <span class="grow">123 <span class="grow">
124 </span>124 </span>
125+<details class="menu vmenu">
126+<summary class="btn" aria-label="Diff view">
127+<span hx-live="this.textContent = q('#diffs').attr['data-diff'] === 'split' ? 'Split' : 'Unified'">Unified</span>
128+</summary>
129+<div class="menu-pop" role="group" aria-label="Diff view">
125 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" aria-current="true" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>130 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" aria-current="true" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>
126 </a>131 </a>
127 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>132 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>
@@ -131,9 +136,11 @@
131 <kbd aria-hidden="true">x</kbd>136 <kbd aria-hidden="true">x</kbd>
132 </button>137 </button>
133 </div>138 </div>
139+</details>
140+</div>
134 <p class="notice">this diff holds invisible characters (direction controls, zero-width characters). They are shown as <span class="inv">&lt;U+XXXX&gt;</span> markers, never applied.</p>141 <p class="notice">this diff holds invisible characters (direction controls, zero-width characters). They are shown as <span class="inv">&lt;U+XXXX&gt;</span> markers, never applied.</p>
135 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">142 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
136-<details class="dfile lnw-2" id="f1" data-file open>143+<details class="dfile lnw-2" id="f1" data-file="evil.go" open>
137 <summary>144 <summary>
138 <span class="st">A</span>145 <span class="st">A</span>
139 <span class="p">evil.go</span>146 <span class="p">evil.go</span>
@@ -490,6 +497,12 @@
490 </div>497 </div>
491 <div>498 <div>
492 <dt>499 <dt>
500+<kbd>w</kbd>
501+</dt>
502+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
503+</div>
504+<div>
505+<dt>
493 <kbd>y</kbd>506 <kbd>y</kbd>
494 </dt>507 </dt>
495 <dd>copy a permalink pinned to the commit</dd>508 <dd>copy a permalink pinned to the commit</dd>
@@ -667,6 +680,7 @@
667 k-split from:body -> ermite.layout('split');680 k-split from:body -> ermite.layout('split');
668 k-unified from:body -> ermite.layout('unified');681 k-unified from:body -> ermite.layout('unified');
669 k-collapse from:body -> ermite.collapse();682 k-collapse from:body -> ermite.collapse();
683+ k-wide from:body -> window.ermiteWide.toggle();
670 k-expand from:body -> ermite.expandFile();684 k-expand from:body -> ermite.expandFile();
671 k-expandAll from:body -> ermite.expandAll();685 k-expandAll from:body -> ermite.expandAll();
672 k-permalink from:body -> ermite.permalink();686 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/commits-page2.html +8 -1
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -266,6 +266,12 @@
266266 </div>
267267 <div>
268268 <dt>
269+<kbd>w</kbd>
270+</dt>
271+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
272+</div>
273+<div>
274+<dt>
269275 <kbd>y</kbd>
270276 </dt>
271277 <dd>copy a permalink pinned to the commit</dd>
@@ -429,6 +435,7 @@
429435 k-split from:body -> ermite.layout('split');
430436 k-unified from:body -> ermite.layout('unified');
431437 k-collapse from:body -> ermite.collapse();
438+ k-wide from:body -> window.ermiteWide.toggle();
432439 k-expand from:body -> ermite.expandFile();
433440 k-expandAll from:body -> ermite.expandAll();
434441 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -266,6 +266,12 @@
266 </div>266 </div>
267 <div>267 <div>
268 <dt>268 <dt>
269+<kbd>w</kbd>
270+</dt>
271+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
272+</div>
273+<div>
274+<dt>
269 <kbd>y</kbd>275 <kbd>y</kbd>
270 </dt>276 </dt>
271 <dd>copy a permalink pinned to the commit</dd>277 <dd>copy a permalink pinned to the commit</dd>
@@ -429,6 +435,7 @@
429 k-split from:body -> ermite.layout('split');435 k-split from:body -> ermite.layout('split');
430 k-unified from:body -> ermite.layout('unified');436 k-unified from:body -> ermite.layout('unified');
431 k-collapse from:body -> ermite.collapse();437 k-collapse from:body -> ermite.collapse();
438+ k-wide from:body -> window.ermiteWide.toggle();
432 k-expand from:body -> ermite.expandFile();439 k-expand from:body -> ermite.expandFile();
433 k-expandAll from:body -> ermite.expandAll();440 k-expandAll from:body -> ermite.expandAll();
434 k-permalink from:body -> ermite.permalink();441 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/commits-path.html +8 -1
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -281,6 +281,12 @@
281281 </div>
282282 <div>
283283 <dt>
284+<kbd>w</kbd>
285+</dt>
286+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
287+</div>
288+<div>
289+<dt>
284290 <kbd>y</kbd>
285291 </dt>
286292 <dd>copy a permalink pinned to the commit</dd>
@@ -444,6 +450,7 @@
444450 k-split from:body -> ermite.layout('split');
445451 k-unified from:body -> ermite.layout('unified');
446452 k-collapse from:body -> ermite.collapse();
453+ k-wide from:body -> window.ermiteWide.toggle();
447454 k-expand from:body -> ermite.expandFile();
448455 k-expandAll from:body -> ermite.expandAll();
449456 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -281,6 +281,12 @@
281 </div>281 </div>
282 <div>282 <div>
283 <dt>283 <dt>
284+<kbd>w</kbd>
285+</dt>
286+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
287+</div>
288+<div>
289+<dt>
284 <kbd>y</kbd>290 <kbd>y</kbd>
285 </dt>291 </dt>
286 <dd>copy a permalink pinned to the commit</dd>292 <dd>copy a permalink pinned to the commit</dd>
@@ -444,6 +450,7 @@
444 k-split from:body -> ermite.layout('split');450 k-split from:body -> ermite.layout('split');
445 k-unified from:body -> ermite.layout('unified');451 k-unified from:body -> ermite.layout('unified');
446 k-collapse from:body -> ermite.collapse();452 k-collapse from:body -> ermite.collapse();
453+ k-wide from:body -> window.ermiteWide.toggle();
447 k-expand from:body -> ermite.expandFile();454 k-expand from:body -> ermite.expandFile();
448 k-expandAll from:body -> ermite.expandAll();455 k-expandAll from:body -> ermite.expandAll();
449 k-permalink from:body -> ermite.permalink();456 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/commits-root.html +8 -1
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -1070,6 +1070,12 @@
10701070 </div>
10711071 <div>
10721072 <dt>
1073+<kbd>w</kbd>
1074+</dt>
1075+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
1076+</div>
1077+<div>
1078+<dt>
10731079 <kbd>y</kbd>
10741080 </dt>
10751081 <dd>copy a permalink pinned to the commit</dd>
@@ -1233,6 +1239,7 @@
12331239 k-split from:body -> ermite.layout('split');
12341240 k-unified from:body -> ermite.layout('unified');
12351241 k-collapse from:body -> ermite.collapse();
1242+ k-wide from:body -> window.ermiteWide.toggle();
12361243 k-expand from:body -> ermite.expandFile();
12371244 k-expandAll from:body -> ermite.expandAll();
12381245 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -1070,6 +1070,12 @@
1070 </div>1070 </div>
1071 <div>1071 <div>
1072 <dt>1072 <dt>
1073+<kbd>w</kbd>
1074+</dt>
1075+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
1076+</div>
1077+<div>
1078+<dt>
1073 <kbd>y</kbd>1079 <kbd>y</kbd>
1074 </dt>1080 </dt>
1075 <dd>copy a permalink pinned to the commit</dd>1081 <dd>copy a permalink pinned to the commit</dd>
@@ -1233,6 +1239,7 @@
1233 k-split from:body -> ermite.layout('split');1239 k-split from:body -> ermite.layout('split');
1234 k-unified from:body -> ermite.layout('unified');1240 k-unified from:body -> ermite.layout('unified');
1235 k-collapse from:body -> ermite.collapse();1241 k-collapse from:body -> ermite.collapse();
1242+ k-wide from:body -> window.ermiteWide.toggle();
1236 k-expand from:body -> ermite.expandFile();1243 k-expand from:body -> ermite.expandFile();
1237 k-expandAll from:body -> ermite.expandAll();1244 k-expandAll from:body -> ermite.expandAll();
1238 k-permalink from:body -> ermite.permalink();1245 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/commits-small.html +8 -1
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -415,6 +415,12 @@
415415 </div>
416416 <div>
417417 <dt>
418+<kbd>w</kbd>
419+</dt>
420+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
421+</div>
422+<div>
423+<dt>
418424 <kbd>y</kbd>
419425 </dt>
420426 <dd>copy a permalink pinned to the commit</dd>
@@ -598,6 +604,7 @@
598604 k-split from:body -> ermite.layout('split');
599605 k-unified from:body -> ermite.layout('unified');
600606 k-collapse from:body -> ermite.collapse();
607+ k-wide from:body -> window.ermiteWide.toggle();
601608 k-expand from:body -> ermite.expandFile();
602609 k-expandAll from:body -> ermite.expandAll();
603610 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -415,6 +415,12 @@
415 </div>415 </div>
416 <div>416 <div>
417 <dt>417 <dt>
418+<kbd>w</kbd>
419+</dt>
420+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
421+</div>
422+<div>
423+<dt>
418 <kbd>y</kbd>424 <kbd>y</kbd>
419 </dt>425 </dt>
420 <dd>copy a permalink pinned to the commit</dd>426 <dd>copy a permalink pinned to the commit</dd>
@@ -598,6 +604,7 @@
598 k-split from:body -> ermite.layout('split');604 k-split from:body -> ermite.layout('split');
599 k-unified from:body -> ermite.layout('unified');605 k-unified from:body -> ermite.layout('unified');
600 k-collapse from:body -> ermite.collapse();606 k-collapse from:body -> ermite.collapse();
607+ k-wide from:body -> window.ermiteWide.toggle();
601 k-expand from:body -> ermite.expandFile();608 k-expand from:body -> ermite.expandFile();
602 k-expandAll from:body -> ermite.expandAll();609 k-expandAll from:body -> ermite.expandAll();
603 k-permalink from:body -> ermite.permalink();610 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/empty.html +8 -1
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -188,6 +188,12 @@
188188 </div>
189189 <div>
190190 <dt>
191+<kbd>w</kbd>
192+</dt>
193+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
194+</div>
195+<div>
196+<dt>
191197 <kbd>y</kbd>
192198 </dt>
193199 <dd>copy a permalink pinned to the commit</dd>
@@ -282,6 +288,7 @@
282288 k-split from:body -> ermite.layout('split');
283289 k-unified from:body -> ermite.layout('unified');
284290 k-collapse from:body -> ermite.collapse();
291+ k-wide from:body -> window.ermiteWide.toggle();
285292 k-expand from:body -> ermite.expandFile();
286293 k-expandAll from:body -> ermite.expandAll();
287294 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -188,6 +188,12 @@
188 </div>188 </div>
189 <div>189 <div>
190 <dt>190 <dt>
191+<kbd>w</kbd>
192+</dt>
193+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
194+</div>
195+<div>
196+<dt>
191 <kbd>y</kbd>197 <kbd>y</kbd>
192 </dt>198 </dt>
193 <dd>copy a permalink pinned to the commit</dd>199 <dd>copy a permalink pinned to the commit</dd>
@@ -282,6 +288,7 @@
282 k-split from:body -> ermite.layout('split');288 k-split from:body -> ermite.layout('split');
283 k-unified from:body -> ermite.layout('unified');289 k-unified from:body -> ermite.layout('unified');
284 k-collapse from:body -> ermite.collapse();290 k-collapse from:body -> ermite.collapse();
291+ k-wide from:body -> window.ermiteWide.toggle();
285 k-expand from:body -> ermite.expandFile();292 k-expand from:body -> ermite.expandFile();
286 k-expandAll from:body -> ermite.expandAll();293 k-expandAll from:body -> ermite.expandAll();
287 k-permalink from:body -> ermite.permalink();294 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/home.html +40 -31
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -61,7 +61,7 @@
6161 </div>
6262 <nav class="sort" aria-label="Sort repositories">
6363 <span class="mute">sort</span>
64-<a href="/?sort=push&amp;dir=asc" aria-current="true">last push <span aria-hidden="true">▼</span>
64+<a href="/?sort=activity&amp;dir=asc" aria-current="true">last activity <span aria-hidden="true">▼</span>
6565 <span class="sr">, descending, select for ascending</span>
6666 </a>
6767 <a href="/?sort=name&amp;dir=asc">name </a>
@@ -81,8 +81,31 @@
8181 </button>
8282 </div>
8383 </div>
84-<ul class="repos" id="repo-rows" data-rows>
85-<li id="repo-demo" class="repo" data-cursor>
84+<ul class="repos" id="repo-rows" data-rows data-sort="activity" data-desc="true">
85+<li id="repo-quiet" class="repo" data-cursor>
86+<div class="repo-main">
87+<h2 class="repo-name">
88+<a href="/quiet">quiet</a>
89+</h2>
90+<p class="repo-desc mute">no description</p>
91+<div class="repo-topics" aria-hidden="true">
92+</div>
93+<div class="repo-pulse" aria-hidden="true">
94+</div>
95+</div>
96+<div class="repo-head">
97+<dl class="repo-stats" aria-label="Figures of quiet">
98+<div class="rs-active">
99+<dt>last activity</dt>
100+<dd>
101+<time datetime="STAMP" title="STAMP">AGO</time>
102+</dd>
103+</div>
104+</dl>
105+</div>
106+<p class="repo-commits mute">no commits yet</p>
107+</li>
108+<li id="repo-demo" class="repo">
86109 <div class="repo-main">
87110 <h2 class="repo-name">
88111 <a href="/demo">demo</a>
@@ -148,27 +171,6 @@
148171 </li>
149172 </ul>
150173 </li>
151-<li id="repo-quiet" class="repo">
152-<div class="repo-main">
153-<h2 class="repo-name">
154-<a href="/quiet">quiet</a>
155-</h2>
156-<p class="repo-desc mute">no description</p>
157-<div class="repo-topics" aria-hidden="true">
158-</div>
159-<div class="repo-pulse" aria-hidden="true">
160-</div>
161-</div>
162-<div class="repo-head">
163-<dl class="repo-stats" aria-label="Figures of quiet">
164-<div class="rs-active">
165-<dt>last activity</dt>
166-<dd class="mute">never</dd>
167-</div>
168-</dl>
169-</div>
170-<p class="repo-commits mute">no commits yet</p>
171-</li>
172174 </ul>
173175 </section>
174176 </div>
@@ -289,6 +291,12 @@
289291 </div>
290292 <div>
291293 <dt>
294+<kbd>w</kbd>
295+</dt>
296+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
297+</div>
298+<div>
299+<dt>
292300 <kbd>y</kbd>
293301 </dt>
294302 <dd>copy a permalink pinned to the commit</dd>
@@ -337,15 +345,15 @@
337345 <span class="k">go</span>
338346 </a>
339347 </li>
340-<li id="cmd-o1" role="option" aria-selected="false" data-k="repo demo">
341-<a href="/demo" tabindex="-1">
342-<span class="lbl">demo</span>
348+<li id="cmd-o1" role="option" aria-selected="false" data-k="repo quiet">
349+<a href="/quiet" tabindex="-1">
350+<span class="lbl">quiet</span>
343351 <span class="k">repo</span>
344352 </a>
345353 </li>
346-<li id="cmd-o2" role="option" aria-selected="false" data-k="repo quiet">
347-<a href="/quiet" tabindex="-1">
348-<span class="lbl">quiet</span>
354+<li id="cmd-o2" role="option" aria-selected="false" data-k="repo demo">
355+<a href="/demo" tabindex="-1">
356+<span class="lbl">demo</span>
349357 <span class="k">repo</span>
350358 </a>
351359 </li>
@@ -389,6 +397,7 @@
389397 k-split from:body -> ermite.layout('split');
390398 k-unified from:body -> ermite.layout('unified');
391399 k-collapse from:body -> ermite.collapse();
400+ k-wide from:body -> window.ermiteWide.toggle();
392401 k-expand from:body -> ermite.expandFile();
393402 k-expandAll from:body -> ermite.expandAll();
394403 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -61,7 +61,7 @@
61 </div>61 </div>
62 <nav class="sort" aria-label="Sort repositories">62 <nav class="sort" aria-label="Sort repositories">
63 <span class="mute">sort</span>63 <span class="mute">sort</span>
64-<a href="/?sort=push&amp;dir=asc" aria-current="true">last push <span aria-hidden="true">▼</span>64+<a href="/?sort=activity&amp;dir=asc" aria-current="true">last activity <span aria-hidden="true">▼</span>
65 <span class="sr">, descending, select for ascending</span>65 <span class="sr">, descending, select for ascending</span>
66 </a>66 </a>
67 <a href="/?sort=name&amp;dir=asc">name </a>67 <a href="/?sort=name&amp;dir=asc">name </a>
@@ -81,8 +81,31 @@
81 </button>81 </button>
82 </div>82 </div>
83 </div>83 </div>
84-<ul class="repos" id="repo-rows" data-rows>84+<ul class="repos" id="repo-rows" data-rows data-sort="activity" data-desc="true">
85-<li id="repo-demo" class="repo" data-cursor>85+<li id="repo-quiet" class="repo" data-cursor>
86+<div class="repo-main">
87+<h2 class="repo-name">
88+<a href="/quiet">quiet</a>
89+</h2>
90+<p class="repo-desc mute">no description</p>
91+<div class="repo-topics" aria-hidden="true">
92+</div>
93+<div class="repo-pulse" aria-hidden="true">
94+</div>
95+</div>
96+<div class="repo-head">
97+<dl class="repo-stats" aria-label="Figures of quiet">
98+<div class="rs-active">
99+<dt>last activity</dt>
100+<dd>
101+<time datetime="STAMP" title="STAMP">AGO</time>
102+</dd>
103+</div>
104+</dl>
105+</div>
106+<p class="repo-commits mute">no commits yet</p>
107+</li>
108+<li id="repo-demo" class="repo">
86 <div class="repo-main">109 <div class="repo-main">
87 <h2 class="repo-name">110 <h2 class="repo-name">
88 <a href="/demo">demo</a>111 <a href="/demo">demo</a>
@@ -148,27 +171,6 @@
148 </li>171 </li>
149 </ul>172 </ul>
150 </li>173 </li>
151-<li id="repo-quiet" class="repo">
152-<div class="repo-main">
153-<h2 class="repo-name">
154-<a href="/quiet">quiet</a>
155-</h2>
156-<p class="repo-desc mute">no description</p>
157-<div class="repo-topics" aria-hidden="true">
158-</div>
159-<div class="repo-pulse" aria-hidden="true">
160-</div>
161-</div>
162-<div class="repo-head">
163-<dl class="repo-stats" aria-label="Figures of quiet">
164-<div class="rs-active">
165-<dt>last activity</dt>
166-<dd class="mute">never</dd>
167-</div>
168-</dl>
169-</div>
170-<p class="repo-commits mute">no commits yet</p>
171-</li>
172 </ul>174 </ul>
173 </section>175 </section>
174 </div>176 </div>
@@ -289,6 +291,12 @@
289 </div>291 </div>
290 <div>292 <div>
291 <dt>293 <dt>
294+<kbd>w</kbd>
295+</dt>
296+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
297+</div>
298+<div>
299+<dt>
292 <kbd>y</kbd>300 <kbd>y</kbd>
293 </dt>301 </dt>
294 <dd>copy a permalink pinned to the commit</dd>302 <dd>copy a permalink pinned to the commit</dd>
@@ -337,15 +345,15 @@
337 <span class="k">go</span>345 <span class="k">go</span>
338 </a>346 </a>
339 </li>347 </li>
340-<li id="cmd-o1" role="option" aria-selected="false" data-k="repo demo">348+<li id="cmd-o1" role="option" aria-selected="false" data-k="repo quiet">
341-<a href="/demo" tabindex="-1">349+<a href="/quiet" tabindex="-1">
342-<span class="lbl">demo</span>350+<span class="lbl">quiet</span>
343 <span class="k">repo</span>351 <span class="k">repo</span>
344 </a>352 </a>
345 </li>353 </li>
346-<li id="cmd-o2" role="option" aria-selected="false" data-k="repo quiet">354+<li id="cmd-o2" role="option" aria-selected="false" data-k="repo demo">
347-<a href="/quiet" tabindex="-1">355+<a href="/demo" tabindex="-1">
348-<span class="lbl">quiet</span>356+<span class="lbl">demo</span>
349 <span class="k">repo</span>357 <span class="k">repo</span>
350 </a>358 </a>
351 </li>359 </li>
@@ -389,6 +397,7 @@
389 k-split from:body -> ermite.layout('split');397 k-split from:body -> ermite.layout('split');
390 k-unified from:body -> ermite.layout('unified');398 k-unified from:body -> ermite.layout('unified');
391 k-collapse from:body -> ermite.collapse();399 k-collapse from:body -> ermite.collapse();
400+ k-wide from:body -> window.ermiteWide.toggle();
392 k-expand from:body -> ermite.expandFile();401 k-expand from:body -> ermite.expandFile();
393 k-expandAll from:body -> ermite.expandAll();402 k-expandAll from:body -> ermite.expandAll();
394 k-permalink from:body -> ermite.permalink();403 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/not-found-page.html +8 -1
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -177,6 +177,12 @@
177177 </div>
178178 <div>
179179 <dt>
180+<kbd>w</kbd>
181+</dt>
182+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
183+</div>
184+<div>
185+<dt>
180186 <kbd>y</kbd>
181187 </dt>
182188 <dd>copy a permalink pinned to the commit</dd>
@@ -265,6 +271,7 @@
265271 k-split from:body -> ermite.layout('split');
266272 k-unified from:body -> ermite.layout('unified');
267273 k-collapse from:body -> ermite.collapse();
274+ k-wide from:body -> window.ermiteWide.toggle();
268275 k-expand from:body -> ermite.expandFile();
269276 k-expandAll from:body -> ermite.expandAll();
270277 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -177,6 +177,12 @@
177 </div>177 </div>
178 <div>178 <div>
179 <dt>179 <dt>
180+<kbd>w</kbd>
181+</dt>
182+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
183+</div>
184+<div>
185+<dt>
180 <kbd>y</kbd>186 <kbd>y</kbd>
181 </dt>187 </dt>
182 <dd>copy a permalink pinned to the commit</dd>188 <dd>copy a permalink pinned to the commit</dd>
@@ -265,6 +271,7 @@
265 k-split from:body -> ermite.layout('split');271 k-split from:body -> ermite.layout('split');
266 k-unified from:body -> ermite.layout('unified');272 k-unified from:body -> ermite.layout('unified');
267 k-collapse from:body -> ermite.collapse();273 k-collapse from:body -> ermite.collapse();
274+ k-wide from:body -> window.ermiteWide.toggle();
268 k-expand from:body -> ermite.expandFile();275 k-expand from:body -> ermite.expandFile();
269 k-expandAll from:body -> ermite.expandAll();276 k-expandAll from:body -> ermite.expandAll();
270 k-permalink from:body -> ermite.permalink();277 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/not-found.html +8 -1
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -177,6 +177,12 @@
177177 </div>
178178 <div>
179179 <dt>
180+<kbd>w</kbd>
181+</dt>
182+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
183+</div>
184+<div>
185+<dt>
180186 <kbd>y</kbd>
181187 </dt>
182188 <dd>copy a permalink pinned to the commit</dd>
@@ -265,6 +271,7 @@
265271 k-split from:body -> ermite.layout('split');
266272 k-unified from:body -> ermite.layout('unified');
267273 k-collapse from:body -> ermite.collapse();
274+ k-wide from:body -> window.ermiteWide.toggle();
268275 k-expand from:body -> ermite.expandFile();
269276 k-expandAll from:body -> ermite.expandAll();
270277 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -177,6 +177,12 @@
177 </div>177 </div>
178 <div>178 <div>
179 <dt>179 <dt>
180+<kbd>w</kbd>
181+</dt>
182+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
183+</div>
184+<div>
185+<dt>
180 <kbd>y</kbd>186 <kbd>y</kbd>
181 </dt>187 </dt>
182 <dd>copy a permalink pinned to the commit</dd>188 <dd>copy a permalink pinned to the commit</dd>
@@ -265,6 +271,7 @@
265 k-split from:body -> ermite.layout('split');271 k-split from:body -> ermite.layout('split');
266 k-unified from:body -> ermite.layout('unified');272 k-unified from:body -> ermite.layout('unified');
267 k-collapse from:body -> ermite.collapse();273 k-collapse from:body -> ermite.collapse();
274+ k-wide from:body -> window.ermiteWide.toggle();
268 k-expand from:body -> ermite.expandFile();275 k-expand from:body -> ermite.expandFile();
269 k-expandAll from:body -> ermite.expandAll();276 k-expandAll from:body -> ermite.expandAll();
270 k-permalink from:body -> ermite.permalink();277 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/refs.html +8 -1
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -297,6 +297,12 @@
297297 </div>
298298 <div>
299299 <dt>
300+<kbd>w</kbd>
301+</dt>
302+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
303+</div>
304+<div>
305+<dt>
300306 <kbd>y</kbd>
301307 </dt>
302308 <dd>copy a permalink pinned to the commit</dd>
@@ -490,6 +496,7 @@
490496 k-split from:body -> ermite.layout('split');
491497 k-unified from:body -> ermite.layout('unified');
492498 k-collapse from:body -> ermite.collapse();
499+ k-wide from:body -> window.ermiteWide.toggle();
493500 k-expand from:body -> ermite.expandFile();
494501 k-expandAll from:body -> ermite.expandAll();
495502 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -297,6 +297,12 @@
297 </div>297 </div>
298 <div>298 <div>
299 <dt>299 <dt>
300+<kbd>w</kbd>
301+</dt>
302+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
303+</div>
304+<div>
305+<dt>
300 <kbd>y</kbd>306 <kbd>y</kbd>
301 </dt>307 </dt>
302 <dd>copy a permalink pinned to the commit</dd>308 <dd>copy a permalink pinned to the commit</dd>
@@ -490,6 +496,7 @@
490 k-split from:body -> ermite.layout('split');496 k-split from:body -> ermite.layout('split');
491 k-unified from:body -> ermite.layout('unified');497 k-unified from:body -> ermite.layout('unified');
492 k-collapse from:body -> ermite.collapse();498 k-collapse from:body -> ermite.collapse();
499+ k-wide from:body -> window.ermiteWide.toggle();
493 k-expand from:body -> ermite.expandFile();500 k-expand from:body -> ermite.expandFile();
494 k-expandAll from:body -> ermite.expandAll();501 k-expandAll from:body -> ermite.expandAll();
495 k-permalink from:body -> ermite.permalink();502 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/summary.html +8 -1
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -323,6 +323,12 @@
323323 </div>
324324 <div>
325325 <dt>
326+<kbd>w</kbd>
327+</dt>
328+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
329+</div>
330+<div>
331+<dt>
326332 <kbd>y</kbd>
327333 </dt>
328334 <dd>copy a permalink pinned to the commit</dd>
@@ -516,6 +522,7 @@
516522 k-split from:body -> ermite.layout('split');
517523 k-unified from:body -> ermite.layout('unified');
518524 k-collapse from:body -> ermite.collapse();
525+ k-wide from:body -> window.ermiteWide.toggle();
519526 k-expand from:body -> ermite.expandFile();
520527 k-expandAll from:body -> ermite.expandAll();
521528 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -323,6 +323,12 @@
323 </div>323 </div>
324 <div>324 <div>
325 <dt>325 <dt>
326+<kbd>w</kbd>
327+</dt>
328+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
329+</div>
330+<div>
331+<dt>
326 <kbd>y</kbd>332 <kbd>y</kbd>
327 </dt>333 </dt>
328 <dd>copy a permalink pinned to the commit</dd>334 <dd>copy a permalink pinned to the commit</dd>
@@ -516,6 +522,7 @@
516 k-split from:body -> ermite.layout('split');522 k-split from:body -> ermite.layout('split');
517 k-unified from:body -> ermite.layout('unified');523 k-unified from:body -> ermite.layout('unified');
518 k-collapse from:body -> ermite.collapse();524 k-collapse from:body -> ermite.collapse();
525+ k-wide from:body -> window.ermiteWide.toggle();
519 k-expand from:body -> ermite.expandFile();526 k-expand from:body -> ermite.expandFile();
520 k-expandAll from:body -> ermite.expandAll();527 k-expandAll from:body -> ermite.expandAll();
521 k-permalink from:body -> ermite.permalink();528 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/tree-nested.html +8 -1
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -254,6 +254,12 @@
254254 </div>
255255 <div>
256256 <dt>
257+<kbd>w</kbd>
258+</dt>
259+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
260+</div>
261+<div>
262+<dt>
257263 <kbd>y</kbd>
258264 </dt>
259265 <dd>copy a permalink pinned to the commit</dd>
@@ -431,6 +437,7 @@
431437 k-split from:body -> ermite.layout('split');
432438 k-unified from:body -> ermite.layout('unified');
433439 k-collapse from:body -> ermite.collapse();
440+ k-wide from:body -> window.ermiteWide.toggle();
434441 k-expand from:body -> ermite.expandFile();
435442 k-expandAll from:body -> ermite.expandAll();
436443 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -254,6 +254,12 @@
254 </div>254 </div>
255 <div>255 <div>
256 <dt>256 <dt>
257+<kbd>w</kbd>
258+</dt>
259+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
260+</div>
261+<div>
262+<dt>
257 <kbd>y</kbd>263 <kbd>y</kbd>
258 </dt>264 </dt>
259 <dd>copy a permalink pinned to the commit</dd>265 <dd>copy a permalink pinned to the commit</dd>
@@ -431,6 +437,7 @@
431 k-split from:body -> ermite.layout('split');437 k-split from:body -> ermite.layout('split');
432 k-unified from:body -> ermite.layout('unified');438 k-unified from:body -> ermite.layout('unified');
433 k-collapse from:body -> ermite.collapse();439 k-collapse from:body -> ermite.collapse();
440+ k-wide from:body -> window.ermiteWide.toggle();
434 k-expand from:body -> ermite.expandFile();441 k-expand from:body -> ermite.expandFile();
435 k-expandAll from:body -> ermite.expandAll();442 k-expandAll from:body -> ermite.expandAll();
436 k-permalink from:body -> ermite.permalink();443 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/tree-root.html +8 -1
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -475,6 +475,12 @@
475475 </div>
476476 <div>
477477 <dt>
478+<kbd>w</kbd>
479+</dt>
480+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
481+</div>
482+<div>
483+<dt>
478484 <kbd>y</kbd>
479485 </dt>
480486 <dd>copy a permalink pinned to the commit</dd>
@@ -652,6 +658,7 @@
652658 k-split from:body -> ermite.layout('split');
653659 k-unified from:body -> ermite.layout('unified');
654660 k-collapse from:body -> ermite.collapse();
661+ k-wide from:body -> window.ermiteWide.toggle();
655662 k-expand from:body -> ermite.expandFile();
656663 k-expandAll from:body -> ermite.expandAll();
657664 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -475,6 +475,12 @@
475 </div>475 </div>
476 <div>476 <div>
477 <dt>477 <dt>
478+<kbd>w</kbd>
479+</dt>
480+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
481+</div>
482+<div>
483+<dt>
478 <kbd>y</kbd>484 <kbd>y</kbd>
479 </dt>485 </dt>
480 <dd>copy a permalink pinned to the commit</dd>486 <dd>copy a permalink pinned to the commit</dd>
@@ -652,6 +658,7 @@
652 k-split from:body -> ermite.layout('split');658 k-split from:body -> ermite.layout('split');
653 k-unified from:body -> ermite.layout('unified');659 k-unified from:body -> ermite.layout('unified');
654 k-collapse from:body -> ermite.collapse();660 k-collapse from:body -> ermite.collapse();
661+ k-wide from:body -> window.ermiteWide.toggle();
655 k-expand from:body -> ermite.expandFile();662 k-expand from:body -> ermite.expandFile();
656 k-expandAll from:body -> ermite.expandAll();663 k-expandAll from:body -> ermite.expandAll();
657 k-permalink from:body -> ermite.permalink();664 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/tree-slash-ref.html +8 -1
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -251,6 +251,12 @@
251251 </div>
252252 <div>
253253 <dt>
254+<kbd>w</kbd>
255+</dt>
256+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
257+</div>
258+<div>
259+<dt>
254260 <kbd>y</kbd>
255261 </dt>
256262 <dd>copy a permalink pinned to the commit</dd>
@@ -428,6 +434,7 @@
428434 k-split from:body -> ermite.layout('split');
429435 k-unified from:body -> ermite.layout('unified');
430436 k-collapse from:body -> ermite.collapse();
437+ k-wide from:body -> window.ermiteWide.toggle();
431438 k-expand from:body -> ermite.expandFile();
432439 k-expandAll from:body -> ermite.expandAll();
433440 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -251,6 +251,12 @@
251 </div>251 </div>
252 <div>252 <div>
253 <dt>253 <dt>
254+<kbd>w</kbd>
255+</dt>
256+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
257+</div>
258+<div>
259+<dt>
254 <kbd>y</kbd>260 <kbd>y</kbd>
255 </dt>261 </dt>
256 <dd>copy a permalink pinned to the commit</dd>262 <dd>copy a permalink pinned to the commit</dd>
@@ -428,6 +434,7 @@
428 k-split from:body -> ermite.layout('split');434 k-split from:body -> ermite.layout('split');
429 k-unified from:body -> ermite.layout('unified');435 k-unified from:body -> ermite.layout('unified');
430 k-collapse from:body -> ermite.collapse();436 k-collapse from:body -> ermite.collapse();
437+ k-wide from:body -> window.ermiteWide.toggle();
431 k-expand from:body -> ermite.expandFile();438 k-expand from:body -> ermite.expandFile();
432 k-expandAll from:body -> ermite.expandAll();439 k-expandAll from:body -> ermite.expandAll();
433 k-permalink from:body -> ermite.permalink();440 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/tree-subdir.html +8 -1
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -267,6 +267,12 @@
267267 </div>
268268 <div>
269269 <dt>
270+<kbd>w</kbd>
271+</dt>
272+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
273+</div>
274+<div>
275+<dt>
270276 <kbd>y</kbd>
271277 </dt>
272278 <dd>copy a permalink pinned to the commit</dd>
@@ -444,6 +450,7 @@
444450 k-split from:body -> ermite.layout('split');
445451 k-unified from:body -> ermite.layout('unified');
446452 k-collapse from:body -> ermite.collapse();
453+ k-wide from:body -> window.ermiteWide.toggle();
447454 k-expand from:body -> ermite.expandFile();
448455 k-expandAll from:body -> ermite.expandAll();
449456 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -267,6 +267,12 @@
267 </div>267 </div>
268 <div>268 <div>
269 <dt>269 <dt>
270+<kbd>w</kbd>
271+</dt>
272+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
273+</div>
274+<div>
275+<dt>
270 <kbd>y</kbd>276 <kbd>y</kbd>
271 </dt>277 </dt>
272 <dd>copy a permalink pinned to the commit</dd>278 <dd>copy a permalink pinned to the commit</dd>
@@ -444,6 +450,7 @@
444 k-split from:body -> ermite.layout('split');450 k-split from:body -> ermite.layout('split');
445 k-unified from:body -> ermite.layout('unified');451 k-unified from:body -> ermite.layout('unified');
446 k-collapse from:body -> ermite.collapse();452 k-collapse from:body -> ermite.collapse();
453+ k-wide from:body -> window.ermiteWide.toggle();
447 k-expand from:body -> ermite.expandFile();454 k-expand from:body -> ermite.expandFile();
448 k-expandAll from:body -> ermite.expandAll();455 k-expandAll from:body -> ermite.expandAll();
449 k-permalink from:body -> ermite.permalink();456 k-permalink from:body -> ermite.permalink();
M internal/app/web_events_test.go +2 -2
@@ -123,7 +123,7 @@ func TestWebEventsHome(t *testing.T) {
123123 assert.Equal(t, "home-list", ev.name)
124124 assert.NotEmpty(t, ev.id)
125125 assert.Contains(t, ev.data, `<li id="repo-pub"`)
126- assert.Contains(t, ev.data, `new push`)
126+ assert.Contains(t, ev.data, `new activity`)
127127 assert.Contains(t, ev.data, `href="/pub"`)
128128 assert.Contains(t, ev.data, `<ul class="repo-commits" aria-label="Recent commits of pub">`, "the live row lists the commits too")
129129
@@ -134,7 +134,7 @@ func TestWebEventsHome(t *testing.T) {
134134 assert.Equal(t, "home-list", ev.name)
135135 assert.Contains(t, ev.data, `<li id="repo-fresh"`)
136136 assert.Contains(t, ev.data, "a &lt;b&gt;fresh&lt;/b&gt; one", "text is escaped")
137- assert.NotContains(t, ev.data, "new push")
137+ assert.NotContains(t, ev.data, "new activity")
138138
139139 // A repository that turns private is removed from the anonymous list.
140140 priv := domain.VisibilityPrivate
@@ -123,7 +123,7 @@ func TestWebEventsHome(t *testing.T) {
123 assert.Equal(t, "home-list", ev.name)123 assert.Equal(t, "home-list", ev.name)
124 assert.NotEmpty(t, ev.id)124 assert.NotEmpty(t, ev.id)
125 assert.Contains(t, ev.data, `<li id="repo-pub"`)125 assert.Contains(t, ev.data, `<li id="repo-pub"`)
126- assert.Contains(t, ev.data, `new push`)126+ assert.Contains(t, ev.data, `new activity`)
127 assert.Contains(t, ev.data, `href="/pub"`)127 assert.Contains(t, ev.data, `href="/pub"`)
128 assert.Contains(t, ev.data, `<ul class="repo-commits" aria-label="Recent commits of pub">`, "the live row lists the commits too")128 assert.Contains(t, ev.data, `<ul class="repo-commits" aria-label="Recent commits of pub">`, "the live row lists the commits too")
129 129
@@ -134,7 +134,7 @@ func TestWebEventsHome(t *testing.T) {
134 assert.Equal(t, "home-list", ev.name)134 assert.Equal(t, "home-list", ev.name)
135 assert.Contains(t, ev.data, `<li id="repo-fresh"`)135 assert.Contains(t, ev.data, `<li id="repo-fresh"`)
136 assert.Contains(t, ev.data, "a &lt;b&gt;fresh&lt;/b&gt; one", "text is escaped")136 assert.Contains(t, ev.data, "a &lt;b&gt;fresh&lt;/b&gt; one", "text is escaped")
137- assert.NotContains(t, ev.data, "new push")137+ assert.NotContains(t, ev.data, "new activity")
138 138
139 // A repository that turns private is removed from the anonymous list.139 // A repository that turns private is removed from the anonymous list.
140 priv := domain.VisibilityPrivate140 priv := domain.VisibilityPrivate
M internal/app/web_mr_test.go +2 -0
@@ -268,6 +268,8 @@ func TestWebMergeRequestReviewAndFastForward(t *testing.T) {
268268 assert.Contains(t, line.body, `name="path" value="search.go"`)
269269 assert.Contains(t, line.body, `name="line" value="3"`)
270270 assert.Contains(t, line.body, `name="side" value="new"`)
271+ assert.Contains(t, line.body, `comment on search.go:3 (new)`, "one line: the title names it")
272+ assert.Contains(t, b.get(base+"?on=new:1-3:search.go", as(token)).body, `comment on search.go:1-3 (new)`, "a range: the title names all of it")
271273 assert.Equal(t, 0, strings.Count(b.get(base+"?on=new:99:search.go", as(token)).body, `name="line" value="99"`), "a line the diff lacks has no form")
272274 assert.Equal(t, 0, strings.Count(b.get(base+"?on=new:3:search.go", nil).body, `<form`), "nor for a visitor")
273275
@@ -268,6 +268,8 @@ func TestWebMergeRequestReviewAndFastForward(t *testing.T) {
268 assert.Contains(t, line.body, `name="path" value="search.go"`)268 assert.Contains(t, line.body, `name="path" value="search.go"`)
269 assert.Contains(t, line.body, `name="line" value="3"`)269 assert.Contains(t, line.body, `name="line" value="3"`)
270 assert.Contains(t, line.body, `name="side" value="new"`)270 assert.Contains(t, line.body, `name="side" value="new"`)
271+ assert.Contains(t, line.body, `comment on search.go:3 (new)`, "one line: the title names it")
272+ assert.Contains(t, b.get(base+"?on=new:1-3:search.go", as(token)).body, `comment on search.go:1-3 (new)`, "a range: the title names all of it")
271 assert.Equal(t, 0, strings.Count(b.get(base+"?on=new:99:search.go", as(token)).body, `name="line" value="99"`), "a line the diff lacks has no form")273 assert.Equal(t, 0, strings.Count(b.get(base+"?on=new:99:search.go", as(token)).body, `name="line" value="99"`), "a line the diff lacks has no form")
272 assert.Equal(t, 0, strings.Count(b.get(base+"?on=new:3:search.go", nil).body, `<form`), "nor for a visitor")274 assert.Equal(t, 0, strings.Count(b.get(base+"?on=new:3:search.go", nil).body, `<form`), "nor for a visitor")
273 275
M internal/domain/repo.go +1 -0
@@ -42,6 +42,7 @@ type Repo struct {
4242 Visibility Visibility
4343 CreatedAt time.Time
4444 PushedAt time.Time // zero until the first push
45+ ActiveAt time.Time // latest push, merge request or issue activity
4546 // Settings are the merge settings; the stores always fill them in, with
4647 // the defaults for a repository that never stored any.
4748 Settings RepoSettings
@@ -42,6 +42,7 @@ type Repo struct {
42 Visibility Visibility42 Visibility Visibility
43 CreatedAt time.Time43 CreatedAt time.Time
44 PushedAt time.Time // zero until the first push44 PushedAt time.Time // zero until the first push
45+ ActiveAt time.Time // latest push, merge request or issue activity
45 // Settings are the merge settings; the stores always fill them in, with46 // Settings are the merge settings; the stores always fill them in, with
46 // the defaults for a repository that never stored any.47 // the defaults for a repository that never stored any.
47 Settings RepoSettings48 Settings RepoSettings
M internal/ports/dto.go +1 -1
@@ -21,7 +21,7 @@ type RepoPage struct {
2121
2222 // RepoView is a repository with the Git state that belongs in its summary.
2323 type RepoView struct {
24- domain.Repo
24+ domain.Repo // includes ActiveAt, the latest repository activity
2525 // DefaultBranch is the branch HEAD points to, even when it does not exist
2626 // yet; empty when HEAD is detached or the directory is missing.
2727 DefaultBranch string
@@ -21,7 +21,7 @@ type RepoPage struct {
21 21
22 // RepoView is a repository with the Git state that belongs in its summary.22 // RepoView is a repository with the Git state that belongs in its summary.
23 type RepoView struct {23 type RepoView struct {
24- domain.Repo24+ domain.Repo // includes ActiveAt, the latest repository activity
25 // DefaultBranch is the branch HEAD points to, even when it does not exist25 // DefaultBranch is the branch HEAD points to, even when it does not exist
26 // yet; empty when HEAD is detached or the directory is missing.26 // yet; empty when HEAD is detached or the directory is missing.
27 DefaultBranch string27 DefaultBranch string
M internal/ports/mocks/RepoMetaStore.go +36 -21
@@ -289,59 +289,74 @@ func (_c *RepoMetaStore_List_Call) RunAndReturn(run func(ctx context.Context) ([
289289 return _c
290290 }
291291
292-// Update provides a mock function for the type RepoMetaStore
293-func (_mock *RepoMetaStore) Update(ctx context.Context, repo domain.Repo) error {
294- ret := _mock.Called(ctx, repo)
292+// Mutate provides a mock function for the type RepoMetaStore
293+func (_mock *RepoMetaStore) Mutate(ctx context.Context, name domain.RepoName, fn func(*domain.Repo) (changed bool, err error)) (domain.Repo, error) {
294+ ret := _mock.Called(ctx, name, fn)
295295
296296 if len(ret) == 0 {
297- panic("no return value specified for Update")
297+ panic("no return value specified for Mutate")
298298 }
299299
300- var r0 error
301- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Repo) error); ok {
302- r0 = returnFunc(ctx, repo)
300+ var r0 domain.Repo
301+ var r1 error
302+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, func(*domain.Repo) (changed bool, err error)) (domain.Repo, error)); ok {
303+ return returnFunc(ctx, name, fn)
304+ }
305+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, func(*domain.Repo) (changed bool, err error)) domain.Repo); ok {
306+ r0 = returnFunc(ctx, name, fn)
303307 } else {
304- r0 = ret.Error(0)
308+ r0 = ret.Get(0).(domain.Repo)
305309 }
306- return r0
310+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName, func(*domain.Repo) (changed bool, err error)) error); ok {
311+ r1 = returnFunc(ctx, name, fn)
312+ } else {
313+ r1 = ret.Error(1)
314+ }
315+ return r0, r1
307316 }
308317
309-// RepoMetaStore_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update'
310-type RepoMetaStore_Update_Call struct {
318+// RepoMetaStore_Mutate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Mutate'
319+type RepoMetaStore_Mutate_Call struct {
311320 *mock.Call
312321 }
313322
314-// Update is a helper method to define mock.On call
323+// Mutate is a helper method to define mock.On call
315324 // - ctx context.Context
316-// - repo domain.Repo
317-func (_e *RepoMetaStore_Expecter) Update(ctx any, repo any) *RepoMetaStore_Update_Call {
318- return &RepoMetaStore_Update_Call{Call: _e.mock.On("Update", ctx, repo)}
325+// - name domain.RepoName
326+// - fn func(*domain.Repo) (changed bool, err error)
327+func (_e *RepoMetaStore_Expecter) Mutate(ctx any, name any, fn any) *RepoMetaStore_Mutate_Call {
328+ return &RepoMetaStore_Mutate_Call{Call: _e.mock.On("Mutate", ctx, name, fn)}
319329 }
320330
321-func (_c *RepoMetaStore_Update_Call) Run(run func(ctx context.Context, repo domain.Repo)) *RepoMetaStore_Update_Call {
331+func (_c *RepoMetaStore_Mutate_Call) Run(run func(ctx context.Context, name domain.RepoName, fn func(*domain.Repo) (changed bool, err error))) *RepoMetaStore_Mutate_Call {
322332 _c.Call.Run(func(args mock.Arguments) {
323333 var arg0 context.Context
324334 if args[0] != nil {
325335 arg0 = args[0].(context.Context)
326336 }
327- var arg1 domain.Repo
337+ var arg1 domain.RepoName
328338 if args[1] != nil {
329- arg1 = args[1].(domain.Repo)
339+ arg1 = args[1].(domain.RepoName)
340+ }
341+ var arg2 func(*domain.Repo) (changed bool, err error)
342+ if args[2] != nil {
343+ arg2 = args[2].(func(*domain.Repo) (changed bool, err error))
330344 }
331345 run(
332346 arg0,
333347 arg1,
348+ arg2,
334349 )
335350 })
336351 return _c
337352 }
338353
339-func (_c *RepoMetaStore_Update_Call) Return(err error) *RepoMetaStore_Update_Call {
340- _c.Call.Return(err)
354+func (_c *RepoMetaStore_Mutate_Call) Return(repo domain.Repo, err error) *RepoMetaStore_Mutate_Call {
355+ _c.Call.Return(repo, err)
341356 return _c
342357 }
343358
344-func (_c *RepoMetaStore_Update_Call) RunAndReturn(run func(ctx context.Context, repo domain.Repo) error) *RepoMetaStore_Update_Call {
359+func (_c *RepoMetaStore_Mutate_Call) RunAndReturn(run func(ctx context.Context, name domain.RepoName, fn func(*domain.Repo) (changed bool, err error)) (domain.Repo, error)) *RepoMetaStore_Mutate_Call {
345360 _c.Call.Return(run)
346361 return _c
347362 }
@@ -289,59 +289,74 @@ func (_c *RepoMetaStore_List_Call) RunAndReturn(run func(ctx context.Context) ([
289 return _c289 return _c
290 }290 }
291 291
292-// Update provides a mock function for the type RepoMetaStore292+// Mutate provides a mock function for the type RepoMetaStore
293-func (_mock *RepoMetaStore) Update(ctx context.Context, repo domain.Repo) error {293+func (_mock *RepoMetaStore) Mutate(ctx context.Context, name domain.RepoName, fn func(*domain.Repo) (changed bool, err error)) (domain.Repo, error) {
294- ret := _mock.Called(ctx, repo)294+ ret := _mock.Called(ctx, name, fn)
295 295
296 if len(ret) == 0 {296 if len(ret) == 0 {
297- panic("no return value specified for Update")297+ panic("no return value specified for Mutate")
298 }298 }
299 299
300- var r0 error300+ var r0 domain.Repo
301- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Repo) error); ok {301+ var r1 error
302- r0 = returnFunc(ctx, repo)302+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, func(*domain.Repo) (changed bool, err error)) (domain.Repo, error)); ok {
303+ return returnFunc(ctx, name, fn)
304+ }
305+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, func(*domain.Repo) (changed bool, err error)) domain.Repo); ok {
306+ r0 = returnFunc(ctx, name, fn)
303 } else {307 } else {
304- r0 = ret.Error(0)308+ r0 = ret.Get(0).(domain.Repo)
305 }309 }
306- return r0310+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName, func(*domain.Repo) (changed bool, err error)) error); ok {
311+ r1 = returnFunc(ctx, name, fn)
312+ } else {
313+ r1 = ret.Error(1)
314+ }
315+ return r0, r1
307 }316 }
308 317
309-// RepoMetaStore_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update'318+// RepoMetaStore_Mutate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Mutate'
310-type RepoMetaStore_Update_Call struct {319+type RepoMetaStore_Mutate_Call struct {
311 *mock.Call320 *mock.Call
312 }321 }
313 322
314-// Update is a helper method to define mock.On call323+// Mutate is a helper method to define mock.On call
315 // - ctx context.Context324 // - ctx context.Context
316-// - repo domain.Repo325+// - name domain.RepoName
317-func (_e *RepoMetaStore_Expecter) Update(ctx any, repo any) *RepoMetaStore_Update_Call {326+// - fn func(*domain.Repo) (changed bool, err error)
318- return &RepoMetaStore_Update_Call{Call: _e.mock.On("Update", ctx, repo)}327+func (_e *RepoMetaStore_Expecter) Mutate(ctx any, name any, fn any) *RepoMetaStore_Mutate_Call {
328+ return &RepoMetaStore_Mutate_Call{Call: _e.mock.On("Mutate", ctx, name, fn)}
319 }329 }
320 330
321-func (_c *RepoMetaStore_Update_Call) Run(run func(ctx context.Context, repo domain.Repo)) *RepoMetaStore_Update_Call {331+func (_c *RepoMetaStore_Mutate_Call) Run(run func(ctx context.Context, name domain.RepoName, fn func(*domain.Repo) (changed bool, err error))) *RepoMetaStore_Mutate_Call {
322 _c.Call.Run(func(args mock.Arguments) {332 _c.Call.Run(func(args mock.Arguments) {
323 var arg0 context.Context333 var arg0 context.Context
324 if args[0] != nil {334 if args[0] != nil {
325 arg0 = args[0].(context.Context)335 arg0 = args[0].(context.Context)
326 }336 }
327- var arg1 domain.Repo337+ var arg1 domain.RepoName
328 if args[1] != nil {338 if args[1] != nil {
329- arg1 = args[1].(domain.Repo)339+ arg1 = args[1].(domain.RepoName)
340+ }
341+ var arg2 func(*domain.Repo) (changed bool, err error)
342+ if args[2] != nil {
343+ arg2 = args[2].(func(*domain.Repo) (changed bool, err error))
330 }344 }
331 run(345 run(
332 arg0,346 arg0,
333 arg1,347 arg1,
348+ arg2,
334 )349 )
335 })350 })
336 return _c351 return _c
337 }352 }
338 353
339-func (_c *RepoMetaStore_Update_Call) Return(err error) *RepoMetaStore_Update_Call {354+func (_c *RepoMetaStore_Mutate_Call) Return(repo domain.Repo, err error) *RepoMetaStore_Mutate_Call {
340- _c.Call.Return(err)355+ _c.Call.Return(repo, err)
341 return _c356 return _c
342 }357 }
343 358
344-func (_c *RepoMetaStore_Update_Call) RunAndReturn(run func(ctx context.Context, repo domain.Repo) error) *RepoMetaStore_Update_Call {359+func (_c *RepoMetaStore_Mutate_Call) RunAndReturn(run func(ctx context.Context, name domain.RepoName, fn func(*domain.Repo) (changed bool, err error)) (domain.Repo, error)) *RepoMetaStore_Mutate_Call {
345 _c.Call.Return(run)360 _c.Call.Return(run)
346 return _c361 return _c
347 }362 }
M internal/ports/ports.go +7 -4
@@ -32,10 +32,13 @@ type RepoMetaStore interface {
3232 Get(ctx context.Context, name domain.RepoName) (domain.Repo, error)
3333 // List returns every repository ordered by name.
3434 List(ctx context.Context) ([]domain.Repo, error)
35- // Update replaces the stored repository. It returns domain.ErrNotFound if
36- // there is none and domain.ErrConflict if another write changed the
37- // stored repository since it was read.
38- Update(ctx context.Context, repo domain.Repo) error
35+ // Mutate applies fn to the stored repository and writes under the version read.
36+ // Concurrent writes retry fn up to a bounded number of attempts, then return
37+ // domain.ErrConflict. fn must be pure and safe to call several times, and
38+ // must not change Name. changed=false writes nothing. It returns the stored
39+ // repository, or domain.ErrNotFound when absent. Errors from fn are returned
40+ // unchanged with nothing written.
41+ Mutate(ctx context.Context, name domain.RepoName, fn func(*domain.Repo) (changed bool, err error)) (domain.Repo, error)
3942 // Delete removes the repository and everything keyed to it: its merge
4043 // requests, their comments and read positions, and its number sequence.
4144 // A repository created again under the same name starts empty, with its
@@ -32,10 +32,13 @@ type RepoMetaStore interface {
32 Get(ctx context.Context, name domain.RepoName) (domain.Repo, error)32 Get(ctx context.Context, name domain.RepoName) (domain.Repo, error)
33 // List returns every repository ordered by name.33 // List returns every repository ordered by name.
34 List(ctx context.Context) ([]domain.Repo, error)34 List(ctx context.Context) ([]domain.Repo, error)
35- // Update replaces the stored repository. It returns domain.ErrNotFound if35+ // Mutate applies fn to the stored repository and writes under the version read.
36- // there is none and domain.ErrConflict if another write changed the36+ // Concurrent writes retry fn up to a bounded number of attempts, then return
37- // stored repository since it was read.37+ // domain.ErrConflict. fn must be pure and safe to call several times, and
38- Update(ctx context.Context, repo domain.Repo) error38+ // must not change Name. changed=false writes nothing. It returns the stored
39+ // repository, or domain.ErrNotFound when absent. Errors from fn are returned
40+ // unchanged with nothing written.
41+ Mutate(ctx context.Context, name domain.RepoName, fn func(*domain.Repo) (changed bool, err error)) (domain.Repo, error)
39 // Delete removes the repository and everything keyed to it: its merge42 // Delete removes the repository and everything keyed to it: its merge
40 // requests, their comments and read positions, and its number sequence.43 // requests, their comments and read positions, and its number sequence.
41 // A repository created again under the same name starts empty, with its44 // A repository created again under the same name starts empty, with its
A internal/usecase/activity.go +43 -0
A internal/usecase/activity_store_test.go +46 -0
A internal/usecase/activity_test.go +47 -0
M internal/usecase/export_test.go +10 -0
M internal/usecase/issue_moderation.go +1 -0
M internal/usecase/issue_moderation_test.go +1 -0
M internal/usecase/issues.go +3 -0
M internal/usecase/issues_integration_test.go +67 -0
M internal/usecase/issues_test.go +2 -0
M internal/usecase/mergerequests.go +5 -0
M internal/usecase/mergerequests_integration_test.go +11 -0
M internal/usecase/pushes.go +21 -29
M internal/usecase/pushes_test.go +11 -16
A internal/usecase/repo_mutation_test.go +34 -0
M internal/usecase/repos_write.go +6 -14
M internal/usecase/repos_write_test.go +20 -21
M proto/ermite/storage/repo.proto +2 -0

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 42

↑↓ moveenter openesc close