diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index aaad5838a..505859c45 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -482,6 +482,7 @@ jobs: BASE_URL=http://localhost:13581 node test-issue-1279-legend-p2-e2e.js 2>&1 | tee -a e2e-output.txt BASE_URL=http://localhost:13581 node test-issue-1799-label-vocab-e2e.js 2>&1 | tee -a e2e-output.txt CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-home-coverage-e2e.js 2>&1 | tee -a e2e-output.txt + CHROMIUM_REQUIRE=1 node test-issue-2027-my-mesh-node-page-e2e.js 2>&1 | tee -a e2e-output.txt CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-path-inspector-coverage-e2e.js 2>&1 | tee -a e2e-output.txt CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-issue-1206-resize-observer-leak-e2e.js 2>&1 | tee -a e2e-output.txt CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-nav-drawer-1064-e2e.js 2>&1 | tee -a e2e-output.txt diff --git a/public/home.css b/public/home.css index c7a1db9ca..8b01c2c31 100644 --- a/public/home.css +++ b/public/home.css @@ -455,6 +455,13 @@ opacity: 0; } .my-node-card:hover .mnc-remove { opacity: 1; } +/* #2027 follow-up: the card's keydown handler no longer swallows Enter/Space + on nested buttons, so Remove is now genuinely keyboard-activatable. It is + hover-revealed (opacity: 0), which would otherwise hide both the control + and its focus ring from a keyboard user operating a destructive action + (WCAG 2.4.7). Reveal it whenever it, or its card, holds focus. */ +.my-node-card:focus-within .mnc-remove, +.mnc-remove:focus-visible { opacity: 1; } .mnc-remove:hover { background: var(--status-red); color: #fff; } .mnc-status-text { font-size: .8rem; color: var(--text-muted); margin-bottom: 12px; padding-left: 28px; } @@ -471,7 +478,7 @@ .spark-bars { display: flex; align-items: flex-end; gap: 2px; height: 28px; } .spark-bar { flex: 1; background: var(--accent); border-radius: 1px; min-width: 0; } -.mnc-actions { display: flex; gap: 8px; } +.mnc-actions { display: flex; flex-wrap: wrap; gap: 8px; } .mnc-btn { padding: 5px 12px; border-radius: 6px; border: 1px solid var(--border); background: var(--surface-2); color: var(--text); font: 500 .75rem var(--font); diff --git a/public/home.js b/public/home.js index c1d9223b9..7c456d937 100644 --- a/public/home.js +++ b/public/home.js @@ -339,6 +339,7 @@ ${obs.length ? `
Heard by: ${obs.map(o => escapeHtml(o.observer_name || o.observer_id)).join(', ')}
` : ''} ${sparkHtml ? `
${sparkHtml}
` : ''}
+
@@ -356,6 +357,9 @@
${statusMsg}
+
+ +
`; } })); @@ -378,6 +382,7 @@ grid.querySelectorAll('.mnc-btn').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); + if (btn.dataset.action === 'node') window.location.hash = '#/nodes/' + encodeURIComponent(btn.dataset.key); if (btn.dataset.action === 'health') loadHealth(btn.dataset.key); if (btn.dataset.action === 'packets') window.location.hash = '#/packets/' + btn.dataset.key; }); @@ -391,6 +396,12 @@ }; card.addEventListener('click', handler); card.addEventListener('keydown', (e) => { + // #2027 follow-up: a nested action/remove button's own Enter/Space + // activation must not be swallowed by the card's preventDefault() + // below. Bail out before it runs, the same target check `handler` + // already uses for the click case, so the button still fires its + // own click via the browser's native keyboard activation. + if (e.target.closest('.mnc-remove') || e.target.closest('.mnc-btn')) return; if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handler(e); } }); }); diff --git a/test-issue-2027-my-mesh-node-page-e2e.js b/test-issue-2027-my-mesh-node-page-e2e.js new file mode 100644 index 000000000..9211b0d60 --- /dev/null +++ b/test-issue-2027-my-mesh-node-page-e2e.js @@ -0,0 +1,529 @@ +#!/usr/bin/env node +/* Port of upstream Kpa-clawbot/CoreScope#2027 (head e5536d20, "feat(home): + * link My Mesh node cards to the node page") plus a keyboard-activation fix + * found while browser-reproducing the reported finding (not in the upstream + * commit): the card's own keydown handler called preventDefault() + * unconditionally on Enter/Space before checking whether the event came + * from a nested `.mnc-btn`/`.mnc-remove`. A `