- Settings tab: the Installer view (app/static/settings.js), drawn from
app/uilayout.py through the new app/uipage.py: search, six collapsible
groups with "n to fix / n to check / n changed" badges and an "applies
after a restart" chip, radio cards, segmented choices, sliders with units
(the peak limit in kW), entity pickers with search and the live reading,
inverter cards, solar rows, locked measured values behind "Change", and
the add-on's refusals, warnings and info beside the field they name.
- Save path: the page posts its model to the existing config/validate and
config/save; uimodel turns it into options before the shipped validator,
so nothing it refuses is saved. No route added. A value equal to the
stored one is written as stored, so a save that changed nothing writes an
identical blob. Save bar, then the restart dialog that names 0 W.
- The region choice shows its preset year; Wallonia and Brussels are listed
as having no preset yet (TARIFF-22). "Classic settings" keeps /config.
- P1 test findings: (a) "Going to 0 W" until the last accepted power request
is 0 W; (b) the trade blocks and the price line name the feed's own points
("Lowest published price"), the hour bars say "hour average"; (c) the entity
list's WARNING gives a reason on a timeout.
- test_settings.py; test_home, test_haentities, test_uilayout extended.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LyR88rXETvwhTgx6yHE39p
76 lines
4.0 KiB
Markdown
76 lines
4.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.
|
|
|
|
## 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.
|