diff --git a/src/lib/elements/element_ae_obj_field_editor_v3.svelte b/src/lib/elements/element_ae_obj_field_editor_v3.svelte index eedc2913..74c1d060 100644 --- a/src/lib/elements/element_ae_obj_field_editor_v3.svelte +++ b/src/lib/elements/element_ae_obj_field_editor_v3.svelte @@ -47,7 +47,7 @@ object_type, object_id, field_name, - current_value = $bindable(), + current_value, field_type = 'text', allow_null = false, select_options = {}, @@ -70,15 +70,31 @@ let error_message = $state(''); let draft_value = $state(current_value); - // Sync draft with current_value when not editing + // WHY: Optimistic display. After a successful PATCH, liveQuery may not fire + // immediately (or at all if on_success doesn't trigger a Dexie refresh). We show + // draft_value as the display until current_value catches up from liveQuery. + let has_optimistic = $state(false); + let display_value = $derived(has_optimistic ? draft_value : current_value); + + // Sync draft with display_value when not editing. + // Suppress reset if optimistic is active — we already have the right value. $effect(() => { if (!is_editing) { untrack(() => { - draft_value = current_value; + if (!has_optimistic) { + draft_value = current_value; + } }); } }); + // Clear optimistic once liveQuery catches up (current_value matches what we saved) + $effect(() => { + if (has_optimistic && current_value === draft_value) { + has_optimistic = false; + } + }); + async function handle_patch() { if (log_lvl) console.log(`AE Field Editor V3: Patching ${object_type}.${field_name}...`); @@ -98,7 +114,7 @@ if (result) { patch_status = 'success'; - current_value = draft_value; + has_optimistic = true; // show draft_value immediately; cleared when liveQuery catches up if (on_success) on_success(result); // Close edit mode after a brief success indicator @@ -120,6 +136,7 @@ } function cancel_edit() { + has_optimistic = false; draft_value = current_value; is_editing = false; patch_status = 'idle'; @@ -128,7 +145,11 @@ function toggle_edit() { if (is_editing) cancel_edit(); - else is_editing = true; + else { + has_optimistic = false; // clear optimistic so draft syncs from current prop + draft_value = current_value; + is_editing = true; + } } @@ -143,31 +164,33 @@ {#if children} {@render children()} {:else if field_type === 'checkbox'} - - {current_value ? 'Enabled' : 'Disabled'} + + {display_value ? 'Enabled' : 'Disabled'} {:else if field_type === 'tiptap'}
- {@html current_value || 'Empty'} + {@html display_value || 'Empty'}
{:else} - - {current_value || 'Not set'} + + {display_value || 'Not set'} {/if} - {#if $ae_loc.edit_mode} - - {/if} + + @@ -263,7 +286,7 @@ type="button" class="btn btn-sm variant-filled-primary" onclick={handle_patch} - disabled={patch_status === 'processing' || draft_value === current_value} + disabled={patch_status === 'processing' || draft_value === display_value} > {#if patch_status === 'processing'}