- app/ui_text/nl.json and app/uitext.py: every page word in Dutch, English fallback per key; the page picks by browser language. Home's sentences move into ui_text `home_words`, so /status?lang=nl builds them in Dutch. - test_ui_text.py: EN/NL key and placeholder parity, STYLE limits, jargon lists for both languages, no inline details, untranslated Dutch caught. Shared rules moved unchanged to tools/style_rules.py. - tools/ux13_screenshots.sh/.py: DOCS screenshots from the demo fixtures in headless Chromium; docs/img/ux13/. Battery modes (P3): no screenshot. - "Other time windows": a list editor (ui.periods) that builds static_profile and names bad times, empty prices, overlaps and gaps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
97 lines
5.4 KiB
Markdown
97 lines
5.4 KiB
Markdown
# 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 in `config.yaml` is a field, `DERIVED` or `HIDDEN`,
|
|
exactly once.
|
|
- **`app/uimodel.py`**, the derivations the table names: `options_to_ui()` and
|
|
`ui_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/*.yaml` by key. Page-only fields,
|
|
group titles and choices are in `app/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.yaml` gains 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 compare` runs 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 from
|
|
`app/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/validate` and `/config/save`, which turn it into
|
|
options with `ui_to_options()` before the shipped validator runs. No route
|
|
was added.
|
|
- P4: the setup wizard draws the same model by each field's `wizard` step.
|
|
Its tables (`KETTLE`, `SHADOW`) are in `uilayout.py`, and its live checks
|
|
are rules, so they are in `validate.py` (`setup_checks`). settings.js still
|
|
names no key. See [[UX-13-setup-wizard]].
|
|
- P5: the page words are in `app/ui_text/en.json` and `nl.json`, one key set,
|
|
loaded by `app/uitext.py` with an English fallback per key. The page picks
|
|
Dutch when the browser's language is `nl`, as the classic page does. Home's
|
|
sentences moved from `homeview.py` into `home_words`, so `/status?lang=nl`
|
|
builds 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.py` checks both
|
|
files against STYLE.md and each other, using the rules in
|
|
`tools/style_rules.py`, which it shares with test_messages.py.
|
|
- P5: "Other time windows" is a list editor (`ui.periods`, control
|
|
`period_list`). `uimodel._periods_json()` builds `static_profile` and names
|
|
the first bad time, empty price, overlap or gap. Its tiling rule matches
|
|
`static_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's `AppOptions` accepted all 133
|
|
wizard blobs, 17 of them with time windows
|
|
(`tools/ux13_wizard_blobs.py --supervisor`, run offline on 2026-09-28).
|
|
|
|
## 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.
|