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
+5
View File
@@ -2,6 +2,11 @@
## Unreleased
- If the optional language pack (currently German) cannot be downloaded, the
card now says so with a toast instead of silently switching to English; the
locale runtime the card ships is now the same code the i18n test suite
proves ([#354](https://github.com/Matysh/houseplan-card/issues/354)).
- A stale or third-party client can no longer silently replace saved device
positions by sending a whole layout without its revision. Revision-less
bootstrap remains available only while the layout store is empty
+5
View File
@@ -8,6 +8,11 @@
## Не выпущено
- Если необязательный языковой пакет (сейчас — немецкий) не загрузился,
карточка говорит об этом тостом, а не молча переключается на английский;
рантайм локалей в продакшне теперь тот же код, что доказывает тестовый
набор i18n ([#354](https://github.com/Matysh/houseplan-card/issues/354)).
- Устаревший или сторонний клиент больше не может молча заменить сохранённые
позиции устройств, отправив весь layout без его ревизии. Инициализация без
ревизии остаётся доступной только пока хранилище layout пусто
Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 319 KiB

After

Width:  |  Height:  |  Size: 319 KiB

+13 -13
View File
@@ -3,7 +3,7 @@
"fixture": "synthetic-only",
"chromium": "151.0.7922.34",
"oxipng": null,
"sourceFingerprint": "20360427643d04e7068255e2a64cbdb296bfc772d8c11cfeb0baf753520addb5",
"sourceFingerprint": "f8b80b1f920fa0ecaa24abd1ae6c143df1921d892546a22a44831b2b688f7fd0",
"captureScriptSha256": "fe76c8e2e27501e9a377d7aa15dd6cba6a018d55c07deb602dbee097f0a0a5ba",
"command": "npm run build && node demo/docs/capture.mjs",
"scenarios": {
@@ -15,8 +15,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "20360427643d04e7068255e2a64cbdb296bfc772d8c11cfeb0baf753520addb5",
"imageSha256": "78d328498bd17a9ee1ec44b389d244b61fca9ac52c9aad6631aa85c53233fec0"
"sourceSha256": "f8b80b1f920fa0ecaa24abd1ae6c143df1921d892546a22a44831b2b688f7fd0",
"imageSha256": "0ca982495a7749907c48ebbddfd089c54385a4347f79f64e94af2c5a8d5c03eb"
},
"view-touch": {
"file": "02-view-touch.png",
@@ -26,7 +26,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "20360427643d04e7068255e2a64cbdb296bfc772d8c11cfeb0baf753520addb5",
"sourceSha256": "f8b80b1f920fa0ecaa24abd1ae6c143df1921d892546a22a44831b2b688f7fd0",
"imageSha256": "3be239a5a494f6776540a9c0049694ee6f9d2ba927c8bcb4b26ee5a69a228a7d"
},
"space-create": {
@@ -37,7 +37,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "20360427643d04e7068255e2a64cbdb296bfc772d8c11cfeb0baf753520addb5",
"sourceSha256": "f8b80b1f920fa0ecaa24abd1ae6c143df1921d892546a22a44831b2b688f7fd0",
"imageSha256": "a55b463055a565892ca139014e314a8f63fb5301c87fff2e48d7e69210fe9b6c"
},
"room-contour-close": {
@@ -48,7 +48,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "20360427643d04e7068255e2a64cbdb296bfc772d8c11cfeb0baf753520addb5",
"sourceSha256": "f8b80b1f920fa0ecaa24abd1ae6c143df1921d892546a22a44831b2b688f7fd0",
"imageSha256": "dfad9a27371cfa397a3fd8dce7005f42d24649743f5322105ee894067d3a9f16"
},
"plan-context-tray": {
@@ -59,7 +59,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "20360427643d04e7068255e2a64cbdb296bfc772d8c11cfeb0baf753520addb5",
"sourceSha256": "f8b80b1f920fa0ecaa24abd1ae6c143df1921d892546a22a44831b2b688f7fd0",
"imageSha256": "b75f348c632296da5c2fc142d53df613f757e1516acac315be50f5b4557729cc"
},
"device-editor": {
@@ -70,7 +70,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "20360427643d04e7068255e2a64cbdb296bfc772d8c11cfeb0baf753520addb5",
"sourceSha256": "f8b80b1f920fa0ecaa24abd1ae6c143df1921d892546a22a44831b2b688f7fd0",
"imageSha256": "8affadfce926fe2f6cf344c33f611c44fcbc79c1969f69c3a6935ff2e99c36c3"
},
"device-display-preview": {
@@ -81,8 +81,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "20360427643d04e7068255e2a64cbdb296bfc772d8c11cfeb0baf753520addb5",
"imageSha256": "6f628abe6b89d7c4b352e6d73ac0b126526f6fdba5927bb774c5b884e484ca19"
"sourceSha256": "f8b80b1f920fa0ecaa24abd1ae6c143df1921d892546a22a44831b2b688f7fd0",
"imageSha256": "6d323b6f641b2cc7db64252f70567348bd3f2551f82e61fd3a2b4674d244918e"
},
"background-editor": {
"file": "07-background-editor.png",
@@ -92,7 +92,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "20360427643d04e7068255e2a64cbdb296bfc772d8c11cfeb0baf753520addb5",
"sourceSha256": "f8b80b1f920fa0ecaa24abd1ae6c143df1921d892546a22a44831b2b688f7fd0",
"imageSha256": "a1d1a905f1410f73231d2ea5d1dfbd8ae79c2fe8b0a79cff87ebb977ac7a3103"
},
"room-card": {
@@ -103,7 +103,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "20360427643d04e7068255e2a64cbdb296bfc772d8c11cfeb0baf753520addb5",
"sourceSha256": "f8b80b1f920fa0ecaa24abd1ae6c143df1921d892546a22a44831b2b688f7fd0",
"imageSha256": "48a5685039a5e0de2a28190d857b193c86e2e63606e3c7a8c40fc9d2b190da4e"
},
"device-info": {
@@ -114,7 +114,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "20360427643d04e7068255e2a64cbdb296bfc772d8c11cfeb0baf753520addb5",
"sourceSha256": "f8b80b1f920fa0ecaa24abd1ae6c143df1921d892546a22a44831b2b688f7fd0",
"imageSha256": "29a342811aaedaebc54ba337792d60cb454a7cf4c3063b210f6c4bafe0a4d87b"
}
}