diff --git a/frontend/src/lib/nav/AppShell.svelte b/frontend/src/lib/nav/AppShell.svelte
new file mode 100644
index 0000000..dba02e4
--- /dev/null
+++ b/frontend/src/lib/nav/AppShell.svelte
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+ {@render children?.()}
+
+
+
+
diff --git a/frontend/src/lib/nav/AppShell.test.ts b/frontend/src/lib/nav/AppShell.test.ts
new file mode 100644
index 0000000..63e9eb0
--- /dev/null
+++ b/frontend/src/lib/nav/AppShell.test.ts
@@ -0,0 +1,37 @@
+import { describe, it, expect, vi } from 'vitest';
+import { render, screen } from '@testing-library/svelte';
+import AppShell from './AppShell.svelte';
+
+vi.mock('$app/stores', () => {
+ const { readable } = require('svelte/store');
+ return {
+ page: readable({ url: new URL('http://localhost/planner') })
+ };
+});
+
+describe('AppShell', () => {
+ const defaultProps = { appName: 'Mealprep', householdName: 'Familie Müller' };
+
+ it('renders the DesktopSidebar', () => {
+ render(AppShell, { props: defaultProps });
+ expect(screen.getByTestId('variety-widget-slot')).toBeInTheDocument();
+ });
+
+ it('renders the MobileTabBar nav', () => {
+ render(AppShell, { props: defaultProps });
+ const navs = screen.getAllByLabelText('Hauptnavigation');
+ expect(navs.length).toBeGreaterThanOrEqual(2);
+ });
+
+ it('renders a main content area', () => {
+ render(AppShell, { props: defaultProps });
+ expect(screen.getByRole('main')).toBeInTheDocument();
+ });
+
+ it('renders all navigation links from all nav variants', () => {
+ render(AppShell, { props: defaultProps });
+ const links = screen.getAllByRole('link');
+ // Mobile: 4, Tablet: 4, Desktop: 5 = 13 total
+ expect(links).toHaveLength(13);
+ });
+});