Show live values on the mockup, blink a screen to identify it, try an alert, copy and share layouts (0.2.74)

The tiles draw what Home Assistant reports (a new /api/states answers for the tiles on the page); the ···
menu gets Identify (the screen's own show_alert with flash), Copy layout from another screen, Export and
Import as JSON; the Alerts page opens with Try it (/api/alerts/test, one screen or all); the library filters
by room and hides what is placed, with lit icons for what is on; ⌘K searches screens, entities and actions;
the Update badge lists what's new from the changelog (copied into the image) and a running update shows a
progress bar with the ESPHome stage and the last log line.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Max Gramser
2026-09-18 08:21:37 +02:00
parent ef8e94f6d1
commit 6148dfd211
27 changed files with 818 additions and 79 deletions
+21 -2
View File
@@ -1,7 +1,7 @@
<script setup lang="ts">
// Everything around the screens: firmware updates, alerts, Claude.
import { computed, ref } from "vue";
import { go, installClaudeSkill, runUpdateAll, setAutoUpdate, state } from "../store";
import { anyUpdating, go, installClaudeSkill, runUpdateAll, setAutoUpdate, state, updateProgress } from "../store";
const u = computed(() => state.inventory.updates);
const outdated = computed(() => state.inventory.screens.filter((s) => s.update?.available).length);
@@ -12,6 +12,16 @@ const updatesHint = computed(() => !u.value ? "" : u.value.busy
: outdated.value
? `Firmware ${u.value.target} is available for ${outdated.value} screen${outdated.value === 1 ? "" : "s"}.`
: `All screens have firmware ${u.value.target}.`);
const running = computed(() => state.inventory.screens.find((s) => s.update?.state === "running" || state.updating.includes(s.id)));
const progress = computed(() => (running.value ? updateProgress(running.value) : null));
const logTail = computed(() => (state.firmwareJob?.logs || []).slice(-12).join("\n"));
// What the current firmware brings: the changelog sections that mention it.
const targetNotes = computed(() => {
const sections = (u.value as any)?.changelog as { app: string; firmware: string; lines: string[] }[] | undefined;
const target = u.value?.target;
if (!sections || !target) return [];
return sections.filter((s) => s.firmware === target).flatMap((s) => s.lines).slice(0, 10);
});
const skill = computed(() => state.inventory.claude_skill);
const installing = ref(false);
async function install() {
@@ -41,7 +51,16 @@ async function install() {
<section v-if="u" class="card updates" id="updates">
<h2>Firmware updates</h2>
<p id="updates-hint">{{ updatesHint }}</p>
<template v-if="running && progress">
<div class="progress" role="progressbar" :aria-valuenow="progress.percent" aria-valuemin="0" aria-valuemax="100"><i :style="{ width: progress.percent + '%' }"></i></div>
<div class="progress-text"><span>{{ running.name }} · {{ progress.percent }} %</span><span>{{ progress.text }}</span></div>
<pre v-if="logTail" class="log-lines">{{ logTail }}</pre>
</template>
<button v-if="u.pending && !u.busy && u.pending >= 2" id="update-all" type="button" class="btn primary" @click="runUpdateAll">Update all {{ u.pending }} screens</button>
<details v-if="targetNotes.length && !anyUpdating()" class="whatsnew">
<summary>What's new in firmware {{ u.target }}</summary>
<ul><li v-for="line in targetNotes" :key="line">{{ line }}</li></ul>
</details>
<label class="check">
<input type="checkbox" id="auto-update" :checked="Boolean(u.auto)" @change="setAutoUpdate(($event.target as HTMLInputElement).checked)" />
<span>Update automatically every night<small>Between 03:00 and 06:00, one screen at a time. Stops on an error and reports it in Home Assistant.</small></span>
@@ -49,7 +68,7 @@ async function install() {
</section>
<section class="card">
<h2>Alerts</h2>
<p>A card over the whole screen from an automation, on one screen or on all of them. The cheatsheet has the action names, fields, icons, and colors.</p>
<p>A card over the whole screen from an automation, on one screen or on all of them. The cheatsheet has the action names, fields, icons, and colors, and a form to try one.</p>
<div class="tools">
<button type="button" class="tool" @click="go('#alerts')"><span class="tool-icon">!</span><span class="tx"><b>Alerts</b><small>Full-screen notifications, from an automation</small></span></button>
</div>