diff --git a/frontend/src/lib/components/document/FileSwitcherStrip.svelte b/frontend/src/lib/components/document/FileSwitcherStrip.svelte
new file mode 100644
index 00000000..e7badb4d
--- /dev/null
+++ b/frontend/src/lib/components/document/FileSwitcherStrip.svelte
@@ -0,0 +1,91 @@
+
+
+
+ {#each files as entry (entry.id)}
+ -
+
+
+
+ {/each}
+
diff --git a/frontend/src/lib/components/document/FileSwitcherStrip.svelte.spec.ts b/frontend/src/lib/components/document/FileSwitcherStrip.svelte.spec.ts
new file mode 100644
index 00000000..86968e24
--- /dev/null
+++ b/frontend/src/lib/components/document/FileSwitcherStrip.svelte.spec.ts
@@ -0,0 +1,97 @@
+import { describe, it, expect, vi, afterEach } from 'vitest';
+import { cleanup, render } from 'vitest-browser-svelte';
+import { page, userEvent } from 'vitest/browser';
+import FileSwitcherStrip from './FileSwitcherStrip.svelte';
+
+afterEach(cleanup);
+
+export interface FileEntry {
+ id: string;
+ file: File;
+ title: string;
+ status: 'idle' | 'error';
+}
+
+function makeFiles(n: number): FileEntry[] {
+ return Array.from({ length: n }, (_, i) => ({
+ id: `id-${i}`,
+ file: new File([''], `file${i}.pdf`),
+ title: `File ${i}`,
+ status: 'idle' as const
+ }));
+}
+
+describe('FileSwitcherStrip', () => {
+ it('renders N chips for N files', async () => {
+ const files = makeFiles(4);
+ render(FileSwitcherStrip, {
+ files,
+ activeId: files[0].id,
+ onSelect: vi.fn(),
+ onRemove: vi.fn()
+ });
+ const chips = page.getByRole('listitem');
+ await expect.element(chips.nth(0)).toBeInTheDocument();
+ await expect.element(chips.nth(3)).toBeInTheDocument();
+ });
+
+ it('active chip has aria-current="true"', async () => {
+ const files = makeFiles(3);
+ const { container } = render(FileSwitcherStrip, {
+ files,
+ activeId: files[1].id,
+ onSelect: vi.fn(),
+ onRemove: vi.fn()
+ });
+ const activeBtn = container.querySelector('[aria-current="true"]');
+ expect(activeBtn).not.toBeNull();
+ expect(activeBtn?.textContent).toContain('File 1');
+ });
+
+ it('clicking a chip fires onSelect with its id', async () => {
+ const files = makeFiles(3);
+ const onSelect = vi.fn();
+ const { container } = render(FileSwitcherStrip, {
+ files,
+ activeId: files[0].id,
+ onSelect,
+ onRemove: vi.fn()
+ });
+ const chip = container.querySelector('[data-chip-id="id-2"]') as HTMLElement;
+ expect(chip).not.toBeNull();
+ chip.click();
+ expect(onSelect).toHaveBeenCalledWith('id-2');
+ });
+
+ it('error chip has aria-label containing warning indicator', async () => {
+ const files: FileEntry[] = [
+ { id: 'e1', file: new File([''], 'bad.pdf'), title: 'Bad file', status: 'error' }
+ ];
+ const { container } = render(FileSwitcherStrip, {
+ files,
+ activeId: 'e1',
+ onSelect: vi.fn(),
+ onRemove: vi.fn()
+ });
+ const errBtn = container.querySelector('[data-status="error"]');
+ expect(errBtn).not.toBeNull();
+ });
+
+ it('ArrowRight moves focus to next chip without leaving strip', async () => {
+ const files = makeFiles(3);
+ const { container } = render(FileSwitcherStrip, {
+ files,
+ activeId: files[0].id,
+ onSelect: vi.fn(),
+ onRemove: vi.fn()
+ });
+ const firstBtn = container.querySelectorAll('[role="button"]')[0] as HTMLElement;
+ firstBtn.focus();
+ await userEvent.keyboard('{ArrowRight}');
+ const focused = document.activeElement;
+ expect(focused).not.toBe(firstBtn);
+ // The new focused element should still be inside the strip
+ const strip = container.querySelector('[data-testid="file-switcher-strip"]');
+ expect(strip?.contains(focused)).toBe(true);
+ });
+});