From 14c1895bebc2fa5bf7b43ae1157a31902edcb91a Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Fri, 1 May 2026 22:14:39 -0400 Subject: [PATCH] =?UTF-8?q?feat(web):=20flatten=20admin=20shell=20?= =?UTF-8?q?=E2=80=94=20horizontal=20tabs,=20drop=20redundant=20banner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The /admin layout had three nested concentric shells: the global Shell header + main nav, then a per-admin header banner with a Sword icon, then a left-aligned admin sub-nav (AdminSidebar) — two competing left-edge navigation columns plus a redundant 'Admin' label that the URL and active nav already conveyed. Replaces the side sub-nav with a horizontal tab strip across the top of the admin content (matches the discover-page tab pattern), drops the duplicate header banner, and removes the disabled Users/Library placeholder items so the tab strip only shows surfaces that actually ship today. The component is renamed AdminSidebar -> AdminTabs to match its new shape. Co-Authored-By: Claude Opus 4.7 (1M context) --- web/src/lib/components/AdminSidebar.svelte | 59 ------------------ web/src/lib/components/AdminSidebar.test.ts | 66 --------------------- web/src/lib/components/AdminTabs.svelte | 38 ++++++++++++ web/src/lib/components/AdminTabs.test.ts | 58 ++++++++++++++++++ web/src/routes/admin/+layout.svelte | 17 ++---- 5 files changed, 100 insertions(+), 138 deletions(-) delete mode 100644 web/src/lib/components/AdminSidebar.svelte delete mode 100644 web/src/lib/components/AdminSidebar.test.ts create mode 100644 web/src/lib/components/AdminTabs.svelte create mode 100644 web/src/lib/components/AdminTabs.test.ts diff --git a/web/src/lib/components/AdminSidebar.svelte b/web/src/lib/components/AdminSidebar.svelte deleted file mode 100644 index 99114878..00000000 --- a/web/src/lib/components/AdminSidebar.svelte +++ /dev/null @@ -1,59 +0,0 @@ - - - diff --git a/web/src/lib/components/AdminSidebar.test.ts b/web/src/lib/components/AdminSidebar.test.ts deleted file mode 100644 index 8e73d0ab..00000000 --- a/web/src/lib/components/AdminSidebar.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { describe, expect, test, vi } from 'vitest'; -import { render, screen } from '@testing-library/svelte'; - -const state = vi.hoisted(() => ({ - pageUrl: new URL('http://localhost/admin') -})); - -vi.mock('$app/state', () => ({ - page: { get url() { return state.pageUrl; } } -})); - -import AdminSidebar from './AdminSidebar.svelte'; - -describe('AdminSidebar', () => { - test('Overview link is active when on /admin', () => { - state.pageUrl = new URL('http://localhost/admin'); - render(AdminSidebar); - const overview = screen.getByRole('link', { name: /overview/i }); - expect(overview).toHaveAttribute('aria-current', 'page'); - }); - - test('Integrations link is active when on /admin/integrations', () => { - state.pageUrl = new URL('http://localhost/admin/integrations'); - render(AdminSidebar); - expect(screen.getByRole('link', { name: /integrations/i })).toHaveAttribute( - 'aria-current', - 'page' - ); - expect(screen.getByRole('link', { name: /overview/i })).not.toHaveAttribute('aria-current'); - }); - - test('Requests link is active when on /admin/requests', () => { - state.pageUrl = new URL('http://localhost/admin/requests'); - render(AdminSidebar); - expect(screen.getByRole('link', { name: /requests/i })).toHaveAttribute( - 'aria-current', - 'page' - ); - }); - - test('Quarantine link is active when on /admin/quarantine', () => { - state.pageUrl = new URL('http://localhost/admin/quarantine'); - render(AdminSidebar); - expect(screen.getByRole('link', { name: /quarantine/i })).toHaveAttribute( - 'aria-current', - 'page' - ); - }); - - test('Quarantine renders as a real link', () => { - state.pageUrl = new URL('http://localhost/admin'); - render(AdminSidebar); - const link = screen.getByRole('link', { name: /quarantine/i }); - expect(link).toHaveAttribute('href', '/admin/quarantine'); - }); - - test('placeholder items render as non-links with aria-disabled', () => { - state.pageUrl = new URL('http://localhost/admin'); - render(AdminSidebar); - // Users + Library are still placeholders today. - expect(screen.queryByRole('link', { name: /^users$/i })).not.toBeInTheDocument(); - expect(screen.queryByRole('link', { name: /^library$/i })).not.toBeInTheDocument(); - const users = screen.getByText(/^users$/i); - expect(users.closest('[aria-disabled="true"]')).toBeInTheDocument(); - }); -}); diff --git a/web/src/lib/components/AdminTabs.svelte b/web/src/lib/components/AdminTabs.svelte new file mode 100644 index 00000000..618ea22c --- /dev/null +++ b/web/src/lib/components/AdminTabs.svelte @@ -0,0 +1,38 @@ + + + diff --git a/web/src/lib/components/AdminTabs.test.ts b/web/src/lib/components/AdminTabs.test.ts new file mode 100644 index 00000000..cbdb6efe --- /dev/null +++ b/web/src/lib/components/AdminTabs.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, test, vi } from 'vitest'; +import { render, screen } from '@testing-library/svelte'; + +const state = vi.hoisted(() => ({ + pageUrl: new URL('http://localhost/admin') +})); + +vi.mock('$app/state', () => ({ + page: { get url() { return state.pageUrl; } } +})); + +import AdminTabs from './AdminTabs.svelte'; + +describe('AdminTabs', () => { + test('Overview tab is current when on /admin', () => { + state.pageUrl = new URL('http://localhost/admin'); + render(AdminTabs); + const overview = screen.getByRole('link', { name: /overview/i }); + expect(overview).toHaveAttribute('aria-current', 'page'); + }); + + test('Integrations tab is current when on /admin/integrations', () => { + state.pageUrl = new URL('http://localhost/admin/integrations'); + render(AdminTabs); + expect(screen.getByRole('link', { name: /integrations/i })).toHaveAttribute( + 'aria-current', + 'page' + ); + expect(screen.getByRole('link', { name: /overview/i })).not.toHaveAttribute('aria-current'); + }); + + test('Requests tab is current when on /admin/requests', () => { + state.pageUrl = new URL('http://localhost/admin/requests'); + render(AdminTabs); + expect(screen.getByRole('link', { name: /requests/i })).toHaveAttribute('aria-current', 'page'); + }); + + test('Quarantine tab is current when on /admin/quarantine', () => { + state.pageUrl = new URL('http://localhost/admin/quarantine'); + render(AdminTabs); + expect(screen.getByRole('link', { name: /quarantine/i })).toHaveAttribute( + 'aria-current', + 'page' + ); + }); + + test('renders exactly four tabs (Users + Library deferred until built)', () => { + state.pageUrl = new URL('http://localhost/admin'); + render(AdminTabs); + const links = screen.getAllByRole('link'); + expect(links.map((l) => l.textContent?.trim())).toEqual([ + 'Overview', + 'Integrations', + 'Requests', + 'Quarantine' + ]); + }); +}); diff --git a/web/src/routes/admin/+layout.svelte b/web/src/routes/admin/+layout.svelte index 072dd601..83eec272 100644 --- a/web/src/routes/admin/+layout.svelte +++ b/web/src/routes/admin/+layout.svelte @@ -1,19 +1,10 @@ -
-
- -

Admin

-
-
- -
- {@render children?.()} -
-
+
+ + {@render children?.()}