- Batteries: one card per battery with On/Off, the mode list (cheap hours and selling greyed out until OPT-11 P3), level, and Now / Today / Why lines built in homeview.py. Replaces the OPT-11 P1 minimal cards and uses the existing pack_use / pack_mode actions and store keys. - An Off battery the peak limit still needs says so with its figure; no line shows 0 W beside moving power (OPT-11 P1 test F2). Today says "Not planned yet" until OPT-11 P3/P4. - Home's plan leaves Off batteries out, except for the peak limit. - Settings opens on Simple: the layout's simple fields with an applies chip and a note, plus the battery switches and modes. Installer is one tap. - English and Dutch words in app/ui_text; test_batteries_screen.py; browser check tools/ux13_browser_check.py (CHECK=1 ux13_screenshots.sh); new and changed screenshots only. No control, allocator, arbiter or command module changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UBtqPrG1mDLXzTEBnhEkkV
5.9 KiB
UX-13 — one layout table, and why UI code may now name an option key
Status: DECIDED 2026-09-27 (owner approved the UX-13 design and mockup).
Card 6ab979852f3eca02fb40f69e. Amends UX-02 §3.1. Design note and
mockup: goodwe-session/UX-13-design.md, UX-13-mockup.html.
1. What UX-02 said, and why it no longer fits
UX-02 built the configuration panel from the Supervisor's schema, so
app/configui.py contains "no option key, no label and no rule", and
test_selfconfig.py part 5 checks that the page script names no key. That kept
a second copy of the option surface from drifting, and it is why the panel is
a flat list of fields in schema order.
UX-13 needs things a schema cannot say: groups and sub-groups, a slider in kW
for a value stored in W, a picker for an entity, a field shown only for one
meter source, locked measured values, a Simple view and a setup wizard. It
also needs page fields that are not options at all: one inverter card that is
stored as six flat keys, solar rows that become pv_entity or pv_entities,
a contract and two prices that become static_profile JSON, and a region
that becomes the levies figure. None of that can be read off the schema, so
some code has to name option keys.
2. The decision
Option keys may appear in UI code in exactly two files:
app/uilayout.py, the layout table: screen → group → part → field, with control, unit, display scale, step, entity kind, when it is shown, whether it is on the Simple view or a wizard step, and whether it applies now or after a restart. Every option inconfig.yamlis a field,DERIVEDorHIDDEN, exactly once.app/uimodel.py, the derivations the table names:options_to_ui()andui_to_options(). It may name only the keys it writes.
What UX-02 made authoritative stays authoritative:
- Ranges come from the schema (
uilayout.range_of()); the table writes none. A unit holds no digit. - Labels and help come from
translations/*.yamlby key. Page-only fields, group titles and choices are inapp/ui_text/*.json, held to STYLE by test_messages.py. - Rules stay in
validate.py. It gains a warning tier (validate.Note, "warn" or "info") for the page. A note never blocks a save, and passing the notes list does not change the refusals returned. - The stored blob is still the one truth. The page model carries the stored
values of every derived key (
keep). A derivation whose page fields are unchanged writes those values back untouched, so a blob the page cannot express survives a save of another field.
3. How it is held
- test_uilayout.py fails when
config.yamlgains an option, or an inverter field, that the table does not place. It also checks that uimodel.py names only the keys it writes and that web.py and haentities.py name none. - test_uimodel.py round-trips options → page model → JSON → options for every
validator call the suite made at
652097a(scenarios/ux13/), the TEST-23 option files and config.yaml's defaults. All must be identical. tools/ux13_validator_calls.py compareruns those calls through the old and new validator. At P0: 261 calls, 158 refused, no refusal changed.- test_selfconfig.py part 5 still checks the old page script; P1 moves the page
into
app/static/and the same check applies there. - P2: the Installer view (
app/static/settings.js) draws fromapp/uipage.py, which serialises the table and names no key itself (test_uilayout part 5, test_settings part 2). The view posts its page model to the existing/config/validateand/config/save, which turn it into options withui_to_options()before the shipped validator runs. No route was added. - P4: the setup wizard draws the same model by each field's
wizardstep. Its tables (KETTLE,SHADOW) are inuilayout.py, and its live checks are rules, so they are invalidate.py(setup_checks). settings.js still names no key. See UX-13-setup-wizard. - P5: the page words are in
app/ui_text/en.jsonandnl.json, one key set, loaded byapp/uitext.pywith an English fallback per key. The page picks Dutch when the browser's language isnl, as the classic page does. Home's sentences moved fromhomeview.pyintohome_words, so/status?lang=nlbuilds them in Dutch. Status rows, banners, hints and validator sentences (messages.py,validate.py) stay English: they are shared with notifications, the log and the start refusal.test_ui_text.pychecks both files against STYLE.md and each other, using the rules intools/style_rules.py, which it shares with test_messages.py. - P5: "Other time windows" is a list editor (
ui.periods, controlperiod_list).uimodel._periods_json()buildsstatic_profileand names the first bad time, empty price, overlap or gap. Its tiling rule matchesstatic_tariff._check_tiling. A stored profile whose days the editor has no choice for (for example "mon,wed") is still kept as stored and edited in the Configuration tab. Supervisor 2026.08.0'sAppOptionsaccepted all 133 wizard blobs, 17 of them with time windows (tools/ux13_wizard_blobs.py --supervisor, run offline on 2026-09-28). - P3: Settings, Simple draws the fields the table marks
simple, with an optionalnoteunder each (words insettings.simple.note), plus the batteries' switches and modes. Those are store state set through the existing/actionvalues, not options, so the table places no key for them. The Batteries screen's Now, Today and Why lines are built inhomeview.pyfrom what the add-on knows this cycle; the plan per mode is OPT-11 P3/P4, so until then Today says "Not planned yet" (test_batteries_screen.py).
4. What this costs
The table is a second place that knows the option surface, which UX-02 set out to avoid. The cost is bounded by the coverage test: an option added to config.yaml and not placed here fails CI, rather than silently missing from the page. Changing a key's type or range needs no change here.