detached 5d69f2b

fix(web): ARIA semantics for MR panels, dialogs, palette, forms and status regions (!31)

- fix(web): improve ARIA semantics of MR panels, dialogs, palette, forms and status regions
- fix(web): keep MR panels open on refresh, expose one option per palette result, clear finder status
- test(app): update web goldens for the ARIA changes
commit
5d69f2b26c46390bac7207c48c805519b99b6653
author
brice · · 2026-10-22 07:30 UTC
committer
ermite · · 2026-10-22 08:49 UTC
parent
cf26365
signature
SSH VerifiedSHA256:lY9fILbB/A+hln6Vh/iHoiiog74bbw/4qWrf/lrTU3U · ermite instance key Verified for the committer email
checks
4/4
55 files · +1051 -671
A internal/adapters/web/accessibility_test.go +53 -0
@@ -0,0 +1,53 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+
3+package web
4+
5+import (
6+ "testing"
7+
8+ "github.com/stretchr/testify/assert"
9+)
10+
11+func TestIssueFieldAccessibility(t *testing.T) {
12+ for _, field := range []string{"title", "body", "author"} {
13+ t.Run(field, func(t *testing.T) {
14+ html := renderToString(t, IssueForm(Layout{}, IssueFormPage{ErrorField: field, Error: "Too long"}))
15+ assert.Contains(t, html, `id="issue-`+field+`" aria-describedby="issue-`+field+`-count issue-`+field+`-error" aria-invalid="true"`)
16+ assert.Contains(t, html, `<p id="issue-`+field+`-error" class="f-error" role="alert">Too long</p>`)
17+ for _, f := range []string{"title", "body", "author"} {
18+ assert.Contains(t, html, `<output id="issue-`+f+`-count" aria-live="off"`)
19+ }
20+ })
21+ }
22+}
23+
24+func TestHintAccessibility(t *testing.T) {
25+ html := renderToString(t, Hint(Layout{Live: true, LiveURL: "/events"}))
26+ assert.Contains(t, html, `<div class="hint"><span aria-hidden="true" class="hint-nav"`)
27+ assert.Contains(t, html, `<span role="status" aria-live="polite"><span class="hint-sel"`)
28+ assert.Contains(t, html, `</span></span><span class="hint-live" role="status"`)
29+}
30+
31+func TestPaletteAccessibilityWithoutNavigationItems(t *testing.T) {
32+ html := renderToString(t, OverlayCmd(nil))
33+ // The shortcut preference action is always present, even with no targets.
34+ assert.Contains(t, html, `aria-expanded="true"`)
35+ assert.Contains(t, html, `aria-activedescendant="cmd-o0"`)
36+ assert.Contains(t, html, `id="cmd-o0" role="option" aria-selected="true" data-cursor`)
37+ assert.Contains(t, html, `<a role="presentation" data-fire="keys">`)
38+ assert.Contains(t, html, `aria-label="Close (esc)"`)
39+ assert.NotContains(t, html, ` href=`)
40+ assert.NotContains(t, html, `tabindex="-1"`)
41+}
42+
43+func TestFinderStatusOutsideHiddenResults(t *testing.T) {
44+ html := renderToString(t, Tree(Layout{}, TreePage{}))
45+ assert.Contains(t, html, `<p id="finder-status" class="dim" role="status"></p><div id="finder" class="tree-card" hidden>`)
46+}
47+
48+func TestPaletteNavigationOptionsAccessibility(t *testing.T) {
49+ html := renderToString(t, OverlayCmd([]CmdItem{{Kind: "section", Label: "Files", Href: "/files"}}))
50+ assert.Contains(t, html, `<a role="presentation" data-href="/files">`)
51+ assert.NotContains(t, html, ` href=`)
52+ assert.NotContains(t, html, `tabindex="-1"`)
53+}
M internal/adapters/web/fragments.templ +1 -0
@@ -94,6 +94,7 @@ templ Live(l Layout) {
94 if l.Live && l.LiveURL != "" {94 if l.Live && l.LiveURL != "" {
95 <span95 <span
96 class="hint-live"96 class="hint-live"
97+ role="status"
97 data-state="connecting"98 data-state="connecting"
98 hx-sse:connect={ l.LiveURL }99 hx-sse:connect={ l.LiveURL }
99 hx-swap="none"100 hx-swap="none"
M internal/adapters/web/fragments_templ.go +2 -2 generated file: collapsed
M internal/adapters/web/issues_form.templ +47 -9
@@ -29,22 +29,52 @@ templ IssueForm(l Layout, p IssueFormPage) {
29 <input type="hidden" name="form_token" value={ p.FormToken }/>29 <input type="hidden" name="form_token" value={ p.FormToken }/>
30 <input type="hidden" name="solution" value=""/>30 <input type="hidden" name="solution" value=""/>
31 <label for="issue-title">title</label>31 <label for="issue-title">title</label>
32- <input id="issue-title" name="title" type="text" required autocomplete="off" value={ p.Title }/>32+ <input
33- <output data-for="issue-title" data-max={ itoa(p.MaxTitle) } for="issue-title">{ itoa(runeCount(p.Title)) } / { itoa(p.MaxTitle) }</output>33+ id="issue-title"
34+ aria-describedby={ issueFieldDescription("title", p.ErrorField) }
35+ if p.ErrorField == "title" {
36+ aria-invalid="true"
37+ }
38+ name="title"
39+ type="text"
40+ required
41+ autocomplete="off"
42+ value={ p.Title }
43+ />
44+ <output id="issue-title-count" aria-live="off" data-for="issue-title" data-max={ itoa(p.MaxTitle) } for="issue-title">{ itoa(runeCount(p.Title)) } / { itoa(p.MaxTitle) }</output>
34 if p.ErrorField == "title" {45 if p.ErrorField == "title" {
35- <p class="f-error" role="alert">{ p.Error }</p>46+ <p id="issue-title-error" class="f-error" role="alert">{ p.Error }</p>
36 }47 }
37 <label for="issue-body">description</label>48 <label for="issue-body">description</label>
38- <textarea id="issue-body" name="body" rows="8" placeholder="Markdown, optional">{ p.Body }</textarea>49+ <textarea
39- <output data-for="issue-body" data-max={ itoa(p.MaxBody) } for="issue-body">{ itoa(runeCount(p.Body)) } / { itoa(p.MaxBody) }</output>50+ id="issue-body"
51+ aria-describedby={ issueFieldDescription("body", p.ErrorField) }
52+ if p.ErrorField == "body" {
53+ aria-invalid="true"
54+ }
55+ name="body"
56+ rows="8"
57+ placeholder="Markdown, optional"
58+ >{ p.Body }</textarea>
59+ <output id="issue-body-count" aria-live="off" data-for="issue-body" data-max={ itoa(p.MaxBody) } for="issue-body">{ itoa(runeCount(p.Body)) } / { itoa(p.MaxBody) }</output>
40 if p.ErrorField == "body" {60 if p.ErrorField == "body" {
41- <p class="f-error" role="alert">{ p.Error }</p>61+ <p id="issue-body-error" class="f-error" role="alert">{ p.Error }</p>
42 }62 }
43 <label for="issue-author">name (optional, not verified)</label>63 <label for="issue-author">name (optional, not verified)</label>
44- <input id="issue-author" name="author" type="text" autocomplete="off" value={ p.Author }/>64+ <input
45- <output data-for="issue-author" data-max={ itoa(p.MaxAuthor) } for="issue-author">{ itoa(runeCount(p.Author)) } / { itoa(p.MaxAuthor) }</output>65+ id="issue-author"
66+ aria-describedby={ issueFieldDescription("author", p.ErrorField) }
67+ if p.ErrorField == "author" {
68+ aria-invalid="true"
69+ }
70+ name="author"
71+ type="text"
72+ autocomplete="off"
73+ value={ p.Author }
74+ />
75+ <output id="issue-author-count" aria-live="off" data-for="issue-author" data-max={ itoa(p.MaxAuthor) } for="issue-author">{ itoa(runeCount(p.Author)) } / { itoa(p.MaxAuthor) }</output>
46 if p.ErrorField == "author" {76 if p.ErrorField == "author" {
47- <p class="f-error" role="alert">{ p.Error }</p>77+ <p id="issue-author-error" class="f-error" role="alert">{ p.Error }</p>
48 }78 }
49 <div class="hp" aria-hidden="true">79 <div class="hp" aria-hidden="true">
50 <label for="issue-website">Leave this field empty</label>80 <label for="issue-website">Leave this field empty</label>
@@ -65,3 +95,11 @@ templ issueFormRail() {
65 <p class="dim">The owner reads every issue before it appears. Plain text and Markdown only; no address of yours is kept.</p>95 <p class="dim">The owner reads every issue before it appears. Plain text and Markdown only; no address of yours is kept.</p>
66 </section>96 </section>
67 }97 }
98+
99+func issueFieldDescription(field, invalid string) string {
100+ ids := "issue-" + field + "-count"
101+ if field == invalid {
102+ ids += " issue-" + field + "-error"
103+ }
104+ return ids
105+}
M internal/adapters/web/issues_form_templ.go +161 -84 generated file: collapsed
M internal/adapters/web/issues_form_test.go +2 -2
@@ -79,9 +79,9 @@ func TestIssueFormGet(t *testing.T) {
79 `action="/proj/issues/new"`, ` hidden`, `data-issue-form`, `data-nonce="AAECAwQFBgcICQoLDA0ODw"`,79 `action="/proj/issues/new"`, ` hidden`, `data-issue-form`, `data-nonce="AAECAwQFBgcICQoLDA0ODw"`,
80 `data-difficulty="18"`, `data-min-submit-at="1700000003000"`, `data-worker="/-/static/js/pow-worker.`,80 `data-difficulty="18"`, `data-min-submit-at="1700000003000"`, `data-worker="/-/static/js/pow-worker.`,
81 `name="token" value="tok-1.sig"`, `name="form_token" value="form-1"`, `name="solution" value=""`,81 `name="token" value="tok-1.sig"`, `name="form_token" value="form-1"`, `name="solution" value=""`,
82- `name="title" type="text" required`, `<output data-for="issue-body" data-max="10000"`, `<output data-for="issue-author" data-max="40"`,82+ `name="title" type="text" required`, `<output id="issue-body-count" aria-live="off" data-for="issue-body" data-max="10000"`, `<output id="issue-author-count" aria-live="off" data-for="issue-author" data-max="40"`,
83 `<div class="hp" aria-hidden="true">`, `name="website" type="text" tabindex="-1" autocomplete="off"`,83 `<div class="hp" aria-hidden="true">`, `name="website" type="text" tabindex="-1" autocomplete="off"`,
84- `<output data-for="issue-title" data-max="120"`, `0 / 10000`, `Checking your browser… (this takes a few seconds)`,84+ `<output id="issue-title-count" aria-live="off" data-for="issue-title" data-max="120"`, `0 / 10000`, `Checking your browser… (this takes a few seconds)`,
85 `/-/static/js/issue-form.`,85 `/-/static/js/issue-form.`,
86 } {86 } {
87 assert.Contains(t, body, want)87 assert.Contains(t, body, want)
M internal/adapters/web/jstest/a11y.test.mjs +79 -1
@@ -83,7 +83,7 @@ function keydown(target, key, elementHandler, extra = {}) {
83 // ---- the palette --------------------------------------------------------83 // ---- the palette --------------------------------------------------------
84 84
85 function palette() {85 function palette() {
86- const li = (k, label) => el('li', { attrs: { 'data-k': k } }, el('a', { attrs: { href: '/' + label } }));86+ const li = (k, label) => el('li', { attrs: { 'data-k': k } }, el('a', { attrs: { 'data-href': '/' + label, role: 'presentation' } }));
87 const items = [li('go home', 'home'), li('section files', 'files'), li('branch main', 'main')];87 const items = [li('go home', 'home'), li('section files', 'files'), li('branch main', 'main')];
88 items[0].setAttribute('data-cursor', '');88 items[0].setAttribute('data-cursor', '');
89 const input = el('input', { id: 'cmd-q', attrs: { role: 'combobox', 'aria-controls': 'cmd-list' } });89 const input = el('input', { id: 'cmd-q', attrs: { role: 'combobox', 'aria-controls': 'cmd-list' } });
@@ -137,6 +137,8 @@ test('the palette: the file hits that arrive later get ids, roles and the active
137 assert.ok(hits.length > 0, 'a file hit came');137 assert.ok(hits.length > 0, 'a file hit came');
138 for (const h of hits) {138 for (const h of hits) {
139 assert.equal(h.getAttribute('role'), 'option');139 assert.equal(h.getAttribute('role'), 'option');
140+ assert.equal(h.querySelector('a[href]'), null);
141+ assert.equal(h.querySelector('a').hasAttribute('tabindex'), false);
140 assert.ok(h.id);142 assert.ok(h.id);
141 }143 }
142 assert.equal(p.list.children.length, new Set(p.list.children.map((i) => i.id)).size, 'ids stay unique');144 assert.equal(p.list.children.length, new Set(p.list.children.map((i) => i.id)).size, 'ids stay unique');
@@ -173,6 +175,7 @@ test('Escape in the finder clears the query, keeps the focus in the field and do
173 f.q.closest = () => f.q; // the global handler sees a field being typed in175 f.q.closest = () => f.q; // the global handler sees a field being typed in
174 const e = keydown(f.q, 'Escape', (ev) => F.key(f.q, ev));176 const e = keydown(f.q, 'Escape', (ev) => F.key(f.q, ev));
175 assert.equal(f.q.value, '');177 assert.equal(f.q.value, '');
178+ assert.equal(doc.getElementById('finder-status').textContent, '');
176 assert.equal(e.stopped, true);179 assert.equal(e.stopped, true);
177 assert.equal(e.prevented, true);180 assert.equal(e.prevented, true);
178 assert.equal(blurred, false, 'the field keeps the focus');181 assert.equal(blurred, false, 'the field keeps the focus');
@@ -381,3 +384,78 @@ test('mobile MR layout shortcuts preserve the saved preference until back on des
381 doc.querySelector = oldQuery;384 doc.querySelector = oldQuery;
382 }385 }
383 });386 });
387+
388+test('dialog restores its row opener on close, but not over a replacement dialog', () => {
389+ const row = el('li', { tabindex: '0' });
390+ const first = el('dialog', { id: 'focus-first' });
391+ const second = el('dialog', { id: 'focus-second' });
392+ dom.body.append(row, first, second);
393+ const queued = [];
394+ const all = doc.querySelectorAll;
395+ const one = doc.querySelector;
396+ const open = () => [first, second].filter((d) => d.hasAttribute('open'));
397+ doc.querySelectorAll = (sel) => sel === 'dialog[open]' ? open() : all.call(doc, sel);
398+ doc.querySelector = (sel) => sel === 'dialog[open]' ? open()[0] ?? null : one.call(doc, sel);
399+ for (const d of [first, second]) {
400+ let onClose;
401+ d.addEventListener = (type, fn) => { if (type === 'close') onClose = fn; };
402+ d.showModal = () => { d.setAttribute('open', ''); d.focus(); };
403+ d.close = () => { d.removeAttribute('open'); queued.push(onClose); };
404+ }
405+ row.focus();
406+ window.ermite.dialog('focus-first');
407+ first.close();
408+ queued.shift()();
409+ assert.equal(doc.activeElement, row);
410+
411+ row.focus();
412+ window.ermite.dialog('focus-first');
413+ window.ermite.dialog('focus-second');
414+ queued.shift()();
415+ assert.equal(doc.activeElement, second);
416+ second.close();
417+ queued.shift()();
418+ doc.querySelectorAll = all;
419+ doc.querySelector = one;
420+});
421+
422+
423+test('listbox result wrappers have no links; Enter and delegated clicks navigate', async () => {
424+ doc.querySelector = query.one;
425+ doc.querySelectorAll = query.all;
426+ const assigned = [];
427+ location.assign = (href) => assigned.push(href);
428+ const activate = (anchor) => {
429+ const e = { target: anchor, button: 0, preventDefault() {} };
430+ listeners.click.find((fn) => fn.toString().includes('location.assign'))(e);
431+ };
432+ const p = palette();
433+ p.list.setAttribute('role', 'listbox');
434+ window.ermiteCmd.sync();
435+ for (const item of p.items) assert.equal(item.querySelector('a[href]'), null);
436+ const a = p.items[0].querySelector('a');
437+ a.click = () => activate(a);
438+ window.ermite.cmdKey({ key: 'Enter', preventDefault() {} });
439+ assert.deepEqual(assigned, ['/home']);
440+ activate(p.items[1].querySelector('a'));
441+ assert.deepEqual(assigned, ['/home', '/files']);
442+
443+ const f = finder();
444+ f.list.setAttribute('role', 'listbox');
445+ f.q.value = 'main';
446+ F.input(f.q);
447+ flushFrames();
448+ await settle();
449+ assert.equal(f.list.querySelector('a[href]'), null);
450+ const hit = f.list.children[0].querySelector('a');
451+ assert.equal(hit.hasAttribute('tabindex'), false);
452+ hit.click = () => activate(hit);
453+ F.key(f.q, { key: 'Enter', preventDefault() {} });
454+ assert.equal(assigned.at(-1), hit.dataset.href);
455+ activate(hit);
456+ assert.equal(assigned.at(-1), hit.dataset.href);
457+ assert.notEqual(doc.getElementById('finder-status').textContent, '');
458+ f.q.value = '';
459+ F.input(f.q);
460+ assert.equal(doc.getElementById('finder-status').textContent, '');
461+});
M internal/adapters/web/jstest/finder.test.mjs +9 -8
@@ -53,7 +53,7 @@ const key = (k, extra = {}) => {
53 return e;53 return e;
54 };54 };
55 const status = () => document.getElementById('finder-status').textContent;55 const status = () => document.getElementById('finder-status').textContent;
56-const hrefs = (list) => list.children.map((li) => li.querySelector('a').href);56+const hrefs = (list) => list.children.map((li) => li.querySelector('a').dataset.href);
57 57
58 test('Enter before the results opens the top hit once they come', async () => {58 test('Enter before the results opens the top hit once they come', async () => {
59 const p = page();59 const p = page();
@@ -66,8 +66,8 @@ test('Enter before the results opens the top hit once they come', async () => {
66 await settle();66 await settle();
67 assert.equal(p.finderList.children.length > 0, true);67 assert.equal(p.finderList.children.length > 0, true);
68 assert.equal(dom.clicked.length, 1);68 assert.equal(dom.clicked.length, 1);
69- assert.equal(dom.clicked[0].href, hrefs(p.finderList)[0]);69+ assert.equal(dom.clicked[0].dataset.href, hrefs(p.finderList)[0]);
70- assert.match(dom.clicked[0].href, /^\/blob\/main\/.*tree/);70+ assert.match(dom.clicked[0].dataset.href, /^\/blob\/main\/.*tree/);
71 assert.equal(fetches, 1);71 assert.equal(fetches, 1);
72 });72 });
73 73
@@ -112,7 +112,7 @@ test('Enter on current results opens the cursor row, whichever way the cursor mo
112 assert.equal(items[2].getAttribute('aria-selected'), 'true');112 assert.equal(items[2].getAttribute('aria-selected'), 'true');
113 assert.equal(items[0].getAttribute('aria-selected'), 'false');113 assert.equal(items[0].getAttribute('aria-selected'), 'false');
114 assert.equal(p.q.getAttribute('aria-activedescendant'), items[2].id);114 assert.equal(p.q.getAttribute('aria-activedescendant'), items[2].id);
115- assert.equal(dom.clicked[0].href, items[2].querySelector('a').href);115+ assert.equal(dom.clicked[0].dataset.href, items[2].querySelector('a').dataset.href);
116 // The arrows move from there.116 // The arrows move from there.
117 F.key(p.q, key('ArrowUp'));117 F.key(p.q, key('ArrowUp'));
118 assert.equal(p.q.getAttribute('aria-activedescendant'), items[1].id);118 assert.equal(p.q.getAttribute('aria-activedescendant'), items[1].id);
@@ -179,13 +179,14 @@ test('the palette lists the file hits under its entries', async () => {
179 const hits = p.cmdList.children.filter((li) => li.className === 'finder-hit');179 const hits = p.cmdList.children.filter((li) => li.className === 'finder-hit');
180 assert.equal(hits.length, 4);180 assert.equal(hits.length, 4);
181 assert.equal(p.cmdList.children[0].className, 'entry', 'the entries stay first');181 assert.equal(p.cmdList.children[0].className, 'entry', 'the entries stay first');
182- assert.deepEqual(hits.map((li) => li.querySelector('a').href).sort(), [182+ assert.deepEqual(hits.map((li) => li.querySelector('a').dataset.href).sort(), [
183 '/blob/main/docs/web/tree-view.md',183 '/blob/main/docs/web/tree-view.md',
184 '/blob/main/internal/adapters/web/tree.go',184 '/blob/main/internal/adapters/web/tree.go',
185 '/blob/main/internal/adapters/web/tree.templ',185 '/blob/main/internal/adapters/web/tree.templ',
186 '/blob/main/src/treehouse.txt',186 '/blob/main/src/treehouse.txt',
187 ]);187 ]);
188- assert.equal(hits[0].querySelector('a').tabIndex, -1);188+ assert.equal(hits[0].querySelector('a').hasAttribute('href'), false);
189+ assert.equal(hits[0].querySelector('a').hasAttribute('tabindex'), false);
189 assert.equal(hits[0].querySelector('.k').textContent, 'file');190 assert.equal(hits[0].querySelector('.k').textContent, 'file');
190 assert.equal(p.cmdList.children.filter((li) => li.hasAttribute('data-cursor')).length, 1, 'one cursor');191 assert.equal(p.cmdList.children.filter((li) => li.hasAttribute('data-cursor')).length, 1, 'one cursor');
191 192
@@ -213,7 +214,7 @@ test('Enter typed before the frame of the query runs opens the top hit', async (
213 assert.equal(dom.clicked.length, 0, 'nothing to open before the frame');214 assert.equal(dom.clicked.length, 0, 'nothing to open before the frame');
214 flushFrames();215 flushFrames();
215 await settle();216 await settle();
216- assert.deepEqual(dom.clicked.map((a) => a.href), ['/blob/main/cmd/ermite/main.go']);217+ assert.deepEqual(dom.clicked.map((a) => a.dataset.href), ['/blob/main/cmd/ermite/main.go']);
217 } finally {218 } finally {
218 dom.holdFrames = false;219 dom.holdFrames = false;
219 }220 }
@@ -257,7 +258,7 @@ test('the palette drops the results of a query that is stale', async () => {
257 p.cmdQ.value = 'readme'; // typed before the search of "tree" finished258 p.cmdQ.value = 'readme'; // typed before the search of "tree" finished
258 F.palette(p.cmdQ);259 F.palette(p.cmdQ);
259 await settle();260 await settle();
260- const got = p.cmdList.children.filter((li) => li.className === 'finder-hit').map((li) => li.querySelector('a').href);261+ const got = p.cmdList.children.filter((li) => li.className === 'finder-hit').map((li) => li.querySelector('a').dataset.href);
261 assert.deepEqual(got, ['/blob/main/README.md']);262 assert.deepEqual(got, ['/blob/main/README.md']);
262 263
263 // A field changed without a new call: the pending search is dropped.264 // A field changed without a new call: the pending search is dropped.
M internal/adapters/web/jstest/mr.test.mjs +33 -1
@@ -106,7 +106,7 @@ function footerHarness(extra = {}) {
106 const nav = node('tabs'); nav.querySelectorAll = () => tabs;106 const nav = node('tabs'); nav.querySelectorAll = () => tabs;
107 const listeners = {}, replaced = [];107 const listeners = {}, replaced = [];
108 globalThis.document = { querySelector: (s) => s === '.mr-footer' ? footer : s === '[data-mr-tabs]' ? nav : s === '#mr-ck' ? extra.summary ?? null : null,108 globalThis.document = { querySelector: (s) => s === '.mr-footer' ? footer : s === '[data-mr-tabs]' ? nav : s === '#mr-ck' ? extra.summary ?? null : null,
109- querySelectorAll: (s) => s === '[data-mr-tabs] a' ? tabs : [], addEventListener(k, v) { listeners[k] = v; } };109+ querySelectorAll: (s) => s === '[data-mr-tabs] button' ? tabs : [], addEventListener(k, v) { listeners[k] = v; } };
110 globalThis.location = { hash: '', pathname: '/demo/merge-requests/1', search: '?x=1' };110 globalThis.location = { hash: '', pathname: '/demo/merge-requests/1', search: '?x=1' };
111 globalThis.addEventListener = (k, v) => { listeners[k] = v; };111 globalThis.addEventListener = (k, v) => { listeners[k] = v; };
112 globalThis.history = { replaceState: (...args) => replaced.push(args) };112 globalThis.history = { replaceState: (...args) => replaced.push(args) };
@@ -531,3 +531,35 @@ test('intercepted Cancel resets the entire comment form', () => {
531 assert.equal(window.ermiteMR.hasDraft(), false);531 assert.equal(window.ermiteMR.hasDraft(), false);
532 } finally { Object.assign(globalThis, saved); c.restore(); }532 } finally { Object.assign(globalThis, saved); c.restore(); }
533 });533 });
534+
535+
536+test('live refresh preserves the open button panel even when the hash points elsewhere', async () => {
537+ const { readFileSync } = await import('node:fs');
538+ const src = readFileSync(new URL('../static/js/mr.js', import.meta.url), 'utf8');
539+ const start = src.indexOf(' async function refreshPage(page)');
540+ const end = src.indexOf(' function refreshNotice', start);
541+ const { el, installDom } = await import('./dom.mjs');
542+ const saved = globalThis.document;
543+ const h = footerHarness();
544+ try {
545+ window.ermiteMR.panels();
546+ h.tabs[1].events.click({ preventDefault() {} });
547+ location.hash = '#f99';
548+ const doc = installDom();
549+ Object.defineProperty(doc, 'activeElement', { get: () => null });
550+ const diffs = el('div');
551+ diffs.dataset.diff = 'unified';
552+ const fresh = el('footer', {}, el('section', { attrs: { 'data-mr-panel': '' } }), el('section', { attrs: { 'data-mr-panel': '' } }));
553+ const page = { body: { dataset: {} } };
554+ const $ = (s, root) => s === '#diffs' ? diffs : s === '.mr-footer' ? (root === page ? fresh : h.footer) : el('div');
555+ const $$ = (s, root) => s === '[data-mr-tabs] button' ? h.tabs : s === '[data-mr-panel]' ? fresh.children : [];
556+ let selected;
557+ const lazy = { reset() {}, watch() {} };
558+ const refresh = new Function('$', '$$', 'scrollAnchor', 'hasDraft', 'replaceRegion', 'panels', 'watchBar', 'foldFiles', 'touchComments', 'restoreAnchor', 'window', 'document',
559+ 'let revealGen = 0; ' + src.slice(start, end) + '; return refreshPage;')(
560+ $, $$, () => ({}), () => false, async () => {}, (i) => { selected = i; }, () => {}, () => {}, () => {}, () => {}, { ermiteLazy: lazy }, doc);
561+ await refresh(page);
562+ assert.equal(selected, 1);
563+ assert.deepEqual(fresh.children.map((p) => p.hidden), [true, false]);
564+ } finally { h.restore(); globalThis.document = saved; }
565+});
M internal/adapters/web/mr.templ +9 -9
@@ -296,7 +296,7 @@ templ commentView(c CommentView, suffix string) {
296 <button type="button" class="btn" data-js data-quote={ c.Quote }>Quote</button>296 <button type="button" class="btn" data-js data-quote={ c.Quote }>Quote</button>
297 }297 }
298 if c.Edit != nil {298 if c.Edit != nil {
299- <details><summary>Edit</summary><form method="post" action={ templ.SafeURL(c.Edit.Action) }><input type="hidden" name="from" value={ itoa(c.Edit.From) }/><textarea name="text" maxlength="8000">{ c.Edit.Text }</textarea><button class="btn" type="submit">Save</button></form></details>299+ <details><summary>Edit</summary><form method="post" action={ templ.SafeURL(c.Edit.Action) }><input type="hidden" name="from" value={ itoa(c.Edit.From) }/><textarea name="text" maxlength="8000" aria-label="Edit note">{ c.Edit.Text }</textarea><button class="btn" type="submit">Save</button></form></details>
300 }300 }
301 if c.Delete != nil {301 if c.Delete != nil {
302 <details><summary>Delete</summary><form method="post" action={ templ.SafeURL(c.Delete.Action) }><input type="hidden" name="from" value={ itoa(c.Delete.From) }/><p>Delete this note?</p><button class="btn" type="submit">Confirm delete</button></form></details>302 <details><summary>Delete</summary><form method="post" action={ templ.SafeURL(c.Delete.Action) }><input type="hidden" name="from" value={ itoa(c.Delete.From) }/><p>Delete this note?</p><button class="btn" type="submit">Confirm delete</button></form></details>
@@ -373,9 +373,9 @@ templ commentForm(f CommentForm, suffix string) {
373 373
374 templ mrFooter(p MRPage) {374 templ mrFooter(p MRPage) {
375 <footer class="mr-footer">375 <footer class="mr-footer">
376- <nav data-mr-tabs aria-label="Branch details">376+ <div data-mr-tabs data-js role="group" aria-label="Branch details">
377- <a id="mr-tab-commits" href="#mr-commits">Commits <span class="count">{ p.CommitCount }</span></a>377+ <button type="button" id="mr-tab-commits" aria-expanded="true" aria-controls="mr-commits">Commits <span class="count">{ p.CommitCount }</span></button>
378- <a id="mr-tab-checks" href="#mr-checks">378+ <button type="button" id="mr-tab-checks" aria-expanded="true" aria-controls="mr-checks-panel">
379 { "Checks " }379 { "Checks " }
380 <span data-checks-label>380 <span data-checks-label>
381 if p.ChecksSum.Total > 0 {381 if p.ChecksSum.Total > 0 {
@@ -384,10 +384,10 @@ templ mrFooter(p MRPage) {
384 no checks384 no checks
385 }385 }
386 </span>386 </span>
387- </a>387+ </button>
388- </nav>388+ </div>
389- <div id="mr-commits" data-mr-panel role="region" aria-labelledby="mr-tab-commits">389+ <div id="mr-commits" data-mr-panel role="group" aria-labelledby="mr-tab-commits">
390- <table class="mr-commits">390+ <table class="mr-commits" aria-label="Commits">
391 <thead><tr><th>Commit</th><th>Subject</th><th>Author</th><th>Date</th><th>Signature</th><th>Status</th></tr></thead>391 <thead><tr><th>Commit</th><th>Subject</th><th>Author</th><th>Date</th><th>Signature</th><th>Status</th></tr></thead>
392 <tbody>392 <tbody>
393 for _, c := range p.Commits {393 for _, c := range p.Commits {
@@ -421,7 +421,7 @@ templ mrFooter(p MRPage) {
421 <p class="note">{ p.CommitsNote }</p>421 <p class="note">{ p.CommitsNote }</p>
422 }422 }
423 </div>423 </div>
424- <div id="mr-checks-panel" data-mr-panel role="region" aria-labelledby="mr-tab-checks">424+ <div id="mr-checks-panel" data-mr-panel role="group" aria-labelledby="mr-tab-checks">
425 @MRChecks(p.Checks)425 @MRChecks(p.Checks)
426 </div>426 </div>
427 </footer>427 </footer>
M internal/adapters/web/mr_templ.go +7 -7 generated file: collapsed
M internal/adapters/web/mr_test.go +8 -7
@@ -137,21 +137,22 @@ func TestMRPanelsAndMenus(t *testing.T) {
137 p := MRPage{CommitCount: "500+", CommitsNote: "more commits", Modes: []string{"squash", "merge"}, ChecksBlock: true,137 p := MRPage{CommitCount: "500+", CommitsNote: "more commits", Modes: []string{"squash", "merge"}, ChecksBlock: true,
138 ChecksSum: ChecksSummary{Total: 2, Label: "1/2", Title: "1/2 failed"}, Box: &MergeBox{Signed: true, Fingerprint: "fingerprint", HeadGone: true}}138 ChecksSum: ChecksSummary{Total: 2, Label: "1/2", Title: "1/2 failed"}, Box: &MergeBox{Signed: true, Fingerprint: "fingerprint", HeadGone: true}}
139 footer := renderToString(t, mrFooter(p))139 footer := renderToString(t, mrFooter(p))
140- assert.Contains(t, footer, `<footer class="mr-footer"><nav data-mr-tabs`)140+ assert.Contains(t, footer, `<footer class="mr-footer"><div data-mr-tabs data-js role="group"`)
141 assert.NotContains(t, footer, `<details`)141 assert.NotContains(t, footer, `<details`)
142 assert.NotContains(t, footer, `<summary`)142 assert.NotContains(t, footer, `<summary`)
143- // No hidden attribute: the CSS shows the panel of the URL fragment until143+ // Both panels are readable without scripts; JS enhances native buttons.
144- // mr.js takes over, so the tabs work as anchors without scripts.144+ assert.Contains(t, footer, `id="mr-commits" data-mr-panel role="group"`)
145- assert.Contains(t, footer, `id="mr-commits" data-mr-panel role="region"`)145+ assert.Contains(t, footer, `id="mr-checks-panel" data-mr-panel role="group"`)
146- assert.Contains(t, footer, `id="mr-checks-panel" data-mr-panel role="region"`)
147 assert.NotContains(t, footer, `data-mr-panel hidden`)146 assert.NotContains(t, footer, `data-mr-panel hidden`)
148 css, err := staticFS.ReadFile("static/css/60-mr.css")147 css, err := staticFS.ReadFile("static/css/60-mr.css")
149 require.NoError(t, err)148 require.NoError(t, err)
150- assert.Contains(t, string(css), `&:not([data-panels]) [data-mr-panel]:not(:target, :has(:target)) { display: none; }`)149+ assert.NotContains(t, string(css), `:not([data-panels])`)
150+ assert.Contains(t, footer, `<button type="button" id="mr-tab-commits" aria-expanded="true" aria-controls="mr-commits">`)
151+ assert.Contains(t, footer, `<button type="button" id="mr-tab-checks" aria-expanded="true" aria-controls="mr-checks-panel">`)
151 assert.Contains(t, footer, `data-mr-tabs`)152 assert.Contains(t, footer, `data-mr-tabs`)
152 assert.Contains(t, footer, `id="mr-commits"`)153 assert.Contains(t, footer, `id="mr-commits"`)
153 assert.Contains(t, footer, `id="mr-checks"`)154 assert.Contains(t, footer, `id="mr-checks"`)
154- assert.Contains(t, footer, `<table class="mr-commits">`)155+ assert.Contains(t, footer, `<table class="mr-commits" aria-label="Commits">`)
155 assert.Contains(t, footer, "500+")156 assert.Contains(t, footer, "500+")
156 assert.Contains(t, footer, "more commits")157 assert.Contains(t, footer, "more commits")
157 assert.Contains(t, footer, "1/2 failed")158 assert.Contains(t, footer, "1/2 failed")
M internal/adapters/web/overlays.templ +5 -5
@@ -11,7 +11,7 @@ import (
11 // show on that page only.11 // show on that page only.
12 templ OverlayHelp(mr bool) {12 templ OverlayHelp(mr bool) {
13 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">13 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
14- <div class="panel-head"><h2>Keyboard shortcuts</h2><button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"><kbd>esc</kbd> close</button></div>14+ <div class="panel-head"><h2>Keyboard shortcuts</h2><button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()"><kbd>esc</kbd> close</button></div>
15 <div class="keys-pref">15 <div class="keys-pref">
16 <label><input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"/> Turn off single-key shortcuts</label>16 <label><input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"/> Turn off single-key shortcuts</label>
17 <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p>17 <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p>
@@ -60,7 +60,7 @@ templ OverlayRefs(l Layout) {
60 data-pick="#panel-refs li"60 data-pick="#panel-refs li"
61 hx-on:input="ermite.filter(this)"61 hx-on:input="ermite.filter(this)"
62 />62 />
63- <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"><kbd>esc</kbd></button>63+ <button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()"><kbd>esc</kbd></button>
64 </div>64 </div>
65 <div class="panel-cols" data-picklist>65 <div class="panel-cols" data-picklist>
66 <section aria-label="Branches">66 <section aria-label="Branches">
@@ -123,13 +123,13 @@ templ OverlayCmd(items []CmdItem) {
123 hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)"123 hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)"
124 hx-on:keydown="ermite.cmdKey(event)"124 hx-on:keydown="ermite.cmdKey(event)"
125 />125 />
126- <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"><kbd>esc</kbd></button>126+ <button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()"><kbd>esc</kbd></button>
127 </div>127 </div>
128 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">128 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
129 for i, c := range items {129 for i, c := range items {
130- <li id={ "cmd-o" + itoa(i) } role="option" aria-selected={ strconv.FormatBool(i == 0) } data-k={ strings.ToLower(c.Kind + " " + c.Label) } data-cursor?={ i == 0 }><a href={ templ.SafeURL(c.Href) } tabindex="-1"><span class="lbl">{ c.Label }</span><span class="k">{ c.Kind }</span></a></li>130+ <li id={ "cmd-o" + itoa(i) } role="option" aria-selected={ strconv.FormatBool(i == 0) } data-k={ strings.ToLower(c.Kind + " " + c.Label) } data-cursor?={ i == 0 }><a role="presentation" data-href={ c.Href }><span class="lbl">{ c.Label }</span><span class="k">{ c.Kind }</span></a></li>
131 }131 }
132- <li id={ "cmd-o" + itoa(len(items)) } role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"><a href="#" data-fire="keys" tabindex="-1"><span class="lbl">turn single-key shortcuts off</span><span class="k">action</span></a></li>132+ <li id={ "cmd-o" + itoa(len(items)) } role="option" aria-selected={ strconv.FormatBool(len(items) == 0) } data-cursor?={ len(items) == 0 } data-k="action turn single-key shortcuts on off keyboard"><a role="presentation" data-fire="keys"><span class="lbl">turn single-key shortcuts off</span><span class="k">action</span></a></li>
133 </ul>133 </ul>
134 <p class="panel-foot"><span><kbd>↑</kbd><kbd>↓</kbd> move</span><span><kbd>enter</kbd> open</span><span><kbd>esc</kbd> close</span></p>134 <p class="panel-foot"><span><kbd>↑</kbd><kbd>↓</kbd> move</span><span><kbd>enter</kbd> open</span><span><kbd>esc</kbd> close</span></p>
135 </dialog>135 </dialog>
M internal/adapters/web/overlays_templ.go +35 -12 generated file: collapsed
M internal/adapters/web/shell.templ +4 -4
@@ -207,10 +207,10 @@ templ Footer(l Layout) {
207 // move, the selected lines of a file, a flash message (permalink copied) and207 // move, the selected lines of a file, a flash message (permalink copied) and
208 // a lost event stream. Empty, it takes no room.208 // a lost event stream. Empty, it takes no room.
209 templ Hint(l Layout) {209 templ Hint(l Layout) {
210- <div class="hint" role="status" aria-live="polite">210+ <div class="hint">
211- <span class="hint-nav" :text="document.body.dataset.nav || ''"></span>211+ <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''"></span>
212- <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''"></span>212+ <span role="status" aria-live="polite"><span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''"></span>
213- <span class="hint-flash" :text="document.body.dataset.flash || ''"></span>213+ <span class="hint-flash" :text="document.body.dataset.flash || ''"></span></span>
214 @Live(l)214 @Live(l)
215 </div>215 </div>
216 }216 }
M internal/adapters/web/shell_templ.go +1 -1 generated file: collapsed
M internal/adapters/web/static/css/20-shell.css +2 -1
@@ -204,12 +204,13 @@
204 background: var(--bg-overlay); border: 1px solid var(--border-strong); border-radius: var(--r); box-shadow: var(--shadow);204 background: var(--bg-overlay); border: 1px solid var(--border-strong); border-radius: var(--r); box-shadow: var(--shadow);
205 pointer-events: none;205 pointer-events: none;
206 & > :empty { display: none; }206 & > :empty { display: none; }
207+ & > [role="status"] { display: contents; }
207 & .hint-live { color: var(--red); }208 & .hint-live { color: var(--red); }
208 }209 }
209 /* empty, the badge is out of sight but stays a status region (display: none210 /* empty, the badge is out of sight but stays a status region (display: none
210 would drop it from the accessibility tree and its next message would not be211 would drop it from the accessibility tree and its next message would not be
211 announced) */212 announced) */
212- .hint:not(:has(> :not(:empty))) {213+ .hint:not(:has(.hint-nav:not(:empty), .hint-sel:not(:empty), .hint-flash:not(:empty), .hint-live:not(:empty))) {
213 width: 1px; height: 1px; padding: 0; border: 0; overflow: hidden; clip-path: inset(50%); box-shadow: none;214 width: 1px; height: 1px; padding: 0; border: 0; overflow: hidden; clip-path: inset(50%); box-shadow: none;
214 }215 }
215 216
M internal/adapters/web/static/css/60-mr.css +5 -8
@@ -17,22 +17,19 @@
17 font-size: var(--fs-s);17 font-size: var(--fs-s);
18 /* the tabs are buttons: a thin border, the count in a darker badge;18 /* the tabs are buttons: a thin border, the count in a darker badge;
19 the open one in the teal accent */19 the open one in the teal accent */
20- & nav { display: flex; flex-wrap: wrap; gap: var(--s2); }20+ & [data-mr-tabs] { display: flex; flex-wrap: wrap; gap: var(--s2); }
21- & [data-mr-tabs] a {21+ & [data-mr-tabs] button {
22 display: inline-flex; align-items: center; gap: var(--s1);22 display: inline-flex; align-items: center; gap: var(--s1);
23 padding: 2px var(--s2); border: 1px solid var(--border-strong); border-radius: var(--r-s);23 padding: 2px var(--s2); border: 1px solid var(--border-strong); border-radius: var(--r-s);
24 color: var(--ink-dim); text-decoration: none; cursor: pointer;24 color: var(--ink-dim); text-decoration: none; cursor: pointer;
25 }25 }
26 & [data-checks-label] { color: var(--ink-dim); font-size: var(--fs-xs); }26 & [data-checks-label] { color: var(--ink-dim); font-size: var(--fs-xs); }
27 & [data-mr-tabs] .count { margin-left: 0; color: var(--ink-hi); background: var(--bg-canvas); box-shadow: inset 0 0 0 1px var(--border); }27 & [data-mr-tabs] .count { margin-left: 0; color: var(--ink-hi); background: var(--bg-canvas); box-shadow: inset 0 0 0 1px var(--border); }
28- & [data-mr-tabs] a:hover { background: var(--bg-strong); color: var(--ink-hi); }28+ & [data-mr-tabs] button:hover { background: var(--bg-strong); color: var(--ink-hi); }
29- & [data-mr-tabs] a[aria-expanded="true"] { border-color: var(--teal); color: var(--teal); background: var(--bg-surface); }29+ & [data-mr-tabs] button[aria-expanded="true"] { border-color: var(--teal); color: var(--teal); background: var(--bg-surface); }
30 & [data-mr-panel] { padding-block-start: var(--s2); overflow-x: auto; }30 & [data-mr-panel] { padding-block-start: var(--s2); overflow-x: auto; }
31 & [hidden] { display: none; }31 & [hidden] { display: none; }
32- /* Until mr.js drives the panels (data-panels, then [hidden]), only the32+ /* Without scripts both panels remain visible; disclosure controls use data-js. */
33- panel of the URL fragment shows: the tabs work as anchors without
34- scripts, and no panel flashes open before the script runs. */
35- &:not([data-panels]) [data-mr-panel]:not(:target, :has(:target)) { display: none; }
36 & section h2 { font-size: var(--fs-s); color: var(--ink-dim); }33 & section h2 { font-size: var(--fs-s); color: var(--ink-dim); }
37 & table { width: 100%; border-collapse: collapse; }34 & table { width: 100%; border-collapse: collapse; }
38 & th, & td { text-align: left; padding: var(--s2); border-bottom: 1px solid var(--border); }35 & th, & td { text-align: left; padding: var(--s2); border-bottom: 1px solid var(--border); }
M internal/adapters/web/static/css/85-mobile.css +1 -1
@@ -48,6 +48,6 @@
48 #diffs[data-mr] .ln a[data-range-start] { outline: 2px solid var(--focus); background: var(--sel); }48 #diffs[data-mr] .ln a[data-range-start] { outline: 2px solid var(--focus); background: var(--sel); }
49 body:has(#diffs[data-mr]) :is(.cform .btn, .menu .btn, .mr-touch-range .btn, .c-act button) { min-height: 40px; height: auto; padding-block: var(--s2); }49 body:has(#diffs[data-mr]) :is(.cform .btn, .menu .btn, .mr-touch-range .btn, .c-act button) { min-height: 40px; height: auto; padding-block: var(--s2); }
50 #diffs[data-mr] .cform { scroll-margin-block: calc(var(--hdr-h) + 12px); }50 #diffs[data-mr] .cform { scroll-margin-block: calc(var(--hdr-h) + 12px); }
51- .mr-footer [data-mr-tabs] a { display: inline-flex; align-items: center; min-height: 40px; }51+ .mr-footer [data-mr-tabs] button { display: inline-flex; align-items: center; min-height: 40px; }
52 }52 }
53 }53 }
M internal/adapters/web/static/js/ermite.js +15 -0
@@ -134,7 +134,12 @@
134 dialog(id, focusId) {134 dialog(id, focusId) {
135 const d = document.getElementById(id);135 const d = document.getElementById(id);
136 if (!d) return;136 if (!d) return;
137+ const opener = document.activeElement;
137 ermite.closeDialogs();138 ermite.closeDialogs();
139+ d.addEventListener('close', () => {
140+ // close events are queued: a replacement dialog may already be open.
141+ if (!document.querySelector('dialog[open]') && opener?.isConnected) opener.focus();
142+ }, { once: true });
138 d.showModal();143 d.showModal();
139 if (id === 'panel-cmd') window.ermiteCmd?.sync();144 if (id === 'panel-cmd') window.ermiteCmd?.sync();
140 if (focusId) document.getElementById(focusId)?.focus();145 if (focusId) document.getElementById(focusId)?.focus();
@@ -672,6 +677,16 @@
672 const menu = el.closest('details.menu');677 const menu = el.closest('details.menu');
673 if (menu) closeMenu(menu);678 if (menu) closeMenu(menu);
674 });679 });
680+ // Listbox options own the interaction; their anchors are presentation only.
681+ // Keyboard activation also clicks this wrapper (palette, finder and j/k).
682+ addEventListener('click', (e) => {
683+ const target = e.target.closest?.('a[data-href]');
684+ const list = target?.closest?.('[role="listbox"]');
685+ if (!list || !['cmd-list', 'finder-list'].includes(list.id) || e.button) return;
686+ e.preventDefault();
687+ location.assign(target.dataset.href);
688+ });
689+
675 // A click outside an open menu closes it.690 // A click outside an open menu closes it.
676 addEventListener('click', (e) => {691 addEventListener('click', (e) => {
677 for (const d of $$('details.menu[open]')) if (!d.contains(e.target)) d.open = false;692 for (const d of $$('details.menu[open]')) if (!d.contains(e.target)) d.open = false;
M internal/adapters/web/static/js/files.js +4 -3
@@ -635,12 +635,12 @@ window.ermiteFinder = (() => {
635 return lbl;635 return lbl;
636 }636 }
637 637
638- // hitLink is the link of path i of the result: its label, out of the tab order.638+ // hitLink is the presentation wrapper of path i; the option owns activation.
639 function hitLink(res, i) {639 function hitLink(res, i) {
640 const path = res.idx.paths[i];640 const path = res.idx.paths[i];
641 const a = document.createElement('a');641 const a = document.createElement('a');
642- a.href = blobHref(path);642+ a.dataset.href = blobHref(path);
643- a.tabIndex = -1;643+ a.setAttribute('role', 'presentation');
644 a.append(label(path, positions(res.idx, i, res.q)));644 a.append(label(path, positions(res.idx, i, res.q)));
645 return a;645 return a;
646 }646 }
@@ -684,6 +684,7 @@ window.ermiteFinder = (() => {
684 684
685 // showTree: no query, the directory again, with j/k and Enter on its rows.685 // showTree: no query, the directory again, with j/k and Enter on its rows.
686 function showTree(el) {686 function showTree(el) {
687+ status('');
687 rendered = null;688 rendered = null;
688 pendingEnter = null;689 pendingEnter = null;
689 $('finder').hidden = true;690 $('finder').hidden = true;
M internal/adapters/web/static/js/mr.js +3 -3
@@ -241,7 +241,7 @@ window.ermiteMR = (() => {
241 reveal(id);241 reveal(id);
242 }242 }
243 243
244- // Enhance the ordinary anchor navigation only when scripts are available.244+ // Enhance the server-rendered disclosures when scripts are available.
245 let disposePanels = () => {};245 let disposePanels = () => {};
246 // The tabs are disclosure buttons (aria-expanded + aria-controls), not a246 // The tabs are disclosure buttons (aria-expanded + aria-controls), not a
247 // tablist: a tablist always has a selected tab, and here every panel can be247 // tablist: a tablist always has a selected tab, and here every panel can be
@@ -250,7 +250,7 @@ window.ermiteMR = (() => {
250 disposePanels();250 disposePanels();
251 const footer = $('.mr-footer'), nav = $('[data-mr-tabs]');251 const footer = $('.mr-footer'), nav = $('[data-mr-tabs]');
252 if (!footer || !nav) return;252 if (!footer || !nav) return;
253- const tabs = $$('a', nav), panels = $$('[data-mr-panel]', footer);253+ const tabs = $$('button', nav), panels = $$('[data-mr-panel]', footer);
254 const ids = new Set(['#mr-commits', '#mr-checks', '#mr-checks-panel']);254 const ids = new Set(['#mr-commits', '#mr-checks', '#mr-checks-panel']);
255 let current = -1;255 let current = -1;
256 // show applies the state; fromUser also keeps the URL fragment in step.256 // show applies the state; fromUser also keeps the URL fragment in step.
@@ -522,7 +522,7 @@ window.ermiteMR = (() => {
522 const files = new Map($$('#diffs details.dfile').map((f) => [f.dataset.file, f.open]));522 const files = new Map($$('#diffs details.dfile').map((f) => [f.dataset.file, f.open]));
523 const folded = $('.r-files')?.hasAttribute('data-folded');523 const folded = $('.r-files')?.hasAttribute('data-folded');
524 const railY = $('.rail')?.scrollTop, filesY = $('.r-files .files')?.scrollTop;524 const railY = $('.rail')?.scrollTop, filesY = $('.r-files .files')?.scrollTop;
525- const openPanel = $$('[data-mr-tabs] a').findIndex((t) => t.getAttribute('aria-expanded') === 'true');525+ const openPanel = $$('[data-mr-tabs] button').findIndex((t) => t.getAttribute('aria-expanded') === 'true');
526 const active = document.activeElement;526 const active = document.activeElement;
527 const focus = active && { file: active.closest?.('.files a')?.getAttribute('title'), region: regions.find((sel) => $(sel)?.contains(active)), menu: active.closest?.('details.menu')?.id, id: active.id, tag: active.tagName, name: active.getAttribute('name'), href: active.getAttribute('href') };527 const focus = active && { file: active.closest?.('.files a')?.getAttribute('title'), region: regions.find((sel) => $(sel)?.contains(active)), menu: active.closest?.('details.menu')?.id, id: active.id, tag: active.tagName, name: active.getAttribute('name'), href: active.getAttribute('href') };
528 for (const f of $$('#diffs details.dfile', page)) if (files.has(f.dataset.file)) f.open = files.get(f.dataset.file);528 for (const f of $$('#diffs details.dfile', page)) if (files.has(f.dataset.file)) f.open = files.get(f.dataset.file);
M internal/adapters/web/tree.templ +1 -1
@@ -28,8 +28,8 @@ templ Tree(l Layout, p TreePage) {
28 </div>28 </div>
29 <p class="tree-keys" data-js aria-hidden="true"><kbd>j</kbd> <kbd>k</kbd> move · <kbd>l</kbd> open · <kbd>h</kbd> back</p>29 <p class="tree-keys" data-js aria-hidden="true"><kbd>j</kbd> <kbd>k</kbd> move · <kbd>l</kbd> open · <kbd>h</kbd> back</p>
30 </div>30 </div>
31+ <p id="finder-status" class="dim" role="status"></p>
31 <div id="finder" class="tree-card" hidden>32 <div id="finder" class="tree-card" hidden>
32- <p id="finder-status" class="dim" aria-live="polite"></p>
33 <ul id="finder-list" class="cmd-list" role="listbox" aria-label="Files"></ul>33 <ul id="finder-list" class="cmd-list" role="listbox" aria-label="Files"></ul>
34 </div>34 </div>
35 <div id="tree-view" class="tree-card">35 <div id="tree-view" class="tree-card">
M internal/adapters/web/tree_templ.go +1 -1 generated file: collapsed
M internal/app/testdata/web/blob-binary.html +19 -17
@@ -141,18 +141,20 @@
141 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">141 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
142 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>142 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
143 </footer>143 </footer>
144-<div class="hint" role="status" aria-live="polite">144+<div class="hint">
145-<span class="hint-nav" :text="document.body.dataset.nav || ''">145+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
146 </span>146 </span>
147+<span role="status" aria-live="polite">
147 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">148 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
148 </span>149 </span>
149 <span class="hint-flash" :text="document.body.dataset.flash || ''">150 <span class="hint-flash" :text="document.body.dataset.flash || ''">
150 </span>151 </span>
152+</span>
151 </div>153 </div>
152 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">154 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
153 <div class="panel-head">155 <div class="panel-head">
154 <h2>Keyboard shortcuts</h2>156 <h2>Keyboard shortcuts</h2>
155-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">157+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
156 <kbd>esc</kbd> close</button>158 <kbd>esc</kbd> close</button>
157 </div>159 </div>
158 <div class="keys-pref">160 <div class="keys-pref">
@@ -286,7 +288,7 @@
286 <div class="panel-head">288 <div class="panel-head">
287 <label for="refs-filter" class="sr">filter branches and tags</label>289 <label for="refs-filter" class="sr">filter branches and tags</label>
288 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">290 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
289-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">291+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
290 <kbd>esc</kbd>292 <kbd>esc</kbd>
291 </button>293 </button>
292 </div>294 </div>
@@ -321,79 +323,79 @@
321 <div class="panel-head">323 <div class="panel-head">
322 <label for="cmd-q" class="sr">go to</label>324 <label for="cmd-q" class="sr">go to</label>
323 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">325 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
324-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">326+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
325 <kbd>esc</kbd>327 <kbd>esc</kbd>
326 </button>328 </button>
327 </div>329 </div>
328 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">330 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
329 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>331 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
330-<a href="/" tabindex="-1">332+<a role="presentation" data-href="/">
331 <span class="lbl">home</span>333 <span class="lbl">home</span>
332 <span class="k">go</span>334 <span class="k">go</span>
333 </a>335 </a>
334 </li>336 </li>
335 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">337 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
336-<a href="/files" tabindex="-1">338+<a role="presentation" data-href="/files">
337 <span class="lbl">summary</span>339 <span class="lbl">summary</span>
338 <span class="k">section</span>340 <span class="k">section</span>
339 </a>341 </a>
340 </li>342 </li>
341 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">343 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
342-<a href="/files/tree/main" tabindex="-1">344+<a role="presentation" data-href="/files/tree/main">
343 <span class="lbl">files</span>345 <span class="lbl">files</span>
344 <span class="k">section</span>346 <span class="k">section</span>
345 </a>347 </a>
346 </li>348 </li>
347 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">349 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
348-<a href="/files/commits/main" tabindex="-1">350+<a role="presentation" data-href="/files/commits/main">
349 <span class="lbl">commits</span>351 <span class="lbl">commits</span>
350 <span class="k">section</span>352 <span class="k">section</span>
351 </a>353 </a>
352 </li>354 </li>
353 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">355 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
354-<a href="/files/refs" tabindex="-1">356+<a role="presentation" data-href="/files/refs">
355 <span class="lbl">branches &amp; tags</span>357 <span class="lbl">branches &amp; tags</span>
356 <span class="k">section</span>358 <span class="k">section</span>
357 </a>359 </a>
358 </li>360 </li>
359 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">361 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
360-<a href="/files/merge-requests" tabindex="-1">362+<a role="presentation" data-href="/files/merge-requests">
361 <span class="lbl">merge requests</span>363 <span class="lbl">merge requests</span>
362 <span class="k">section</span>364 <span class="k">section</span>
363 </a>365 </a>
364 </li>366 </li>
365 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">367 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
366-<a href="/files/releases" tabindex="-1">368+<a role="presentation" data-href="/files/releases">
367 <span class="lbl">releases</span>369 <span class="lbl">releases</span>
368 <span class="k">section</span>370 <span class="k">section</span>
369 </a>371 </a>
370 </li>372 </li>
371 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">373 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
372-<a href="/files/actions" tabindex="-1">374+<a role="presentation" data-href="/files/actions">
373 <span class="lbl">actions</span>375 <span class="lbl">actions</span>
374 <span class="k">section</span>376 <span class="k">section</span>
375 </a>377 </a>
376 </li>378 </li>
377 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">379 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">
378-<a href="/files/tree/feature/x" tabindex="-1">380+<a role="presentation" data-href="/files/tree/feature/x">
379 <span class="lbl">feature/x</span>381 <span class="lbl">feature/x</span>
380 <span class="k">branch</span>382 <span class="k">branch</span>
381 </a>383 </a>
382 </li>384 </li>
383 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">385 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">
384-<a href="/files/tree/main" tabindex="-1">386+<a role="presentation" data-href="/files/tree/main">
385 <span class="lbl">main</span>387 <span class="lbl">main</span>
386 <span class="k">branch</span>388 <span class="k">branch</span>
387 </a>389 </a>
388 </li>390 </li>
389 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">391 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">
390-<a href="/files/tree/v1.0" tabindex="-1">392+<a role="presentation" data-href="/files/tree/v1.0">
391 <span class="lbl">v1.0</span>393 <span class="lbl">v1.0</span>
392 <span class="k">tag</span>394 <span class="k">tag</span>
393 </a>395 </a>
394 </li>396 </li>
395 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">397 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
396-<a href="#" data-fire="keys" tabindex="-1">398+<a role="presentation" data-fire="keys">
397 <span class="lbl">turn single-key shortcuts off</span>399 <span class="lbl">turn single-key shortcuts off</span>
398 <span class="k">action</span>400 <span class="k">action</span>
399 </a>401 </a>
M internal/app/testdata/web/blob-code.html +19 -17
@@ -211,18 +211,20 @@
211 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">211 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
212 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>212 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
213 </footer>213 </footer>
214-<div class="hint" role="status" aria-live="polite">214+<div class="hint">
215-<span class="hint-nav" :text="document.body.dataset.nav || ''">215+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
216 </span>216 </span>
217+<span role="status" aria-live="polite">
217 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">218 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
218 </span>219 </span>
219 <span class="hint-flash" :text="document.body.dataset.flash || ''">220 <span class="hint-flash" :text="document.body.dataset.flash || ''">
220 </span>221 </span>
222+</span>
221 </div>223 </div>
222 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">224 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
223 <div class="panel-head">225 <div class="panel-head">
224 <h2>Keyboard shortcuts</h2>226 <h2>Keyboard shortcuts</h2>
225-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">227+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
226 <kbd>esc</kbd> close</button>228 <kbd>esc</kbd> close</button>
227 </div>229 </div>
228 <div class="keys-pref">230 <div class="keys-pref">
@@ -356,7 +358,7 @@
356 <div class="panel-head">358 <div class="panel-head">
357 <label for="refs-filter" class="sr">filter branches and tags</label>359 <label for="refs-filter" class="sr">filter branches and tags</label>
358 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">360 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
359-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">361+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
360 <kbd>esc</kbd>362 <kbd>esc</kbd>
361 </button>363 </button>
362 </div>364 </div>
@@ -391,79 +393,79 @@
391 <div class="panel-head">393 <div class="panel-head">
392 <label for="cmd-q" class="sr">go to</label>394 <label for="cmd-q" class="sr">go to</label>
393 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">395 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
394-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">396+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
395 <kbd>esc</kbd>397 <kbd>esc</kbd>
396 </button>398 </button>
397 </div>399 </div>
398 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">400 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
399 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>401 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
400-<a href="/" tabindex="-1">402+<a role="presentation" data-href="/">
401 <span class="lbl">home</span>403 <span class="lbl">home</span>
402 <span class="k">go</span>404 <span class="k">go</span>
403 </a>405 </a>
404 </li>406 </li>
405 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">407 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
406-<a href="/files" tabindex="-1">408+<a role="presentation" data-href="/files">
407 <span class="lbl">summary</span>409 <span class="lbl">summary</span>
408 <span class="k">section</span>410 <span class="k">section</span>
409 </a>411 </a>
410 </li>412 </li>
411 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">413 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
412-<a href="/files/tree/main" tabindex="-1">414+<a role="presentation" data-href="/files/tree/main">
413 <span class="lbl">files</span>415 <span class="lbl">files</span>
414 <span class="k">section</span>416 <span class="k">section</span>
415 </a>417 </a>
416 </li>418 </li>
417 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">419 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
418-<a href="/files/commits/main" tabindex="-1">420+<a role="presentation" data-href="/files/commits/main">
419 <span class="lbl">commits</span>421 <span class="lbl">commits</span>
420 <span class="k">section</span>422 <span class="k">section</span>
421 </a>423 </a>
422 </li>424 </li>
423 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">425 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
424-<a href="/files/refs" tabindex="-1">426+<a role="presentation" data-href="/files/refs">
425 <span class="lbl">branches &amp; tags</span>427 <span class="lbl">branches &amp; tags</span>
426 <span class="k">section</span>428 <span class="k">section</span>
427 </a>429 </a>
428 </li>430 </li>
429 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">431 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
430-<a href="/files/merge-requests" tabindex="-1">432+<a role="presentation" data-href="/files/merge-requests">
431 <span class="lbl">merge requests</span>433 <span class="lbl">merge requests</span>
432 <span class="k">section</span>434 <span class="k">section</span>
433 </a>435 </a>
434 </li>436 </li>
435 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">437 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
436-<a href="/files/releases" tabindex="-1">438+<a role="presentation" data-href="/files/releases">
437 <span class="lbl">releases</span>439 <span class="lbl">releases</span>
438 <span class="k">section</span>440 <span class="k">section</span>
439 </a>441 </a>
440 </li>442 </li>
441 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">443 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
442-<a href="/files/actions" tabindex="-1">444+<a role="presentation" data-href="/files/actions">
443 <span class="lbl">actions</span>445 <span class="lbl">actions</span>
444 <span class="k">section</span>446 <span class="k">section</span>
445 </a>447 </a>
446 </li>448 </li>
447 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">449 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">
448-<a href="/files/tree/feature/x" tabindex="-1">450+<a role="presentation" data-href="/files/tree/feature/x">
449 <span class="lbl">feature/x</span>451 <span class="lbl">feature/x</span>
450 <span class="k">branch</span>452 <span class="k">branch</span>
451 </a>453 </a>
452 </li>454 </li>
453 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">455 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">
454-<a href="/files/tree/main" tabindex="-1">456+<a role="presentation" data-href="/files/tree/main">
455 <span class="lbl">main</span>457 <span class="lbl">main</span>
456 <span class="k">branch</span>458 <span class="k">branch</span>
457 </a>459 </a>
458 </li>460 </li>
459 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">461 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">
460-<a href="/files/tree/v1.0" tabindex="-1">462+<a role="presentation" data-href="/files/tree/v1.0">
461 <span class="lbl">v1.0</span>463 <span class="lbl">v1.0</span>
462 <span class="k">tag</span>464 <span class="k">tag</span>
463 </a>465 </a>
464 </li>466 </li>
465 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">467 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
466-<a href="#" data-fire="keys" tabindex="-1">468+<a role="presentation" data-fire="keys">
467 <span class="lbl">turn single-key shortcuts off</span>469 <span class="lbl">turn single-key shortcuts off</span>
468 <span class="k">action</span>470 <span class="k">action</span>
469 </a>471 </a>
M internal/app/testdata/web/blob-empty.html +19 -17
@@ -141,18 +141,20 @@
141 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">141 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
142 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>142 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
143 </footer>143 </footer>
144-<div class="hint" role="status" aria-live="polite">144+<div class="hint">
145-<span class="hint-nav" :text="document.body.dataset.nav || ''">145+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
146 </span>146 </span>
147+<span role="status" aria-live="polite">
147 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">148 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
148 </span>149 </span>
149 <span class="hint-flash" :text="document.body.dataset.flash || ''">150 <span class="hint-flash" :text="document.body.dataset.flash || ''">
150 </span>151 </span>
152+</span>
151 </div>153 </div>
152 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">154 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
153 <div class="panel-head">155 <div class="panel-head">
154 <h2>Keyboard shortcuts</h2>156 <h2>Keyboard shortcuts</h2>
155-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">157+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
156 <kbd>esc</kbd> close</button>158 <kbd>esc</kbd> close</button>
157 </div>159 </div>
158 <div class="keys-pref">160 <div class="keys-pref">
@@ -286,7 +288,7 @@
286 <div class="panel-head">288 <div class="panel-head">
287 <label for="refs-filter" class="sr">filter branches and tags</label>289 <label for="refs-filter" class="sr">filter branches and tags</label>
288 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">290 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
289-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">291+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
290 <kbd>esc</kbd>292 <kbd>esc</kbd>
291 </button>293 </button>
292 </div>294 </div>
@@ -321,79 +323,79 @@
321 <div class="panel-head">323 <div class="panel-head">
322 <label for="cmd-q" class="sr">go to</label>324 <label for="cmd-q" class="sr">go to</label>
323 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">325 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
324-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">326+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
325 <kbd>esc</kbd>327 <kbd>esc</kbd>
326 </button>328 </button>
327 </div>329 </div>
328 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">330 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
329 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>331 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
330-<a href="/" tabindex="-1">332+<a role="presentation" data-href="/">
331 <span class="lbl">home</span>333 <span class="lbl">home</span>
332 <span class="k">go</span>334 <span class="k">go</span>
333 </a>335 </a>
334 </li>336 </li>
335 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">337 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
336-<a href="/files" tabindex="-1">338+<a role="presentation" data-href="/files">
337 <span class="lbl">summary</span>339 <span class="lbl">summary</span>
338 <span class="k">section</span>340 <span class="k">section</span>
339 </a>341 </a>
340 </li>342 </li>
341 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">343 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
342-<a href="/files/tree/main" tabindex="-1">344+<a role="presentation" data-href="/files/tree/main">
343 <span class="lbl">files</span>345 <span class="lbl">files</span>
344 <span class="k">section</span>346 <span class="k">section</span>
345 </a>347 </a>
346 </li>348 </li>
347 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">349 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
348-<a href="/files/commits/main" tabindex="-1">350+<a role="presentation" data-href="/files/commits/main">
349 <span class="lbl">commits</span>351 <span class="lbl">commits</span>
350 <span class="k">section</span>352 <span class="k">section</span>
351 </a>353 </a>
352 </li>354 </li>
353 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">355 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
354-<a href="/files/refs" tabindex="-1">356+<a role="presentation" data-href="/files/refs">
355 <span class="lbl">branches &amp; tags</span>357 <span class="lbl">branches &amp; tags</span>
356 <span class="k">section</span>358 <span class="k">section</span>
357 </a>359 </a>
358 </li>360 </li>
359 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">361 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
360-<a href="/files/merge-requests" tabindex="-1">362+<a role="presentation" data-href="/files/merge-requests">
361 <span class="lbl">merge requests</span>363 <span class="lbl">merge requests</span>
362 <span class="k">section</span>364 <span class="k">section</span>
363 </a>365 </a>
364 </li>366 </li>
365 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">367 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
366-<a href="/files/releases" tabindex="-1">368+<a role="presentation" data-href="/files/releases">
367 <span class="lbl">releases</span>369 <span class="lbl">releases</span>
368 <span class="k">section</span>370 <span class="k">section</span>
369 </a>371 </a>
370 </li>372 </li>
371 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">373 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
372-<a href="/files/actions" tabindex="-1">374+<a role="presentation" data-href="/files/actions">
373 <span class="lbl">actions</span>375 <span class="lbl">actions</span>
374 <span class="k">section</span>376 <span class="k">section</span>
375 </a>377 </a>
376 </li>378 </li>
377 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">379 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">
378-<a href="/files/tree/feature/x" tabindex="-1">380+<a role="presentation" data-href="/files/tree/feature/x">
379 <span class="lbl">feature/x</span>381 <span class="lbl">feature/x</span>
380 <span class="k">branch</span>382 <span class="k">branch</span>
381 </a>383 </a>
382 </li>384 </li>
383 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">385 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">
384-<a href="/files/tree/main" tabindex="-1">386+<a role="presentation" data-href="/files/tree/main">
385 <span class="lbl">main</span>387 <span class="lbl">main</span>
386 <span class="k">branch</span>388 <span class="k">branch</span>
387 </a>389 </a>
388 </li>390 </li>
389 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">391 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">
390-<a href="/files/tree/v1.0" tabindex="-1">392+<a role="presentation" data-href="/files/tree/v1.0">
391 <span class="lbl">v1.0</span>393 <span class="lbl">v1.0</span>
392 <span class="k">tag</span>394 <span class="k">tag</span>
393 </a>395 </a>
394 </li>396 </li>
395 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">397 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
396-<a href="#" data-fire="keys" tabindex="-1">398+<a role="presentation" data-fire="keys">
397 <span class="lbl">turn single-key shortcuts off</span>399 <span class="lbl">turn single-key shortcuts off</span>
398 <span class="k">action</span>400 <span class="k">action</span>
399 </a>401 </a>
M internal/app/testdata/web/blob-image.html +19 -17
@@ -143,18 +143,20 @@
143 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">143 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
144 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>144 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
145 </footer>145 </footer>
146-<div class="hint" role="status" aria-live="polite">146+<div class="hint">
147-<span class="hint-nav" :text="document.body.dataset.nav || ''">147+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
148 </span>148 </span>
149+<span role="status" aria-live="polite">
149 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">150 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
150 </span>151 </span>
151 <span class="hint-flash" :text="document.body.dataset.flash || ''">152 <span class="hint-flash" :text="document.body.dataset.flash || ''">
152 </span>153 </span>
154+</span>
153 </div>155 </div>
154 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">156 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
155 <div class="panel-head">157 <div class="panel-head">
156 <h2>Keyboard shortcuts</h2>158 <h2>Keyboard shortcuts</h2>
157-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">159+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
158 <kbd>esc</kbd> close</button>160 <kbd>esc</kbd> close</button>
159 </div>161 </div>
160 <div class="keys-pref">162 <div class="keys-pref">
@@ -288,7 +290,7 @@
288 <div class="panel-head">290 <div class="panel-head">
289 <label for="refs-filter" class="sr">filter branches and tags</label>291 <label for="refs-filter" class="sr">filter branches and tags</label>
290 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">292 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
291-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">293+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
292 <kbd>esc</kbd>294 <kbd>esc</kbd>
293 </button>295 </button>
294 </div>296 </div>
@@ -323,79 +325,79 @@
323 <div class="panel-head">325 <div class="panel-head">
324 <label for="cmd-q" class="sr">go to</label>326 <label for="cmd-q" class="sr">go to</label>
325 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">327 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
326-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">328+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
327 <kbd>esc</kbd>329 <kbd>esc</kbd>
328 </button>330 </button>
329 </div>331 </div>
330 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">332 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
331 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>333 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
332-<a href="/" tabindex="-1">334+<a role="presentation" data-href="/">
333 <span class="lbl">home</span>335 <span class="lbl">home</span>
334 <span class="k">go</span>336 <span class="k">go</span>
335 </a>337 </a>
336 </li>338 </li>
337 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">339 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
338-<a href="/files" tabindex="-1">340+<a role="presentation" data-href="/files">
339 <span class="lbl">summary</span>341 <span class="lbl">summary</span>
340 <span class="k">section</span>342 <span class="k">section</span>
341 </a>343 </a>
342 </li>344 </li>
343 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">345 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
344-<a href="/files/tree/main" tabindex="-1">346+<a role="presentation" data-href="/files/tree/main">
345 <span class="lbl">files</span>347 <span class="lbl">files</span>
346 <span class="k">section</span>348 <span class="k">section</span>
347 </a>349 </a>
348 </li>350 </li>
349 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">351 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
350-<a href="/files/commits/main" tabindex="-1">352+<a role="presentation" data-href="/files/commits/main">
351 <span class="lbl">commits</span>353 <span class="lbl">commits</span>
352 <span class="k">section</span>354 <span class="k">section</span>
353 </a>355 </a>
354 </li>356 </li>
355 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">357 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
356-<a href="/files/refs" tabindex="-1">358+<a role="presentation" data-href="/files/refs">
357 <span class="lbl">branches &amp; tags</span>359 <span class="lbl">branches &amp; tags</span>
358 <span class="k">section</span>360 <span class="k">section</span>
359 </a>361 </a>
360 </li>362 </li>
361 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">363 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
362-<a href="/files/merge-requests" tabindex="-1">364+<a role="presentation" data-href="/files/merge-requests">
363 <span class="lbl">merge requests</span>365 <span class="lbl">merge requests</span>
364 <span class="k">section</span>366 <span class="k">section</span>
365 </a>367 </a>
366 </li>368 </li>
367 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">369 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
368-<a href="/files/releases" tabindex="-1">370+<a role="presentation" data-href="/files/releases">
369 <span class="lbl">releases</span>371 <span class="lbl">releases</span>
370 <span class="k">section</span>372 <span class="k">section</span>
371 </a>373 </a>
372 </li>374 </li>
373 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">375 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
374-<a href="/files/actions" tabindex="-1">376+<a role="presentation" data-href="/files/actions">
375 <span class="lbl">actions</span>377 <span class="lbl">actions</span>
376 <span class="k">section</span>378 <span class="k">section</span>
377 </a>379 </a>
378 </li>380 </li>
379 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">381 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">
380-<a href="/files/tree/feature/x" tabindex="-1">382+<a role="presentation" data-href="/files/tree/feature/x">
381 <span class="lbl">feature/x</span>383 <span class="lbl">feature/x</span>
382 <span class="k">branch</span>384 <span class="k">branch</span>
383 </a>385 </a>
384 </li>386 </li>
385 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">387 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">
386-<a href="/files/tree/main" tabindex="-1">388+<a role="presentation" data-href="/files/tree/main">
387 <span class="lbl">main</span>389 <span class="lbl">main</span>
388 <span class="k">branch</span>390 <span class="k">branch</span>
389 </a>391 </a>
390 </li>392 </li>
391 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">393 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">
392-<a href="/files/tree/v1.0" tabindex="-1">394+<a role="presentation" data-href="/files/tree/v1.0">
393 <span class="lbl">v1.0</span>395 <span class="lbl">v1.0</span>
394 <span class="k">tag</span>396 <span class="k">tag</span>
395 </a>397 </a>
396 </li>398 </li>
397 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">399 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
398-<a href="#" data-fire="keys" tabindex="-1">400+<a role="presentation" data-fire="keys">
399 <span class="lbl">turn single-key shortcuts off</span>401 <span class="lbl">turn single-key shortcuts off</span>
400 <span class="k">action</span>402 <span class="k">action</span>
401 </a>403 </a>
M internal/app/testdata/web/blob-markdown.html +19 -17
@@ -208,18 +208,20 @@
208 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">208 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
209 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>209 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
210 </footer>210 </footer>
211-<div class="hint" role="status" aria-live="polite">211+<div class="hint">
212-<span class="hint-nav" :text="document.body.dataset.nav || ''">212+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
213 </span>213 </span>
214+<span role="status" aria-live="polite">
214 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">215 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
215 </span>216 </span>
216 <span class="hint-flash" :text="document.body.dataset.flash || ''">217 <span class="hint-flash" :text="document.body.dataset.flash || ''">
217 </span>218 </span>
219+</span>
218 </div>220 </div>
219 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">221 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
220 <div class="panel-head">222 <div class="panel-head">
221 <h2>Keyboard shortcuts</h2>223 <h2>Keyboard shortcuts</h2>
222-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">224+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
223 <kbd>esc</kbd> close</button>225 <kbd>esc</kbd> close</button>
224 </div>226 </div>
225 <div class="keys-pref">227 <div class="keys-pref">
@@ -353,7 +355,7 @@
353 <div class="panel-head">355 <div class="panel-head">
354 <label for="refs-filter" class="sr">filter branches and tags</label>356 <label for="refs-filter" class="sr">filter branches and tags</label>
355 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">357 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
356-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">358+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
357 <kbd>esc</kbd>359 <kbd>esc</kbd>
358 </button>360 </button>
359 </div>361 </div>
@@ -388,79 +390,79 @@
388 <div class="panel-head">390 <div class="panel-head">
389 <label for="cmd-q" class="sr">go to</label>391 <label for="cmd-q" class="sr">go to</label>
390 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">392 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
391-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">393+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
392 <kbd>esc</kbd>394 <kbd>esc</kbd>
393 </button>395 </button>
394 </div>396 </div>
395 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">397 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
396 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>398 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
397-<a href="/" tabindex="-1">399+<a role="presentation" data-href="/">
398 <span class="lbl">home</span>400 <span class="lbl">home</span>
399 <span class="k">go</span>401 <span class="k">go</span>
400 </a>402 </a>
401 </li>403 </li>
402 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">404 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
403-<a href="/files" tabindex="-1">405+<a role="presentation" data-href="/files">
404 <span class="lbl">summary</span>406 <span class="lbl">summary</span>
405 <span class="k">section</span>407 <span class="k">section</span>
406 </a>408 </a>
407 </li>409 </li>
408 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">410 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
409-<a href="/files/tree/main" tabindex="-1">411+<a role="presentation" data-href="/files/tree/main">
410 <span class="lbl">files</span>412 <span class="lbl">files</span>
411 <span class="k">section</span>413 <span class="k">section</span>
412 </a>414 </a>
413 </li>415 </li>
414 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">416 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
415-<a href="/files/commits/main" tabindex="-1">417+<a role="presentation" data-href="/files/commits/main">
416 <span class="lbl">commits</span>418 <span class="lbl">commits</span>
417 <span class="k">section</span>419 <span class="k">section</span>
418 </a>420 </a>
419 </li>421 </li>
420 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">422 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
421-<a href="/files/refs" tabindex="-1">423+<a role="presentation" data-href="/files/refs">
422 <span class="lbl">branches &amp; tags</span>424 <span class="lbl">branches &amp; tags</span>
423 <span class="k">section</span>425 <span class="k">section</span>
424 </a>426 </a>
425 </li>427 </li>
426 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">428 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
427-<a href="/files/merge-requests" tabindex="-1">429+<a role="presentation" data-href="/files/merge-requests">
428 <span class="lbl">merge requests</span>430 <span class="lbl">merge requests</span>
429 <span class="k">section</span>431 <span class="k">section</span>
430 </a>432 </a>
431 </li>433 </li>
432 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">434 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
433-<a href="/files/releases" tabindex="-1">435+<a role="presentation" data-href="/files/releases">
434 <span class="lbl">releases</span>436 <span class="lbl">releases</span>
435 <span class="k">section</span>437 <span class="k">section</span>
436 </a>438 </a>
437 </li>439 </li>
438 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">440 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
439-<a href="/files/actions" tabindex="-1">441+<a role="presentation" data-href="/files/actions">
440 <span class="lbl">actions</span>442 <span class="lbl">actions</span>
441 <span class="k">section</span>443 <span class="k">section</span>
442 </a>444 </a>
443 </li>445 </li>
444 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">446 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">
445-<a href="/files/tree/feature/x" tabindex="-1">447+<a role="presentation" data-href="/files/tree/feature/x">
446 <span class="lbl">feature/x</span>448 <span class="lbl">feature/x</span>
447 <span class="k">branch</span>449 <span class="k">branch</span>
448 </a>450 </a>
449 </li>451 </li>
450 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">452 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">
451-<a href="/files/tree/main" tabindex="-1">453+<a role="presentation" data-href="/files/tree/main">
452 <span class="lbl">main</span>454 <span class="lbl">main</span>
453 <span class="k">branch</span>455 <span class="k">branch</span>
454 </a>456 </a>
455 </li>457 </li>
456 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">458 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">
457-<a href="/files/tree/v1.0" tabindex="-1">459+<a role="presentation" data-href="/files/tree/v1.0">
458 <span class="lbl">v1.0</span>460 <span class="lbl">v1.0</span>
459 <span class="k">tag</span>461 <span class="k">tag</span>
460 </a>462 </a>
461 </li>463 </li>
462 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">464 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
463-<a href="#" data-fire="keys" tabindex="-1">465+<a role="presentation" data-fire="keys">
464 <span class="lbl">turn single-key shortcuts off</span>466 <span class="lbl">turn single-key shortcuts off</span>
465 <span class="k">action</span>467 <span class="k">action</span>
466 </a>468 </a>
M internal/app/testdata/web/blob-svg.html +19 -17
@@ -166,18 +166,20 @@
166 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">166 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
167 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>167 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
168 </footer>168 </footer>
169-<div class="hint" role="status" aria-live="polite">169+<div class="hint">
170-<span class="hint-nav" :text="document.body.dataset.nav || ''">170+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
171 </span>171 </span>
172+<span role="status" aria-live="polite">
172 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">173 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
173 </span>174 </span>
174 <span class="hint-flash" :text="document.body.dataset.flash || ''">175 <span class="hint-flash" :text="document.body.dataset.flash || ''">
175 </span>176 </span>
177+</span>
176 </div>178 </div>
177 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">179 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
178 <div class="panel-head">180 <div class="panel-head">
179 <h2>Keyboard shortcuts</h2>181 <h2>Keyboard shortcuts</h2>
180-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">182+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
181 <kbd>esc</kbd> close</button>183 <kbd>esc</kbd> close</button>
182 </div>184 </div>
183 <div class="keys-pref">185 <div class="keys-pref">
@@ -311,7 +313,7 @@
311 <div class="panel-head">313 <div class="panel-head">
312 <label for="refs-filter" class="sr">filter branches and tags</label>314 <label for="refs-filter" class="sr">filter branches and tags</label>
313 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">315 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
314-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">316+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
315 <kbd>esc</kbd>317 <kbd>esc</kbd>
316 </button>318 </button>
317 </div>319 </div>
@@ -346,79 +348,79 @@
346 <div class="panel-head">348 <div class="panel-head">
347 <label for="cmd-q" class="sr">go to</label>349 <label for="cmd-q" class="sr">go to</label>
348 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">350 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
349-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">351+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
350 <kbd>esc</kbd>352 <kbd>esc</kbd>
351 </button>353 </button>
352 </div>354 </div>
353 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">355 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
354 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>356 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
355-<a href="/" tabindex="-1">357+<a role="presentation" data-href="/">
356 <span class="lbl">home</span>358 <span class="lbl">home</span>
357 <span class="k">go</span>359 <span class="k">go</span>
358 </a>360 </a>
359 </li>361 </li>
360 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">362 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
361-<a href="/files" tabindex="-1">363+<a role="presentation" data-href="/files">
362 <span class="lbl">summary</span>364 <span class="lbl">summary</span>
363 <span class="k">section</span>365 <span class="k">section</span>
364 </a>366 </a>
365 </li>367 </li>
366 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">368 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
367-<a href="/files/tree/main" tabindex="-1">369+<a role="presentation" data-href="/files/tree/main">
368 <span class="lbl">files</span>370 <span class="lbl">files</span>
369 <span class="k">section</span>371 <span class="k">section</span>
370 </a>372 </a>
371 </li>373 </li>
372 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">374 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
373-<a href="/files/commits/main" tabindex="-1">375+<a role="presentation" data-href="/files/commits/main">
374 <span class="lbl">commits</span>376 <span class="lbl">commits</span>
375 <span class="k">section</span>377 <span class="k">section</span>
376 </a>378 </a>
377 </li>379 </li>
378 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">380 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
379-<a href="/files/refs" tabindex="-1">381+<a role="presentation" data-href="/files/refs">
380 <span class="lbl">branches &amp; tags</span>382 <span class="lbl">branches &amp; tags</span>
381 <span class="k">section</span>383 <span class="k">section</span>
382 </a>384 </a>
383 </li>385 </li>
384 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">386 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
385-<a href="/files/merge-requests" tabindex="-1">387+<a role="presentation" data-href="/files/merge-requests">
386 <span class="lbl">merge requests</span>388 <span class="lbl">merge requests</span>
387 <span class="k">section</span>389 <span class="k">section</span>
388 </a>390 </a>
389 </li>391 </li>
390 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">392 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
391-<a href="/files/releases" tabindex="-1">393+<a role="presentation" data-href="/files/releases">
392 <span class="lbl">releases</span>394 <span class="lbl">releases</span>
393 <span class="k">section</span>395 <span class="k">section</span>
394 </a>396 </a>
395 </li>397 </li>
396 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">398 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
397-<a href="/files/actions" tabindex="-1">399+<a role="presentation" data-href="/files/actions">
398 <span class="lbl">actions</span>400 <span class="lbl">actions</span>
399 <span class="k">section</span>401 <span class="k">section</span>
400 </a>402 </a>
401 </li>403 </li>
402 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">404 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">
403-<a href="/files/tree/feature/x" tabindex="-1">405+<a role="presentation" data-href="/files/tree/feature/x">
404 <span class="lbl">feature/x</span>406 <span class="lbl">feature/x</span>
405 <span class="k">branch</span>407 <span class="k">branch</span>
406 </a>408 </a>
407 </li>409 </li>
408 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">410 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">
409-<a href="/files/tree/main" tabindex="-1">411+<a role="presentation" data-href="/files/tree/main">
410 <span class="lbl">main</span>412 <span class="lbl">main</span>
411 <span class="k">branch</span>413 <span class="k">branch</span>
412 </a>414 </a>
413 </li>415 </li>
414 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">416 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">
415-<a href="/files/tree/v1.0" tabindex="-1">417+<a role="presentation" data-href="/files/tree/v1.0">
416 <span class="lbl">v1.0</span>418 <span class="lbl">v1.0</span>
417 <span class="k">tag</span>419 <span class="k">tag</span>
418 </a>420 </a>
419 </li>421 </li>
420 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">422 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
421-<a href="#" data-fire="keys" tabindex="-1">423+<a role="presentation" data-fire="keys">
422 <span class="lbl">turn single-key shortcuts off</span>424 <span class="lbl">turn single-key shortcuts off</span>
423 <span class="k">action</span>425 <span class="k">action</span>
424 </a>426 </a>
M internal/app/testdata/web/blob-text.html +19 -17
@@ -161,18 +161,20 @@
161 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">161 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
162 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>162 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
163 </footer>163 </footer>
164-<div class="hint" role="status" aria-live="polite">164+<div class="hint">
165-<span class="hint-nav" :text="document.body.dataset.nav || ''">165+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
166 </span>166 </span>
167+<span role="status" aria-live="polite">
167 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">168 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
168 </span>169 </span>
169 <span class="hint-flash" :text="document.body.dataset.flash || ''">170 <span class="hint-flash" :text="document.body.dataset.flash || ''">
170 </span>171 </span>
172+</span>
171 </div>173 </div>
172 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">174 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
173 <div class="panel-head">175 <div class="panel-head">
174 <h2>Keyboard shortcuts</h2>176 <h2>Keyboard shortcuts</h2>
175-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">177+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
176 <kbd>esc</kbd> close</button>178 <kbd>esc</kbd> close</button>
177 </div>179 </div>
178 <div class="keys-pref">180 <div class="keys-pref">
@@ -306,7 +308,7 @@
306 <div class="panel-head">308 <div class="panel-head">
307 <label for="refs-filter" class="sr">filter branches and tags</label>309 <label for="refs-filter" class="sr">filter branches and tags</label>
308 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">310 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
309-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">311+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
310 <kbd>esc</kbd>312 <kbd>esc</kbd>
311 </button>313 </button>
312 </div>314 </div>
@@ -341,79 +343,79 @@
341 <div class="panel-head">343 <div class="panel-head">
342 <label for="cmd-q" class="sr">go to</label>344 <label for="cmd-q" class="sr">go to</label>
343 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">345 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
344-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">346+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
345 <kbd>esc</kbd>347 <kbd>esc</kbd>
346 </button>348 </button>
347 </div>349 </div>
348 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">350 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
349 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>351 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
350-<a href="/" tabindex="-1">352+<a role="presentation" data-href="/">
351 <span class="lbl">home</span>353 <span class="lbl">home</span>
352 <span class="k">go</span>354 <span class="k">go</span>
353 </a>355 </a>
354 </li>356 </li>
355 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">357 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
356-<a href="/files" tabindex="-1">358+<a role="presentation" data-href="/files">
357 <span class="lbl">summary</span>359 <span class="lbl">summary</span>
358 <span class="k">section</span>360 <span class="k">section</span>
359 </a>361 </a>
360 </li>362 </li>
361 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">363 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
362-<a href="/files/tree/main" tabindex="-1">364+<a role="presentation" data-href="/files/tree/main">
363 <span class="lbl">files</span>365 <span class="lbl">files</span>
364 <span class="k">section</span>366 <span class="k">section</span>
365 </a>367 </a>
366 </li>368 </li>
367 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">369 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
368-<a href="/files/commits/main" tabindex="-1">370+<a role="presentation" data-href="/files/commits/main">
369 <span class="lbl">commits</span>371 <span class="lbl">commits</span>
370 <span class="k">section</span>372 <span class="k">section</span>
371 </a>373 </a>
372 </li>374 </li>
373 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">375 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
374-<a href="/files/refs" tabindex="-1">376+<a role="presentation" data-href="/files/refs">
375 <span class="lbl">branches &amp; tags</span>377 <span class="lbl">branches &amp; tags</span>
376 <span class="k">section</span>378 <span class="k">section</span>
377 </a>379 </a>
378 </li>380 </li>
379 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">381 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
380-<a href="/files/merge-requests" tabindex="-1">382+<a role="presentation" data-href="/files/merge-requests">
381 <span class="lbl">merge requests</span>383 <span class="lbl">merge requests</span>
382 <span class="k">section</span>384 <span class="k">section</span>
383 </a>385 </a>
384 </li>386 </li>
385 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">387 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
386-<a href="/files/releases" tabindex="-1">388+<a role="presentation" data-href="/files/releases">
387 <span class="lbl">releases</span>389 <span class="lbl">releases</span>
388 <span class="k">section</span>390 <span class="k">section</span>
389 </a>391 </a>
390 </li>392 </li>
391 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">393 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
392-<a href="/files/actions" tabindex="-1">394+<a role="presentation" data-href="/files/actions">
393 <span class="lbl">actions</span>395 <span class="lbl">actions</span>
394 <span class="k">section</span>396 <span class="k">section</span>
395 </a>397 </a>
396 </li>398 </li>
397 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">399 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">
398-<a href="/files/tree/feature/x" tabindex="-1">400+<a role="presentation" data-href="/files/tree/feature/x">
399 <span class="lbl">feature/x</span>401 <span class="lbl">feature/x</span>
400 <span class="k">branch</span>402 <span class="k">branch</span>
401 </a>403 </a>
402 </li>404 </li>
403 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">405 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">
404-<a href="/files/tree/main" tabindex="-1">406+<a role="presentation" data-href="/files/tree/main">
405 <span class="lbl">main</span>407 <span class="lbl">main</span>
406 <span class="k">branch</span>408 <span class="k">branch</span>
407 </a>409 </a>
408 </li>410 </li>
409 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">411 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">
410-<a href="/files/tree/v1.0" tabindex="-1">412+<a role="presentation" data-href="/files/tree/v1.0">
411 <span class="lbl">v1.0</span>413 <span class="lbl">v1.0</span>
412 <span class="k">tag</span>414 <span class="k">tag</span>
413 </a>415 </a>
414 </li>416 </li>
415 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">417 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
416-<a href="#" data-fire="keys" tabindex="-1">418+<a role="presentation" data-fire="keys">
417 <span class="lbl">turn single-key shortcuts off</span>419 <span class="lbl">turn single-key shortcuts off</span>
418 <span class="k">action</span>420 <span class="k">action</span>
419 </a>421 </a>
M internal/app/testdata/web/blob-too-large.html +19 -17
@@ -141,18 +141,20 @@
141 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">141 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
142 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>142 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
143 </footer>143 </footer>
144-<div class="hint" role="status" aria-live="polite">144+<div class="hint">
145-<span class="hint-nav" :text="document.body.dataset.nav || ''">145+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
146 </span>146 </span>
147+<span role="status" aria-live="polite">
147 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">148 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
148 </span>149 </span>
149 <span class="hint-flash" :text="document.body.dataset.flash || ''">150 <span class="hint-flash" :text="document.body.dataset.flash || ''">
150 </span>151 </span>
152+</span>
151 </div>153 </div>
152 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">154 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
153 <div class="panel-head">155 <div class="panel-head">
154 <h2>Keyboard shortcuts</h2>156 <h2>Keyboard shortcuts</h2>
155-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">157+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
156 <kbd>esc</kbd> close</button>158 <kbd>esc</kbd> close</button>
157 </div>159 </div>
158 <div class="keys-pref">160 <div class="keys-pref">
@@ -286,7 +288,7 @@
286 <div class="panel-head">288 <div class="panel-head">
287 <label for="refs-filter" class="sr">filter branches and tags</label>289 <label for="refs-filter" class="sr">filter branches and tags</label>
288 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">290 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
289-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">291+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
290 <kbd>esc</kbd>292 <kbd>esc</kbd>
291 </button>293 </button>
292 </div>294 </div>
@@ -321,79 +323,79 @@
321 <div class="panel-head">323 <div class="panel-head">
322 <label for="cmd-q" class="sr">go to</label>324 <label for="cmd-q" class="sr">go to</label>
323 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">325 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
324-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">326+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
325 <kbd>esc</kbd>327 <kbd>esc</kbd>
326 </button>328 </button>
327 </div>329 </div>
328 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">330 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
329 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>331 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
330-<a href="/" tabindex="-1">332+<a role="presentation" data-href="/">
331 <span class="lbl">home</span>333 <span class="lbl">home</span>
332 <span class="k">go</span>334 <span class="k">go</span>
333 </a>335 </a>
334 </li>336 </li>
335 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">337 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
336-<a href="/files" tabindex="-1">338+<a role="presentation" data-href="/files">
337 <span class="lbl">summary</span>339 <span class="lbl">summary</span>
338 <span class="k">section</span>340 <span class="k">section</span>
339 </a>341 </a>
340 </li>342 </li>
341 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">343 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
342-<a href="/files/tree/main" tabindex="-1">344+<a role="presentation" data-href="/files/tree/main">
343 <span class="lbl">files</span>345 <span class="lbl">files</span>
344 <span class="k">section</span>346 <span class="k">section</span>
345 </a>347 </a>
346 </li>348 </li>
347 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">349 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
348-<a href="/files/commits/main" tabindex="-1">350+<a role="presentation" data-href="/files/commits/main">
349 <span class="lbl">commits</span>351 <span class="lbl">commits</span>
350 <span class="k">section</span>352 <span class="k">section</span>
351 </a>353 </a>
352 </li>354 </li>
353 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">355 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
354-<a href="/files/refs" tabindex="-1">356+<a role="presentation" data-href="/files/refs">
355 <span class="lbl">branches &amp; tags</span>357 <span class="lbl">branches &amp; tags</span>
356 <span class="k">section</span>358 <span class="k">section</span>
357 </a>359 </a>
358 </li>360 </li>
359 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">361 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
360-<a href="/files/merge-requests" tabindex="-1">362+<a role="presentation" data-href="/files/merge-requests">
361 <span class="lbl">merge requests</span>363 <span class="lbl">merge requests</span>
362 <span class="k">section</span>364 <span class="k">section</span>
363 </a>365 </a>
364 </li>366 </li>
365 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">367 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
366-<a href="/files/releases" tabindex="-1">368+<a role="presentation" data-href="/files/releases">
367 <span class="lbl">releases</span>369 <span class="lbl">releases</span>
368 <span class="k">section</span>370 <span class="k">section</span>
369 </a>371 </a>
370 </li>372 </li>
371 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">373 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
372-<a href="/files/actions" tabindex="-1">374+<a role="presentation" data-href="/files/actions">
373 <span class="lbl">actions</span>375 <span class="lbl">actions</span>
374 <span class="k">section</span>376 <span class="k">section</span>
375 </a>377 </a>
376 </li>378 </li>
377 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">379 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">
378-<a href="/files/tree/feature/x" tabindex="-1">380+<a role="presentation" data-href="/files/tree/feature/x">
379 <span class="lbl">feature/x</span>381 <span class="lbl">feature/x</span>
380 <span class="k">branch</span>382 <span class="k">branch</span>
381 </a>383 </a>
382 </li>384 </li>
383 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">385 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">
384-<a href="/files/tree/main" tabindex="-1">386+<a role="presentation" data-href="/files/tree/main">
385 <span class="lbl">main</span>387 <span class="lbl">main</span>
386 <span class="k">branch</span>388 <span class="k">branch</span>
387 </a>389 </a>
388 </li>390 </li>
389 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">391 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">
390-<a href="/files/tree/v1.0" tabindex="-1">392+<a role="presentation" data-href="/files/tree/v1.0">
391 <span class="lbl">v1.0</span>393 <span class="lbl">v1.0</span>
392 <span class="k">tag</span>394 <span class="k">tag</span>
393 </a>395 </a>
394 </li>396 </li>
395 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">397 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
396-<a href="#" data-fire="keys" tabindex="-1">398+<a role="presentation" data-fire="keys">
397 <span class="lbl">turn single-key shortcuts off</span>399 <span class="lbl">turn single-key shortcuts off</span>
398 <span class="k">action</span>400 <span class="k">action</span>
399 </a>401 </a>
M internal/app/testdata/web/blob-trojan.html +19 -17
@@ -208,18 +208,20 @@
208 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">208 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
209 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>209 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
210 </footer>210 </footer>
211-<div class="hint" role="status" aria-live="polite">211+<div class="hint">
212-<span class="hint-nav" :text="document.body.dataset.nav || ''">212+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
213 </span>213 </span>
214+<span role="status" aria-live="polite">
214 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">215 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
215 </span>216 </span>
216 <span class="hint-flash" :text="document.body.dataset.flash || ''">217 <span class="hint-flash" :text="document.body.dataset.flash || ''">
217 </span>218 </span>
219+</span>
218 </div>220 </div>
219 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">221 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
220 <div class="panel-head">222 <div class="panel-head">
221 <h2>Keyboard shortcuts</h2>223 <h2>Keyboard shortcuts</h2>
222-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">224+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
223 <kbd>esc</kbd> close</button>225 <kbd>esc</kbd> close</button>
224 </div>226 </div>
225 <div class="keys-pref">227 <div class="keys-pref">
@@ -353,7 +355,7 @@
353 <div class="panel-head">355 <div class="panel-head">
354 <label for="refs-filter" class="sr">filter branches and tags</label>356 <label for="refs-filter" class="sr">filter branches and tags</label>
355 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">357 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
356-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">358+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
357 <kbd>esc</kbd>359 <kbd>esc</kbd>
358 </button>360 </button>
359 </div>361 </div>
@@ -388,79 +390,79 @@
388 <div class="panel-head">390 <div class="panel-head">
389 <label for="cmd-q" class="sr">go to</label>391 <label for="cmd-q" class="sr">go to</label>
390 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">392 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
391-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">393+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
392 <kbd>esc</kbd>394 <kbd>esc</kbd>
393 </button>395 </button>
394 </div>396 </div>
395 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">397 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
396 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>398 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
397-<a href="/" tabindex="-1">399+<a role="presentation" data-href="/">
398 <span class="lbl">home</span>400 <span class="lbl">home</span>
399 <span class="k">go</span>401 <span class="k">go</span>
400 </a>402 </a>
401 </li>403 </li>
402 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">404 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
403-<a href="/files" tabindex="-1">405+<a role="presentation" data-href="/files">
404 <span class="lbl">summary</span>406 <span class="lbl">summary</span>
405 <span class="k">section</span>407 <span class="k">section</span>
406 </a>408 </a>
407 </li>409 </li>
408 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">410 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
409-<a href="/files/tree/main" tabindex="-1">411+<a role="presentation" data-href="/files/tree/main">
410 <span class="lbl">files</span>412 <span class="lbl">files</span>
411 <span class="k">section</span>413 <span class="k">section</span>
412 </a>414 </a>
413 </li>415 </li>
414 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">416 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
415-<a href="/files/commits/main" tabindex="-1">417+<a role="presentation" data-href="/files/commits/main">
416 <span class="lbl">commits</span>418 <span class="lbl">commits</span>
417 <span class="k">section</span>419 <span class="k">section</span>
418 </a>420 </a>
419 </li>421 </li>
420 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">422 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
421-<a href="/files/refs" tabindex="-1">423+<a role="presentation" data-href="/files/refs">
422 <span class="lbl">branches &amp; tags</span>424 <span class="lbl">branches &amp; tags</span>
423 <span class="k">section</span>425 <span class="k">section</span>
424 </a>426 </a>
425 </li>427 </li>
426 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">428 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
427-<a href="/files/merge-requests" tabindex="-1">429+<a role="presentation" data-href="/files/merge-requests">
428 <span class="lbl">merge requests</span>430 <span class="lbl">merge requests</span>
429 <span class="k">section</span>431 <span class="k">section</span>
430 </a>432 </a>
431 </li>433 </li>
432 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">434 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
433-<a href="/files/releases" tabindex="-1">435+<a role="presentation" data-href="/files/releases">
434 <span class="lbl">releases</span>436 <span class="lbl">releases</span>
435 <span class="k">section</span>437 <span class="k">section</span>
436 </a>438 </a>
437 </li>439 </li>
438 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">440 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
439-<a href="/files/actions" tabindex="-1">441+<a role="presentation" data-href="/files/actions">
440 <span class="lbl">actions</span>442 <span class="lbl">actions</span>
441 <span class="k">section</span>443 <span class="k">section</span>
442 </a>444 </a>
443 </li>445 </li>
444 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">446 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">
445-<a href="/files/tree/feature/x" tabindex="-1">447+<a role="presentation" data-href="/files/tree/feature/x">
446 <span class="lbl">feature/x</span>448 <span class="lbl">feature/x</span>
447 <span class="k">branch</span>449 <span class="k">branch</span>
448 </a>450 </a>
449 </li>451 </li>
450 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">452 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">
451-<a href="/files/tree/main" tabindex="-1">453+<a role="presentation" data-href="/files/tree/main">
452 <span class="lbl">main</span>454 <span class="lbl">main</span>
453 <span class="k">branch</span>455 <span class="k">branch</span>
454 </a>456 </a>
455 </li>457 </li>
456 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">458 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">
457-<a href="/files/tree/v1.0" tabindex="-1">459+<a role="presentation" data-href="/files/tree/v1.0">
458 <span class="lbl">v1.0</span>460 <span class="lbl">v1.0</span>
459 <span class="k">tag</span>461 <span class="k">tag</span>
460 </a>462 </a>
461 </li>463 </li>
462 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">464 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
463-<a href="#" data-fire="keys" tabindex="-1">465+<a role="presentation" data-fire="keys">
464 <span class="lbl">turn single-key shortcuts off</span>466 <span class="lbl">turn single-key shortcuts off</span>
465 <span class="k">action</span>467 <span class="k">action</span>
466 </a>468 </a>
M internal/app/testdata/web/commit-big.html +19 -17
@@ -193,18 +193,20 @@
193 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">193 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
194 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>194 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
195 </footer>195 </footer>
196-<div class="hint" role="status" aria-live="polite">196+<div class="hint">
197-<span class="hint-nav" :text="document.body.dataset.nav || ''">197+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
198 </span>198 </span>
199+<span role="status" aria-live="polite">
199 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">200 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
200 </span>201 </span>
201 <span class="hint-flash" :text="document.body.dataset.flash || ''">202 <span class="hint-flash" :text="document.body.dataset.flash || ''">
202 </span>203 </span>
204+</span>
203 </div>205 </div>
204 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">206 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
205 <div class="panel-head">207 <div class="panel-head">
206 <h2>Keyboard shortcuts</h2>208 <h2>Keyboard shortcuts</h2>
207-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">209+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
208 <kbd>esc</kbd> close</button>210 <kbd>esc</kbd> close</button>
209 </div>211 </div>
210 <div class="keys-pref">212 <div class="keys-pref">
@@ -338,7 +340,7 @@
338 <div class="panel-head">340 <div class="panel-head">
339 <label for="refs-filter" class="sr">filter branches and tags</label>341 <label for="refs-filter" class="sr">filter branches and tags</label>
340 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">342 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
341-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">343+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
342 <kbd>esc</kbd>344 <kbd>esc</kbd>
343 </button>345 </button>
344 </div>346 </div>
@@ -373,79 +375,79 @@
373 <div class="panel-head">375 <div class="panel-head">
374 <label for="cmd-q" class="sr">go to</label>376 <label for="cmd-q" class="sr">go to</label>
375 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">377 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
376-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">378+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
377 <kbd>esc</kbd>379 <kbd>esc</kbd>
378 </button>380 </button>
379 </div>381 </div>
380 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">382 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
381 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>383 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
382-<a href="/" tabindex="-1">384+<a role="presentation" data-href="/">
383 <span class="lbl">home</span>385 <span class="lbl">home</span>
384 <span class="k">go</span>386 <span class="k">go</span>
385 </a>387 </a>
386 </li>388 </li>
387 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">389 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
388-<a href="/diffs" tabindex="-1">390+<a role="presentation" data-href="/diffs">
389 <span class="lbl">summary</span>391 <span class="lbl">summary</span>
390 <span class="k">section</span>392 <span class="k">section</span>
391 </a>393 </a>
392 </li>394 </li>
393 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">395 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
394-<a href="/diffs/tree/main" tabindex="-1">396+<a role="presentation" data-href="/diffs/tree/main">
395 <span class="lbl">files</span>397 <span class="lbl">files</span>
396 <span class="k">section</span>398 <span class="k">section</span>
397 </a>399 </a>
398 </li>400 </li>
399 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">401 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
400-<a href="/diffs/commits/main" tabindex="-1">402+<a role="presentation" data-href="/diffs/commits/main">
401 <span class="lbl">commits</span>403 <span class="lbl">commits</span>
402 <span class="k">section</span>404 <span class="k">section</span>
403 </a>405 </a>
404 </li>406 </li>
405 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">407 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
406-<a href="/diffs/refs" tabindex="-1">408+<a role="presentation" data-href="/diffs/refs">
407 <span class="lbl">branches &amp; tags</span>409 <span class="lbl">branches &amp; tags</span>
408 <span class="k">section</span>410 <span class="k">section</span>
409 </a>411 </a>
410 </li>412 </li>
411 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">413 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
412-<a href="/diffs/merge-requests" tabindex="-1">414+<a role="presentation" data-href="/diffs/merge-requests">
413 <span class="lbl">merge requests</span>415 <span class="lbl">merge requests</span>
414 <span class="k">section</span>416 <span class="k">section</span>
415 </a>417 </a>
416 </li>418 </li>
417 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">419 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
418-<a href="/diffs/releases" tabindex="-1">420+<a role="presentation" data-href="/diffs/releases">
419 <span class="lbl">releases</span>421 <span class="lbl">releases</span>
420 <span class="k">section</span>422 <span class="k">section</span>
421 </a>423 </a>
422 </li>424 </li>
423 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">425 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
424-<a href="/diffs/actions" tabindex="-1">426+<a role="presentation" data-href="/diffs/actions">
425 <span class="lbl">actions</span>427 <span class="lbl">actions</span>
426 <span class="k">section</span>428 <span class="k">section</span>
427 </a>429 </a>
428 </li>430 </li>
429 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">431 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">
430-<a href="/diffs/tree/main" tabindex="-1">432+<a role="presentation" data-href="/diffs/tree/main">
431 <span class="lbl">main</span>433 <span class="lbl">main</span>
432 <span class="k">branch</span>434 <span class="k">branch</span>
433 </a>435 </a>
434 </li>436 </li>
435 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch side">437 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch side">
436-<a href="/diffs/tree/side" tabindex="-1">438+<a role="presentation" data-href="/diffs/tree/side">
437 <span class="lbl">side</span>439 <span class="lbl">side</span>
438 <span class="k">branch</span>440 <span class="k">branch</span>
439 </a>441 </a>
440 </li>442 </li>
441 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0.0">443 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0.0">
442-<a href="/diffs/tree/v1.0.0" tabindex="-1">444+<a role="presentation" data-href="/diffs/tree/v1.0.0">
443 <span class="lbl">v1.0.0</span>445 <span class="lbl">v1.0.0</span>
444 <span class="k">tag</span>446 <span class="k">tag</span>
445 </a>447 </a>
446 </li>448 </li>
447 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">449 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
448-<a href="#" data-fire="keys" tabindex="-1">450+<a role="presentation" data-fire="keys">
449 <span class="lbl">turn single-key shortcuts off</span>451 <span class="lbl">turn single-key shortcuts off</span>
450 <span class="k">action</span>452 <span class="k">action</span>
451 </a>453 </a>
M internal/app/testdata/web/commit-binary.html +19 -17
@@ -184,18 +184,20 @@
184 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">184 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
185 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>185 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
186 </footer>186 </footer>
187-<div class="hint" role="status" aria-live="polite">187+<div class="hint">
188-<span class="hint-nav" :text="document.body.dataset.nav || ''">188+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
189 </span>189 </span>
190+<span role="status" aria-live="polite">
190 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">191 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
191 </span>192 </span>
192 <span class="hint-flash" :text="document.body.dataset.flash || ''">193 <span class="hint-flash" :text="document.body.dataset.flash || ''">
193 </span>194 </span>
195+</span>
194 </div>196 </div>
195 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">197 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
196 <div class="panel-head">198 <div class="panel-head">
197 <h2>Keyboard shortcuts</h2>199 <h2>Keyboard shortcuts</h2>
198-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">200+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
199 <kbd>esc</kbd> close</button>201 <kbd>esc</kbd> close</button>
200 </div>202 </div>
201 <div class="keys-pref">203 <div class="keys-pref">
@@ -329,7 +331,7 @@
329 <div class="panel-head">331 <div class="panel-head">
330 <label for="refs-filter" class="sr">filter branches and tags</label>332 <label for="refs-filter" class="sr">filter branches and tags</label>
331 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">333 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
332-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">334+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
333 <kbd>esc</kbd>335 <kbd>esc</kbd>
334 </button>336 </button>
335 </div>337 </div>
@@ -364,79 +366,79 @@
364 <div class="panel-head">366 <div class="panel-head">
365 <label for="cmd-q" class="sr">go to</label>367 <label for="cmd-q" class="sr">go to</label>
366 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">368 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
367-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">369+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
368 <kbd>esc</kbd>370 <kbd>esc</kbd>
369 </button>371 </button>
370 </div>372 </div>
371 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">373 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
372 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>374 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
373-<a href="/" tabindex="-1">375+<a role="presentation" data-href="/">
374 <span class="lbl">home</span>376 <span class="lbl">home</span>
375 <span class="k">go</span>377 <span class="k">go</span>
376 </a>378 </a>
377 </li>379 </li>
378 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">380 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
379-<a href="/diffs" tabindex="-1">381+<a role="presentation" data-href="/diffs">
380 <span class="lbl">summary</span>382 <span class="lbl">summary</span>
381 <span class="k">section</span>383 <span class="k">section</span>
382 </a>384 </a>
383 </li>385 </li>
384 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">386 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
385-<a href="/diffs/tree/main" tabindex="-1">387+<a role="presentation" data-href="/diffs/tree/main">
386 <span class="lbl">files</span>388 <span class="lbl">files</span>
387 <span class="k">section</span>389 <span class="k">section</span>
388 </a>390 </a>
389 </li>391 </li>
390 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">392 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
391-<a href="/diffs/commits/main" tabindex="-1">393+<a role="presentation" data-href="/diffs/commits/main">
392 <span class="lbl">commits</span>394 <span class="lbl">commits</span>
393 <span class="k">section</span>395 <span class="k">section</span>
394 </a>396 </a>
395 </li>397 </li>
396 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">398 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
397-<a href="/diffs/refs" tabindex="-1">399+<a role="presentation" data-href="/diffs/refs">
398 <span class="lbl">branches &amp; tags</span>400 <span class="lbl">branches &amp; tags</span>
399 <span class="k">section</span>401 <span class="k">section</span>
400 </a>402 </a>
401 </li>403 </li>
402 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">404 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
403-<a href="/diffs/merge-requests" tabindex="-1">405+<a role="presentation" data-href="/diffs/merge-requests">
404 <span class="lbl">merge requests</span>406 <span class="lbl">merge requests</span>
405 <span class="k">section</span>407 <span class="k">section</span>
406 </a>408 </a>
407 </li>409 </li>
408 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">410 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
409-<a href="/diffs/releases" tabindex="-1">411+<a role="presentation" data-href="/diffs/releases">
410 <span class="lbl">releases</span>412 <span class="lbl">releases</span>
411 <span class="k">section</span>413 <span class="k">section</span>
412 </a>414 </a>
413 </li>415 </li>
414 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">416 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
415-<a href="/diffs/actions" tabindex="-1">417+<a role="presentation" data-href="/diffs/actions">
416 <span class="lbl">actions</span>418 <span class="lbl">actions</span>
417 <span class="k">section</span>419 <span class="k">section</span>
418 </a>420 </a>
419 </li>421 </li>
420 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">422 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">
421-<a href="/diffs/tree/main" tabindex="-1">423+<a role="presentation" data-href="/diffs/tree/main">
422 <span class="lbl">main</span>424 <span class="lbl">main</span>
423 <span class="k">branch</span>425 <span class="k">branch</span>
424 </a>426 </a>
425 </li>427 </li>
426 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch side">428 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch side">
427-<a href="/diffs/tree/side" tabindex="-1">429+<a role="presentation" data-href="/diffs/tree/side">
428 <span class="lbl">side</span>430 <span class="lbl">side</span>
429 <span class="k">branch</span>431 <span class="k">branch</span>
430 </a>432 </a>
431 </li>433 </li>
432 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0.0">434 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0.0">
433-<a href="/diffs/tree/v1.0.0" tabindex="-1">435+<a role="presentation" data-href="/diffs/tree/v1.0.0">
434 <span class="lbl">v1.0.0</span>436 <span class="lbl">v1.0.0</span>
435 <span class="k">tag</span>437 <span class="k">tag</span>
436 </a>438 </a>
437 </li>439 </li>
438 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">440 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
439-<a href="#" data-fire="keys" tabindex="-1">441+<a role="presentation" data-fire="keys">
440 <span class="lbl">turn single-key shortcuts off</span>442 <span class="lbl">turn single-key shortcuts off</span>
441 <span class="k">action</span>443 <span class="k">action</span>
442 </a>444 </a>
M internal/app/testdata/web/commit-merge.html +19 -17
@@ -226,18 +226,20 @@
226 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">226 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
227 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>227 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
228 </footer>228 </footer>
229-<div class="hint" role="status" aria-live="polite">229+<div class="hint">
230-<span class="hint-nav" :text="document.body.dataset.nav || ''">230+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
231 </span>231 </span>
232+<span role="status" aria-live="polite">
232 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">233 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
233 </span>234 </span>
234 <span class="hint-flash" :text="document.body.dataset.flash || ''">235 <span class="hint-flash" :text="document.body.dataset.flash || ''">
235 </span>236 </span>
237+</span>
236 </div>238 </div>
237 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">239 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
238 <div class="panel-head">240 <div class="panel-head">
239 <h2>Keyboard shortcuts</h2>241 <h2>Keyboard shortcuts</h2>
240-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">242+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
241 <kbd>esc</kbd> close</button>243 <kbd>esc</kbd> close</button>
242 </div>244 </div>
243 <div class="keys-pref">245 <div class="keys-pref">
@@ -371,7 +373,7 @@
371 <div class="panel-head">373 <div class="panel-head">
372 <label for="refs-filter" class="sr">filter branches and tags</label>374 <label for="refs-filter" class="sr">filter branches and tags</label>
373 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">375 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
374-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">376+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
375 <kbd>esc</kbd>377 <kbd>esc</kbd>
376 </button>378 </button>
377 </div>379 </div>
@@ -406,79 +408,79 @@
406 <div class="panel-head">408 <div class="panel-head">
407 <label for="cmd-q" class="sr">go to</label>409 <label for="cmd-q" class="sr">go to</label>
408 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">410 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
409-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">411+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
410 <kbd>esc</kbd>412 <kbd>esc</kbd>
411 </button>413 </button>
412 </div>414 </div>
413 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">415 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
414 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>416 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
415-<a href="/" tabindex="-1">417+<a role="presentation" data-href="/">
416 <span class="lbl">home</span>418 <span class="lbl">home</span>
417 <span class="k">go</span>419 <span class="k">go</span>
418 </a>420 </a>
419 </li>421 </li>
420 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">422 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
421-<a href="/diffs" tabindex="-1">423+<a role="presentation" data-href="/diffs">
422 <span class="lbl">summary</span>424 <span class="lbl">summary</span>
423 <span class="k">section</span>425 <span class="k">section</span>
424 </a>426 </a>
425 </li>427 </li>
426 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">428 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
427-<a href="/diffs/tree/main" tabindex="-1">429+<a role="presentation" data-href="/diffs/tree/main">
428 <span class="lbl">files</span>430 <span class="lbl">files</span>
429 <span class="k">section</span>431 <span class="k">section</span>
430 </a>432 </a>
431 </li>433 </li>
432 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">434 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
433-<a href="/diffs/commits/main" tabindex="-1">435+<a role="presentation" data-href="/diffs/commits/main">
434 <span class="lbl">commits</span>436 <span class="lbl">commits</span>
435 <span class="k">section</span>437 <span class="k">section</span>
436 </a>438 </a>
437 </li>439 </li>
438 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">440 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
439-<a href="/diffs/refs" tabindex="-1">441+<a role="presentation" data-href="/diffs/refs">
440 <span class="lbl">branches &amp; tags</span>442 <span class="lbl">branches &amp; tags</span>
441 <span class="k">section</span>443 <span class="k">section</span>
442 </a>444 </a>
443 </li>445 </li>
444 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">446 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
445-<a href="/diffs/merge-requests" tabindex="-1">447+<a role="presentation" data-href="/diffs/merge-requests">
446 <span class="lbl">merge requests</span>448 <span class="lbl">merge requests</span>
447 <span class="k">section</span>449 <span class="k">section</span>
448 </a>450 </a>
449 </li>451 </li>
450 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">452 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
451-<a href="/diffs/releases" tabindex="-1">453+<a role="presentation" data-href="/diffs/releases">
452 <span class="lbl">releases</span>454 <span class="lbl">releases</span>
453 <span class="k">section</span>455 <span class="k">section</span>
454 </a>456 </a>
455 </li>457 </li>
456 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">458 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
457-<a href="/diffs/actions" tabindex="-1">459+<a role="presentation" data-href="/diffs/actions">
458 <span class="lbl">actions</span>460 <span class="lbl">actions</span>
459 <span class="k">section</span>461 <span class="k">section</span>
460 </a>462 </a>
461 </li>463 </li>
462 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">464 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">
463-<a href="/diffs/tree/main" tabindex="-1">465+<a role="presentation" data-href="/diffs/tree/main">
464 <span class="lbl">main</span>466 <span class="lbl">main</span>
465 <span class="k">branch</span>467 <span class="k">branch</span>
466 </a>468 </a>
467 </li>469 </li>
468 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch side">470 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch side">
469-<a href="/diffs/tree/side" tabindex="-1">471+<a role="presentation" data-href="/diffs/tree/side">
470 <span class="lbl">side</span>472 <span class="lbl">side</span>
471 <span class="k">branch</span>473 <span class="k">branch</span>
472 </a>474 </a>
473 </li>475 </li>
474 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0.0">476 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0.0">
475-<a href="/diffs/tree/v1.0.0" tabindex="-1">477+<a role="presentation" data-href="/diffs/tree/v1.0.0">
476 <span class="lbl">v1.0.0</span>478 <span class="lbl">v1.0.0</span>
477 <span class="k">tag</span>479 <span class="k">tag</span>
478 </a>480 </a>
479 </li>481 </li>
480 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">482 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
481-<a href="#" data-fire="keys" tabindex="-1">483+<a role="presentation" data-fire="keys">
482 <span class="lbl">turn single-key shortcuts off</span>484 <span class="lbl">turn single-key shortcuts off</span>
483 <span class="k">action</span>485 <span class="k">action</span>
484 </a>486 </a>
M internal/app/testdata/web/commit-modify-split.html +19 -17
@@ -349,18 +349,20 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
349 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">349 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
350 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>350 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
351 </footer>351 </footer>
352-<div class="hint" role="status" aria-live="polite">352+<div class="hint">
353-<span class="hint-nav" :text="document.body.dataset.nav || ''">353+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
354 </span>354 </span>
355+<span role="status" aria-live="polite">
355 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">356 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
356 </span>357 </span>
357 <span class="hint-flash" :text="document.body.dataset.flash || ''">358 <span class="hint-flash" :text="document.body.dataset.flash || ''">
358 </span>359 </span>
360+</span>
359 </div>361 </div>
360 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">362 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
361 <div class="panel-head">363 <div class="panel-head">
362 <h2>Keyboard shortcuts</h2>364 <h2>Keyboard shortcuts</h2>
363-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">365+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
364 <kbd>esc</kbd> close</button>366 <kbd>esc</kbd> close</button>
365 </div>367 </div>
366 <div class="keys-pref">368 <div class="keys-pref">
@@ -494,7 +496,7 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
494 <div class="panel-head">496 <div class="panel-head">
495 <label for="refs-filter" class="sr">filter branches and tags</label>497 <label for="refs-filter" class="sr">filter branches and tags</label>
496 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">498 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
497-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">499+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
498 <kbd>esc</kbd>500 <kbd>esc</kbd>
499 </button>501 </button>
500 </div>502 </div>
@@ -529,79 +531,79 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
529 <div class="panel-head">531 <div class="panel-head">
530 <label for="cmd-q" class="sr">go to</label>532 <label for="cmd-q" class="sr">go to</label>
531 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">533 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
532-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">534+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
533 <kbd>esc</kbd>535 <kbd>esc</kbd>
534 </button>536 </button>
535 </div>537 </div>
536 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">538 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
537 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>539 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
538-<a href="/" tabindex="-1">540+<a role="presentation" data-href="/">
539 <span class="lbl">home</span>541 <span class="lbl">home</span>
540 <span class="k">go</span>542 <span class="k">go</span>
541 </a>543 </a>
542 </li>544 </li>
543 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">545 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
544-<a href="/diffs" tabindex="-1">546+<a role="presentation" data-href="/diffs">
545 <span class="lbl">summary</span>547 <span class="lbl">summary</span>
546 <span class="k">section</span>548 <span class="k">section</span>
547 </a>549 </a>
548 </li>550 </li>
549 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">551 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
550-<a href="/diffs/tree/main" tabindex="-1">552+<a role="presentation" data-href="/diffs/tree/main">
551 <span class="lbl">files</span>553 <span class="lbl">files</span>
552 <span class="k">section</span>554 <span class="k">section</span>
553 </a>555 </a>
554 </li>556 </li>
555 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">557 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
556-<a href="/diffs/commits/main" tabindex="-1">558+<a role="presentation" data-href="/diffs/commits/main">
557 <span class="lbl">commits</span>559 <span class="lbl">commits</span>
558 <span class="k">section</span>560 <span class="k">section</span>
559 </a>561 </a>
560 </li>562 </li>
561 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">563 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
562-<a href="/diffs/refs" tabindex="-1">564+<a role="presentation" data-href="/diffs/refs">
563 <span class="lbl">branches &amp; tags</span>565 <span class="lbl">branches &amp; tags</span>
564 <span class="k">section</span>566 <span class="k">section</span>
565 </a>567 </a>
566 </li>568 </li>
567 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">569 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
568-<a href="/diffs/merge-requests" tabindex="-1">570+<a role="presentation" data-href="/diffs/merge-requests">
569 <span class="lbl">merge requests</span>571 <span class="lbl">merge requests</span>
570 <span class="k">section</span>572 <span class="k">section</span>
571 </a>573 </a>
572 </li>574 </li>
573 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">575 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
574-<a href="/diffs/releases" tabindex="-1">576+<a role="presentation" data-href="/diffs/releases">
575 <span class="lbl">releases</span>577 <span class="lbl">releases</span>
576 <span class="k">section</span>578 <span class="k">section</span>
577 </a>579 </a>
578 </li>580 </li>
579 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">581 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
580-<a href="/diffs/actions" tabindex="-1">582+<a role="presentation" data-href="/diffs/actions">
581 <span class="lbl">actions</span>583 <span class="lbl">actions</span>
582 <span class="k">section</span>584 <span class="k">section</span>
583 </a>585 </a>
584 </li>586 </li>
585 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">587 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">
586-<a href="/diffs/tree/main" tabindex="-1">588+<a role="presentation" data-href="/diffs/tree/main">
587 <span class="lbl">main</span>589 <span class="lbl">main</span>
588 <span class="k">branch</span>590 <span class="k">branch</span>
589 </a>591 </a>
590 </li>592 </li>
591 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch side">593 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch side">
592-<a href="/diffs/tree/side" tabindex="-1">594+<a role="presentation" data-href="/diffs/tree/side">
593 <span class="lbl">side</span>595 <span class="lbl">side</span>
594 <span class="k">branch</span>596 <span class="k">branch</span>
595 </a>597 </a>
596 </li>598 </li>
597 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0.0">599 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0.0">
598-<a href="/diffs/tree/v1.0.0" tabindex="-1">600+<a role="presentation" data-href="/diffs/tree/v1.0.0">
599 <span class="lbl">v1.0.0</span>601 <span class="lbl">v1.0.0</span>
600 <span class="k">tag</span>602 <span class="k">tag</span>
601 </a>603 </a>
602 </li>604 </li>
603 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">605 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
604-<a href="#" data-fire="keys" tabindex="-1">606+<a role="presentation" data-fire="keys">
605 <span class="lbl">turn single-key shortcuts off</span>607 <span class="lbl">turn single-key shortcuts off</span>
606 <span class="k">action</span>608 <span class="k">action</span>
607 </a>609 </a>
M internal/app/testdata/web/commit-modify.html +19 -17
@@ -349,18 +349,20 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
349 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">349 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
350 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>350 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
351 </footer>351 </footer>
352-<div class="hint" role="status" aria-live="polite">352+<div class="hint">
353-<span class="hint-nav" :text="document.body.dataset.nav || ''">353+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
354 </span>354 </span>
355+<span role="status" aria-live="polite">
355 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">356 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
356 </span>357 </span>
357 <span class="hint-flash" :text="document.body.dataset.flash || ''">358 <span class="hint-flash" :text="document.body.dataset.flash || ''">
358 </span>359 </span>
360+</span>
359 </div>361 </div>
360 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">362 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
361 <div class="panel-head">363 <div class="panel-head">
362 <h2>Keyboard shortcuts</h2>364 <h2>Keyboard shortcuts</h2>
363-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">365+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
364 <kbd>esc</kbd> close</button>366 <kbd>esc</kbd> close</button>
365 </div>367 </div>
366 <div class="keys-pref">368 <div class="keys-pref">
@@ -494,7 +496,7 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
494 <div class="panel-head">496 <div class="panel-head">
495 <label for="refs-filter" class="sr">filter branches and tags</label>497 <label for="refs-filter" class="sr">filter branches and tags</label>
496 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">498 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
497-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">499+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
498 <kbd>esc</kbd>500 <kbd>esc</kbd>
499 </button>501 </button>
500 </div>502 </div>
@@ -529,79 +531,79 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
529 <div class="panel-head">531 <div class="panel-head">
530 <label for="cmd-q" class="sr">go to</label>532 <label for="cmd-q" class="sr">go to</label>
531 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">533 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
532-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">534+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
533 <kbd>esc</kbd>535 <kbd>esc</kbd>
534 </button>536 </button>
535 </div>537 </div>
536 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">538 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
537 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>539 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
538-<a href="/" tabindex="-1">540+<a role="presentation" data-href="/">
539 <span class="lbl">home</span>541 <span class="lbl">home</span>
540 <span class="k">go</span>542 <span class="k">go</span>
541 </a>543 </a>
542 </li>544 </li>
543 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">545 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
544-<a href="/diffs" tabindex="-1">546+<a role="presentation" data-href="/diffs">
545 <span class="lbl">summary</span>547 <span class="lbl">summary</span>
546 <span class="k">section</span>548 <span class="k">section</span>
547 </a>549 </a>
548 </li>550 </li>
549 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">551 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
550-<a href="/diffs/tree/main" tabindex="-1">552+<a role="presentation" data-href="/diffs/tree/main">
551 <span class="lbl">files</span>553 <span class="lbl">files</span>
552 <span class="k">section</span>554 <span class="k">section</span>
553 </a>555 </a>
554 </li>556 </li>
555 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">557 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
556-<a href="/diffs/commits/main" tabindex="-1">558+<a role="presentation" data-href="/diffs/commits/main">
557 <span class="lbl">commits</span>559 <span class="lbl">commits</span>
558 <span class="k">section</span>560 <span class="k">section</span>
559 </a>561 </a>
560 </li>562 </li>
561 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">563 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
562-<a href="/diffs/refs" tabindex="-1">564+<a role="presentation" data-href="/diffs/refs">
563 <span class="lbl">branches &amp; tags</span>565 <span class="lbl">branches &amp; tags</span>
564 <span class="k">section</span>566 <span class="k">section</span>
565 </a>567 </a>
566 </li>568 </li>
567 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">569 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
568-<a href="/diffs/merge-requests" tabindex="-1">570+<a role="presentation" data-href="/diffs/merge-requests">
569 <span class="lbl">merge requests</span>571 <span class="lbl">merge requests</span>
570 <span class="k">section</span>572 <span class="k">section</span>
571 </a>573 </a>
572 </li>574 </li>
573 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">575 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
574-<a href="/diffs/releases" tabindex="-1">576+<a role="presentation" data-href="/diffs/releases">
575 <span class="lbl">releases</span>577 <span class="lbl">releases</span>
576 <span class="k">section</span>578 <span class="k">section</span>
577 </a>579 </a>
578 </li>580 </li>
579 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">581 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
580-<a href="/diffs/actions" tabindex="-1">582+<a role="presentation" data-href="/diffs/actions">
581 <span class="lbl">actions</span>583 <span class="lbl">actions</span>
582 <span class="k">section</span>584 <span class="k">section</span>
583 </a>585 </a>
584 </li>586 </li>
585 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">587 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">
586-<a href="/diffs/tree/main" tabindex="-1">588+<a role="presentation" data-href="/diffs/tree/main">
587 <span class="lbl">main</span>589 <span class="lbl">main</span>
588 <span class="k">branch</span>590 <span class="k">branch</span>
589 </a>591 </a>
590 </li>592 </li>
591 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch side">593 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch side">
592-<a href="/diffs/tree/side" tabindex="-1">594+<a role="presentation" data-href="/diffs/tree/side">
593 <span class="lbl">side</span>595 <span class="lbl">side</span>
594 <span class="k">branch</span>596 <span class="k">branch</span>
595 </a>597 </a>
596 </li>598 </li>
597 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0.0">599 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0.0">
598-<a href="/diffs/tree/v1.0.0" tabindex="-1">600+<a role="presentation" data-href="/diffs/tree/v1.0.0">
599 <span class="lbl">v1.0.0</span>601 <span class="lbl">v1.0.0</span>
600 <span class="k">tag</span>602 <span class="k">tag</span>
601 </a>603 </a>
602 </li>604 </li>
603 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">605 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
604-<a href="#" data-fire="keys" tabindex="-1">606+<a role="presentation" data-fire="keys">
605 <span class="lbl">turn single-key shortcuts off</span>607 <span class="lbl">turn single-key shortcuts off</span>
606 <span class="k">action</span>608 <span class="k">action</span>
607 </a>609 </a>
M internal/app/testdata/web/commit-rename.html +19 -17
@@ -253,18 +253,20 @@
253 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">253 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
254 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>254 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
255 </footer>255 </footer>
256-<div class="hint" role="status" aria-live="polite">256+<div class="hint">
257-<span class="hint-nav" :text="document.body.dataset.nav || ''">257+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
258 </span>258 </span>
259+<span role="status" aria-live="polite">
259 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">260 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
260 </span>261 </span>
261 <span class="hint-flash" :text="document.body.dataset.flash || ''">262 <span class="hint-flash" :text="document.body.dataset.flash || ''">
262 </span>263 </span>
264+</span>
263 </div>265 </div>
264 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">266 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
265 <div class="panel-head">267 <div class="panel-head">
266 <h2>Keyboard shortcuts</h2>268 <h2>Keyboard shortcuts</h2>
267-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">269+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
268 <kbd>esc</kbd> close</button>270 <kbd>esc</kbd> close</button>
269 </div>271 </div>
270 <div class="keys-pref">272 <div class="keys-pref">
@@ -398,7 +400,7 @@
398 <div class="panel-head">400 <div class="panel-head">
399 <label for="refs-filter" class="sr">filter branches and tags</label>401 <label for="refs-filter" class="sr">filter branches and tags</label>
400 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">402 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
401-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">403+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
402 <kbd>esc</kbd>404 <kbd>esc</kbd>
403 </button>405 </button>
404 </div>406 </div>
@@ -433,79 +435,79 @@
433 <div class="panel-head">435 <div class="panel-head">
434 <label for="cmd-q" class="sr">go to</label>436 <label for="cmd-q" class="sr">go to</label>
435 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">437 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
436-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">438+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
437 <kbd>esc</kbd>439 <kbd>esc</kbd>
438 </button>440 </button>
439 </div>441 </div>
440 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">442 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
441 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>443 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
442-<a href="/" tabindex="-1">444+<a role="presentation" data-href="/">
443 <span class="lbl">home</span>445 <span class="lbl">home</span>
444 <span class="k">go</span>446 <span class="k">go</span>
445 </a>447 </a>
446 </li>448 </li>
447 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">449 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
448-<a href="/diffs" tabindex="-1">450+<a role="presentation" data-href="/diffs">
449 <span class="lbl">summary</span>451 <span class="lbl">summary</span>
450 <span class="k">section</span>452 <span class="k">section</span>
451 </a>453 </a>
452 </li>454 </li>
453 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">455 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
454-<a href="/diffs/tree/main" tabindex="-1">456+<a role="presentation" data-href="/diffs/tree/main">
455 <span class="lbl">files</span>457 <span class="lbl">files</span>
456 <span class="k">section</span>458 <span class="k">section</span>
457 </a>459 </a>
458 </li>460 </li>
459 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">461 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
460-<a href="/diffs/commits/main" tabindex="-1">462+<a role="presentation" data-href="/diffs/commits/main">
461 <span class="lbl">commits</span>463 <span class="lbl">commits</span>
462 <span class="k">section</span>464 <span class="k">section</span>
463 </a>465 </a>
464 </li>466 </li>
465 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">467 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
466-<a href="/diffs/refs" tabindex="-1">468+<a role="presentation" data-href="/diffs/refs">
467 <span class="lbl">branches &amp; tags</span>469 <span class="lbl">branches &amp; tags</span>
468 <span class="k">section</span>470 <span class="k">section</span>
469 </a>471 </a>
470 </li>472 </li>
471 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">473 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
472-<a href="/diffs/merge-requests" tabindex="-1">474+<a role="presentation" data-href="/diffs/merge-requests">
473 <span class="lbl">merge requests</span>475 <span class="lbl">merge requests</span>
474 <span class="k">section</span>476 <span class="k">section</span>
475 </a>477 </a>
476 </li>478 </li>
477 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">479 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
478-<a href="/diffs/releases" tabindex="-1">480+<a role="presentation" data-href="/diffs/releases">
479 <span class="lbl">releases</span>481 <span class="lbl">releases</span>
480 <span class="k">section</span>482 <span class="k">section</span>
481 </a>483 </a>
482 </li>484 </li>
483 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">485 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
484-<a href="/diffs/actions" tabindex="-1">486+<a role="presentation" data-href="/diffs/actions">
485 <span class="lbl">actions</span>487 <span class="lbl">actions</span>
486 <span class="k">section</span>488 <span class="k">section</span>
487 </a>489 </a>
488 </li>490 </li>
489 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">491 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">
490-<a href="/diffs/tree/main" tabindex="-1">492+<a role="presentation" data-href="/diffs/tree/main">
491 <span class="lbl">main</span>493 <span class="lbl">main</span>
492 <span class="k">branch</span>494 <span class="k">branch</span>
493 </a>495 </a>
494 </li>496 </li>
495 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch side">497 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch side">
496-<a href="/diffs/tree/side" tabindex="-1">498+<a role="presentation" data-href="/diffs/tree/side">
497 <span class="lbl">side</span>499 <span class="lbl">side</span>
498 <span class="k">branch</span>500 <span class="k">branch</span>
499 </a>501 </a>
500 </li>502 </li>
501 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0.0">503 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0.0">
502-<a href="/diffs/tree/v1.0.0" tabindex="-1">504+<a role="presentation" data-href="/diffs/tree/v1.0.0">
503 <span class="lbl">v1.0.0</span>505 <span class="lbl">v1.0.0</span>
504 <span class="k">tag</span>506 <span class="k">tag</span>
505 </a>507 </a>
506 </li>508 </li>
507 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">509 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
508-<a href="#" data-fire="keys" tabindex="-1">510+<a role="presentation" data-fire="keys">
509 <span class="lbl">turn single-key shortcuts off</span>511 <span class="lbl">turn single-key shortcuts off</span>
510 <span class="k">action</span>512 <span class="k">action</span>
511 </a>513 </a>
M internal/app/testdata/web/commit-root.html +19 -17
@@ -572,18 +572,20 @@
572 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">572 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
573 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>573 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
574 </footer>574 </footer>
575-<div class="hint" role="status" aria-live="polite">575+<div class="hint">
576-<span class="hint-nav" :text="document.body.dataset.nav || ''">576+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
577 </span>577 </span>
578+<span role="status" aria-live="polite">
578 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">579 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
579 </span>580 </span>
580 <span class="hint-flash" :text="document.body.dataset.flash || ''">581 <span class="hint-flash" :text="document.body.dataset.flash || ''">
581 </span>582 </span>
583+</span>
582 </div>584 </div>
583 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">585 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
584 <div class="panel-head">586 <div class="panel-head">
585 <h2>Keyboard shortcuts</h2>587 <h2>Keyboard shortcuts</h2>
586-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">588+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
587 <kbd>esc</kbd> close</button>589 <kbd>esc</kbd> close</button>
588 </div>590 </div>
589 <div class="keys-pref">591 <div class="keys-pref">
@@ -717,7 +719,7 @@
717 <div class="panel-head">719 <div class="panel-head">
718 <label for="refs-filter" class="sr">filter branches and tags</label>720 <label for="refs-filter" class="sr">filter branches and tags</label>
719 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">721 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
720-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">722+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
721 <kbd>esc</kbd>723 <kbd>esc</kbd>
722 </button>724 </button>
723 </div>725 </div>
@@ -752,79 +754,79 @@
752 <div class="panel-head">754 <div class="panel-head">
753 <label for="cmd-q" class="sr">go to</label>755 <label for="cmd-q" class="sr">go to</label>
754 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">756 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
755-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">757+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
756 <kbd>esc</kbd>758 <kbd>esc</kbd>
757 </button>759 </button>
758 </div>760 </div>
759 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">761 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
760 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>762 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
761-<a href="/" tabindex="-1">763+<a role="presentation" data-href="/">
762 <span class="lbl">home</span>764 <span class="lbl">home</span>
763 <span class="k">go</span>765 <span class="k">go</span>
764 </a>766 </a>
765 </li>767 </li>
766 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">768 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
767-<a href="/diffs" tabindex="-1">769+<a role="presentation" data-href="/diffs">
768 <span class="lbl">summary</span>770 <span class="lbl">summary</span>
769 <span class="k">section</span>771 <span class="k">section</span>
770 </a>772 </a>
771 </li>773 </li>
772 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">774 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
773-<a href="/diffs/tree/main" tabindex="-1">775+<a role="presentation" data-href="/diffs/tree/main">
774 <span class="lbl">files</span>776 <span class="lbl">files</span>
775 <span class="k">section</span>777 <span class="k">section</span>
776 </a>778 </a>
777 </li>779 </li>
778 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">780 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
779-<a href="/diffs/commits/main" tabindex="-1">781+<a role="presentation" data-href="/diffs/commits/main">
780 <span class="lbl">commits</span>782 <span class="lbl">commits</span>
781 <span class="k">section</span>783 <span class="k">section</span>
782 </a>784 </a>
783 </li>785 </li>
784 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">786 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
785-<a href="/diffs/refs" tabindex="-1">787+<a role="presentation" data-href="/diffs/refs">
786 <span class="lbl">branches &amp; tags</span>788 <span class="lbl">branches &amp; tags</span>
787 <span class="k">section</span>789 <span class="k">section</span>
788 </a>790 </a>
789 </li>791 </li>
790 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">792 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
791-<a href="/diffs/merge-requests" tabindex="-1">793+<a role="presentation" data-href="/diffs/merge-requests">
792 <span class="lbl">merge requests</span>794 <span class="lbl">merge requests</span>
793 <span class="k">section</span>795 <span class="k">section</span>
794 </a>796 </a>
795 </li>797 </li>
796 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">798 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
797-<a href="/diffs/releases" tabindex="-1">799+<a role="presentation" data-href="/diffs/releases">
798 <span class="lbl">releases</span>800 <span class="lbl">releases</span>
799 <span class="k">section</span>801 <span class="k">section</span>
800 </a>802 </a>
801 </li>803 </li>
802 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">804 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
803-<a href="/diffs/actions" tabindex="-1">805+<a role="presentation" data-href="/diffs/actions">
804 <span class="lbl">actions</span>806 <span class="lbl">actions</span>
805 <span class="k">section</span>807 <span class="k">section</span>
806 </a>808 </a>
807 </li>809 </li>
808 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">810 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">
809-<a href="/diffs/tree/main" tabindex="-1">811+<a role="presentation" data-href="/diffs/tree/main">
810 <span class="lbl">main</span>812 <span class="lbl">main</span>
811 <span class="k">branch</span>813 <span class="k">branch</span>
812 </a>814 </a>
813 </li>815 </li>
814 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch side">816 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch side">
815-<a href="/diffs/tree/side" tabindex="-1">817+<a role="presentation" data-href="/diffs/tree/side">
816 <span class="lbl">side</span>818 <span class="lbl">side</span>
817 <span class="k">branch</span>819 <span class="k">branch</span>
818 </a>820 </a>
819 </li>821 </li>
820 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0.0">822 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0.0">
821-<a href="/diffs/tree/v1.0.0" tabindex="-1">823+<a role="presentation" data-href="/diffs/tree/v1.0.0">
822 <span class="lbl">v1.0.0</span>824 <span class="lbl">v1.0.0</span>
823 <span class="k">tag</span>825 <span class="k">tag</span>
824 </a>826 </a>
825 </li>827 </li>
826 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">828 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
827-<a href="#" data-fire="keys" tabindex="-1">829+<a role="presentation" data-fire="keys">
828 <span class="lbl">turn single-key shortcuts off</span>830 <span class="lbl">turn single-key shortcuts off</span>
829 <span class="k">action</span>831 <span class="k">action</span>
830 </a>832 </a>
M internal/app/testdata/web/commit-trojan.html +19 -17
@@ -401,18 +401,20 @@
401 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">401 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
402 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>402 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
403 </footer>403 </footer>
404-<div class="hint" role="status" aria-live="polite">404+<div class="hint">
405-<span class="hint-nav" :text="document.body.dataset.nav || ''">405+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
406 </span>406 </span>
407+<span role="status" aria-live="polite">
407 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">408 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
408 </span>409 </span>
409 <span class="hint-flash" :text="document.body.dataset.flash || ''">410 <span class="hint-flash" :text="document.body.dataset.flash || ''">
410 </span>411 </span>
412+</span>
411 </div>413 </div>
412 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">414 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
413 <div class="panel-head">415 <div class="panel-head">
414 <h2>Keyboard shortcuts</h2>416 <h2>Keyboard shortcuts</h2>
415-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">417+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
416 <kbd>esc</kbd> close</button>418 <kbd>esc</kbd> close</button>
417 </div>419 </div>
418 <div class="keys-pref">420 <div class="keys-pref">
@@ -546,7 +548,7 @@
546 <div class="panel-head">548 <div class="panel-head">
547 <label for="refs-filter" class="sr">filter branches and tags</label>549 <label for="refs-filter" class="sr">filter branches and tags</label>
548 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">550 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
549-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">551+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
550 <kbd>esc</kbd>552 <kbd>esc</kbd>
551 </button>553 </button>
552 </div>554 </div>
@@ -581,79 +583,79 @@
581 <div class="panel-head">583 <div class="panel-head">
582 <label for="cmd-q" class="sr">go to</label>584 <label for="cmd-q" class="sr">go to</label>
583 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">585 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
584-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">586+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
585 <kbd>esc</kbd>587 <kbd>esc</kbd>
586 </button>588 </button>
587 </div>589 </div>
588 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">590 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
589 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>591 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
590-<a href="/" tabindex="-1">592+<a role="presentation" data-href="/">
591 <span class="lbl">home</span>593 <span class="lbl">home</span>
592 <span class="k">go</span>594 <span class="k">go</span>
593 </a>595 </a>
594 </li>596 </li>
595 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">597 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
596-<a href="/diffs" tabindex="-1">598+<a role="presentation" data-href="/diffs">
597 <span class="lbl">summary</span>599 <span class="lbl">summary</span>
598 <span class="k">section</span>600 <span class="k">section</span>
599 </a>601 </a>
600 </li>602 </li>
601 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">603 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
602-<a href="/diffs/tree/main" tabindex="-1">604+<a role="presentation" data-href="/diffs/tree/main">
603 <span class="lbl">files</span>605 <span class="lbl">files</span>
604 <span class="k">section</span>606 <span class="k">section</span>
605 </a>607 </a>
606 </li>608 </li>
607 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">609 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
608-<a href="/diffs/commits/main" tabindex="-1">610+<a role="presentation" data-href="/diffs/commits/main">
609 <span class="lbl">commits</span>611 <span class="lbl">commits</span>
610 <span class="k">section</span>612 <span class="k">section</span>
611 </a>613 </a>
612 </li>614 </li>
613 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">615 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
614-<a href="/diffs/refs" tabindex="-1">616+<a role="presentation" data-href="/diffs/refs">
615 <span class="lbl">branches &amp; tags</span>617 <span class="lbl">branches &amp; tags</span>
616 <span class="k">section</span>618 <span class="k">section</span>
617 </a>619 </a>
618 </li>620 </li>
619 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">621 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
620-<a href="/diffs/merge-requests" tabindex="-1">622+<a role="presentation" data-href="/diffs/merge-requests">
621 <span class="lbl">merge requests</span>623 <span class="lbl">merge requests</span>
622 <span class="k">section</span>624 <span class="k">section</span>
623 </a>625 </a>
624 </li>626 </li>
625 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">627 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
626-<a href="/diffs/releases" tabindex="-1">628+<a role="presentation" data-href="/diffs/releases">
627 <span class="lbl">releases</span>629 <span class="lbl">releases</span>
628 <span class="k">section</span>630 <span class="k">section</span>
629 </a>631 </a>
630 </li>632 </li>
631 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">633 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
632-<a href="/diffs/actions" tabindex="-1">634+<a role="presentation" data-href="/diffs/actions">
633 <span class="lbl">actions</span>635 <span class="lbl">actions</span>
634 <span class="k">section</span>636 <span class="k">section</span>
635 </a>637 </a>
636 </li>638 </li>
637 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">639 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">
638-<a href="/diffs/tree/main" tabindex="-1">640+<a role="presentation" data-href="/diffs/tree/main">
639 <span class="lbl">main</span>641 <span class="lbl">main</span>
640 <span class="k">branch</span>642 <span class="k">branch</span>
641 </a>643 </a>
642 </li>644 </li>
643 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch side">645 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch side">
644-<a href="/diffs/tree/side" tabindex="-1">646+<a role="presentation" data-href="/diffs/tree/side">
645 <span class="lbl">side</span>647 <span class="lbl">side</span>
646 <span class="k">branch</span>648 <span class="k">branch</span>
647 </a>649 </a>
648 </li>650 </li>
649 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0.0">651 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0.0">
650-<a href="/diffs/tree/v1.0.0" tabindex="-1">652+<a role="presentation" data-href="/diffs/tree/v1.0.0">
651 <span class="lbl">v1.0.0</span>653 <span class="lbl">v1.0.0</span>
652 <span class="k">tag</span>654 <span class="k">tag</span>
653 </a>655 </a>
654 </li>656 </li>
655 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">657 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
656-<a href="#" data-fire="keys" tabindex="-1">658+<a role="presentation" data-fire="keys">
657 <span class="lbl">turn single-key shortcuts off</span>659 <span class="lbl">turn single-key shortcuts off</span>
658 <span class="k">action</span>660 <span class="k">action</span>
659 </a>661 </a>
M internal/app/testdata/web/commits-page2.html +18 -16
@@ -168,18 +168,20 @@
168 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">168 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
169 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>169 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
170 </footer>170 </footer>
171-<div class="hint" role="status" aria-live="polite">171+<div class="hint">
172-<span class="hint-nav" :text="document.body.dataset.nav || ''">172+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
173 </span>173 </span>
174+<span role="status" aria-live="polite">
174 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">175 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
175 </span>176 </span>
176 <span class="hint-flash" :text="document.body.dataset.flash || ''">177 <span class="hint-flash" :text="document.body.dataset.flash || ''">
177 </span>178 </span>
179+</span>
178 </div>180 </div>
179 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">181 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
180 <div class="panel-head">182 <div class="panel-head">
181 <h2>Keyboard shortcuts</h2>183 <h2>Keyboard shortcuts</h2>
182-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">184+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
183 <kbd>esc</kbd> close</button>185 <kbd>esc</kbd> close</button>
184 </div>186 </div>
185 <div class="keys-pref">187 <div class="keys-pref">
@@ -313,7 +315,7 @@
313 <div class="panel-head">315 <div class="panel-head">
314 <label for="refs-filter" class="sr">filter branches and tags</label>316 <label for="refs-filter" class="sr">filter branches and tags</label>
315 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">317 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
316-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">318+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
317 <kbd>esc</kbd>319 <kbd>esc</kbd>
318 </button>320 </button>
319 </div>321 </div>
@@ -340,73 +342,73 @@
340 <div class="panel-head">342 <div class="panel-head">
341 <label for="cmd-q" class="sr">go to</label>343 <label for="cmd-q" class="sr">go to</label>
342 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">344 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
343-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">345+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
344 <kbd>esc</kbd>346 <kbd>esc</kbd>
345 </button>347 </button>
346 </div>348 </div>
347 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">349 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
348 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>350 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
349-<a href="/" tabindex="-1">351+<a role="presentation" data-href="/">
350 <span class="lbl">home</span>352 <span class="lbl">home</span>
351 <span class="k">go</span>353 <span class="k">go</span>
352 </a>354 </a>
353 </li>355 </li>
354 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">356 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
355-<a href="/long" tabindex="-1">357+<a role="presentation" data-href="/long">
356 <span class="lbl">summary</span>358 <span class="lbl">summary</span>
357 <span class="k">section</span>359 <span class="k">section</span>
358 </a>360 </a>
359 </li>361 </li>
360 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">362 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
361-<a href="/long/tree/main" tabindex="-1">363+<a role="presentation" data-href="/long/tree/main">
362 <span class="lbl">files</span>364 <span class="lbl">files</span>
363 <span class="k">section</span>365 <span class="k">section</span>
364 </a>366 </a>
365 </li>367 </li>
366 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">368 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
367-<a href="/long/commits/main" tabindex="-1">369+<a role="presentation" data-href="/long/commits/main">
368 <span class="lbl">commits</span>370 <span class="lbl">commits</span>
369 <span class="k">section</span>371 <span class="k">section</span>
370 </a>372 </a>
371 </li>373 </li>
372 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">374 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
373-<a href="/long/refs" tabindex="-1">375+<a role="presentation" data-href="/long/refs">
374 <span class="lbl">branches &amp; tags</span>376 <span class="lbl">branches &amp; tags</span>
375 <span class="k">section</span>377 <span class="k">section</span>
376 </a>378 </a>
377 </li>379 </li>
378 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">380 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
379-<a href="/long/merge-requests" tabindex="-1">381+<a role="presentation" data-href="/long/merge-requests">
380 <span class="lbl">merge requests</span>382 <span class="lbl">merge requests</span>
381 <span class="k">section</span>383 <span class="k">section</span>
382 </a>384 </a>
383 </li>385 </li>
384 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">386 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
385-<a href="/long/releases" tabindex="-1">387+<a role="presentation" data-href="/long/releases">
386 <span class="lbl">releases</span>388 <span class="lbl">releases</span>
387 <span class="k">section</span>389 <span class="k">section</span>
388 </a>390 </a>
389 </li>391 </li>
390 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">392 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
391-<a href="/long/actions" tabindex="-1">393+<a role="presentation" data-href="/long/actions">
392 <span class="lbl">actions</span>394 <span class="lbl">actions</span>
393 <span class="k">section</span>395 <span class="k">section</span>
394 </a>396 </a>
395 </li>397 </li>
396 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">398 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">
397-<a href="/long/tree/main" tabindex="-1">399+<a role="presentation" data-href="/long/tree/main">
398 <span class="lbl">main</span>400 <span class="lbl">main</span>
399 <span class="k">branch</span>401 <span class="k">branch</span>
400 </a>402 </a>
401 </li>403 </li>
402 <li id="cmd-o9" role="option" aria-selected="false" data-k="repo diffs">404 <li id="cmd-o9" role="option" aria-selected="false" data-k="repo diffs">
403-<a href="/diffs" tabindex="-1">405+<a role="presentation" data-href="/diffs">
404 <span class="lbl">diffs</span>406 <span class="lbl">diffs</span>
405 <span class="k">repo</span>407 <span class="k">repo</span>
406 </a>408 </a>
407 </li>409 </li>
408 <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">410 <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
409-<a href="#" data-fire="keys" tabindex="-1">411+<a role="presentation" data-fire="keys">
410 <span class="lbl">turn single-key shortcuts off</span>412 <span class="lbl">turn single-key shortcuts off</span>
411 <span class="k">action</span>413 <span class="k">action</span>
412 </a>414 </a>
M internal/app/testdata/web/commits-path.html +18 -16
@@ -183,18 +183,20 @@
183 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">183 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
184 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>184 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
185 </footer>185 </footer>
186-<div class="hint" role="status" aria-live="polite">186+<div class="hint">
187-<span class="hint-nav" :text="document.body.dataset.nav || ''">187+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
188 </span>188 </span>
189+<span role="status" aria-live="polite">
189 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">190 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
190 </span>191 </span>
191 <span class="hint-flash" :text="document.body.dataset.flash || ''">192 <span class="hint-flash" :text="document.body.dataset.flash || ''">
192 </span>193 </span>
194+</span>
193 </div>195 </div>
194 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">196 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
195 <div class="panel-head">197 <div class="panel-head">
196 <h2>Keyboard shortcuts</h2>198 <h2>Keyboard shortcuts</h2>
197-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">199+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
198 <kbd>esc</kbd> close</button>200 <kbd>esc</kbd> close</button>
199 </div>201 </div>
200 <div class="keys-pref">202 <div class="keys-pref">
@@ -328,7 +330,7 @@
328 <div class="panel-head">330 <div class="panel-head">
329 <label for="refs-filter" class="sr">filter branches and tags</label>331 <label for="refs-filter" class="sr">filter branches and tags</label>
330 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">332 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
331-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">333+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
332 <kbd>esc</kbd>334 <kbd>esc</kbd>
333 </button>335 </button>
334 </div>336 </div>
@@ -355,73 +357,73 @@
355 <div class="panel-head">357 <div class="panel-head">
356 <label for="cmd-q" class="sr">go to</label>358 <label for="cmd-q" class="sr">go to</label>
357 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">359 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
358-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">360+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
359 <kbd>esc</kbd>361 <kbd>esc</kbd>
360 </button>362 </button>
361 </div>363 </div>
362 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">364 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
363 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>365 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
364-<a href="/" tabindex="-1">366+<a role="presentation" data-href="/">
365 <span class="lbl">home</span>367 <span class="lbl">home</span>
366 <span class="k">go</span>368 <span class="k">go</span>
367 </a>369 </a>
368 </li>370 </li>
369 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">371 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
370-<a href="/long" tabindex="-1">372+<a role="presentation" data-href="/long">
371 <span class="lbl">summary</span>373 <span class="lbl">summary</span>
372 <span class="k">section</span>374 <span class="k">section</span>
373 </a>375 </a>
374 </li>376 </li>
375 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">377 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
376-<a href="/long/tree/main" tabindex="-1">378+<a role="presentation" data-href="/long/tree/main">
377 <span class="lbl">files</span>379 <span class="lbl">files</span>
378 <span class="k">section</span>380 <span class="k">section</span>
379 </a>381 </a>
380 </li>382 </li>
381 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">383 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
382-<a href="/long/commits/main" tabindex="-1">384+<a role="presentation" data-href="/long/commits/main">
383 <span class="lbl">commits</span>385 <span class="lbl">commits</span>
384 <span class="k">section</span>386 <span class="k">section</span>
385 </a>387 </a>
386 </li>388 </li>
387 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">389 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
388-<a href="/long/refs" tabindex="-1">390+<a role="presentation" data-href="/long/refs">
389 <span class="lbl">branches &amp; tags</span>391 <span class="lbl">branches &amp; tags</span>
390 <span class="k">section</span>392 <span class="k">section</span>
391 </a>393 </a>
392 </li>394 </li>
393 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">395 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
394-<a href="/long/merge-requests" tabindex="-1">396+<a role="presentation" data-href="/long/merge-requests">
395 <span class="lbl">merge requests</span>397 <span class="lbl">merge requests</span>
396 <span class="k">section</span>398 <span class="k">section</span>
397 </a>399 </a>
398 </li>400 </li>
399 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">401 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
400-<a href="/long/releases" tabindex="-1">402+<a role="presentation" data-href="/long/releases">
401 <span class="lbl">releases</span>403 <span class="lbl">releases</span>
402 <span class="k">section</span>404 <span class="k">section</span>
403 </a>405 </a>
404 </li>406 </li>
405 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">407 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
406-<a href="/long/actions" tabindex="-1">408+<a role="presentation" data-href="/long/actions">
407 <span class="lbl">actions</span>409 <span class="lbl">actions</span>
408 <span class="k">section</span>410 <span class="k">section</span>
409 </a>411 </a>
410 </li>412 </li>
411 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">413 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">
412-<a href="/long/tree/main" tabindex="-1">414+<a role="presentation" data-href="/long/tree/main">
413 <span class="lbl">main</span>415 <span class="lbl">main</span>
414 <span class="k">branch</span>416 <span class="k">branch</span>
415 </a>417 </a>
416 </li>418 </li>
417 <li id="cmd-o9" role="option" aria-selected="false" data-k="repo diffs">419 <li id="cmd-o9" role="option" aria-selected="false" data-k="repo diffs">
418-<a href="/diffs" tabindex="-1">420+<a role="presentation" data-href="/diffs">
419 <span class="lbl">diffs</span>421 <span class="lbl">diffs</span>
420 <span class="k">repo</span>422 <span class="k">repo</span>
421 </a>423 </a>
422 </li>424 </li>
423 <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">425 <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
424-<a href="#" data-fire="keys" tabindex="-1">426+<a role="presentation" data-fire="keys">
425 <span class="lbl">turn single-key shortcuts off</span>427 <span class="lbl">turn single-key shortcuts off</span>
426 <span class="k">action</span>428 <span class="k">action</span>
427 </a>429 </a>
M internal/app/testdata/web/commits-root.html +19 -17
@@ -965,14 +965,16 @@
965 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">965 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
966 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>966 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
967 </footer>967 </footer>
968-<div class="hint" role="status" aria-live="polite">968+<div class="hint">
969-<span class="hint-nav" :text="document.body.dataset.nav || ''">969+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
970 </span>970 </span>
971+<span role="status" aria-live="polite">
971 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">972 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
972 </span>973 </span>
973 <span class="hint-flash" :text="document.body.dataset.flash || ''">974 <span class="hint-flash" :text="document.body.dataset.flash || ''">
974 </span>975 </span>
975-<span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Along&amp;view=commits&amp;ref=main&amp;head=SHA40" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> ermiteLive.back();976+</span>
977+<span class="hint-live" role="status" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Along&amp;view=commits&amp;ref=main&amp;head=SHA40" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> ermiteLive.back();
976 htmx:sse:close, htmx:sse:error -> ermiteLive.lost();978 htmx:sse:close, htmx:sse:error -> ermiteLive.lost();
977 htmx:sse:error -> ermiteLive.error(event.detail.status);979 htmx:sse:error -> ermiteLive.error(event.detail.status);
978 htmx:sse:before:connection -> ermiteLive.gate(event);980 htmx:sse:before:connection -> ermiteLive.gate(event);
@@ -984,7 +986,7 @@
984 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">986 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
985 <div class="panel-head">987 <div class="panel-head">
986 <h2>Keyboard shortcuts</h2>988 <h2>Keyboard shortcuts</h2>
987-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">989+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
988 <kbd>esc</kbd> close</button>990 <kbd>esc</kbd> close</button>
989 </div>991 </div>
990 <div class="keys-pref">992 <div class="keys-pref">
@@ -1118,7 +1120,7 @@
1118 <div class="panel-head">1120 <div class="panel-head">
1119 <label for="refs-filter" class="sr">filter branches and tags</label>1121 <label for="refs-filter" class="sr">filter branches and tags</label>
1120 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">1122 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
1121-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">1123+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
1122 <kbd>esc</kbd>1124 <kbd>esc</kbd>
1123 </button>1125 </button>
1124 </div>1126 </div>
@@ -1145,73 +1147,73 @@
1145 <div class="panel-head">1147 <div class="panel-head">
1146 <label for="cmd-q" class="sr">go to</label>1148 <label for="cmd-q" class="sr">go to</label>
1147 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">1149 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
1148-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">1150+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
1149 <kbd>esc</kbd>1151 <kbd>esc</kbd>
1150 </button>1152 </button>
1151 </div>1153 </div>
1152 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">1154 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
1153 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>1155 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
1154-<a href="/" tabindex="-1">1156+<a role="presentation" data-href="/">
1155 <span class="lbl">home</span>1157 <span class="lbl">home</span>
1156 <span class="k">go</span>1158 <span class="k">go</span>
1157 </a>1159 </a>
1158 </li>1160 </li>
1159 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">1161 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
1160-<a href="/long" tabindex="-1">1162+<a role="presentation" data-href="/long">
1161 <span class="lbl">summary</span>1163 <span class="lbl">summary</span>
1162 <span class="k">section</span>1164 <span class="k">section</span>
1163 </a>1165 </a>
1164 </li>1166 </li>
1165 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">1167 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
1166-<a href="/long/tree/main" tabindex="-1">1168+<a role="presentation" data-href="/long/tree/main">
1167 <span class="lbl">files</span>1169 <span class="lbl">files</span>
1168 <span class="k">section</span>1170 <span class="k">section</span>
1169 </a>1171 </a>
1170 </li>1172 </li>
1171 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">1173 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
1172-<a href="/long/commits/main" tabindex="-1">1174+<a role="presentation" data-href="/long/commits/main">
1173 <span class="lbl">commits</span>1175 <span class="lbl">commits</span>
1174 <span class="k">section</span>1176 <span class="k">section</span>
1175 </a>1177 </a>
1176 </li>1178 </li>
1177 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">1179 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
1178-<a href="/long/refs" tabindex="-1">1180+<a role="presentation" data-href="/long/refs">
1179 <span class="lbl">branches &amp; tags</span>1181 <span class="lbl">branches &amp; tags</span>
1180 <span class="k">section</span>1182 <span class="k">section</span>
1181 </a>1183 </a>
1182 </li>1184 </li>
1183 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">1185 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
1184-<a href="/long/merge-requests" tabindex="-1">1186+<a role="presentation" data-href="/long/merge-requests">
1185 <span class="lbl">merge requests</span>1187 <span class="lbl">merge requests</span>
1186 <span class="k">section</span>1188 <span class="k">section</span>
1187 </a>1189 </a>
1188 </li>1190 </li>
1189 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">1191 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
1190-<a href="/long/releases" tabindex="-1">1192+<a role="presentation" data-href="/long/releases">
1191 <span class="lbl">releases</span>1193 <span class="lbl">releases</span>
1192 <span class="k">section</span>1194 <span class="k">section</span>
1193 </a>1195 </a>
1194 </li>1196 </li>
1195 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">1197 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
1196-<a href="/long/actions" tabindex="-1">1198+<a role="presentation" data-href="/long/actions">
1197 <span class="lbl">actions</span>1199 <span class="lbl">actions</span>
1198 <span class="k">section</span>1200 <span class="k">section</span>
1199 </a>1201 </a>
1200 </li>1202 </li>
1201 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">1203 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">
1202-<a href="/long/tree/main" tabindex="-1">1204+<a role="presentation" data-href="/long/tree/main">
1203 <span class="lbl">main</span>1205 <span class="lbl">main</span>
1204 <span class="k">branch</span>1206 <span class="k">branch</span>
1205 </a>1207 </a>
1206 </li>1208 </li>
1207 <li id="cmd-o9" role="option" aria-selected="false" data-k="repo diffs">1209 <li id="cmd-o9" role="option" aria-selected="false" data-k="repo diffs">
1208-<a href="/diffs" tabindex="-1">1210+<a role="presentation" data-href="/diffs">
1209 <span class="lbl">diffs</span>1211 <span class="lbl">diffs</span>
1210 <span class="k">repo</span>1212 <span class="k">repo</span>
1211 </a>1213 </a>
1212 </li>1214 </li>
1213 <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">1215 <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
1214-<a href="#" data-fire="keys" tabindex="-1">1216+<a role="presentation" data-fire="keys">
1215 <span class="lbl">turn single-key shortcuts off</span>1217 <span class="lbl">turn single-key shortcuts off</span>
1216 <span class="k">action</span>1218 <span class="k">action</span>
1217 </a>1219 </a>
M internal/app/testdata/web/commits-small.html +21 -19
@@ -310,14 +310,16 @@
310 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">310 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
311 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>311 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
312 </footer>312 </footer>
313-<div class="hint" role="status" aria-live="polite">313+<div class="hint">
314-<span class="hint-nav" :text="document.body.dataset.nav || ''">314+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
315 </span>315 </span>
316+<span role="status" aria-live="polite">
316 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">317 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
317 </span>318 </span>
318 <span class="hint-flash" :text="document.body.dataset.flash || ''">319 <span class="hint-flash" :text="document.body.dataset.flash || ''">
319 </span>320 </span>
320-<span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Adiffs&amp;view=commits&amp;ref=main&amp;head=SHA40" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> ermiteLive.back();321+</span>
322+<span class="hint-live" role="status" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Adiffs&amp;view=commits&amp;ref=main&amp;head=SHA40" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> ermiteLive.back();
321 htmx:sse:close, htmx:sse:error -> ermiteLive.lost();323 htmx:sse:close, htmx:sse:error -> ermiteLive.lost();
322 htmx:sse:error -> ermiteLive.error(event.detail.status);324 htmx:sse:error -> ermiteLive.error(event.detail.status);
323 htmx:sse:before:connection -> ermiteLive.gate(event);325 htmx:sse:before:connection -> ermiteLive.gate(event);
@@ -329,7 +331,7 @@
329 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">331 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
330 <div class="panel-head">332 <div class="panel-head">
331 <h2>Keyboard shortcuts</h2>333 <h2>Keyboard shortcuts</h2>
332-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">334+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
333 <kbd>esc</kbd> close</button>335 <kbd>esc</kbd> close</button>
334 </div>336 </div>
335 <div class="keys-pref">337 <div class="keys-pref">
@@ -463,7 +465,7 @@
463 <div class="panel-head">465 <div class="panel-head">
464 <label for="refs-filter" class="sr">filter branches and tags</label>466 <label for="refs-filter" class="sr">filter branches and tags</label>
465 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">467 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
466-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">468+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
467 <kbd>esc</kbd>469 <kbd>esc</kbd>
468 </button>470 </button>
469 </div>471 </div>
@@ -498,85 +500,85 @@
498 <div class="panel-head">500 <div class="panel-head">
499 <label for="cmd-q" class="sr">go to</label>501 <label for="cmd-q" class="sr">go to</label>
500 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">502 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
501-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">503+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
502 <kbd>esc</kbd>504 <kbd>esc</kbd>
503 </button>505 </button>
504 </div>506 </div>
505 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">507 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
506 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>508 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
507-<a href="/" tabindex="-1">509+<a role="presentation" data-href="/">
508 <span class="lbl">home</span>510 <span class="lbl">home</span>
509 <span class="k">go</span>511 <span class="k">go</span>
510 </a>512 </a>
511 </li>513 </li>
512 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">514 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
513-<a href="/diffs" tabindex="-1">515+<a role="presentation" data-href="/diffs">
514 <span class="lbl">summary</span>516 <span class="lbl">summary</span>
515 <span class="k">section</span>517 <span class="k">section</span>
516 </a>518 </a>
517 </li>519 </li>
518 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">520 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
519-<a href="/diffs/tree/main" tabindex="-1">521+<a role="presentation" data-href="/diffs/tree/main">
520 <span class="lbl">files</span>522 <span class="lbl">files</span>
521 <span class="k">section</span>523 <span class="k">section</span>
522 </a>524 </a>
523 </li>525 </li>
524 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">526 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
525-<a href="/diffs/commits/main" tabindex="-1">527+<a role="presentation" data-href="/diffs/commits/main">
526 <span class="lbl">commits</span>528 <span class="lbl">commits</span>
527 <span class="k">section</span>529 <span class="k">section</span>
528 </a>530 </a>
529 </li>531 </li>
530 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">532 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
531-<a href="/diffs/refs" tabindex="-1">533+<a role="presentation" data-href="/diffs/refs">
532 <span class="lbl">branches &amp; tags</span>534 <span class="lbl">branches &amp; tags</span>
533 <span class="k">section</span>535 <span class="k">section</span>
534 </a>536 </a>
535 </li>537 </li>
536 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">538 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
537-<a href="/diffs/merge-requests" tabindex="-1">539+<a role="presentation" data-href="/diffs/merge-requests">
538 <span class="lbl">merge requests</span>540 <span class="lbl">merge requests</span>
539 <span class="k">section</span>541 <span class="k">section</span>
540 </a>542 </a>
541 </li>543 </li>
542 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">544 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
543-<a href="/diffs/releases" tabindex="-1">545+<a role="presentation" data-href="/diffs/releases">
544 <span class="lbl">releases</span>546 <span class="lbl">releases</span>
545 <span class="k">section</span>547 <span class="k">section</span>
546 </a>548 </a>
547 </li>549 </li>
548 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">550 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
549-<a href="/diffs/actions" tabindex="-1">551+<a role="presentation" data-href="/diffs/actions">
550 <span class="lbl">actions</span>552 <span class="lbl">actions</span>
551 <span class="k">section</span>553 <span class="k">section</span>
552 </a>554 </a>
553 </li>555 </li>
554 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">556 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main">
555-<a href="/diffs/tree/main" tabindex="-1">557+<a role="presentation" data-href="/diffs/tree/main">
556 <span class="lbl">main</span>558 <span class="lbl">main</span>
557 <span class="k">branch</span>559 <span class="k">branch</span>
558 </a>560 </a>
559 </li>561 </li>
560 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch side">562 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch side">
561-<a href="/diffs/tree/side" tabindex="-1">563+<a role="presentation" data-href="/diffs/tree/side">
562 <span class="lbl">side</span>564 <span class="lbl">side</span>
563 <span class="k">branch</span>565 <span class="k">branch</span>
564 </a>566 </a>
565 </li>567 </li>
566 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0.0">568 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0.0">
567-<a href="/diffs/tree/v1.0.0" tabindex="-1">569+<a role="presentation" data-href="/diffs/tree/v1.0.0">
568 <span class="lbl">v1.0.0</span>570 <span class="lbl">v1.0.0</span>
569 <span class="k">tag</span>571 <span class="k">tag</span>
570 </a>572 </a>
571 </li>573 </li>
572 <li id="cmd-o11" role="option" aria-selected="false" data-k="repo long">574 <li id="cmd-o11" role="option" aria-selected="false" data-k="repo long">
573-<a href="/long" tabindex="-1">575+<a role="presentation" data-href="/long">
574 <span class="lbl">long</span>576 <span class="lbl">long</span>
575 <span class="k">repo</span>577 <span class="k">repo</span>
576 </a>578 </a>
577 </li>579 </li>
578 <li id="cmd-o12" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">580 <li id="cmd-o12" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
579-<a href="#" data-fire="keys" tabindex="-1">581+<a role="presentation" data-fire="keys">
580 <span class="lbl">turn single-key shortcuts off</span>582 <span class="lbl">turn single-key shortcuts off</span>
581 <span class="k">action</span>583 <span class="k">action</span>
582 </a>584 </a>
M internal/app/testdata/web/empty.html +9 -7
@@ -86,18 +86,20 @@
86 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">86 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
87 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>87 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
88 </footer>88 </footer>
89-<div class="hint" role="status" aria-live="polite">89+<div class="hint">
90-<span class="hint-nav" :text="document.body.dataset.nav || ''">90+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
91 </span>91 </span>
92+<span role="status" aria-live="polite">
92 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">93 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
93 </span>94 </span>
94 <span class="hint-flash" :text="document.body.dataset.flash || ''">95 <span class="hint-flash" :text="document.body.dataset.flash || ''">
95 </span>96 </span>
97+</span>
96 </div>98 </div>
97 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">99 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
98 <div class="panel-head">100 <div class="panel-head">
99 <h2>Keyboard shortcuts</h2>101 <h2>Keyboard shortcuts</h2>
100-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">102+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
101 <kbd>esc</kbd> close</button>103 <kbd>esc</kbd> close</button>
102 </div>104 </div>
103 <div class="keys-pref">105 <div class="keys-pref">
@@ -231,25 +233,25 @@
231 <div class="panel-head">233 <div class="panel-head">
232 <label for="cmd-q" class="sr">go to</label>234 <label for="cmd-q" class="sr">go to</label>
233 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">235 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
234-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">236+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
235 <kbd>esc</kbd>237 <kbd>esc</kbd>
236 </button>238 </button>
237 </div>239 </div>
238 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">240 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
239 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>241 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
240-<a href="/" tabindex="-1">242+<a role="presentation" data-href="/">
241 <span class="lbl">home</span>243 <span class="lbl">home</span>
242 <span class="k">go</span>244 <span class="k">go</span>
243 </a>245 </a>
244 </li>246 </li>
245 <li id="cmd-o1" role="option" aria-selected="false" data-k="repo demo">247 <li id="cmd-o1" role="option" aria-selected="false" data-k="repo demo">
246-<a href="/demo" tabindex="-1">248+<a role="presentation" data-href="/demo">
247 <span class="lbl">demo</span>249 <span class="lbl">demo</span>
248 <span class="k">repo</span>250 <span class="k">repo</span>
249 </a>251 </a>
250 </li>252 </li>
251 <li id="cmd-o2" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">253 <li id="cmd-o2" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
252-<a href="#" data-fire="keys" tabindex="-1">254+<a role="presentation" data-fire="keys">
253 <span class="lbl">turn single-key shortcuts off</span>255 <span class="lbl">turn single-key shortcuts off</span>
254 <span class="k">action</span>256 <span class="k">action</span>
255 </a>257 </a>
M internal/app/testdata/web/home.html +11 -9
@@ -182,14 +182,16 @@
182 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">182 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
183 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>183 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
184 </footer>184 </footer>
185-<div class="hint" role="status" aria-live="polite">185+<div class="hint">
186-<span class="hint-nav" :text="document.body.dataset.nav || ''">186+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
187 </span>187 </span>
188+<span role="status" aria-live="polite">
188 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">189 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
189 </span>190 </span>
190 <span class="hint-flash" :text="document.body.dataset.flash || ''">191 <span class="hint-flash" :text="document.body.dataset.flash || ''">
191 </span>192 </span>
192-<span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=home" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> ermiteLive.back();193+</span>
194+<span class="hint-live" role="status" data-state="connecting" hx-sse:connect="/-/events?topic=home" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> ermiteLive.back();
193 htmx:sse:close, htmx:sse:error -> ermiteLive.lost();195 htmx:sse:close, htmx:sse:error -> ermiteLive.lost();
194 htmx:sse:error -> ermiteLive.error(event.detail.status);196 htmx:sse:error -> ermiteLive.error(event.detail.status);
195 htmx:sse:before:connection -> ermiteLive.gate(event);197 htmx:sse:before:connection -> ermiteLive.gate(event);
@@ -201,7 +203,7 @@
201 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">203 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
202 <div class="panel-head">204 <div class="panel-head">
203 <h2>Keyboard shortcuts</h2>205 <h2>Keyboard shortcuts</h2>
204-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">206+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
205 <kbd>esc</kbd> close</button>207 <kbd>esc</kbd> close</button>
206 </div>208 </div>
207 <div class="keys-pref">209 <div class="keys-pref">
@@ -335,31 +337,31 @@
335 <div class="panel-head">337 <div class="panel-head">
336 <label for="cmd-q" class="sr">go to</label>338 <label for="cmd-q" class="sr">go to</label>
337 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">339 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
338-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">340+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
339 <kbd>esc</kbd>341 <kbd>esc</kbd>
340 </button>342 </button>
341 </div>343 </div>
342 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">344 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
343 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>345 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
344-<a href="/" tabindex="-1">346+<a role="presentation" data-href="/">
345 <span class="lbl">home</span>347 <span class="lbl">home</span>
346 <span class="k">go</span>348 <span class="k">go</span>
347 </a>349 </a>
348 </li>350 </li>
349 <li id="cmd-o1" role="option" aria-selected="false" data-k="repo quiet">351 <li id="cmd-o1" role="option" aria-selected="false" data-k="repo quiet">
350-<a href="/quiet" tabindex="-1">352+<a role="presentation" data-href="/quiet">
351 <span class="lbl">quiet</span>353 <span class="lbl">quiet</span>
352 <span class="k">repo</span>354 <span class="k">repo</span>
353 </a>355 </a>
354 </li>356 </li>
355 <li id="cmd-o2" role="option" aria-selected="false" data-k="repo demo">357 <li id="cmd-o2" role="option" aria-selected="false" data-k="repo demo">
356-<a href="/demo" tabindex="-1">358+<a role="presentation" data-href="/demo">
357 <span class="lbl">demo</span>359 <span class="lbl">demo</span>
358 <span class="k">repo</span>360 <span class="k">repo</span>
359 </a>361 </a>
360 </li>362 </li>
361 <li id="cmd-o3" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">363 <li id="cmd-o3" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
362-<a href="#" data-fire="keys" tabindex="-1">364+<a role="presentation" data-fire="keys">
363 <span class="lbl">turn single-key shortcuts off</span>365 <span class="lbl">turn single-key shortcuts off</span>
364 <span class="k">action</span>366 <span class="k">action</span>
365 </a>367 </a>
M internal/app/testdata/web/not-found-page.html +8 -6
@@ -75,18 +75,20 @@
75 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">75 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
76 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>76 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
77 </footer>77 </footer>
78-<div class="hint" role="status" aria-live="polite">78+<div class="hint">
79-<span class="hint-nav" :text="document.body.dataset.nav || ''">79+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
80 </span>80 </span>
81+<span role="status" aria-live="polite">
81 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">82 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
82 </span>83 </span>
83 <span class="hint-flash" :text="document.body.dataset.flash || ''">84 <span class="hint-flash" :text="document.body.dataset.flash || ''">
84 </span>85 </span>
86+</span>
85 </div>87 </div>
86 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">88 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
87 <div class="panel-head">89 <div class="panel-head">
88 <h2>Keyboard shortcuts</h2>90 <h2>Keyboard shortcuts</h2>
89-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">91+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
90 <kbd>esc</kbd> close</button>92 <kbd>esc</kbd> close</button>
91 </div>93 </div>
92 <div class="keys-pref">94 <div class="keys-pref">
@@ -220,19 +222,19 @@
220 <div class="panel-head">222 <div class="panel-head">
221 <label for="cmd-q" class="sr">go to</label>223 <label for="cmd-q" class="sr">go to</label>
222 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">224 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
223-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">225+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
224 <kbd>esc</kbd>226 <kbd>esc</kbd>
225 </button>227 </button>
226 </div>228 </div>
227 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">229 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
228 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>230 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
229-<a href="/" tabindex="-1">231+<a role="presentation" data-href="/">
230 <span class="lbl">home</span>232 <span class="lbl">home</span>
231 <span class="k">go</span>233 <span class="k">go</span>
232 </a>234 </a>
233 </li>235 </li>
234 <li id="cmd-o1" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">236 <li id="cmd-o1" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
235-<a href="#" data-fire="keys" tabindex="-1">237+<a role="presentation" data-fire="keys">
236 <span class="lbl">turn single-key shortcuts off</span>238 <span class="lbl">turn single-key shortcuts off</span>
237 <span class="k">action</span>239 <span class="k">action</span>
238 </a>240 </a>
M internal/app/testdata/web/not-found.html +8 -6
@@ -75,18 +75,20 @@
75 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">75 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
76 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>76 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
77 </footer>77 </footer>
78-<div class="hint" role="status" aria-live="polite">78+<div class="hint">
79-<span class="hint-nav" :text="document.body.dataset.nav || ''">79+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
80 </span>80 </span>
81+<span role="status" aria-live="polite">
81 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">82 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
82 </span>83 </span>
83 <span class="hint-flash" :text="document.body.dataset.flash || ''">84 <span class="hint-flash" :text="document.body.dataset.flash || ''">
84 </span>85 </span>
86+</span>
85 </div>87 </div>
86 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">88 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
87 <div class="panel-head">89 <div class="panel-head">
88 <h2>Keyboard shortcuts</h2>90 <h2>Keyboard shortcuts</h2>
89-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">91+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
90 <kbd>esc</kbd> close</button>92 <kbd>esc</kbd> close</button>
91 </div>93 </div>
92 <div class="keys-pref">94 <div class="keys-pref">
@@ -220,19 +222,19 @@
220 <div class="panel-head">222 <div class="panel-head">
221 <label for="cmd-q" class="sr">go to</label>223 <label for="cmd-q" class="sr">go to</label>
222 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">224 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
223-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">225+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
224 <kbd>esc</kbd>226 <kbd>esc</kbd>
225 </button>227 </button>
226 </div>228 </div>
227 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">229 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
228 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>230 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
229-<a href="/" tabindex="-1">231+<a role="presentation" data-href="/">
230 <span class="lbl">home</span>232 <span class="lbl">home</span>
231 <span class="k">go</span>233 <span class="k">go</span>
232 </a>234 </a>
233 </li>235 </li>
234 <li id="cmd-o1" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">236 <li id="cmd-o1" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
235-<a href="#" data-fire="keys" tabindex="-1">237+<a role="presentation" data-fire="keys">
236 <span class="lbl">turn single-key shortcuts off</span>238 <span class="lbl">turn single-key shortcuts off</span>
237 <span class="k">action</span>239 <span class="k">action</span>
238 </a>240 </a>
M internal/app/testdata/web/refs.html +22 -20
@@ -192,14 +192,16 @@
192 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">192 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
193 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>193 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
194 </footer>194 </footer>
195-<div class="hint" role="status" aria-live="polite">195+<div class="hint">
196-<span class="hint-nav" :text="document.body.dataset.nav || ''">196+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
197 </span>197 </span>
198+<span role="status" aria-live="polite">
198 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">199 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
199 </span>200 </span>
200 <span class="hint-flash" :text="document.body.dataset.flash || ''">201 <span class="hint-flash" :text="document.body.dataset.flash || ''">
201 </span>202 </span>
202-<span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Ademo&amp;view=refs" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> ermiteLive.back();203+</span>
204+<span class="hint-live" role="status" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Ademo&amp;view=refs" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> ermiteLive.back();
203 htmx:sse:close, htmx:sse:error -> ermiteLive.lost();205 htmx:sse:close, htmx:sse:error -> ermiteLive.lost();
204 htmx:sse:error -> ermiteLive.error(event.detail.status);206 htmx:sse:error -> ermiteLive.error(event.detail.status);
205 htmx:sse:before:connection -> ermiteLive.gate(event);207 htmx:sse:before:connection -> ermiteLive.gate(event);
@@ -211,7 +213,7 @@
211 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">213 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
212 <div class="panel-head">214 <div class="panel-head">
213 <h2>Keyboard shortcuts</h2>215 <h2>Keyboard shortcuts</h2>
214-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">216+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
215 <kbd>esc</kbd> close</button>217 <kbd>esc</kbd> close</button>
216 </div>218 </div>
217 <div class="keys-pref">219 <div class="keys-pref">
@@ -345,7 +347,7 @@
345 <div class="panel-head">347 <div class="panel-head">
346 <label for="refs-filter" class="sr">filter branches and tags</label>348 <label for="refs-filter" class="sr">filter branches and tags</label>
347 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">349 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
348-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">350+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
349 <kbd>esc</kbd>351 <kbd>esc</kbd>
350 </button>352 </button>
351 </div>353 </div>
@@ -384,91 +386,91 @@
384 <div class="panel-head">386 <div class="panel-head">
385 <label for="cmd-q" class="sr">go to</label>387 <label for="cmd-q" class="sr">go to</label>
386 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">388 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
387-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">389+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
388 <kbd>esc</kbd>390 <kbd>esc</kbd>
389 </button>391 </button>
390 </div>392 </div>
391 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">393 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
392 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>394 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
393-<a href="/" tabindex="-1">395+<a role="presentation" data-href="/">
394 <span class="lbl">home</span>396 <span class="lbl">home</span>
395 <span class="k">go</span>397 <span class="k">go</span>
396 </a>398 </a>
397 </li>399 </li>
398 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">400 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
399-<a href="/demo" tabindex="-1">401+<a role="presentation" data-href="/demo">
400 <span class="lbl">summary</span>402 <span class="lbl">summary</span>
401 <span class="k">section</span>403 <span class="k">section</span>
402 </a>404 </a>
403 </li>405 </li>
404 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">406 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
405-<a href="/demo/tree/main" tabindex="-1">407+<a role="presentation" data-href="/demo/tree/main">
406 <span class="lbl">files</span>408 <span class="lbl">files</span>
407 <span class="k">section</span>409 <span class="k">section</span>
408 </a>410 </a>
409 </li>411 </li>
410 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">412 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
411-<a href="/demo/commits/main" tabindex="-1">413+<a role="presentation" data-href="/demo/commits/main">
412 <span class="lbl">commits</span>414 <span class="lbl">commits</span>
413 <span class="k">section</span>415 <span class="k">section</span>
414 </a>416 </a>
415 </li>417 </li>
416 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">418 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
417-<a href="/demo/refs" tabindex="-1">419+<a role="presentation" data-href="/demo/refs">
418 <span class="lbl">branches &amp; tags</span>420 <span class="lbl">branches &amp; tags</span>
419 <span class="k">section</span>421 <span class="k">section</span>
420 </a>422 </a>
421 </li>423 </li>
422 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">424 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
423-<a href="/demo/merge-requests" tabindex="-1">425+<a role="presentation" data-href="/demo/merge-requests">
424 <span class="lbl">merge requests</span>426 <span class="lbl">merge requests</span>
425 <span class="k">section</span>427 <span class="k">section</span>
426 </a>428 </a>
427 </li>429 </li>
428 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">430 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
429-<a href="/demo/releases" tabindex="-1">431+<a role="presentation" data-href="/demo/releases">
430 <span class="lbl">releases</span>432 <span class="lbl">releases</span>
431 <span class="k">section</span>433 <span class="k">section</span>
432 </a>434 </a>
433 </li>435 </li>
434 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">436 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
435-<a href="/demo/actions" tabindex="-1">437+<a role="presentation" data-href="/demo/actions">
436 <span class="lbl">actions</span>438 <span class="lbl">actions</span>
437 <span class="k">section</span>439 <span class="k">section</span>
438 </a>440 </a>
439 </li>441 </li>
440 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">442 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">
441-<a href="/demo/tree/feature/x" tabindex="-1">443+<a role="presentation" data-href="/demo/tree/feature/x">
442 <span class="lbl">feature/x</span>444 <span class="lbl">feature/x</span>
443 <span class="k">branch</span>445 <span class="k">branch</span>
444 </a>446 </a>
445 </li>447 </li>
446 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">448 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">
447-<a href="/demo/tree/main" tabindex="-1">449+<a role="presentation" data-href="/demo/tree/main">
448 <span class="lbl">main</span>450 <span class="lbl">main</span>
449 <span class="k">branch</span>451 <span class="k">branch</span>
450 </a>452 </a>
451 </li>453 </li>
452 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v0.1.0">454 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v0.1.0">
453-<a href="/demo/tree/v0.1.0" tabindex="-1">455+<a role="presentation" data-href="/demo/tree/v0.1.0">
454 <span class="lbl">v0.1.0</span>456 <span class="lbl">v0.1.0</span>
455 <span class="k">tag</span>457 <span class="k">tag</span>
456 </a>458 </a>
457 </li>459 </li>
458 <li id="cmd-o11" role="option" aria-selected="false" data-k="tag v0.2.0">460 <li id="cmd-o11" role="option" aria-selected="false" data-k="tag v0.2.0">
459-<a href="/demo/tree/v0.2.0" tabindex="-1">461+<a role="presentation" data-href="/demo/tree/v0.2.0">
460 <span class="lbl">v0.2.0</span>462 <span class="lbl">v0.2.0</span>
461 <span class="k">tag</span>463 <span class="k">tag</span>
462 </a>464 </a>
463 </li>465 </li>
464 <li id="cmd-o12" role="option" aria-selected="false" data-k="repo quiet">466 <li id="cmd-o12" role="option" aria-selected="false" data-k="repo quiet">
465-<a href="/quiet" tabindex="-1">467+<a role="presentation" data-href="/quiet">
466 <span class="lbl">quiet</span>468 <span class="lbl">quiet</span>
467 <span class="k">repo</span>469 <span class="k">repo</span>
468 </a>470 </a>
469 </li>471 </li>
470 <li id="cmd-o13" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">472 <li id="cmd-o13" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
471-<a href="#" data-fire="keys" tabindex="-1">473+<a role="presentation" data-fire="keys">
472 <span class="lbl">turn single-key shortcuts off</span>474 <span class="lbl">turn single-key shortcuts off</span>
473 <span class="k">action</span>475 <span class="k">action</span>
474 </a>476 </a>
M internal/app/testdata/web/summary.html +22 -20
@@ -218,14 +218,16 @@
218 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">218 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
219 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>219 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
220 </footer>220 </footer>
221-<div class="hint" role="status" aria-live="polite">221+<div class="hint">
222-<span class="hint-nav" :text="document.body.dataset.nav || ''">222+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
223 </span>223 </span>
224+<span role="status" aria-live="polite">
224 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">225 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
225 </span>226 </span>
226 <span class="hint-flash" :text="document.body.dataset.flash || ''">227 <span class="hint-flash" :text="document.body.dataset.flash || ''">
227 </span>228 </span>
228-<span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Ademo&amp;view=summary" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> ermiteLive.back();229+</span>
230+<span class="hint-live" role="status" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Ademo&amp;view=summary" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> ermiteLive.back();
229 htmx:sse:close, htmx:sse:error -> ermiteLive.lost();231 htmx:sse:close, htmx:sse:error -> ermiteLive.lost();
230 htmx:sse:error -> ermiteLive.error(event.detail.status);232 htmx:sse:error -> ermiteLive.error(event.detail.status);
231 htmx:sse:before:connection -> ermiteLive.gate(event);233 htmx:sse:before:connection -> ermiteLive.gate(event);
@@ -237,7 +239,7 @@
237 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">239 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
238 <div class="panel-head">240 <div class="panel-head">
239 <h2>Keyboard shortcuts</h2>241 <h2>Keyboard shortcuts</h2>
240-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">242+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
241 <kbd>esc</kbd> close</button>243 <kbd>esc</kbd> close</button>
242 </div>244 </div>
243 <div class="keys-pref">245 <div class="keys-pref">
@@ -371,7 +373,7 @@
371 <div class="panel-head">373 <div class="panel-head">
372 <label for="refs-filter" class="sr">filter branches and tags</label>374 <label for="refs-filter" class="sr">filter branches and tags</label>
373 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">375 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
374-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">376+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
375 <kbd>esc</kbd>377 <kbd>esc</kbd>
376 </button>378 </button>
377 </div>379 </div>
@@ -410,91 +412,91 @@
410 <div class="panel-head">412 <div class="panel-head">
411 <label for="cmd-q" class="sr">go to</label>413 <label for="cmd-q" class="sr">go to</label>
412 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">414 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
413-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">415+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
414 <kbd>esc</kbd>416 <kbd>esc</kbd>
415 </button>417 </button>
416 </div>418 </div>
417 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">419 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
418 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>420 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
419-<a href="/" tabindex="-1">421+<a role="presentation" data-href="/">
420 <span class="lbl">home</span>422 <span class="lbl">home</span>
421 <span class="k">go</span>423 <span class="k">go</span>
422 </a>424 </a>
423 </li>425 </li>
424 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">426 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
425-<a href="/demo" tabindex="-1">427+<a role="presentation" data-href="/demo">
426 <span class="lbl">summary</span>428 <span class="lbl">summary</span>
427 <span class="k">section</span>429 <span class="k">section</span>
428 </a>430 </a>
429 </li>431 </li>
430 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">432 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
431-<a href="/demo/tree/main" tabindex="-1">433+<a role="presentation" data-href="/demo/tree/main">
432 <span class="lbl">files</span>434 <span class="lbl">files</span>
433 <span class="k">section</span>435 <span class="k">section</span>
434 </a>436 </a>
435 </li>437 </li>
436 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">438 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
437-<a href="/demo/commits/main" tabindex="-1">439+<a role="presentation" data-href="/demo/commits/main">
438 <span class="lbl">commits</span>440 <span class="lbl">commits</span>
439 <span class="k">section</span>441 <span class="k">section</span>
440 </a>442 </a>
441 </li>443 </li>
442 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">444 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
443-<a href="/demo/refs" tabindex="-1">445+<a role="presentation" data-href="/demo/refs">
444 <span class="lbl">branches &amp; tags</span>446 <span class="lbl">branches &amp; tags</span>
445 <span class="k">section</span>447 <span class="k">section</span>
446 </a>448 </a>
447 </li>449 </li>
448 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">450 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
449-<a href="/demo/merge-requests" tabindex="-1">451+<a role="presentation" data-href="/demo/merge-requests">
450 <span class="lbl">merge requests</span>452 <span class="lbl">merge requests</span>
451 <span class="k">section</span>453 <span class="k">section</span>
452 </a>454 </a>
453 </li>455 </li>
454 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">456 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
455-<a href="/demo/releases" tabindex="-1">457+<a role="presentation" data-href="/demo/releases">
456 <span class="lbl">releases</span>458 <span class="lbl">releases</span>
457 <span class="k">section</span>459 <span class="k">section</span>
458 </a>460 </a>
459 </li>461 </li>
460 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">462 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
461-<a href="/demo/actions" tabindex="-1">463+<a role="presentation" data-href="/demo/actions">
462 <span class="lbl">actions</span>464 <span class="lbl">actions</span>
463 <span class="k">section</span>465 <span class="k">section</span>
464 </a>466 </a>
465 </li>467 </li>
466 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">468 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">
467-<a href="/demo/tree/feature/x" tabindex="-1">469+<a role="presentation" data-href="/demo/tree/feature/x">
468 <span class="lbl">feature/x</span>470 <span class="lbl">feature/x</span>
469 <span class="k">branch</span>471 <span class="k">branch</span>
470 </a>472 </a>
471 </li>473 </li>
472 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">474 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">
473-<a href="/demo/tree/main" tabindex="-1">475+<a role="presentation" data-href="/demo/tree/main">
474 <span class="lbl">main</span>476 <span class="lbl">main</span>
475 <span class="k">branch</span>477 <span class="k">branch</span>
476 </a>478 </a>
477 </li>479 </li>
478 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v0.1.0">480 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v0.1.0">
479-<a href="/demo/tree/v0.1.0" tabindex="-1">481+<a role="presentation" data-href="/demo/tree/v0.1.0">
480 <span class="lbl">v0.1.0</span>482 <span class="lbl">v0.1.0</span>
481 <span class="k">tag</span>483 <span class="k">tag</span>
482 </a>484 </a>
483 </li>485 </li>
484 <li id="cmd-o11" role="option" aria-selected="false" data-k="tag v0.2.0">486 <li id="cmd-o11" role="option" aria-selected="false" data-k="tag v0.2.0">
485-<a href="/demo/tree/v0.2.0" tabindex="-1">487+<a role="presentation" data-href="/demo/tree/v0.2.0">
486 <span class="lbl">v0.2.0</span>488 <span class="lbl">v0.2.0</span>
487 <span class="k">tag</span>489 <span class="k">tag</span>
488 </a>490 </a>
489 </li>491 </li>
490 <li id="cmd-o12" role="option" aria-selected="false" data-k="repo quiet">492 <li id="cmd-o12" role="option" aria-selected="false" data-k="repo quiet">
491-<a href="/quiet" tabindex="-1">493+<a role="presentation" data-href="/quiet">
492 <span class="lbl">quiet</span>494 <span class="lbl">quiet</span>
493 <span class="k">repo</span>495 <span class="k">repo</span>
494 </a>496 </a>
495 </li>497 </li>
496 <li id="cmd-o13" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">498 <li id="cmd-o13" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
497-<a href="#" data-fire="keys" tabindex="-1">499+<a role="presentation" data-fire="keys">
498 <span class="lbl">turn single-key shortcuts off</span>500 <span class="lbl">turn single-key shortcuts off</span>
499 <span class="k">action</span>501 <span class="k">action</span>
500 </a>502 </a>
M internal/app/testdata/web/tree-nested.html +21 -19
@@ -117,9 +117,9 @@
117 <kbd>j</kbd>117 <kbd>j</kbd>
118 <kbd>k</kbd> move · <kbd>l</kbd> open · <kbd>h</kbd> back</p>118 <kbd>k</kbd> move · <kbd>l</kbd> open · <kbd>h</kbd> back</p>
119 </div>119 </div>
120-<div id="finder" class="tree-card" hidden>120+<p id="finder-status" class="dim" role="status">
121-<p id="finder-status" class="dim" aria-live="polite">
122 </p>121 </p>
122+<div id="finder" class="tree-card" hidden>
123 <ul id="finder-list" class="cmd-list" role="listbox" aria-label="Files">123 <ul id="finder-list" class="cmd-list" role="listbox" aria-label="Files">
124 </ul>124 </ul>
125 </div>125 </div>
@@ -156,18 +156,20 @@
156 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">156 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
157 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>157 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
158 </footer>158 </footer>
159-<div class="hint" role="status" aria-live="polite">159+<div class="hint">
160-<span class="hint-nav" :text="document.body.dataset.nav || ''">160+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
161 </span>161 </span>
162+<span role="status" aria-live="polite">
162 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">163 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
163 </span>164 </span>
164 <span class="hint-flash" :text="document.body.dataset.flash || ''">165 <span class="hint-flash" :text="document.body.dataset.flash || ''">
165 </span>166 </span>
167+</span>
166 </div>168 </div>
167 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">169 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
168 <div class="panel-head">170 <div class="panel-head">
169 <h2>Keyboard shortcuts</h2>171 <h2>Keyboard shortcuts</h2>
170-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">172+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
171 <kbd>esc</kbd> close</button>173 <kbd>esc</kbd> close</button>
172 </div>174 </div>
173 <div class="keys-pref">175 <div class="keys-pref">
@@ -301,7 +303,7 @@
301 <div class="panel-head">303 <div class="panel-head">
302 <label for="refs-filter" class="sr">filter branches and tags</label>304 <label for="refs-filter" class="sr">filter branches and tags</label>
303 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">305 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
304-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">306+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
305 <kbd>esc</kbd>307 <kbd>esc</kbd>
306 </button>308 </button>
307 </div>309 </div>
@@ -336,79 +338,79 @@
336 <div class="panel-head">338 <div class="panel-head">
337 <label for="cmd-q" class="sr">go to</label>339 <label for="cmd-q" class="sr">go to</label>
338 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">340 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
339-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">341+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
340 <kbd>esc</kbd>342 <kbd>esc</kbd>
341 </button>343 </button>
342 </div>344 </div>
343 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">345 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
344 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>346 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
345-<a href="/" tabindex="-1">347+<a role="presentation" data-href="/">
346 <span class="lbl">home</span>348 <span class="lbl">home</span>
347 <span class="k">go</span>349 <span class="k">go</span>
348 </a>350 </a>
349 </li>351 </li>
350 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">352 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
351-<a href="/files" tabindex="-1">353+<a role="presentation" data-href="/files">
352 <span class="lbl">summary</span>354 <span class="lbl">summary</span>
353 <span class="k">section</span>355 <span class="k">section</span>
354 </a>356 </a>
355 </li>357 </li>
356 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">358 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
357-<a href="/files/tree/main" tabindex="-1">359+<a role="presentation" data-href="/files/tree/main">
358 <span class="lbl">files</span>360 <span class="lbl">files</span>
359 <span class="k">section</span>361 <span class="k">section</span>
360 </a>362 </a>
361 </li>363 </li>
362 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">364 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
363-<a href="/files/commits/main" tabindex="-1">365+<a role="presentation" data-href="/files/commits/main">
364 <span class="lbl">commits</span>366 <span class="lbl">commits</span>
365 <span class="k">section</span>367 <span class="k">section</span>
366 </a>368 </a>
367 </li>369 </li>
368 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">370 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
369-<a href="/files/refs" tabindex="-1">371+<a role="presentation" data-href="/files/refs">
370 <span class="lbl">branches &amp; tags</span>372 <span class="lbl">branches &amp; tags</span>
371 <span class="k">section</span>373 <span class="k">section</span>
372 </a>374 </a>
373 </li>375 </li>
374 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">376 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
375-<a href="/files/merge-requests" tabindex="-1">377+<a role="presentation" data-href="/files/merge-requests">
376 <span class="lbl">merge requests</span>378 <span class="lbl">merge requests</span>
377 <span class="k">section</span>379 <span class="k">section</span>
378 </a>380 </a>
379 </li>381 </li>
380 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">382 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
381-<a href="/files/releases" tabindex="-1">383+<a role="presentation" data-href="/files/releases">
382 <span class="lbl">releases</span>384 <span class="lbl">releases</span>
383 <span class="k">section</span>385 <span class="k">section</span>
384 </a>386 </a>
385 </li>387 </li>
386 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">388 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
387-<a href="/files/actions" tabindex="-1">389+<a role="presentation" data-href="/files/actions">
388 <span class="lbl">actions</span>390 <span class="lbl">actions</span>
389 <span class="k">section</span>391 <span class="k">section</span>
390 </a>392 </a>
391 </li>393 </li>
392 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">394 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">
393-<a href="/files/tree/feature/x" tabindex="-1">395+<a role="presentation" data-href="/files/tree/feature/x">
394 <span class="lbl">feature/x</span>396 <span class="lbl">feature/x</span>
395 <span class="k">branch</span>397 <span class="k">branch</span>
396 </a>398 </a>
397 </li>399 </li>
398 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">400 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">
399-<a href="/files/tree/main" tabindex="-1">401+<a role="presentation" data-href="/files/tree/main">
400 <span class="lbl">main</span>402 <span class="lbl">main</span>
401 <span class="k">branch</span>403 <span class="k">branch</span>
402 </a>404 </a>
403 </li>405 </li>
404 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">406 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">
405-<a href="/files/tree/v1.0" tabindex="-1">407+<a role="presentation" data-href="/files/tree/v1.0">
406 <span class="lbl">v1.0</span>408 <span class="lbl">v1.0</span>
407 <span class="k">tag</span>409 <span class="k">tag</span>
408 </a>410 </a>
409 </li>411 </li>
410 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">412 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
411-<a href="#" data-fire="keys" tabindex="-1">413+<a role="presentation" data-fire="keys">
412 <span class="lbl">turn single-key shortcuts off</span>414 <span class="lbl">turn single-key shortcuts off</span>
413 <span class="k">action</span>415 <span class="k">action</span>
414 </a>416 </a>
M internal/app/testdata/web/tree-root.html +21 -19
@@ -111,9 +111,9 @@
111 <kbd>j</kbd>111 <kbd>j</kbd>
112 <kbd>k</kbd> move · <kbd>l</kbd> open · <kbd>h</kbd> back</p>112 <kbd>k</kbd> move · <kbd>l</kbd> open · <kbd>h</kbd> back</p>
113 </div>113 </div>
114-<div id="finder" class="tree-card" hidden>114+<p id="finder-status" class="dim" role="status">
115-<p id="finder-status" class="dim" aria-live="polite">
116 </p>115 </p>
116+<div id="finder" class="tree-card" hidden>
117 <ul id="finder-list" class="cmd-list" role="listbox" aria-label="Files">117 <ul id="finder-list" class="cmd-list" role="listbox" aria-label="Files">
118 </ul>118 </ul>
119 </div>119 </div>
@@ -377,18 +377,20 @@
377 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">377 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
378 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>378 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
379 </footer>379 </footer>
380-<div class="hint" role="status" aria-live="polite">380+<div class="hint">
381-<span class="hint-nav" :text="document.body.dataset.nav || ''">381+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
382 </span>382 </span>
383+<span role="status" aria-live="polite">
383 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">384 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
384 </span>385 </span>
385 <span class="hint-flash" :text="document.body.dataset.flash || ''">386 <span class="hint-flash" :text="document.body.dataset.flash || ''">
386 </span>387 </span>
388+</span>
387 </div>389 </div>
388 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">390 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
389 <div class="panel-head">391 <div class="panel-head">
390 <h2>Keyboard shortcuts</h2>392 <h2>Keyboard shortcuts</h2>
391-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">393+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
392 <kbd>esc</kbd> close</button>394 <kbd>esc</kbd> close</button>
393 </div>395 </div>
394 <div class="keys-pref">396 <div class="keys-pref">
@@ -522,7 +524,7 @@
522 <div class="panel-head">524 <div class="panel-head">
523 <label for="refs-filter" class="sr">filter branches and tags</label>525 <label for="refs-filter" class="sr">filter branches and tags</label>
524 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">526 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
525-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">527+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
526 <kbd>esc</kbd>528 <kbd>esc</kbd>
527 </button>529 </button>
528 </div>530 </div>
@@ -557,79 +559,79 @@
557 <div class="panel-head">559 <div class="panel-head">
558 <label for="cmd-q" class="sr">go to</label>560 <label for="cmd-q" class="sr">go to</label>
559 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">561 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
560-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">562+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
561 <kbd>esc</kbd>563 <kbd>esc</kbd>
562 </button>564 </button>
563 </div>565 </div>
564 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">566 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
565 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>567 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
566-<a href="/" tabindex="-1">568+<a role="presentation" data-href="/">
567 <span class="lbl">home</span>569 <span class="lbl">home</span>
568 <span class="k">go</span>570 <span class="k">go</span>
569 </a>571 </a>
570 </li>572 </li>
571 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">573 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
572-<a href="/files" tabindex="-1">574+<a role="presentation" data-href="/files">
573 <span class="lbl">summary</span>575 <span class="lbl">summary</span>
574 <span class="k">section</span>576 <span class="k">section</span>
575 </a>577 </a>
576 </li>578 </li>
577 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">579 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
578-<a href="/files/tree/main" tabindex="-1">580+<a role="presentation" data-href="/files/tree/main">
579 <span class="lbl">files</span>581 <span class="lbl">files</span>
580 <span class="k">section</span>582 <span class="k">section</span>
581 </a>583 </a>
582 </li>584 </li>
583 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">585 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
584-<a href="/files/commits/main" tabindex="-1">586+<a role="presentation" data-href="/files/commits/main">
585 <span class="lbl">commits</span>587 <span class="lbl">commits</span>
586 <span class="k">section</span>588 <span class="k">section</span>
587 </a>589 </a>
588 </li>590 </li>
589 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">591 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
590-<a href="/files/refs" tabindex="-1">592+<a role="presentation" data-href="/files/refs">
591 <span class="lbl">branches &amp; tags</span>593 <span class="lbl">branches &amp; tags</span>
592 <span class="k">section</span>594 <span class="k">section</span>
593 </a>595 </a>
594 </li>596 </li>
595 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">597 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
596-<a href="/files/merge-requests" tabindex="-1">598+<a role="presentation" data-href="/files/merge-requests">
597 <span class="lbl">merge requests</span>599 <span class="lbl">merge requests</span>
598 <span class="k">section</span>600 <span class="k">section</span>
599 </a>601 </a>
600 </li>602 </li>
601 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">603 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
602-<a href="/files/releases" tabindex="-1">604+<a role="presentation" data-href="/files/releases">
603 <span class="lbl">releases</span>605 <span class="lbl">releases</span>
604 <span class="k">section</span>606 <span class="k">section</span>
605 </a>607 </a>
606 </li>608 </li>
607 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">609 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
608-<a href="/files/actions" tabindex="-1">610+<a role="presentation" data-href="/files/actions">
609 <span class="lbl">actions</span>611 <span class="lbl">actions</span>
610 <span class="k">section</span>612 <span class="k">section</span>
611 </a>613 </a>
612 </li>614 </li>
613 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">615 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">
614-<a href="/files/tree/feature/x" tabindex="-1">616+<a role="presentation" data-href="/files/tree/feature/x">
615 <span class="lbl">feature/x</span>617 <span class="lbl">feature/x</span>
616 <span class="k">branch</span>618 <span class="k">branch</span>
617 </a>619 </a>
618 </li>620 </li>
619 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">621 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">
620-<a href="/files/tree/main" tabindex="-1">622+<a role="presentation" data-href="/files/tree/main">
621 <span class="lbl">main</span>623 <span class="lbl">main</span>
622 <span class="k">branch</span>624 <span class="k">branch</span>
623 </a>625 </a>
624 </li>626 </li>
625 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">627 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">
626-<a href="/files/tree/v1.0" tabindex="-1">628+<a role="presentation" data-href="/files/tree/v1.0">
627 <span class="lbl">v1.0</span>629 <span class="lbl">v1.0</span>
628 <span class="k">tag</span>630 <span class="k">tag</span>
629 </a>631 </a>
630 </li>632 </li>
631 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">633 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
632-<a href="#" data-fire="keys" tabindex="-1">634+<a role="presentation" data-fire="keys">
633 <span class="lbl">turn single-key shortcuts off</span>635 <span class="lbl">turn single-key shortcuts off</span>
634 <span class="k">action</span>636 <span class="k">action</span>
635 </a>637 </a>
M internal/app/testdata/web/tree-slash-ref.html +21 -19
@@ -114,9 +114,9 @@
114 <kbd>j</kbd>114 <kbd>j</kbd>
115 <kbd>k</kbd> move · <kbd>l</kbd> open · <kbd>h</kbd> back</p>115 <kbd>k</kbd> move · <kbd>l</kbd> open · <kbd>h</kbd> back</p>
116 </div>116 </div>
117-<div id="finder" class="tree-card" hidden>117+<p id="finder-status" class="dim" role="status">
118-<p id="finder-status" class="dim" aria-live="polite">
119 </p>118 </p>
119+<div id="finder" class="tree-card" hidden>
120 <ul id="finder-list" class="cmd-list" role="listbox" aria-label="Files">120 <ul id="finder-list" class="cmd-list" role="listbox" aria-label="Files">
121 </ul>121 </ul>
122 </div>122 </div>
@@ -153,18 +153,20 @@
153 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">153 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
154 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>154 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
155 </footer>155 </footer>
156-<div class="hint" role="status" aria-live="polite">156+<div class="hint">
157-<span class="hint-nav" :text="document.body.dataset.nav || ''">157+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
158 </span>158 </span>
159+<span role="status" aria-live="polite">
159 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">160 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
160 </span>161 </span>
161 <span class="hint-flash" :text="document.body.dataset.flash || ''">162 <span class="hint-flash" :text="document.body.dataset.flash || ''">
162 </span>163 </span>
164+</span>
163 </div>165 </div>
164 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">166 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
165 <div class="panel-head">167 <div class="panel-head">
166 <h2>Keyboard shortcuts</h2>168 <h2>Keyboard shortcuts</h2>
167-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">169+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
168 <kbd>esc</kbd> close</button>170 <kbd>esc</kbd> close</button>
169 </div>171 </div>
170 <div class="keys-pref">172 <div class="keys-pref">
@@ -298,7 +300,7 @@
298 <div class="panel-head">300 <div class="panel-head">
299 <label for="refs-filter" class="sr">filter branches and tags</label>301 <label for="refs-filter" class="sr">filter branches and tags</label>
300 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">302 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
301-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">303+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
302 <kbd>esc</kbd>304 <kbd>esc</kbd>
303 </button>305 </button>
304 </div>306 </div>
@@ -333,79 +335,79 @@
333 <div class="panel-head">335 <div class="panel-head">
334 <label for="cmd-q" class="sr">go to</label>336 <label for="cmd-q" class="sr">go to</label>
335 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">337 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
336-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">338+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
337 <kbd>esc</kbd>339 <kbd>esc</kbd>
338 </button>340 </button>
339 </div>341 </div>
340 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">342 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
341 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>343 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
342-<a href="/" tabindex="-1">344+<a role="presentation" data-href="/">
343 <span class="lbl">home</span>345 <span class="lbl">home</span>
344 <span class="k">go</span>346 <span class="k">go</span>
345 </a>347 </a>
346 </li>348 </li>
347 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">349 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
348-<a href="/files" tabindex="-1">350+<a role="presentation" data-href="/files">
349 <span class="lbl">summary</span>351 <span class="lbl">summary</span>
350 <span class="k">section</span>352 <span class="k">section</span>
351 </a>353 </a>
352 </li>354 </li>
353 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">355 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
354-<a href="/files/tree/main" tabindex="-1">356+<a role="presentation" data-href="/files/tree/main">
355 <span class="lbl">files</span>357 <span class="lbl">files</span>
356 <span class="k">section</span>358 <span class="k">section</span>
357 </a>359 </a>
358 </li>360 </li>
359 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">361 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
360-<a href="/files/commits/main" tabindex="-1">362+<a role="presentation" data-href="/files/commits/main">
361 <span class="lbl">commits</span>363 <span class="lbl">commits</span>
362 <span class="k">section</span>364 <span class="k">section</span>
363 </a>365 </a>
364 </li>366 </li>
365 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">367 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
366-<a href="/files/refs" tabindex="-1">368+<a role="presentation" data-href="/files/refs">
367 <span class="lbl">branches &amp; tags</span>369 <span class="lbl">branches &amp; tags</span>
368 <span class="k">section</span>370 <span class="k">section</span>
369 </a>371 </a>
370 </li>372 </li>
371 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">373 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
372-<a href="/files/merge-requests" tabindex="-1">374+<a role="presentation" data-href="/files/merge-requests">
373 <span class="lbl">merge requests</span>375 <span class="lbl">merge requests</span>
374 <span class="k">section</span>376 <span class="k">section</span>
375 </a>377 </a>
376 </li>378 </li>
377 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">379 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
378-<a href="/files/releases" tabindex="-1">380+<a role="presentation" data-href="/files/releases">
379 <span class="lbl">releases</span>381 <span class="lbl">releases</span>
380 <span class="k">section</span>382 <span class="k">section</span>
381 </a>383 </a>
382 </li>384 </li>
383 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">385 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
384-<a href="/files/actions" tabindex="-1">386+<a role="presentation" data-href="/files/actions">
385 <span class="lbl">actions</span>387 <span class="lbl">actions</span>
386 <span class="k">section</span>388 <span class="k">section</span>
387 </a>389 </a>
388 </li>390 </li>
389 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">391 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">
390-<a href="/files/tree/feature/x" tabindex="-1">392+<a role="presentation" data-href="/files/tree/feature/x">
391 <span class="lbl">feature/x</span>393 <span class="lbl">feature/x</span>
392 <span class="k">branch</span>394 <span class="k">branch</span>
393 </a>395 </a>
394 </li>396 </li>
395 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">397 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">
396-<a href="/files/tree/main" tabindex="-1">398+<a role="presentation" data-href="/files/tree/main">
397 <span class="lbl">main</span>399 <span class="lbl">main</span>
398 <span class="k">branch</span>400 <span class="k">branch</span>
399 </a>401 </a>
400 </li>402 </li>
401 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">403 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">
402-<a href="/files/tree/v1.0" tabindex="-1">404+<a role="presentation" data-href="/files/tree/v1.0">
403 <span class="lbl">v1.0</span>405 <span class="lbl">v1.0</span>
404 <span class="k">tag</span>406 <span class="k">tag</span>
405 </a>407 </a>
406 </li>408 </li>
407 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">409 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
408-<a href="#" data-fire="keys" tabindex="-1">410+<a role="presentation" data-fire="keys">
409 <span class="lbl">turn single-key shortcuts off</span>411 <span class="lbl">turn single-key shortcuts off</span>
410 <span class="k">action</span>412 <span class="k">action</span>
411 </a>413 </a>
M internal/app/testdata/web/tree-subdir.html +21 -19
@@ -114,9 +114,9 @@
114 <kbd>j</kbd>114 <kbd>j</kbd>
115 <kbd>k</kbd> move · <kbd>l</kbd> open · <kbd>h</kbd> back</p>115 <kbd>k</kbd> move · <kbd>l</kbd> open · <kbd>h</kbd> back</p>
116 </div>116 </div>
117-<div id="finder" class="tree-card" hidden>117+<p id="finder-status" class="dim" role="status">
118-<p id="finder-status" class="dim" aria-live="polite">
119 </p>118 </p>
119+<div id="finder" class="tree-card" hidden>
120 <ul id="finder-list" class="cmd-list" role="listbox" aria-label="Files">120 <ul id="finder-list" class="cmd-list" role="listbox" aria-label="Files">
121 </ul>121 </ul>
122 </div>122 </div>
@@ -169,18 +169,20 @@
169 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">169 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
170 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>170 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
171 </footer>171 </footer>
172-<div class="hint" role="status" aria-live="polite">172+<div class="hint">
173-<span class="hint-nav" :text="document.body.dataset.nav || ''">173+<span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
174 </span>174 </span>
175+<span role="status" aria-live="polite">
175 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">176 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''">
176 </span>177 </span>
177 <span class="hint-flash" :text="document.body.dataset.flash || ''">178 <span class="hint-flash" :text="document.body.dataset.flash || ''">
178 </span>179 </span>
180+</span>
179 </div>181 </div>
180 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">182 <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()">
181 <div class="panel-head">183 <div class="panel-head">
182 <h2>Keyboard shortcuts</h2>184 <h2>Keyboard shortcuts</h2>
183-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">185+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
184 <kbd>esc</kbd> close</button>186 <kbd>esc</kbd> close</button>
185 </div>187 </div>
186 <div class="keys-pref">188 <div class="keys-pref">
@@ -314,7 +316,7 @@
314 <div class="panel-head">316 <div class="panel-head">
315 <label for="refs-filter" class="sr">filter branches and tags</label>317 <label for="refs-filter" class="sr">filter branches and tags</label>
316 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">318 <input id="refs-filter" type="search" placeholder="Switch branch or tag…" autocomplete="off" data-pick="#panel-refs li" hx-on:input="ermite.filter(this)">
317-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">319+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
318 <kbd>esc</kbd>320 <kbd>esc</kbd>
319 </button>321 </button>
320 </div>322 </div>
@@ -349,79 +351,79 @@
349 <div class="panel-head">351 <div class="panel-head">
350 <label for="cmd-q" class="sr">go to</label>352 <label for="cmd-q" class="sr">go to</label>
351 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">353 <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)">
352-<button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()">354+<button type="button" class="panel-x" aria-label="Close (esc)" hx-on:click="closest('dialog').close()">
353 <kbd>esc</kbd>355 <kbd>esc</kbd>
354 </button>356 </button>
355 </div>357 </div>
356 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">358 <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets">
357 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>359 <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor>
358-<a href="/" tabindex="-1">360+<a role="presentation" data-href="/">
359 <span class="lbl">home</span>361 <span class="lbl">home</span>
360 <span class="k">go</span>362 <span class="k">go</span>
361 </a>363 </a>
362 </li>364 </li>
363 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">365 <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary">
364-<a href="/files" tabindex="-1">366+<a role="presentation" data-href="/files">
365 <span class="lbl">summary</span>367 <span class="lbl">summary</span>
366 <span class="k">section</span>368 <span class="k">section</span>
367 </a>369 </a>
368 </li>370 </li>
369 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">371 <li id="cmd-o2" role="option" aria-selected="false" data-k="section files">
370-<a href="/files/tree/main" tabindex="-1">372+<a role="presentation" data-href="/files/tree/main">
371 <span class="lbl">files</span>373 <span class="lbl">files</span>
372 <span class="k">section</span>374 <span class="k">section</span>
373 </a>375 </a>
374 </li>376 </li>
375 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">377 <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits">
376-<a href="/files/commits/main" tabindex="-1">378+<a role="presentation" data-href="/files/commits/main">
377 <span class="lbl">commits</span>379 <span class="lbl">commits</span>
378 <span class="k">section</span>380 <span class="k">section</span>
379 </a>381 </a>
380 </li>382 </li>
381 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">383 <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches &amp; tags">
382-<a href="/files/refs" tabindex="-1">384+<a role="presentation" data-href="/files/refs">
383 <span class="lbl">branches &amp; tags</span>385 <span class="lbl">branches &amp; tags</span>
384 <span class="k">section</span>386 <span class="k">section</span>
385 </a>387 </a>
386 </li>388 </li>
387 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">389 <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests">
388-<a href="/files/merge-requests" tabindex="-1">390+<a role="presentation" data-href="/files/merge-requests">
389 <span class="lbl">merge requests</span>391 <span class="lbl">merge requests</span>
390 <span class="k">section</span>392 <span class="k">section</span>
391 </a>393 </a>
392 </li>394 </li>
393 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">395 <li id="cmd-o6" role="option" aria-selected="false" data-k="section releases">
394-<a href="/files/releases" tabindex="-1">396+<a role="presentation" data-href="/files/releases">
395 <span class="lbl">releases</span>397 <span class="lbl">releases</span>
396 <span class="k">section</span>398 <span class="k">section</span>
397 </a>399 </a>
398 </li>400 </li>
399 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">401 <li id="cmd-o7" role="option" aria-selected="false" data-k="section actions">
400-<a href="/files/actions" tabindex="-1">402+<a role="presentation" data-href="/files/actions">
401 <span class="lbl">actions</span>403 <span class="lbl">actions</span>
402 <span class="k">section</span>404 <span class="k">section</span>
403 </a>405 </a>
404 </li>406 </li>
405 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">407 <li id="cmd-o8" role="option" aria-selected="false" data-k="branch feature/x">
406-<a href="/files/tree/feature/x" tabindex="-1">408+<a role="presentation" data-href="/files/tree/feature/x">
407 <span class="lbl">feature/x</span>409 <span class="lbl">feature/x</span>
408 <span class="k">branch</span>410 <span class="k">branch</span>
409 </a>411 </a>
410 </li>412 </li>
411 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">413 <li id="cmd-o9" role="option" aria-selected="false" data-k="branch main">
412-<a href="/files/tree/main" tabindex="-1">414+<a role="presentation" data-href="/files/tree/main">
413 <span class="lbl">main</span>415 <span class="lbl">main</span>
414 <span class="k">branch</span>416 <span class="k">branch</span>
415 </a>417 </a>
416 </li>418 </li>
417 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">419 <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v1.0">
418-<a href="/files/tree/v1.0" tabindex="-1">420+<a role="presentation" data-href="/files/tree/v1.0">
419 <span class="lbl">v1.0</span>421 <span class="lbl">v1.0</span>
420 <span class="k">tag</span>422 <span class="k">tag</span>
421 </a>423 </a>
422 </li>424 </li>
423 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">425 <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard">
424-<a href="#" data-fire="keys" tabindex="-1">426+<a role="presentation" data-fire="keys">
425 <span class="lbl">turn single-key shortcuts off</span>427 <span class="lbl">turn single-key shortcuts off</span>
426 <span class="k">action</span>428 <span class="k">action</span>
427 </a>429 </a>

Keyboard shortcuts

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

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

Branches 2

Tags 41

↑↓ moveenter openesc close