From 86ad5ca9b3c30ef5dde97c46bdbc0755ad7b626b Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 29 Apr 2026 01:14:37 +0200 Subject: [PATCH] fix(person-mention): show loading state during debounce + fetch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Leonie #5507 concern 7: on slow networks the popup sat empty for up to 1.5s while the user wondered if anything was happening. Add a loading flag that flips on as soon as scheduleSearch is asked to query and back off in the fetch's finally branch. Reuses the existing comp_typeahead_loading message ("Suche…") so no new i18n keys. Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/lib/components/PersonMentionEditor.svelte | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/components/PersonMentionEditor.svelte b/frontend/src/lib/components/PersonMentionEditor.svelte index ebbf49d0..3034b3cb 100644 --- a/frontend/src/lib/components/PersonMentionEditor.svelte +++ b/frontend/src/lib/components/PersonMentionEditor.svelte @@ -37,6 +37,7 @@ let query: string | null = $state(null); let results: Person[] = $state([]); let highlightedIndex = $state(0); let mentionStart = $state(0); +let loading = $state(false); let textarea: HTMLTextAreaElement | null = null; let debounceTimer: ReturnType | undefined; @@ -90,8 +91,10 @@ function scheduleSearch(q: string) { // Empty query: keep popup open with last results so the user can browse, // but don't fire a backend call until they actually type something. results = []; + loading = false; return; } + loading = true; debounceTimer = setTimeout(async () => { try { // SECURITY: relies on the SvelteKit Vite proxy injecting the auth_token @@ -108,6 +111,8 @@ function scheduleSearch(q: string) { } } catch { results = []; + } finally { + loading = false; } }, 200); } @@ -140,6 +145,7 @@ function closePopup() { query = null; results = []; highlightedIndex = 0; + loading = false; clearTimeout(debounceTimer); } @@ -208,7 +214,9 @@ const popupOpen = $derived(query !== null); role="listbox" aria-label={m.person_mention_btn_label()} > - {#if results.length === 0} + {#if loading} +

{m.comp_typeahead_loading()}

+ {:else if results.length === 0}

{m.person_mention_popup_empty()}

{:else} {#each results as person, i (person.id)}