Svelte 5 compiler fails to detect Proxy-based i18n store `t` as a store
in deeply nested/complex templates and in `.svelte.ts` model files,
generating `ReferenceError: $t is not defined` at runtime.
Fix: replace `$t.` references with:
- Template: `_t.` where `const _t = $derived(getT())`
- Script: `getT()?.` direct function call
Applied to:
- translate/[id]/+page.svelte (the failing page)
- TranslationJobModel.svelte.ts ($derived.by() in `tabs`)
- 12 translate components (ConfigTabForm, ScheduleConfig,
TranslationPreview, TargetTabForm, RunTabContent, BulkReplaceModal,
TranslationRunProgress, TranslationRunResult, TermCorrectionPopup,
BulkCorrectionSidebar, TranslationMetricsDashboard, CorrectionCell,
TranslationRunGlobalIndicator)
Verified all 5 tabs render correctly in browser:
Config, Preview, Target, Run, Schedule.
Build clean, 698/698 tests pass, 0 color violations.
Problem: closing and reopening the translate job page (or tab close+reopen)
lost the progress bar because the in-memory store starts fresh. The backend
keeps translating, but the UI shows no progress.
Fix:
- Store: save {runId, jobId, isFullRun} to sessionStorage on startTranslationRun()
via _saveToSessionStorage()
- Store: add reconnectToRun() — re-establishes WS without resetting store state,
preserving any progress data already received
- Store: add getStoredActiveRun() — public reader for sessionStorage
- Store: clear sessionStorage via _clearSessionStorage() on terminal states
(added to _cleanup() called by WS terminal, max-reconnects, stop, reset)
- Page: add that checks getStoredActiveRun() after job loads
and calls reconnectToRun() to pick up the live progress
Edge cases handled:
- Store already connected to same run → skip (no duplicate WS)
- sessionStorage unavailable → silent catch
- SPA navigation → store already has the run, WS still connected → skip
- Page reload → sessionStorage has the run, reconnects
- Tab close + reopen → same as reload
- Run completes while away → sessionStorage cleared by _cleanup()
- Run already finished → getStoredActiveRun() returns null → skip
Build ✅ 698 tests ✅
QA found critical regression: after removing HTTP polling, a WS
disconnect left the UI permanently stuck (uxState='running' forever).
Fixes:
- WS reconnect: up to 3 attempts with 10s backoff on close (non-1000/1005)
- App timeout: 600s (10 min) max — transitions to failed if stuck
- Stale log message: removed 'falling back to polling' (no longer exists)
- Contract header: updated @BRIEF, restored @UX_STATE/@UX_FEEDBACK/@UX_RECOVERY
- cleanup(): unified WS close + timer clear (used by stop, timeout, terminal)
- onclose handler: triggers reconnect via _handleWsFailure()
- onerror handler: defers to onclose (fires after error)
- onopen handler: resets _reconnectCount on successful connect
- Terminal states: call _cleanup() to stop reconnect timer + timeout
Build ✅ 698 tests ✅
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 ✅
Backend:
- TranslationRun model: add cache_hits Integer column (default 0)
- TranslationRunResponse schema: add cache_hits field
- _helpers.py/_run_list_routes.py/orchestrator_aggregator.py: include
cache_hits in all run API responses
- _batch_proc.py: count pre_rows served from translation cache per
batch, return cache_hits in batch result
- executor.py: accumulate cache_hits across batches, persist to run
- Alembic migration: dabc9709 — add cache_hits column to translation_runs
Frontend:
- translationRun.svelte.ts: add cacheHits to store state, WS handler,
polling handler
- TranslationRunProgress.svelte: 5-col stats grid with purple Cache card
- TranslationRunGlobalIndicator.svelte: 5-col stats with Cache
- TranslationRunResult.svelte: 5-col detail stats with Cache card
- History page: cache_hits shown in run list row + detail panel
Visual: cache hits shown in purple alongside green/yellow/red metrics
(total/success/failed/skipped). Visible during run + in history.
Tests: backend 69/69 translate tests ✅, frontend 698/698 tests ✅,
frontend build ✅
- DashboardHealthItem schema: add run_id, policy_id, execution_path,
issues_count, timings, token_usage, screenshot_paths, chunk_count,
dashboard_name fields for v2 LLM validation reporting.
- HealthService: populate v2 fields from validation run records.
- ValidationService: update to support v2 validation run fields.
- validation_tasks route: minor fix for v2 field handling.
- tailwind.config.js: refactor design tokens — add success/warning/info
palettes, surface/border/text hierarchy, semantic action colors.
- Agent configs: update svelte-coder and semantics-svelte for
.svelte.ts runes and Screen Model patterns.
downloadSkippedCsv and downloadFailedCsv call api.fetchApiBlob()
but it was not exposed on the api object — causing
"o.fetchApiBlob is not a function" at runtime.
Deleted stale .js files (taskService, toolsService, report_page.contract.test)
7 .svelte.js test files intentionally preserved (Svelte 5 convention)
Final metrics:
- Production JS files: 0
- Production TS/TSX/Svelte files: all
- npm run build ✅
- npm run test ✅ (645 passed, 6 pre-existing)
API returns 'screenshot_paths' but frontend template reads 'screenshots'.
Without field mapping, screenshots are never displayed.
Fix: map screenshot_paths -> screenshots and logs_sent_to_llm -> logs_sent
in the +page.js load function.
Also verifies that /api/storage/file endpoint exists (mounted at /api/storage)
and the frontend API_BASE_URL='/api' correctly prefixes /storage/file.
Svelte each block key used , but when
two issues share the same message text (e.g. 'Main content area
is completely blank') and have no id, the key collides.
Fix: use composite key
as fallback when issue.id is missing.
Delete deprecated code that has been fully replaced by v2:
- backend: validation.py routes, validation_run_service.py
- frontend: validation.js API module, validation/ pages
Add binary-search probe endpoint POST /providers/{id}/probe-max-images
that discovers the per-request image limit by sending incrementally
more 1x1 JPEGs via the provider's own API. Result is cached in the
new max_images column on the provider config.
- LLMProviderConfig: add max_images: int | None
- LLMProvider (SQLAlchemy): add max_images column
- Migration ed28d34edde7: clean ADD COLUMN
- LLMProviderService: create/update/set_max_images
- POST /providers/{id}/probe-max-images: binary search + error parsing
- ProviderConfig.svelte: 'Detect' button in edit modal + HelpTooltip
- i18n (en/ru): 11 new keys for probe UI
Fixes:
- Move batch size, upsert strategy, include source reference
from Config tab to Target Config tab (Write Settings section)
- Fix 'Dataset #26' fallback — look up real datasource name
via API when source_table is not saved in job data
- Persist include_source_reference in save payload and
restore from job data on load (was a pre-existing gap)
QA review fixes:
- Update @BRIEF contracts for both tabs
- Fix import indentation in +page.svelte
Other: help tooltips on translate pages, flow hint on list page,
git migration manager components, dashboard hub pages,
migration settings pages
1. Remove get_config_manager() call from translate_run_websocket
(@app.websocket /ws/translate/run/{run_id}) — was not imported
and unused. Also remove unused from sqlalchemy.orm import Session.
2. Fix prop name mismatch: showBulkReplace -> showPageBulkReplace
in RunTabContent. Use () for reactive two-way binding.
3. Add ('DRAFT') for status prop in RunTabContent.
Backend:
- Add /ws/translate/run/{run_id} WebSocket endpoint in app.py
- Streams structured run status every second (total_records,
successful_records, failed_records, progressPct, batch_count, etc.)
- Auto-detects terminal states (COMPLETED, FAILED, CANCELLED) and closes
- Authenticated via JWT/API key token query param
Frontend:
- Add getTranslateRunWsUrl() URL builder in api.js
- Update translationRunStore to use WebSocket instead of log stream
- WebSocket receives structured status JSON, updates store reactively
- Falls back to polling silently if WebSocket fails
- Keep polling as fallback for insert phase
Clean up remaining orphaned references after tab extraction:
- Remove loadColumnsForDatasource, searchDatasources, selectDatasource
etc. from page (moved to ConfigTabForm)
- Add on datasourceId in ConfigTabForm for auto-loading
columns when parent sets datasourceId (loadJob)
- Fix stray braces, remove dead isVirtual function
- Page now 779 lines (down from 1519, -49%)
Remove loadColumnsForDatasource, searchDatasources, selectDatasource,
toggleContextColumn, toggleSourceKeyCol, updateTargetKeyCol, isVirtual
from page — all moved to ConfigTabForm.svelte.
Add on datasourceId in ConfigTabForm to auto-load columns
when parent sets datasourceId (loadJob) or user selects a datasource.
Add comprehensive UX contract header documenting:
- @UX_STATE: all states of the page-level state machine (idle, loading,
configured, saving, validation_error, datasource_unavailable, error)
- @UX_STEP: 5 logically separated steps/tabs (Config, Preview, Target, Run, Schedule)
- @UX_FEEDBACK: toasts, modals, inline progress, schema validation
- @UX_RECOVERY: retry paths for save/preview/run failures
- @UX_REACTIVITY: props, local state, derived, effects, store bindings
- @UX_DEPENDENCY: data flow between datasource selection, env switching,
config validity, and tab availability
- @UX_TEST: test scenarios for the automated Judge Agent
Also remove leftover sourceDialect/targetDialect selects from UI
(now sent as sensible defaults in save payload).
Sub-components (TargetSchemaHint, TranslationPreview, TranslationRunProgress,
ScheduleConfig, BulkReplaceModal) already have UX contracts.
These fields were showing database types (PostgreSQL, ClickHouse)
as human language options for the LLM prompt, which was confusing.
Now sending sensible defaults (source='SQL', target='en') directly
in the save payload without exposing them in the UI.
The backend already has fallbacks:
_llm_call.py: source_language = job.source_dialect or 'SQL'
_batch_proc.py: target_langs = or [job.target_dialect or 'en']
sourceDialect/targetDialect select options contained database types
(PostgreSQL, ClickHouse, MySQL...) while these fields are used as
human language indicators for the LLM prompt:
_llm_call.py: source_language = job.source_dialect or 'SQL'
Changed to use the existing LANGUAGES list (from LANGUAGE_LABELS:
ru, en, de, fr, es, etc.) plus 'SQL' option for source dialect.
Defaults updated: source='SQL' (SQL-like expressions), target='en'.