Release 0.2.73: the Vue editor with live values, Identify, a test alert, layout copying and ⌘K

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Max Gramser
2026-09-18 08:32:08 +02:00
parent 8d11de8a28
commit 1ed42dc425
12 changed files with 53 additions and 16 deletions
+1 -1
View File
@@ -54,7 +54,7 @@ and screens for lookups and tests. If that file is missing you are not on his ma
- Keep base hardware and UI in `home-like-2432s028.yaml`; personal data belongs
in the gitignored local profiles.
- The editor is `web/` (Vue 3 + Vite, TypeScript; app 0.2.74+). `screen_manager/app/static` is its build
- The editor is `web/` (Vue 3 + Vite, TypeScript; app 0.2.73+). `screen_manager/app/static` is its build
output: change `web/src`, run `cd web && npm test && npm run build`, and commit both. Keep every URL the page asks for
relative (`api/...`), so it works behind Home Assistant's ingress path. Tests read the source through
`tests/editor_sources.py`; docs/RELEASING.md "Local development" has the dev-server recipe.
+37
View File
@@ -0,0 +1,37 @@
# Test results 0.2.73 (firmware 0.2.61, unchanged)
Date: 2026-09-18. Branch `editor-vue`, rebased on 9f5694b (0.2.72). The firmware and the packages did not change;
this release is the editor (web/, Vue 3 + Vite) and four additive add-on endpoints.
## Automated
- Python: `.venv-portal/bin/python -m unittest discover -s tests` → 398 tests OK. New: `tests/test_editor_live.py`
(`/api/states`, `/api/screens/{inbox}/identify`, `/api/alerts/test`, the changelog in the update summary, and the
page side of every new feature read from the Vue sources). The nine tests that used to read `static/app.js` now
read the sources through `tests/editor_sources.py`; `tests/test_page_assets.py` checks the committed Vite build.
- Editor: `cd web && npm test` → Vitest, 37 tests OK (grid rules, top bar rules, store, TileCard, Library,
CommandPalette in jsdom). `npm run check` (vue-tsc) clean. `npm run build` → `screen_manager/app/static`.
- `tools/generate_icons.py --check` → 150 pickable icons, 309 glyphs verified (the editor font now lives in
`web/src/assets`).
## By hand, on the demo home (real add-on code, fake Home Assistant, port 8199)
- Choose a screen, tile drawer with every field, top bar drawer with the live sample, add to the top bar, Screen
settings tab, add an entity from the library and Save & send (PUT arrives, chip says Saved), drag a tile onto
another (swap), remove with Undo, Esc closes the drawer, ··· menu, Override YAML with the example, New screen,
Firmware & USB, Alerts, Settings; dark mode; 375 px layout.
- New in this release: live values on every tile (temperature with unit, On/Off with a lit icon, setpoint, volume and
position fills, the playing title), Identify (show_alert with flash reaches the demo HA), Try it to one screen and
to all (two calls logged), Copy layout from the other screen, Export (download + clipboard), room filter, Hide
placed, lit and grey avatars, ⌘K search that adds an entity, What's new under the Update badge.
## On Max's own Home Assistant
- The first build of this editor ran as a local add-on (same code, before the six features) against his real screens
and Home Assistant: "ik vind de rest van de editor ook echt geweldig, heb hem getest".
## Not exercised
- The progress bar of a running firmware update (the demo home cannot build firmware); only the mapping from phase
and ESPHome stage to a percentage is unit-tested.
- Drag & drop on a phone (touch), and the six new features inside the Home Assistant ingress iframe.
+1 -1
View File
@@ -5,7 +5,7 @@ ARG BUILD_ARCH
LABEL io.hass.version="${BUILD_VERSION}" io.hass.type="app" io.hass.arch="${BUILD_ARCH}"
RUN pip install --no-cache-dir 'aiohttp>=3.12,<4' PyYAML
COPY app /app
# The Update badge shows what's new from the changelog (app 0.2.74).
# The Update badge shows what's new from the changelog (app 0.2.73).
COPY CHANGELOG.md /app/CHANGELOG.md
WORKDIR /app
ENTRYPOINT []
+1 -1
View File
@@ -1,4 +1,4 @@
"""What's new for the Update badge (app 0.2.74): the CHANGELOG sections since a screen's firmware.
"""What's new for the Update badge (app 0.2.73): the CHANGELOG sections since a screen's firmware.
The Dockerfile copies CHANGELOG.md next to this file; a development checkout has it one folder up.
Each `## <app> (firmware <fw>)` section gives its bullet lines as plain text, newest first.
+3 -3
View File
@@ -1624,7 +1624,7 @@ def create_app(manager, development=False):
'attributes':state_message(i,t,manager.ha.states)['a'],
'options':t.get('options',{})} for i,t in enumerate(layout['tiles'])]})
async def states(request):
"""Live values for the editor's mockup (app 0.2.74): the state, Home Assistant's word and the attributes a
"""Live values for the editor's mockup (app 0.2.73): the state, Home Assistant's word and the attributes a
card shows, for the tiles on the page, saved or not. At most sixty entities per request."""
index = manager.registry_index()
result = {}
@@ -1647,14 +1647,14 @@ def create_app(manager, development=False):
raise ValueError(f"{screen['name']} can't show an alert: {skipped[0][1] if skipped else 'not ready'}.")
return ready[0]
async def identify(request):
"""Identify (app 0.2.74): the screen shows a short card and blinks its backlight, so you know which one it is."""
"""Identify (app 0.2.73): the screen shows a short card and blinks its backlight, so you know which one it is."""
screen = one_alert_target(request.match_info['inbox'])
data, _ = alert_data({'title': f"This is {screen['name']}", 'subtitle': 'Identify, from ESP Screens', 'icon': 'bell-ring',
'color': 'blue', 'button_text': 'OK', 'timeout': 8, 'flash': True})
await manager.ha.call(alert_service(screen['node']), data)
return web.json_response({'ok': True})
async def test_alert(request):
"""Alerts → Try it (app 0.2.74): one alert to one screen or to every screen, with the fields an automation sends."""
"""Alerts → Try it (app 0.2.73): one alert to one screen or to every screen, with the fields an automation sends."""
body = await request.json()
if not isinstance(body, dict):
raise ValueError('Invalid alert.')
+1 -1
View File
@@ -32,7 +32,7 @@ class Updater:
self.manager, self.path = manager, Path(path)
self.auto, self.hosts, self.results, self.last_round = False, {}, {}, None
self.task, self.current, self.queue, self.phase = None, None, [], None
# What's new since a screen's firmware, for the Update badge (app 0.2.74).
# What's new since a screen's firmware, for the Update badge (app 0.2.73).
self.changelog = changelog.load()
if self.path.exists():
raw = json.loads(self.path.read_text())
+1 -1
View File
@@ -1,6 +1,6 @@
"""The editor's source, for the tests that read it.
The page is a Vue app in web/ (app 0.2.74); `npm run build` writes it to screen_manager/app/static. Tests look at
The page is a Vue app in web/ (app 0.2.73); `npm run build` writes it to screen_manager/app/static. Tests look at
the source, not the build: every .ts and .vue file under web/src as one text (SCRIPT, which is also the markup:
a .vue file carries its template), and the stylesheets as another (CSS).
"""
+1 -1
View File
@@ -1,4 +1,4 @@
"""Live values on the mockup, Identify, the test alert and the changelog for the Update badge (app 0.2.74).
"""Live values on the mockup, Identify, the test alert and the changelog for the Update badge (app 0.2.73).
The editor asks /api/states for the tiles it shows and draws Home Assistant's values on the mockup; Identify and
Try it call a screen's own show_alert action, with the same field rules as an alert event; the update summary
+1 -1
View File
@@ -8,7 +8,7 @@ fills in what was missing.
App 0.2.65: the page no longer opens the first screen by itself; the owner picks one. Until then a card asks for
that, or offers the install when there are no screens yet.
App 0.2.74: the page is the Vue app in web/; these tests read its source (tests/editor_sources.py).
App 0.2.73: the page is the Vue app in web/; these tests read its source (tests/editor_sources.py).
"""
import re
import sys
+1 -1
View File
@@ -1,4 +1,4 @@
"""The page loads its own script and styles by content hash (app 0.2.58, Vite build since 0.2.74).
"""The page loads its own script and styles by content hash (app 0.2.58, Vite build since 0.2.73).
After updating to 0.2.57, Safari showed an empty Screen settings panel: it kept a cached script from before
the update. Vite names every built file after a hash of its content and index.html asks for those names, so a
+1 -1
View File
@@ -1,5 +1,5 @@
"""Settings (app 0.2.45): New screen, Firmware & USB, firmware updates, Alerts and Claude moved off the main
page into a view of their own. App 0.2.74: the sidebar carries the way in (New screen, Firmware & USB, Alerts,
page into a view of their own. App 0.2.73: the sidebar carries the way in (New screen, Firmware & USB, Alerts,
Settings), each once; the Settings page keeps the updates and Claude."""
import re
import sys
+4 -4
View File
@@ -165,7 +165,7 @@ export async function loadEntityActions(entity: string) {
}
}
// ---- Live values on the mockup (app 0.2.74): what the screen shows right now ----
// ---- Live values on the mockup (app 0.2.73): what the screen shows right now ----
let statesFlight = false;
export async function loadStates() {
const entities = [...new Set((state.layout?.tiles || []).map((t) => t.entity).filter((id) => !id.startsWith("screen.")))];
@@ -347,7 +347,7 @@ export async function save() {
}
}
// ---- Identify and the test alert (app 0.2.74): a screen's own show_alert action ----
// ---- Identify and the test alert (app 0.2.73): a screen's own show_alert action ----
export const canAlert = (screen: Screen | undefined) =>
Boolean(screen && screen.alert_action && versionAtLeast(screen.firmware, state.inventory.alerts?.min_firmware || "0.2.31"));
export async function identify(screen: Screen) {
@@ -362,7 +362,7 @@ export async function sendTestAlert(target: string, data: Record<string, unknown
return (await send("alerts/test", "POST", { screen: target, data })) as { sent: number; failed: number; skipped: number; unusable?: string[] };
}
// ---- Copying and sharing a layout (app 0.2.74) ----
// ---- Copying and sharing a layout (app 0.2.73) ----
const LAYOUT_KEYS = ["title", "tiles", "header", "pages"] as const;
function adopt(source: Partial<Layout>, what: string) {
const layout = state.layout;
@@ -418,7 +418,7 @@ export function importLayout(text: string) {
adopt(data, "Layout imported");
}
// ---- Updates with content (app 0.2.74): what a screen gets, and how far its update is ----
// ---- Updates with content (app 0.2.73): what a screen gets, and how far its update is ----
export function whatsNew(screen: Screen): string[] {
const target = state.inventory.updates?.target;
const sections = (state.inventory.updates as any)?.changelog as { app: string; firmware: string; lines: string[] }[] | undefined;