fix(frontend): environment select resets to default on click

Root cause: onchange on native <select> passes a DOM Event.
The parent handler used e.detail, which is always 0 for native events.
Sequence:
1. bind:value sets environmentId correctly
2. onchange fires -> handleEnvChange(0) overwrites to falsy
3. <select> re-renders showing 'Select environment...'

Fix:
- ConfigTabForm: onchange now passes e.target.value (the actual envId)
- +page.svelte: callback receives the envId string directly, not e.detail

@RATIONALE bind:value on <select> fires on the same change event.
The handler must not re-set the same bindable to a stale value.

Verification: browser reconfirmed — ss-dev stays selected after click.
This commit is contained in:
2026-06-03 11:16:01 +03:00
parent 3b5b228f96
commit a697ff2c3d
2 changed files with 35 additions and 11 deletions

View File

@@ -1,12 +1,16 @@
<!-- #region ConfigTabForm [C:4] [TYPE Component] [SEMANTICS translate, config, datasource, llm, columns] -->
<!-- @BRIEF Configuration tab content for Translation Job — datasource selection with auto-detected
DB dialect, column mapping, LLM provider, target languages (multi-select from LANGUAGES),
DB dialect, column mapping, LLM provider, target languages (multi-select from settings-allowed languages),
dictionaries, disable reasoning toggle. -->
<!-- @LAYER UI -->
<!-- @RELATION DEPENDS_ON -> [EXT:frontend:fetchDatasources] -->
<!-- @RELATION DEPENDS_ON -> [EXT:frontend:fetchDatasourceColumns] -->
<!-- @RELATION DEPENDS_ON -> [ApiModule.getAllowedLanguages] -->
<!-- @RELATION DEPENDS_ON -> [MultiSelect] -->
<!-- @RELATION BINDS_TO -> [EXT:frontend:LANGUAGE_LABELS] -->
<!-- @RELATION CALLS -> [filterLanguages]
<!-- @RATIONALE Target language list is now fetched from /settings/allowed-languages instead of static ALL_LANGUAGES.
This ensures only admin-configured languages (Settings → Languages) are shown. Falls back to full list on API error
or when allowed-languages is empty. -->
<!-- @UX_STATE Datasource searching -> Dropdown with filtered results, spinner on load -->
<!-- @UX_STATE Datasource selected -> Columns loaded, DB dialect detected, mapping ready -->
<!-- @UX_STATE Columns loading -> Skeleton pulse in column section -->
@@ -20,7 +24,8 @@
<!-- @UX_REACTIVITY LocalState -> datasourceList, datasourceLoading, showDatasourceDropdown -->
<script lang="ts">
import { getT } from '$lib/i18n/index.svelte.js';
import { ALL_LANGUAGES } from '$lib/i18n/languages.js';
import { ALL_LANGUAGES, filterLanguages } from '$lib/i18n/languages.js';
import { api } from '$lib/api.js';
import { fetchDatasources, fetchDatasourceColumns } from '$lib/api/translate.js';
import MultiSelect from '$lib/components/ui/MultiSelect.svelte';
import HelpTooltip from '$lib/ui/HelpTooltip.svelte';
@@ -59,9 +64,28 @@
let showDatasourceDropdown = $state(false);
let isColumnsLoading = $state(false);
let columnList = $derived(availableColumns);
let allowedLanguageCodes: string[] = $state([]);
// @RATIONALE Svelte 5 store detection fails for `$t` in deeply nested templates.
// Use getT() + $derived to keep reactivity to locale changes.
const _t = $derived(getT());
// Language options filtered by backend-allowed codes (fallback to full list)
let languageOptions = $derived(
allowedLanguageCodes.length > 0
? filterLanguages(allowedLanguageCodes)
: ALL_LANGUAGES
);
// Load allowed languages from settings once on mount
$effect(() => {
api.getAllowedLanguages().then((codes) => {
if (Array.isArray(codes) && codes.length > 0) {
allowedLanguageCodes = codes;
}
}).catch(() => {
// Non-critical: fall back to full list
allowedLanguageCodes = [];
});
});
// ---- Derived ----
let logicalColumns = $derived(availableColumns.filter(c => c.is_physical !== false));
@@ -202,7 +226,7 @@
</label>
<select
bind:value={environmentId}
onchange={onEnvChange}
onchange={(e) => onEnvChange(e.target.value)}
class="w-full px-3 py-2 border border-border-strong rounded-lg text-sm focus-visible:ring-2 focus-visible:ring-primary-ring focus-visible:border-primary-ring"
>
<option value="">{_t.translate?.config?.select_environment}</option>
@@ -443,7 +467,7 @@
</div>
<MultiSelect
label=""
options={ALL_LANGUAGES}
options={languageOptions}
bind:selected={targetLanguages}
searchable={true}
placeholder={_t.translate?.config?.target_language_search_placeholder || 'Search languages...'}

View File

@@ -173,7 +173,7 @@
availableDictionaries={m.availableDictionaries}
bind:disableReasoning={m.disableReasoning}
validationErrors={m.validationErrors}
onEnvChange={(e) => m.handleEnvChange(e.detail)}
onEnvChange={(envId) => m.handleEnvChange(envId)}
/>
</div>
@@ -245,11 +245,11 @@
completedRuns={m.completedRuns}
expandedRunIds={m.expandedRunIds}
bind:showPageBulkReplace={m.showPageBulkReplace}
onTriggerRun={m.handleTriggerRun}
onRetryRun={m.handleRetryRun}
onRetryInsert={m.handleRetryInsert}
onLoadRunHistory={m.loadRunHistory}
onToggleRunDetails={m.toggleRunDetails}
onTriggerRun={(full) => m.handleTriggerRun(full)}
onRetryRun={() => m.handleRetryRun()}
onRetryInsert={() => m.handleRetryInsert()}
onLoadRunHistory={() => m.loadRunHistory()}
onToggleRunDetails={(id) => m.toggleRunDetails(id)}
onBulkReplaceApplied={() => { m.showPageBulkReplace = false; m.loadRunHistory(); }}
onBulkReplaceClose={() => m.showPageBulkReplace = false}
/>