feat: locale-aware command templates, debounced auto-sync, entity pickers

- Locale-aware templates: CommandTemplateSlot now has a locale column,
  allowing each slot to have per-language variants (EN/RU). Templates
  are resolved at runtime from the Telegram user's language_code.

- Merged system configs: "Default Commands (EN)" and "(RU)" merged
  into a single "Default Commands" config with locale-aware slots.
  Migration handles existing data automatically.

- Configurable command descriptions: hardcoded COMMAND_DESCRIPTIONS
  replaced with desc_* template slots (desc_status, desc_help, etc.)
  that users can customize per locale. setMyCommands registers all
  locales explicitly.

- Removed locale from CommandConfig: no longer needed since locale
  is derived from the Telegram user's language at runtime.

- Debounced command auto-sync: after command config/tracker changes,
  affected bots are marked dirty and synced after a 30s debounce
  window. Manual "Sync with Telegram" button still works.

- Entity pickers in LinkedTargetsSection: replaced 6 plain <select>
  elements with EntitySelect components (search, icons, keyboard nav).
  Added onselect callback and size="sm" props to EntitySelect.
This commit is contained in:
2026-03-22 03:14:51 +03:00
parent 751097b347
commit 1167d138a3
47 changed files with 604 additions and 230 deletions
@@ -15,6 +15,8 @@
allowNone = false,
noneLabel = '—',
disabled = false,
size = 'default',
onselect,
}: {
items: EntityItem[];
value: string | number | null;
@@ -22,6 +24,8 @@
allowNone?: boolean;
noneLabel?: string;
disabled?: boolean;
size?: 'sm' | 'default';
onselect?: (value: string | number | null) => void;
} = $props();
let open = $state(false);
@@ -59,6 +63,7 @@
function selectItem(item: EntityItem) {
value = item.value || null;
onselect?.(value);
closePalette();
}
@@ -97,7 +102,7 @@
</script>
<!-- Trigger button -->
<button type="button" class="es-trigger" onclick={openPalette}
<button type="button" class="es-trigger" class:es-sm={size === 'sm'} onclick={openPalette}
style="opacity: {disabled ? 0.5 : 1}; cursor: {disabled ? 'default' : 'pointer'};">
{#if selected}
{#if selected.icon}
@@ -174,6 +179,11 @@
text-align: left;
cursor: pointer;
}
.es-trigger.es-sm {
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
gap: 0.375rem;
}
.es-trigger:hover {
border-color: var(--color-primary);
}