diff --git a/frontend/src/routes/SearchFilterBar.svelte b/frontend/src/routes/SearchFilterBar.svelte
index 2b980974..779ab8f3 100644
--- a/frontend/src/routes/SearchFilterBar.svelte
+++ b/frontend/src/routes/SearchFilterBar.svelte
@@ -3,6 +3,7 @@ import PersonTypeahead from '$lib/person/PersonTypeahead.svelte';
import TagInput from '$lib/tag/TagInput.svelte';
import DateInput from '$lib/shared/primitives/DateInput.svelte';
import SortDropdown from '$lib/shared/primitives/SortDropdown.svelte';
+import SmartModeToggle from './search/SmartModeToggle.svelte';
import { slide } from 'svelte/transition';
import { m } from '$lib/paraglide/messages.js';
@@ -20,12 +21,15 @@ let {
sort = $bindable('DATE'),
dir = $bindable('desc'),
showAdvanced = $bindable(false),
+ smartMode = $bindable(false),
initialSenderName = '',
initialReceiverName = '',
navKey = 0,
isLoading = false,
onSearch,
onSearchImmediate,
+ onSmartSearch,
+ onModeToggle,
onfocus,
onblur
}: {
@@ -42,16 +46,28 @@ let {
sort?: string;
dir?: string;
showAdvanced?: boolean;
+ smartMode?: boolean;
initialSenderName?: string;
initialReceiverName?: string;
navKey?: number;
isLoading?: boolean;
onSearch: () => void;
onSearchImmediate?: () => void;
+ onSmartSearch?: () => void;
+ onModeToggle?: () => void;
onfocus?: () => void;
onblur?: () => void;
} = $props();
+// In smart mode the keyword search must not fire on every keystroke — the NL
+// query is submitted only on Enter (or an explicit button click).
+function onSearchKeydown(event: KeyboardEvent) {
+ if (smartMode && event.key === 'Enter') {
+ event.preventDefault();
+ onSmartSearch?.();
+ }
+}
+
// Plain (non-reactive) flag — not $state, so no reactive assignment inside $effect
let sortDirMounted = false;
@@ -76,14 +92,20 @@ $effect(() => {
-
+
+
{#if isLoading}
+
diff --git a/frontend/src/routes/search/SmartModeToggle.svelte.spec.ts b/frontend/src/routes/search/SmartModeToggle.svelte.spec.ts
index 042a7008..01347271 100644
--- a/frontend/src/routes/search/SmartModeToggle.svelte.spec.ts
+++ b/frontend/src/routes/search/SmartModeToggle.svelte.spec.ts
@@ -1,10 +1,13 @@
-import { describe, expect, it, afterEach } from 'vitest';
+import { describe, expect, it, vi, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import SmartModeToggle from './SmartModeToggle.svelte';
+import SearchFilterBar from '../SearchFilterBar.svelte';
afterEach(() => cleanup());
+const SEARCH_PLACEHOLDER = 'Titel, Personen, Tags durchsuchen…';
+
describe('SmartModeToggle', () => {
it('renders aria-pressed="false" by default and toggles on click', async () => {
render(SmartModeToggle, { smartMode: false });
@@ -34,3 +37,45 @@ describe('SmartModeToggle', () => {
await expect.element(btn).toHaveClass(/bg-primary/);
});
});
+
+describe('SmartModeToggle inside SearchFilterBar', () => {
+ it('adds maxlength="500" to the search input only in smart mode', async () => {
+ render(SearchFilterBar, { onSearch: vi.fn(), sort: 'DATE', dir: 'desc', smartMode: true });
+ await expect
+ .element(page.getByPlaceholder(SEARCH_PLACEHOLDER))
+ .toHaveAttribute('maxlength', '500');
+ });
+
+ it('omits maxlength from the search input in keyword mode', async () => {
+ render(SearchFilterBar, { onSearch: vi.fn(), sort: 'DATE', dir: 'desc', smartMode: false });
+ await expect
+ .element(page.getByPlaceholder(SEARCH_PLACEHOLDER))
+ .not.toHaveAttribute('maxlength');
+ });
+
+ it('does not fire the keyword search on input while in smart mode', async () => {
+ const onSearch = vi.fn();
+ render(SearchFilterBar, { onSearch, sort: 'DATE', dir: 'desc', smartMode: true });
+ await page.getByPlaceholder(SEARCH_PLACEHOLDER).fill('Walter im Krieg');
+ expect(onSearch).not.toHaveBeenCalled();
+ });
+
+ it('fires the smart search callback on Enter in smart mode', async () => {
+ const onSmartSearch = vi.fn();
+ render(SearchFilterBar, {
+ onSearch: vi.fn(),
+ onSmartSearch,
+ sort: 'DATE',
+ dir: 'desc',
+ smartMode: true
+ });
+ const input = page.getByPlaceholder(SEARCH_PLACEHOLDER);
+ await input.fill('Walter im Krieg');
+ await input.click();
+ // Enter submits the NL query in smart mode
+ (document.activeElement as HTMLElement).dispatchEvent(
+ new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })
+ );
+ await vi.waitFor(() => expect(onSmartSearch).toHaveBeenCalled());
+ });
+});