From 3b2d1009cd43a4afb980f2f576d302b5384fc375 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 6 May 2026 21:19:53 -0400 Subject: [PATCH] feat(web/m7-scan-progress): StageBadge + 4-card layout updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each of the 4 stage cards in the admin Library Scan section gains a StageBadge in its header. The badge derives state via stageState() from the existing ScanStatus payload — no new query, no schema change. Active stage shows spinner + "Processing" in moss green; done shows check + "Done" in moss green; pending and skipped show muted text. Card body now shows either the (possibly partial) numbers OR a "Waiting for previous stage…" placeholder when the badge is pending. The previous bottom-of-card "Pending…" / "Skipped." fallback text disappears (the badge replaces it). StageBadge.test.ts covers the four state renderings. admin.test.ts adds one new case asserting the Processing badge shows when scan.library is populated and in_flight: true. Existing admin.test.ts assertions still pass (the default mock data has no stage tallies + in_flight: false → all stages skipped, no Loader/ Check icons rendered). --- web/src/lib/components/StageBadge.svelte | 22 +++++++++++++ web/src/lib/components/StageBadge.test.ts | 31 ++++++++++++++++++ web/src/routes/admin/+page.svelte | 38 ++++++++++++++++------- web/src/routes/admin/admin.test.ts | 27 ++++++++++++++-- 4 files changed, 104 insertions(+), 14 deletions(-) create mode 100644 web/src/lib/components/StageBadge.svelte create mode 100644 web/src/lib/components/StageBadge.test.ts diff --git a/web/src/lib/components/StageBadge.svelte b/web/src/lib/components/StageBadge.svelte new file mode 100644 index 00000000..1da8fd8d --- /dev/null +++ b/web/src/lib/components/StageBadge.svelte @@ -0,0 +1,22 @@ + + +{#if state === 'in_progress'} + + + Processing + +{:else if state === 'done'} + + + Done + +{:else if state === 'pending'} + Pending +{:else} + Skipped +{/if} diff --git a/web/src/lib/components/StageBadge.test.ts b/web/src/lib/components/StageBadge.test.ts new file mode 100644 index 00000000..c03536a3 --- /dev/null +++ b/web/src/lib/components/StageBadge.test.ts @@ -0,0 +1,31 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/svelte'; +import StageBadge from './StageBadge.svelte'; + +describe('StageBadge', () => { + it('renders Processing + spinner when state is in_progress', () => { + render(StageBadge, { props: { state: 'in_progress' } }); + expect(screen.getByText('Processing')).toBeInTheDocument(); + // The spinner is a lucide icon — just confirm a child SVG exists. + expect(document.querySelector('svg')).toBeTruthy(); + }); + + it('renders Done + check when state is done', () => { + render(StageBadge, { props: { state: 'done' } }); + expect(screen.getByText('Done')).toBeInTheDocument(); + expect(document.querySelector('svg')).toBeTruthy(); + }); + + it('renders Pending muted when state is pending', () => { + render(StageBadge, { props: { state: 'pending' } }); + expect(screen.getByText('Pending')).toBeInTheDocument(); + // No icon for the muted states. + expect(document.querySelector('svg')).toBeFalsy(); + }); + + it('renders Skipped muted when state is skipped', () => { + render(StageBadge, { props: { state: 'skipped' } }); + expect(screen.getByText('Skipped')).toBeInTheDocument(); + expect(document.querySelector('svg')).toBeFalsy(); + }); +}); diff --git a/web/src/routes/admin/+page.svelte b/web/src/routes/admin/+page.svelte index 6bb1165b..56f84b66 100644 --- a/web/src/routes/admin/+page.svelte +++ b/web/src/routes/admin/+page.svelte @@ -1,6 +1,8 @@