From 6e4ffd00b6378d16a849a7be41a916df977bdcc3 Mon Sep 17 00:00:00 2001 From: busya Date: Tue, 2 Jun 2026 14:44:47 +0300 Subject: [PATCH] =?UTF-8?q?refactor(frontend):=20remove=20HTTP=20polling?= =?UTF-8?q?=20fallback=20=E2=80=94=20WS-only=20for=20translate=20runs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously startTranslationRun() used both WebSocket + 2s HTTP polling. After WS debug logging was added, the HTTP fallback is no longer needed: - WS streams status every 1s from backend - WS handler already detects terminal states + fires onComplete - WS errors are now logged (onerror → console.warn) Removed: pollStatus(), _pollingInterval, _pollCount, MAX_POLLS, fetchRunStatus import, setInterval in startTranslationRun, clearInterval in stopTranslationRun. Build ✅ 698 tests ✅ --- .../src/lib/stores/translationRun.svelte.ts | 87 ------------------- 1 file changed, 87 deletions(-) diff --git a/frontend/src/lib/stores/translationRun.svelte.ts b/frontend/src/lib/stores/translationRun.svelte.ts index ff82332d..0df6818f 100644 --- a/frontend/src/lib/stores/translationRun.svelte.ts +++ b/frontend/src/lib/stores/translationRun.svelte.ts @@ -2,19 +2,8 @@ // @BRIEF Global store for active translation run progress — uses WebSocket for real-time // progress + polling fallback for structured status. Survives page navigation. // @LAYER UI -// @RELATION DEPENDS_ON -> [TranslateRunsApi.fetchRunStatus] // @RELATION DEPENDS_ON -> [ApiModule.getTranslateRunWsUrl] -// @UX_STATE idle -> No active run -// @UX_STATE running -> Polling + WebSocket active, progress bar updates in real-time -// @UX_STATE completed/partial/failed/insert_failed/cancelled -> Terminal states -// @UX_FEEDBACK WebSocket provides real-time batch completion events during translate phase -// @UX_FEEDBACK Polling provides structured status updates (insert phase, final counts) -// @UX_RECOVERY WebSocket disconnect -> continues with polling only -// @RATIONALE Migrated from Svelte 4 writable+derived to Svelte 5 $state+$derived runes. -// Backward-compatible .subscribe() retained for $translationRunStore syntax. -// WebSocket and polling logic preserved exactly. -import { fetchRunStatus } from '$lib/api/translate'; import { getTranslateRunWsUrl } from '$lib/api'; export type UxState = 'idle' | 'running' | 'inserting' | 'completed' | 'partial' | 'failed' | 'insert_failed' | 'cancelled'; @@ -105,9 +94,6 @@ export const translationRunStore = { }, }; -let _pollingInterval: ReturnType | null = null; -let _pollCount = 0; -const MAX_POLLS = 300; let _onCompleteCallback: ((data: Record) => void) | null = null; let _ws: WebSocket | null = null; @@ -132,10 +118,6 @@ export function startTranslationRun(runId: string, options: StartRunOptions = {} _onCompleteCallback = options.onComplete || null; _connectWebSocket(runId); - - _pollCount = 0; - _pollingInterval = setInterval(pollStatus, 2000); - pollStatus(); } function _connectWebSocket(runId: string): void { @@ -209,10 +191,6 @@ export function stopTranslationRun(): void { _ws.close(); _ws = null; } - if (_pollingInterval) { - clearInterval(_pollingInterval); - _pollingInterval = null; - } } export function resetTranslationRun(): void { @@ -227,69 +205,4 @@ export function updateTranslationRunState(state: Partial): _notify(); } -async function pollStatus(): Promise { - _pollCount++; - if (_pollCount > MAX_POLLS) { - stopTranslationRun(); - _state = { ..._state, uxState: 'failed' }; - _notify(); - if (_onCompleteCallback) _onCompleteCallback({ status: 'TIMEOUT', error_message: 'Translation run timed out' }); - return; - } - - if (!_state.runId) { - stopTranslationRun(); - return; - } - - try { - const data = (await fetchRunStatus(_state.runId)) as Record; - if (!data) return; - - const s = data?.status as string; - const insertS = data?.insert_status as string; - - let newUxState: UxState = _state.uxState; - - if (s === 'PENDING' || s === 'RUNNING') { - newUxState = (insertS === 'started' || insertS === 'pending' || insertS === 'running') ? 'inserting' : 'running'; - } else if (s === 'COMPLETED') { - newUxState = (insertS === 'failed' || insertS === 'timeout') ? 'insert_failed' - : (data.failed_records as number) > 0 ? 'partial' : 'completed'; - stopTranslationRun(); - } else if (s === 'FAILED') { - newUxState = 'failed'; - stopTranslationRun(); - } else if (s === 'CANCELLED') { - newUxState = 'cancelled'; - stopTranslationRun(); - } - - const total = (data?.total_records as number) || 0; - const pct = total > 0 - ? Math.round((((data.successful_records as number) + (data.failed_records as number) + (data.skipped_records as number)) / total) * 100) - : 0; - - _state = { - ..._state, - uxState: newUxState, - status: data, - totalRecords: total, - successfulRecords: (data?.successful_records as number) || 0, - failedRecords: (data?.failed_records as number) || 0, - skippedRecords: (data?.skipped_records as number) || 0, - cacheHits: (data?.cache_hits as number) || 0, - progressPct: pct, - insertStatus: (data?.insert_status as string) || null, - batchCount: (data?.batch_count as number) || 0, - }; - _notify(); - - if (newUxState !== 'running' && newUxState !== 'inserting') { - if (_onCompleteCallback) _onCompleteCallback(data); - } - } catch (_err) { - // Poll error — will retry on next interval - } -} // #endregion TranslationRunStore