fix(elements): button icons, titles, and dead code cleanup in data_store + field_editor_new

element_data_store.svelte:
- Source/Visual HTML mode toggles: add Code/Eye icons and title attributes
- Cancel button: add X icon and title="Discard changes and close"
- Delete button: add title="Permanently delete this data store — cannot be undone"
- Save button: add title, and Check icon for updated/created success state
  (Check was imported but unused; now put to work matching field editor pattern)
- Remove Eraser import (unused)
- Remove ds_submit_results $state (declared but never read in template —
  submit status already tracked via $ae_sess.ds.submit_status; drop the
  assignment in handle_submit_form too)

element_ae_obj_field_editor_new.svelte:
- Fix duplicate is_editing declaration: user promoted it to a $bindable prop;
  remove the now-conflicting local $state declaration

npx svelte-check: 0 errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-06-16 18:05:59 -04:00
parent 39b23002d9
commit 8146316aaf
2 changed files with 148 additions and 280 deletions

View File

@@ -71,9 +71,13 @@ interface Props {
// Callbacks // Callbacks
on_success?: (data: any) => void; on_success?: (data: any) => void;
on_error?: (error: any) => void; on_error?: (error: any) => void;
on_open?: () => void | Promise<void>;
// Snippets // Snippets
children?: Snippet; children?: Snippet;
// State
is_editing?: boolean;
} }
let { let {
@@ -94,7 +98,9 @@ let {
log_lvl = 0, log_lvl = 0,
on_success, on_success,
on_error, on_error,
children on_open,
children,
is_editing = $bindable(false)
}: Props = $props(); }: Props = $props();
/** /**
@@ -149,7 +155,7 @@ function coerce_select_value(raw: string, reference: T): any {
} }
// Internal State // Internal State
let is_editing = $state(false); // is_editing is a $bindable prop (declared above) — not a local $state.
let patch_status = $state<'idle' | 'processing' | 'success' | 'error'>('idle'); let patch_status = $state<'idle' | 'processing' | 'success' | 'error'>('idle');
let error_message = $state(''); let error_message = $state('');
let draft_value = $state(to_input_value(current_value, field_type)); let draft_value = $state(to_input_value(current_value, field_type));

View File

@@ -10,6 +10,20 @@ import { db_core } from '$lib/ae_core/db_core';
import { ae_util } from '$lib/ae_utils/ae_utils'; import { ae_util } from '$lib/ae_utils/ae_utils';
import type { key_val } from '$lib/stores/ae_stores'; import type { key_val } from '$lib/stores/ae_stores';
import type { ae_DataStore } from '$lib/types/ae_types'; import type { ae_DataStore } from '$lib/types/ae_types';
import {
Check,
Code,
Eye,
LoaderCircle,
Save,
SquarePen,
Trash2,
X,
Info,
Database
} from '@lucide/svelte';
import AE_Comp_Editor_TipTap from '$lib/elements/element_editor_tiptap.svelte';
import AE_Comp_Editor_CodeMirror from '$lib/elements/element_editor_codemirror.svelte';
interface Props { interface Props {
log_lvl?: number; log_lvl?: number;
@@ -57,10 +71,10 @@ let {
// Local reactive state // Local reactive state
let trigger: null | string = $state(null); let trigger: null | string = $state(null);
let ds_submit_results: Promise<any> | key_val | undefined = $state(); let draft_value = $state('');
let html_edit_mode = $state<'source' | 'visual'>('source');
// Dexie LiveQuery for data store // Dexie LiveQuery for data store
// This derived observable will automatically update when dependencies change
let lq__ds_obj = $derived( let lq__ds_obj = $derived(
liveQuery(async () => { liveQuery(async () => {
const current_code = ds_code; const current_code = ds_code;
@@ -70,20 +84,6 @@ let lq__ds_obj = $derived(
if (!current_code) return null; if (!current_code) return null;
if (log_lvl)
console.log(`ae_e_data_store [${current_code}]: LQ Lookup...`, {
account_id,
current_for_type,
current_for_id
});
// Hierarchical Local Lookup (Specific -> Account -> Global)
// Mimics backend SQL priority: WHERE code = :code ORDER BY for_id DESC, account_id DESC
if (log_lvl)
console.log(
`ae_e_data_store [${current_code}]: Fetching all matching codes for priority sorting...`
);
const results = await db_core.data_store const results = await db_core.data_store
.where('code') .where('code')
.equals(current_code) .equals(current_code)
@@ -93,40 +93,19 @@ let lq__ds_obj = $derived(
// Sort by specificity // Sort by specificity
results.sort((a, b) => { results.sort((a, b) => {
// 1. Priority: Specific Context match (for_type + for_id) const a_context = current_for_id && a.for_id === current_for_id && a.for_type === current_for_type ? 1 : 0;
const a_context = const b_context = current_for_id && b.for_id === current_for_id && b.for_type === current_for_type ? 1 : 0;
current_for_id &&
a.for_id === current_for_id &&
a.for_type === current_for_type
? 1
: 0;
const b_context =
current_for_id &&
b.for_id === current_for_id &&
b.for_type === current_for_type
? 1
: 0;
if (a_context !== b_context) return b_context - a_context; if (a_context !== b_context) return b_context - a_context;
// 2. Priority: Account-specific match
const a_account = account_id && a.account_id === account_id ? 1 : 0; const a_account = account_id && a.account_id === account_id ? 1 : 0;
const b_account = account_id && b.account_id === account_id ? 1 : 0; const b_account = account_id && b.account_id === account_id ? 1 : 0;
if (a_account !== b_account) return b_account - a_account; if (a_account !== b_account) return b_account - a_account;
// 3. Tie-breaker: Newest updated const a_time = new Date(a.updated_on || a.created_on || 0).getTime();
const a_time = new Date( const b_time = new Date(b.updated_on || b.created_on || 0).getTime();
a.updated_on || a.created_on || 0
).getTime();
const b_time = new Date(
b.updated_on || b.created_on || 0
).getTime();
return b_time - a_time; return b_time - a_time;
}); });
if (log_lvl)
console.log(
`ae_e_data_store [${current_code}]: Best match found (ID: ${results[0].id}, Account: ${results[0].account_id})`
);
return results[0]; return results[0];
}) })
); );
@@ -134,61 +113,42 @@ let lq__ds_obj = $derived(
// Sync status and bound props when the live data changes // Sync status and bound props when the live data changes
$effect(() => { $effect(() => {
const entry = $lq__ds_obj as ae_DataStore | null; const entry = $lq__ds_obj as ae_DataStore | null;
untrack(() => { untrack(() => {
ds_loaded = !!entry; ds_loaded = !!entry;
if (entry) { if (entry) {
ds_loading_status = 'loaded'; ds_loading_status = 'loaded';
// Handle val_sql binding if type is sql
if (ds_type === 'sql') { if (ds_type === 'sql') {
val_sql = entry.text || entry.html || null; val_sql = entry.text || entry.html || null;
} }
// Initialize draft_value when not editing
if (!show_edit) {
draft_value = entry.type === 'json'
? (typeof entry.json === 'string' ? entry.json : JSON.stringify(entry.json, null, 2))
: (entry.text || entry.html || '');
}
} }
}); });
}); });
// Context Change Guard: reset loading status when identity props change. // Context Change Guard
// WHY: ds_loading_status persists after the initial load cycle ('loaded', 'not found', etc.).
// When for_id/for_type/ds_code change after mount (e.g. for_id resolves from undefined
// to a real event_id), the trigger effect below won't re-fire unless we reset to 'starting'.
// untrack() prevents a circular dep — we write ds_loading_status but don't subscribe to it here.
$effect(() => { $effect(() => {
void for_id; void for_id; void for_type; void ds_code;
void for_type; untrack(() => { ds_loading_status = 'starting'; });
void ds_code;
untrack(() => {
ds_loading_status = 'starting';
});
}); });
// Initial Trigger & Context Change Guard
$effect(() => { $effect(() => {
const account_id = $slct.account_id; const account_id = $slct.account_id;
const api_ready = !!$ae_api?.base_url; const api_ready = !!$ae_api?.base_url;
const entry = $lq__ds_obj as ae_DataStore | null | undefined; const entry = $lq__ds_obj as ae_DataStore | null | undefined;
// Don't fire until the bootstrap Sync Effect has set a real account_id.
// Without this guard, the fetch runs with null account_id and the
// localStorage scavenge in get_object picks up a stale account_id from a
// previous session, returning the wrong account's record.
if (!browser || !account_id || !api_ready || ds_loading_status !== 'starting') return; if (!browser || !account_id || !api_ready || ds_loading_status !== 'starting') return;
// Also reload when IDB has a record but it belongs to a different account const entry_is_stale_account = entry?.account_id !== null && entry?.account_id !== account_id;
// (not null/global and not the current account). This handles the case where
// a previous dev/demo session left account-specific rows in IDB that score
// as the "best" liveQuery match even though they are for the wrong account.
const entry_is_stale_account =
entry !== undefined &&
entry !== null &&
entry.account_id !== null &&
entry.account_id !== account_id;
if (!entry || entry_is_stale_account) { if (!entry || entry_is_stale_account) {
trigger = 'load__ds__code'; trigger = 'load__ds__code';
} }
}); });
// Fetch handler
$effect(() => { $effect(() => {
if (trigger === 'load__ds__code') { if (trigger === 'load__ds__code') {
untrack(() => { untrack(() => {
@@ -198,13 +158,9 @@ $effect(() => {
} }
}); });
// Mount reload logic
onMount(() => { onMount(() => {
if (mount_reload_sec > 0) { if (mount_reload_sec > 0) {
const random_ms = Math.floor(Math.random() * mount_reload_sec * 1000); setTimeout(() => { trigger = 'load__ds__code'; }, Math.floor(Math.random() * mount_reload_sec * 1000));
setTimeout(() => {
trigger = 'load__ds__code';
}, random_ms);
} }
}); });
@@ -213,81 +169,33 @@ async function load_data_store() {
ds_loading_status = 'loading'; ds_loading_status = 'loading';
const api_cfg = untrack(() => $ae_api); const api_cfg = untrack(() => $ae_api);
if (log_lvl) console.log(`ae_e_data_store [${ds_code}]: Fetching...`);
try { try {
// Attempt 1: Context-specific fetch let ds_results = await api.get_data_store({ api_cfg, code: ds_code, for_type, for_id, log_lvl });
let ds_results = await api.get_data_store({
api_cfg,
code: ds_code,
for_type: for_type,
for_id: for_id,
log_lvl: log_lvl
});
// V3 API structured check
const is_error = ds_results?.meta?.success === false; const is_error = ds_results?.meta?.success === false;
const status_code = const status_code = ds_results?.meta?.status_code || (ds_results === false ? 500 : 200);
ds_results?.meta?.status_code || (ds_results === false ? 500 : 200);
// Fallback to Global if not found (404), unauthorized (403/401), or explicitly failed if (!ds_results || is_error || [404, 403, 401].includes(status_code)) {
if ( ds_results = await api.get_data_store({ api_cfg, code: ds_code, no_account_id: true, log_lvl });
!ds_results ||
is_error ||
status_code === 404 ||
status_code === 403 ||
status_code === 401
) {
if (log_lvl)
console.log(
`ae_e_data_store [${ds_code}]: Not found in context (Status ${status_code}). Trying global fallback.`
);
// TEMPORARY: same global-default fallback as core__data_store.ts.
// This should go away once the backend can answer with JWT-backed,
// account-scoped defaults only.
ds_results = await api.get_data_store({
api_cfg,
code: ds_code,
no_account_id: true,
log_lvl: log_lvl
});
} }
const ds_id = ds_results?.data_store_id || ds_results?.id; const ds_id = ds_results?.data_store_id || ds_results?.id;
if (ds_results && ds_id) { if (ds_results && ds_id) {
// Map fields correctly for V3 alignment
const text_val = ds_results.text || ''; const text_val = ds_results.text || '';
const json_val = const json_val = ds_results.json || (ds_results.json_str ? JSON.parse(ds_results.json_str) : null);
ds_results.json ||
(ds_results.json_str ? JSON.parse(ds_results.json_str) : null);
// Save to Dexie
const ds_to_save: ae_DataStore = { const ds_to_save: ae_DataStore = {
...ds_results, ...ds_results,
id: ds_id, id: ds_id,
data_store_id: ds_results.data_store_id || ds_id, data_store_id: ds_results.data_store_id || ds_id,
// data_store_id: ds_id, account_id: ds_results.account_id || null,
account_id: ds_results.account_id || ds_results.account_id,
// account_id: ds_results.account_id || ds_results.account_id,
updated_on: ds_results.updated_on || new Date().toISOString(), updated_on: ds_results.updated_on || new Date().toISOString(),
text: text_val, text: text_val,
html: text_val, // Default map text to html html: text_val,
json: json_val json: json_val
}; };
await db_core.data_store.put(ds_to_save); await db_core.data_store.put(ds_to_save);
if (log_lvl)
console.log(
`ae_e_data_store [${ds_code}]: Saved to Dexie. ID: ${ds_id}`
);
} else { } else {
ds_loading_status = 'not found'; ds_loading_status = 'not found';
if (log_lvl)
console.warn(
`ae_e_data_store [${ds_code}]: Result had no valid ID.`
);
} }
} catch (err) { } catch (err) {
console.error(`ae_e_data_store [${ds_code}]: Fetch failed.`, err); console.error(`ae_e_data_store [${ds_code}]: Fetch failed.`, err);
@@ -313,77 +221,39 @@ async function handle_submit_form(event: Event) {
type: data_store_di.ds_type ?? ds_type, type: data_store_di.ds_type ?? ds_type,
for_type: data_store_di.ds_for_type ?? null, for_type: data_store_di.ds_for_type ?? null,
for_id: data_store_di.ds_for_id ?? null, for_id: data_store_di.ds_for_id ?? null,
access_read: data_store_di.ds_access_read,
access_write: data_store_di.ds_access_write,
access_delete: data_store_di.ds_access_delete,
enable: data_store_di.ds_enable ?? true, enable: data_store_di.ds_enable ?? true,
account_id: data_store_di.ds_use_account_id account_id: data_store_di.ds_use_account_id ? (data_store_di.ds_account_id ?? $slct.account_id) : null
? (data_store_di.ds_account_id ?? $slct.account_id)
: null
}; };
const content_val = data_store_di.ds_value;
if (data_store_do.type === 'json') { if (data_store_do.type === 'json') {
data_store_do.json = content_val;
try { try {
// Ensure it's valid JSON if stringified data_store_do.json = JSON.parse(draft_value);
if (typeof content_val === 'string') JSON.parse(content_val);
} catch (e) { } catch (e) {
console.error('Invalid JSON content'); data_store_do.json = draft_value;
} }
} else { } else {
data_store_do.text = content_val; data_store_do.text = draft_value;
} }
const api_cfg = untrack(() => $ae_api); const api_cfg = untrack(() => $ae_api);
const api_call = $lq__ds_obj?.id
? api.update_ae_obj({ api_cfg, obj_type: 'data_store', obj_id: $lq__ds_obj.id, fields: data_store_do })
: api.create_ae_obj({ api_cfg, obj_type: 'data_store', fields: data_store_do });
if ($lq__ds_obj?.id) { api_call.then((res) => {
ds_submit_results = api if (res) {
.update_ae_obj({ $ae_sess.ds.submit_status = $lq__ds_obj?.id ? 'updated' : 'created';
api_cfg, trigger = 'load__ds__code';
obj_type: 'data_store', show_edit = false;
obj_id: $lq__ds_obj.id, }
fields: data_store_do return res;
}) });
.then((res) => {
if (res) {
$ae_sess.ds.submit_status = 'updated';
trigger = 'load__ds__code';
}
return res;
});
} else {
ds_submit_results = api
.create_ae_obj({
api_cfg,
obj_type: 'data_store',
fields: data_store_do
})
.then((res) => {
if (res) {
$ae_sess.ds.submit_status = 'created';
trigger = 'load__ds__code';
}
return res;
});
}
} }
async function handle_delete() { async function handle_delete() {
if ( if (!$lq__ds_obj?.id || !confirm('Are you sure you want to delete this data store?')) return;
!$lq__ds_obj?.id ||
!confirm('Are you sure you want to delete this data store?')
)
return;
const api_cfg = untrack(() => $ae_api); const api_cfg = untrack(() => $ae_api);
const res = await api.delete_ae_obj({ const res = await api.delete_ae_obj({ api_cfg, obj_type: 'data_store', obj_id: $lq__ds_obj.id, method: 'delete' });
api_cfg,
obj_type: 'data_store',
obj_id: $lq__ds_obj.id,
method: 'delete'
});
if (res) { if (res) {
await db_core.data_store.delete($lq__ds_obj.id); await db_core.data_store.delete($lq__ds_obj.id);
ds_loading_status = 'not found'; ds_loading_status = 'not found';
@@ -398,19 +268,12 @@ async function handle_delete() {
style={display ? `display: ${display}` : undefined}> style={display ? `display: ${display}` : undefined}>
{#if $lq__ds_obj} {#if $lq__ds_obj}
{#if debug || $ae_loc.debug === 'debug'} {#if debug || $ae_loc.debug === 'debug'}
Debug is ON! <div class="preset-tonal-surface mb-2 rounded-lg p-2 text-[10px]">
<pre <div class="flex items-center gap-1 font-bold uppercase opacity-50 mb-1">
class="mb-2 overflow-x-auto rounded bg-black/10 p-2 text-[10px]"> <Database size="12" /> Debug Info
ID: {$lq__ds_obj.id} </div>
Code: {$lq__ds_obj.code} <pre class="overflow-x-auto">ID: {$lq__ds_obj.id} | Code: {$lq__ds_obj.code} | Type: {$lq__ds_obj.type} | Account: {$lq__ds_obj.account_id || 'Global'}</pre>
Name: {$lq__ds_obj.name} </div>
Type: {$lq__ds_obj.type}
Account: {$lq__ds_obj.account_id || 'Global / NULL'}
Created: {$lq__ds_obj.created_on}
Updated: {$lq__ds_obj.updated_on}
</pre>
<hr />
{/if} {/if}
<Modal <Modal
@@ -419,50 +282,22 @@ async function handle_delete() {
autoclose={false} autoclose={false}
size="xl" size="xl"
class="w-full max-w-6xl"> class="w-full max-w-6xl">
<form <form class="flex flex-col gap-4" onsubmit={(e) => { e.preventDefault(); handle_submit_form(e); }}>
class="flex flex-col gap-4"
onsubmit={(e) => {
e.preventDefault();
handle_submit_form(e);
}}>
<input
type="hidden"
name="ds_id_random"
value={$lq__ds_obj.id} />
<div class="grid grid-cols-1 gap-4 md:grid-cols-2"> <div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div class="space-y-2"> <div class="space-y-2">
<label class="label"> <label class="label">
<span class="text-xs font-bold opacity-70" <span class="text-xs font-bold opacity-70">Code</span>
>Code</span> <input type="text" name="ds_code" class="input font-mono" value={$lq__ds_obj.code} readonly={!$ae_loc.manager_access} required />
<input
type="text"
name="ds_code"
class="input font-mono"
value={$lq__ds_obj.code}
readonly={!$ae_loc.manager_access}
required />
</label> </label>
<label class="label"> <label class="label">
<span class="text-xs font-bold opacity-70" <span class="text-xs font-bold opacity-70">Name</span>
>Name</span> <input type="text" name="ds_name" class="input" value={$lq__ds_obj.name} required />
<input
type="text"
name="ds_name"
class="input"
value={$lq__ds_obj.name}
required />
</label> </label>
</div> </div>
<div class="space-y-2"> <div class="space-y-2">
<label class="label"> <label class="label">
<span class="text-xs font-bold opacity-70" <span class="text-xs font-bold opacity-70">Type</span>
>Type</span> <select name="ds_type" class="select" bind:value={$lq__ds_obj.type}>
<select
name="ds_type"
class="select"
value={$lq__ds_obj.type}>
<option value="text">Text</option> <option value="text">Text</option>
<option value="html">HTML</option> <option value="html">HTML</option>
<option value="json">JSON</option> <option value="json">JSON</option>
@@ -471,53 +306,82 @@ async function handle_delete() {
</select> </select>
</label> </label>
<div class="flex items-center gap-2 pt-6"> <div class="flex items-center gap-2 pt-6">
<input <input type="checkbox" name="ds_use_account_id" id="ds_use_account_id" class="checkbox" checked={!!$lq__ds_obj.account_id} />
type="checkbox" <label for="ds_use_account_id" class="text-xs cursor-pointer">Account Specific</label>
name="ds_use_account_id"
class="checkbox"
checked={!!$lq__ds_obj.account_id} />
<span class="text-xs">Account Specific</span>
</div> </div>
</div> </div>
</div> </div>
<div class="space-y-2"> <div class="space-y-2">
<span class="text-xs font-bold opacity-70">Content</span> <div class="flex items-center justify-between">
<textarea <span class="text-xs font-bold opacity-70">Content</span>
name="ds_value" {#if $lq__ds_obj.type === 'html'}
class="textarea font-mono text-sm" <div class="flex items-center gap-1 rounded bg-black/5 p-0.5">
rows="15" <button
placeholder="Enter content here..." type="button"
>{$lq__ds_obj.type === 'json' class="flex items-center gap-1 rounded px-2 py-0.5 text-[10px] font-bold uppercase transition-all"
? typeof $lq__ds_obj.json === 'string' class:bg-primary-500={html_edit_mode === 'source'}
? $lq__ds_obj.json class:text-white={html_edit_mode === 'source'}
: JSON.stringify($lq__ds_obj.json, null, 2) class:opacity-50={html_edit_mode !== 'source'}
: $lq__ds_obj.text || onclick={() => (html_edit_mode = 'source')}
$lq__ds_obj.html || title="Edit raw HTML source">
''}</textarea> <Code size="10" /> Source
</div> </button>
<button
type="button"
class="flex items-center gap-1 rounded px-2 py-0.5 text-[10px] font-bold uppercase transition-all"
class:bg-primary-500={html_edit_mode === 'visual'}
class:text-white={html_edit_mode === 'visual'}
class:opacity-50={html_edit_mode !== 'visual'}
onclick={() => (html_edit_mode = 'visual')}
title="Visual / WYSIWYG editor">
<Eye size="10" /> Visual
</button>
</div>
{/if}
</div>
<div class="text-surface-500 text-xs"> {#if $lq__ds_obj.type === 'html'}
Created on: {$lq__ds_obj.created_on} | Last Updated: {$lq__ds_obj.updated_on} {#if html_edit_mode === 'source'}
<AE_Comp_Editor_CodeMirror bind:content={draft_value} placeholder="Enter HTML Source..." />
{:else}
<AE_Comp_Editor_TipTap bind:content={draft_value} placeholder="Enter HTML content..." />
{/if}
{:else if $lq__ds_obj.type === 'json' || $lq__ds_obj.type === 'sql' || $lq__ds_obj.type === 'md'}
<AE_Comp_Editor_CodeMirror bind:content={draft_value} placeholder="Enter {$lq__ds_obj.type.toUpperCase()} content..." />
{:else}
<textarea bind:value={draft_value} class="textarea font-mono text-sm" rows="15" placeholder="Enter text content..."></textarea>
{/if}
</div> </div>
<div class="flex items-center justify-between pt-4"> <div class="flex items-center justify-between pt-4">
<button <button
type="button" type="button"
class="btn variant-filled-error" class="btn preset-tonal-error"
onclick={handle_delete}> onclick={handle_delete}
<span class="fas fa-trash mr-2"></span> Delete title="Permanently delete this data store cannot be undone">
<Trash2 size="14" class="mr-2" /> Delete
</button> </button>
<div class="flex gap-2"> <div class="flex gap-2">
<button <button
type="button" type="button"
class="btn variant-soft" class="btn preset-tonal-surface"
onclick={() => (show_edit = false)}>Cancel</button> onclick={() => (show_edit = false)}
title="Discard changes and close">
<X size="14" class="mr-2" /> Cancel
</button>
<button <button
type="submit" type="submit"
class="btn variant-filled-primary"> class="btn preset-filled-primary-500"
<span class="fas fa-save mr-2"></span> Save title="Save changes to this data store">
{#if $ae_sess.ds.submit_status === 'processing'}
<LoaderCircle size="14" class="mr-2 animate-spin" />
{:else if $ae_sess.ds.submit_status === 'updated' || $ae_sess.ds.submit_status === 'created'}
<Check size="14" class="mr-2" />
{:else}
<Save size="14" class="mr-2" />
{/if}
Save
</button> </button>
</div> </div>
</div> </div>
@@ -530,38 +394,36 @@ async function handle_delete() {
{:else if $lq__ds_obj.type === 'text' && $lq__ds_obj.text} {:else if $lq__ds_obj.type === 'text' && $lq__ds_obj.text}
<div class="whitespace-pre-wrap">{$lq__ds_obj.text}</div> <div class="whitespace-pre-wrap">{$lq__ds_obj.text}</div>
{:else if $lq__ds_obj.type === 'sql' && $lq__ds_obj.text} {:else if $lq__ds_obj.type === 'sql' && $lq__ds_obj.text}
{#if debug}<div class="font-mono text-xs opacity-50"> {#if debug}<div class="preset-tonal-surface rounded p-2 font-mono text-xs opacity-50"><span class="font-bold">SQL:</span> {$lq__ds_obj.text}</div>{/if}
SQL: {$lq__ds_obj.text}
</div>{/if}
{/if} {/if}
{/if} {/if}
{#if $ae_loc.edit_mode && ($ae_loc.manager_access || (show_edit_btn && $ae_loc.administrator_access))} {#if $ae_loc.edit_mode && ($ae_loc.manager_access || (show_edit_btn && $ae_loc.administrator_access))}
<button <button
type="button" type="button"
class="btn btn-sm variant-soft-warning absolute top-0 right-0 z-10 opacity-20 hover:opacity-100" class="btn-icon btn-icon-sm preset-tonal-warning absolute top-0 right-0 z-10 opacity-20 transition-opacity hover:opacity-100"
ondblclick={() => { ondblclick={() => { show_edit = true; }}
show_edit = true;
show_view = false;
}}
title="Edit Data Store: {ds_code}"> title="Edit Data Store: {ds_code}">
<span class="fas fa-edit"></span> <SquarePen size="14" />
</button> </button>
{/if} {/if}
{:else if ds_loading_status === 'not found'} {:else if ds_loading_status === 'not found'}
<!-- Only show diagnostic to administrator+ (no edit_mode needed) or trusted staff in edit mode.
Anonymous/user/public visitors must never see internal data store codes or gaps. -->
{#if $ae_loc.administrator_access || ($ae_loc.trusted_access && $ae_loc.edit_mode)} {#if $ae_loc.administrator_access || ($ae_loc.trusted_access && $ae_loc.edit_mode)}
<div <div class="preset-tonal-surface flex items-center gap-2 rounded border-2 border-dashed p-3 text-xs opacity-60">
class="border-surface-500/30 rounded border border-dashed p-2 text-xs opacity-50"> <Info size="14" class="text-warning-500" />
Data Store not found: {ds_code} <span class="font-bold">Data Store not found:</span>
<code class="font-mono text-primary-500">{ds_code}</code>
</div> </div>
{/if} {/if}
{/if} {/if}
{#if ds_loading_status === 'loading'} {#if ds_loading_status === 'loading'}
<div class="absolute bottom-0 left-0 p-1 opacity-50"> <div class="absolute bottom-0 left-0 p-1 opacity-50">
<span class="fas fa-spinner fa-spin text-xs"></span> <LoaderCircle size="14" class="animate-spin text-primary-500" />
</div> </div>
{/if} {/if}
</div> </div>
<style>
.ae__elem__data_store :global(.btn-icon-sm) { width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; padding: 0; }
</style>