main.qml mixed two things under one `parse*` prefix: turning raw CLI JSON into a validated, bounded structure, and writing that structure onto root properties while managing error text, the loading flag, notification memos and fallback accounting. Only the first half is pure, and only the first half was testable. The pure half moves to contents/ui/ProviderNormalizer.js: the collection bounds, the object-key guards, provider-id screening, payload shape checks, the usage-percentage clamps, and the cost/session/account normalizers. The parse*/state half stays in main.qml and calls into it. The boundary is mechanical - nothing in the new file touches root, a QML property, Qt, Kirigami or i18n. That last exclusion is load-bearing: the file is in the gettext extraction glob, so rateWindowMetrics returns clamped numbers and main.qml still supplies the translated label. The bounds now have one source of truth. main.qml binds its properties to the module rather than repeating 256/128/256/24, so a future change cannot drift the constant away from the call sites the security check asserts. tests/tst_provider_normalizer.qml adds 58 QtTest cases against the payloads a broken or hostile CLI could emit: __proto__/prototype/constructor keys, arrays past every bound, out-of-range and absent percentages, unsupported sessions shapes, alias collisions, off-host status URLs, and unicode that must not be re-sliced. Two of them pin a pre-existing gap rather than fix it - SafeText.boundedDisplayText coerces structured values, so a malformed displayName still reaches the popup as "[object Object]". Changing that is a behavior change and belongs in its own commit; the tests are written so that commit has to update an assertion deliberately. Static assertions that were pinned to main.qml follow the code instead of being weakened: the payload-shape rule now names normalizeProviderConfigEntries and normalizeSessions, and the pace guard names rateWindowMetrics, with added coverage that the percentage clamps survive. Nothing was relaxed to make the checks pass. make check: 143 QML tests pass, up from 85. main.qml drops 4522 -> 4211 lines. Verified at runtime with ./install.sh; plasmashell logs show no ReferenceError, TypeError or SyntaxError. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Simone committed on 8/18/2026, 11:15:59 AMparent1f565f1
6 files changedLine totals unavailable