- The peak limit's Why and plan block need a real power request (floor above 0 W), not a blind or floor-limited guard. An Off battery gets the peak block only while it gives power. Why no longer says every battery gives. - test_batteries_screen pins the fixture clock: unpinned it failed every morning. New checks cover the trade and cycle filters, the blind guard and an Off battery that A covers alone. - Settings keeps only a picked view; a link to Installer is not remembered. - Browser check: badges, unsaved change across views, locked value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
105 lines
6.0 KiB
Markdown
105 lines
6.0 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).
|
|
- P3: Settings, Simple draws the fields the table marks `simple`, with an
|
|
optional `note` under each (words in `settings.simple.note`), plus the
|
|
batteries' switches and modes. Those are store state set through the
|
|
existing `/action` values, not options, so the table places no key for
|
|
them. The Batteries screen's Now, Today and Why lines are built in
|
|
`homeview.py` from what the add-on knows this cycle; the plan per mode is
|
|
OPT-11 P3/P4, so until then Today says "Not planned yet" when no block
|
|
is planned for the battery (`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.
|