diff --git a/frontend/src/lib/planner/EmptyDayTile.svelte b/frontend/src/lib/planner/EmptyDayTile.svelte
new file mode 100644
index 0000000..fed8d2f
--- /dev/null
+++ b/frontend/src/lib/planner/EmptyDayTile.svelte
@@ -0,0 +1,89 @@
+
+
+
+ {#if isPlanner}
+
+ {/if}
+
+ {#if topSuggestion}
+
+ {topSuggestion.recipe.name}
+
+
+ {#if reasoningTags.length > 0}
+
+ {#each reasoningTags as tag (tag.id)}
+
+ {tag.label}
+
+ {/each}
+
+ {/if}
+ {/if}
+
diff --git a/frontend/src/lib/planner/EmptyDayTile.test.ts b/frontend/src/lib/planner/EmptyDayTile.test.ts
new file mode 100644
index 0000000..fa52d9b
--- /dev/null
+++ b/frontend/src/lib/planner/EmptyDayTile.test.ts
@@ -0,0 +1,88 @@
+import { describe, it, expect, vi } from 'vitest';
+import { render, screen } from '@testing-library/svelte';
+import { userEvent } from '@testing-library/user-event';
+import EmptyDayTile from './EmptyDayTile.svelte';
+
+const slotDate = '2026-04-14';
+const slotId = 'slot-1';
+
+const topSuggestionNewProtein = {
+ recipe: {
+ id: 'r1',
+ name: 'Lachs mit Gemüse',
+ cookTimeMin: 20,
+ effort: 'einfach',
+ tags: [{ id: 't1', name: 'Fisch', tagType: 'protein' }]
+ },
+ scoreDelta: 3.2,
+ hasConflict: false
+};
+
+const slotMapEmpty = {};
+
+describe('EmptyDayTile', () => {
+ describe('base render', () => {
+ it('shows + CTA for planner', () => {
+ render(EmptyDayTile, { props: { slotDate, slotId, isPlanner: true, slotMap: slotMapEmpty } });
+ expect(screen.getByRole('button', { name: /Gericht wählen/i })).toBeTruthy();
+ });
+
+ it('hides + CTA for non-planner', () => {
+ render(EmptyDayTile, { props: { slotDate, slotId, isPlanner: false, slotMap: slotMapEmpty } });
+ expect(screen.queryByRole('button', { name: /Gericht wählen/i })).toBeNull();
+ });
+
+ it('calls onaddrecipe when + CTA clicked', async () => {
+ const onaddrecipe = vi.fn();
+ const user = userEvent.setup();
+ render(EmptyDayTile, { props: { slotDate, slotId, isPlanner: true, slotMap: slotMapEmpty, onaddrecipe } });
+ await user.click(screen.getByRole('button', { name: /Gericht wählen/i }));
+ expect(onaddrecipe).toHaveBeenCalledOnce();
+ });
+
+ it('has data-testid="empty-day-tile"', () => {
+ render(EmptyDayTile, { props: { slotDate, slotId, isPlanner: true, slotMap: slotMapEmpty } });
+ expect(screen.getByTestId('empty-day-tile')).toBeTruthy();
+ });
+ });
+
+ describe('reasoning tags', () => {
+ it('shows no tags when no topSuggestion', () => {
+ render(EmptyDayTile, { props: { slotDate, slotId, isPlanner: true, slotMap: slotMapEmpty } });
+ expect(screen.queryByTestId('reasoning-tag')).toBeNull();
+ });
+
+ it('shows Neues Protein tag when topSuggestion has new protein', () => {
+ render(EmptyDayTile, {
+ props: { slotDate, slotId, isPlanner: true, slotMap: slotMapEmpty, topSuggestion: topSuggestionNewProtein }
+ });
+ expect(screen.getByText('Neues Protein')).toBeTruthy();
+ });
+
+ it('shows Aufwand tag for easy suggestion', () => {
+ render(EmptyDayTile, {
+ props: { slotDate, slotId, isPlanner: true, slotMap: slotMapEmpty, topSuggestion: topSuggestionNewProtein }
+ });
+ expect(screen.getByText('Aufwand: leicht')).toBeTruthy();
+ });
+
+ it('shows suggestion recipe name when topSuggestion provided', () => {
+ render(EmptyDayTile, {
+ props: { slotDate, slotId, isPlanner: true, slotMap: slotMapEmpty, topSuggestion: topSuggestionNewProtein }
+ });
+ expect(screen.getByText('Lachs mit Gemüse')).toBeTruthy();
+ });
+
+ it('does not show tags when suggestion has no matching conditions', () => {
+ const heavySuggestion = {
+ recipe: { id: 'r2', name: 'Roulade', cookTimeMin: 120, effort: 'aufwändig', tags: [] },
+ scoreDelta: 1.0,
+ hasConflict: false
+ };
+ render(EmptyDayTile, {
+ props: { slotDate, slotId, isPlanner: true, slotMap: slotMapEmpty, topSuggestion: heavySuggestion }
+ });
+ expect(screen.queryByTestId('reasoning-tag')).toBeNull();
+ });
+ });
+});