From 31873a8ffd5c9de2d1f76d99cda0fbdf3f7d95f2 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Thu, 19 Mar 2026 19:29:52 +0300 Subject: [PATCH] Add real-time Jinja2 syntax validation with debounced API check Validates template syntax as user types (800ms debounce). Calls preview-raw API and shows red error text below the editor if Jinja2 parsing fails. Clears error when template is valid. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/routes/template-configs/+page.svelte | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/frontend/src/routes/template-configs/+page.svelte b/frontend/src/routes/template-configs/+page.svelte index 43246e4..c2cf0b3 100644 --- a/frontend/src/routes/template-configs/+page.svelte +++ b/frontend/src/routes/template-configs/+page.svelte @@ -23,6 +23,25 @@ let error = $state(''); let confirmDelete = $state(null); let slotPreview = $state>({}); + let slotErrors = $state>({}); + let validateTimers: Record> = {}; + + function validateSlot(slotKey: string, template: string) { + // Clear previous timer + if (validateTimers[slotKey]) clearTimeout(validateTimers[slotKey]); + if (!template) { slotErrors = { ...slotErrors, [slotKey]: '' }; return; } + + // Debounce 800ms + validateTimers[slotKey] = setTimeout(async () => { + try { + const res = await api('/template-configs/preview-raw', { method: 'POST', body: JSON.stringify({ template }) }); + slotErrors = { ...slotErrors, [slotKey]: res.error || '' }; + } catch { + // Network error, don't show as template error + slotErrors = { ...slotErrors, [slotKey]: '' }; + } + }, 800); + } const defaultForm = () => ({ name: '', description: '', icon: '', @@ -161,7 +180,10 @@ {#if (slot.rows || 2) > 2} - (form as any)[slot.key] = v} rows={slot.rows || 6} /> + { (form as any)[slot.key] = v; validateSlot(slot.key, v); }} rows={slot.rows || 6} /> + {#if slotErrors[slot.key]} +

Syntax error: {slotErrors[slot.key]}

+ {/if} {#if slotPreview[slot.key]}
{slotPreview[slot.key]}
@@ -197,15 +219,11 @@ {#if config.icon}{/if}

{config.name}

-
{config.message_assets_added?.slice(0, 120)}...
- {#if slotPreview['message_assets_added_' + config.id]} -
-
{slotPreview['message_assets_added_' + config.id]}
-
+ {#if config.description} +

{config.description}

{/if}
- preview(config.id, 'message_assets_added')} /> edit(config)} /> remove(config.id)} variant="danger" />