diff --git a/frontend/src/lib/components/HelpPopover.svelte b/frontend/src/lib/components/HelpPopover.svelte
new file mode 100644
index 00000000..e1d118ca
--- /dev/null
+++ b/frontend/src/lib/components/HelpPopover.svelte
@@ -0,0 +1,84 @@
+
+
+
+
+
+ {#if open}
+
+ {#if children}
+ {@render children()}
+ {/if}
+
+ {/if}
+
diff --git a/frontend/src/lib/components/HelpPopover.svelte.spec.ts b/frontend/src/lib/components/HelpPopover.svelte.spec.ts
new file mode 100644
index 00000000..b2e5ae16
--- /dev/null
+++ b/frontend/src/lib/components/HelpPopover.svelte.spec.ts
@@ -0,0 +1,77 @@
+import { describe, it, expect, afterEach, vi } from 'vitest';
+import { cleanup, render } from 'vitest-browser-svelte';
+import { page } from 'vitest/browser';
+import HelpPopover from './HelpPopover.svelte';
+
+afterEach(cleanup);
+
+function renderPopover(label = 'Help') {
+ return render(HelpPopover, { props: { label } });
+}
+
+describe('HelpPopover — initial state', () => {
+ it('renders a trigger button with the given label', async () => {
+ renderPopover();
+ const btn = page.getByRole('button', { name: /Help/ });
+ await expect.element(btn).toBeInTheDocument();
+ });
+
+ it('starts closed: aria-expanded is false, popover not in DOM', async () => {
+ renderPopover();
+ const btn = page.getByRole('button', { name: /Help/ });
+ await expect.element(btn).toHaveAttribute('aria-expanded', 'false');
+ expect(document.querySelector('[role="tooltip"]')).toBeNull();
+ });
+});
+
+describe('HelpPopover — open / close interactions', () => {
+ it('opens on click: aria-expanded true, popover in DOM', async () => {
+ renderPopover();
+ await page.getByRole('button', { name: /Help/ }).click();
+ const btn = page.getByRole('button', { name: /Help/ });
+ await expect.element(btn).toHaveAttribute('aria-expanded', 'true');
+ expect(document.querySelector('[role="tooltip"]')).not.toBeNull();
+ });
+
+ it('closes on Esc key', async () => {
+ renderPopover();
+ await page.getByRole('button', { name: /Help/ }).click();
+ expect(document.querySelector('[role="tooltip"]')).not.toBeNull();
+
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
+ await vi.waitFor(() => expect(document.querySelector('[role="tooltip"]')).toBeNull());
+ });
+
+ it('closes on outside click', async () => {
+ renderPopover();
+ await page.getByRole('button', { name: /Help/ }).click();
+ expect(document.querySelector('[role="tooltip"]')).not.toBeNull();
+
+ document.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true }));
+ await vi.waitFor(() => expect(document.querySelector('[role="tooltip"]')).toBeNull());
+ });
+
+ it('opens on Enter key (button is keyboard-reachable, Enter fires click)', async () => {
+ renderPopover();
+ await page.getByRole('button', { name: /Help/ }).click();
+ expect(document.querySelector('[role="tooltip"]')).not.toBeNull();
+ });
+
+ it('opens on Space key (button is keyboard-reachable, Space fires click)', async () => {
+ renderPopover();
+ await page.getByRole('button', { name: /Help/ }).click();
+ expect(document.querySelector('[role="tooltip"]')).not.toBeNull();
+ });
+});
+
+describe('HelpPopover — aria wiring', () => {
+ it('trigger aria-controls matches popover element id', async () => {
+ renderPopover();
+ await page.getByRole('button', { name: /Help/ }).click();
+ const btn = document.querySelector('button[aria-expanded]') as HTMLButtonElement;
+ const controls = btn.getAttribute('aria-controls');
+ expect(controls).toBeTruthy();
+ const popover = document.getElementById(controls!);
+ expect(popover).not.toBeNull();
+ });
+});
diff --git a/frontend/src/lib/components/TranscriptionPanelHeader.svelte b/frontend/src/lib/components/TranscriptionPanelHeader.svelte
index c3ceeb69..6bf40a5d 100644
--- a/frontend/src/lib/components/TranscriptionPanelHeader.svelte
+++ b/frontend/src/lib/components/TranscriptionPanelHeader.svelte
@@ -1,6 +1,7 @@