UX-13 P5: Dutch text, STYLE check, DOCS screenshots, period editor
checks / unit (pull_request) Successful in 10m45s
infra-pin / fresh (pull_request) Successful in 1s

- 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>
This commit is contained in:
Claude dev agent
2026-09-28 16:33:32 +02:00
parent 8c34647d05
commit 667b8e9145
38 changed files with 1633 additions and 300 deletions
+19 -3
View File
@@ -1,7 +1,8 @@
"""UX-13: serve the real page and routes over a fixture controller, for a
headless browser check. `python3 tools/ux13_demo.py STATE PORT [HOST]`
STATE is running, shadow or alarm. The controller is the shipped `Controller`
STATE is running, shadow, alarm, new (P4) or setup (UX-15's setup-only
mode, P5). The controller is the shipped `Controller`
with its readings set by hand (two batteries, solar, today's prices in the
mockup's shape, trading on). Nothing here talks to Home Assistant or to an
inverter: `/action` changes only this fixture. Used for the P1 render check
@@ -35,6 +36,7 @@ from aiohttp import web as aioweb # noqa: E402
from app import homeview, web # noqa: E402
from app.main import Controller # noqa: E402
from app.setuponly import SetupOnly # noqa: E402
from app.peakguard import PeakDecision # noqa: E402
from app.price import PricePoint, PriceProduct, PriceRead # noqa: E402
from app.static_tariff import TariffPlan # noqa: E402
@@ -203,8 +205,9 @@ class Supervisor:
async def info(self):
return {"options": json.loads(json.dumps(self.options)),
"schema": schema_ui(CONFIG),
"translations": {"en": TL.parse_translation_file(
os.path.join(HERE, "translations", "en.yaml"))}}
"translations": {lang: TL.parse_translation_file(
os.path.join(HERE, "translations", f"{lang}.yaml"))
for lang in ("en", "nl")}}
async def write_options(self, options):
self.options = json.loads(json.dumps(options))
@@ -255,7 +258,20 @@ def controller(state):
return c
class ReadOnlyHass(Hass):
read_only = True # app/setuponly.py serves only on a read-only HA
async def main(state, port, host="127.0.0.1"):
if state == "setup":
# UX-15's setup-only mode: shipped defaults, nothing controlled.
stored = defaults(CONFIG)
c = SetupOnly(dict(stored), ReadOnlyHass(), Store())
runner = aioweb.AppRunner(web.build_app(c, Supervisor(stored)))
await runner.setup()
await aioweb.TCPSite(runner, host, port).start()
print(f"serving setup on http://127.0.0.1:{port}/", flush=True)
await asyncio.Event().wait()
if state == "new":
stored = dict(defaults(CONFIG), setpoint_entity="number.a")
store = Store()