From 0ad4c6b09cd700f0e38825792d7fb0c4efb2dad0 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Wed, 26 Aug 2026 09:10:46 +0200 Subject: [PATCH] Say what the browser gives a popover trigger, and pin the part all engines can prove --- AGENTS.md | 7 +++++-- e2e-tests/visual.spec.ts | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d87c6b8..0b170e2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -254,8 +254,11 @@ Revisit only if the stated reason stops holding. open-state style and the old-browser fallback both read; the partial **requires a caller-named `id`** and fails loud without one, since that is the `popovertarget` idref (never generate one — nondeterministic HTML forecloses the caching decision); and **neither `aria-expanded` nor - `aria-haspopup` is written**, because a zero-JS invoker cannot keep the first truthful and the - second would promise `role="menu"` semantics these panels don't implement. `
` stays where + `aria-haspopup` is written**: every engine maintains the first itself on a declarative + `popovertarget` invoker, so a hand-written one replaces a live state with a static lie, and the + second would promise `role="menu"` semantics these panels don't implement. **That guarantee is the + declarative attribute's alone** — open a panel from script and no engine applies it, so + "enhancing" one of these triggers is what would cost it its accessibility. `
` stays where it means disclosure rather than popup: the nav tree. `shell.ejs` hand-rolls the same block for the profile menu (its trigger composes escaped user values and its one item is a CSRF POST form) — keep the two in step. diff --git a/e2e-tests/visual.spec.ts b/e2e-tests/visual.spec.ts index bed2c0d..02dd07e 100644 --- a/e2e-tests/visual.spec.ts +++ b/e2e-tests/visual.spec.ts @@ -94,6 +94,7 @@ test("a popover menu sits on its trigger and closes on an outside click or Esc await expect(panel).toBeVisible(); await page.keyboard.press("Escape"); await expect(panel).toBeHidden(); + await expect(trigger).toBeFocused(); // the browser returns focus, so no trigger needs a tabindex }); test("mobile layout hides the sidebar off-canvas behind the hamburger", async ({ page }) => {