fix: the locale runtime the card ships is the code the tests prove (#354)

The production LANGUAGE_RUNTIME was a handwritten twin of the tested
LanguageRuntime class (germanDictionary/Pending/Failed): equivalent on the
day it was written, invisible to every i18n-runtime test afterwards. The
registry now exports one page-scoped `new LanguageRuntime(LANGUAGE_REGISTRY,
…)` instance — the whole existing suite starts proving the object production
actually runs, and a contract unit (instanceof + source free of the old
field names) keeps the duplicate from returning.

The class gains an optional `loadFailed(code)` hook — fired once when a
dictionary load settles into English fallback — and the registry fans it out
through `subscribeLanguageLoadFailures`. Only the View card subscribes (it
alone owns toast infrastructure): a failed language pack now shows the new
`toast.locale_load_failed` message (en/ru/de) instead of a console-only
warning; space card and both GUI editors keep the console warning as before.

Proofs: contract unit, hook unit, subscription unit; smoke_german_locale
extended — the both-attempts-failed scenario now asserts the visible toast;
two new registry mutants (handwritten-twin returns, toast dropped).

Issue: #354
User-Visible: yes
This commit is contained in:
Codex
2026-08-28 17:49:59 +03:00
parent 468eacdb3b
commit 1397afefd5
30 changed files with 331 additions and 213 deletions
+39
View File
@@ -634,6 +634,45 @@ const MUTANT_DEFINITIONS = [
replace: ' // mutant: pointer capture removed\n const plan = resolution.plan;',
}],
},
{
id: 'language-runtime-handwritten-duplicate',
guard: 'node --test --test-name-pattern="#354" test/i18n-runtime.test.mjs',
because: 'a handwritten twin of LanguageRuntime looks equivalent today and rots tomorrow — '
+ 'the i18n suite must prove the object production actually runs (#354 К1/К3)',
patches: [{
file: 'src/i18n/registry.ts',
find: 'export const LANGUAGE_RUNTIME: LanguageRuntimeContract = new LanguageRuntime(\n'
+ ' LANGUAGE_REGISTRY,\n'
+ ' BUILD_FINGERPRINT,\n'
+ ' console.warn,\n'
+ ' (code) => {\n'
+ ' for (const listener of languageLoadFailureListeners) listener(code);\n'
+ ' },\n'
+ ');',
replace: 'const twin = new LanguageRuntime(LANGUAGE_REGISTRY, BUILD_FINGERPRINT, '
+ 'console.warn, (code) => {\n'
+ ' for (const listener of languageLoadFailureListeners) listener(code);\n'
+ '});\n'
+ 'export const LANGUAGE_RUNTIME: LanguageRuntimeContract = {\n'
+ ' state: (code) => twin.state(code),\n'
+ ' dictionary: (code) => twin.dictionary(code),\n'
+ ' ensure: (code) => twin.ensure(code),\n'
+ '};',
}],
},
{
id: 'locale-failure-toast-dropped',
guard: 'node demo/smoke_german_locale.mjs',
because: 'a failed dictionary must be visible on the View card, not only a console line — '
+ 'a German user on a stale tab silently reading English is the N7 hole (#354 К2)',
patches: [{
file: 'src/houseplan-card.ts',
find: " this._languageFailureUnsub = subscribeLanguageLoadFailures(() => {\n"
+ " this._showToast(this._t('toast.locale_load_failed'));\n"
+ ' });',
replace: ' this._languageFailureUnsub = undefined;',
}],
},
{
id: 'lazy-loader-network-failure-terminal',
guard: 'node --test --test-name-pattern="#353 AC1" test/editor-runtime-loader.test.mjs',