From 016b7790483940a56bbfb4e08527f4b3d57a7a72 Mon Sep 17 00:00:00 2001 From: Erik Gieseler Date: Wed, 11 Nov 2026 19:22:46 +0100 Subject: [PATCH 1/2] fix: improve Power Flow and Console responsive layout Keep the low-SoE label inside the battery graphic, center grid/home labels, keep gateway details readable with multiple gateways, and optically center status pills, gateway tags, and the active header link. --- RELEASE.md | 21 ++++++++++++++++ app/static/css/page.css | 8 +++++- app/static/index.html | 50 ++++++++++++++++++++++++++++++------- app/static/powerflow/app.js | 36 +++++++++++++++++++------- 4 files changed, 96 insertions(+), 19 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index da399e73..a408813b 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -2,6 +2,27 @@ ## Version History +### [0.16.1] - Upcoming + +**Fixed:** +- **Power Flow battery percentage at low state of charge** - the SoE label is + bottom-aligned inside the battery graphic when it would otherwise overflow, + so values such as `4%` are no longer cut off at the bottom of the Power Flow + card. +- **Power Flow grid and home labels stay above their icons on narrow cards** - + the labels were anchored to the edges of the diagram, so the right-hand label + could run past the card. They are now centered over the respective icons, and + the two circles move closer together on narrow iframes instead of running + outside the card. +- **Gateway firmware stays readable on narrow Console cards** - the mode and + firmware badges no longer split across lines or squeeze the connection + status; the status sits with the host/IP line, and gateway cards stay wide + enough when multiple gateways share a row. +- **Console MQTT and control status pills are optically centered** - the + `Connected` and `Control enabled` labels use a centered flex line box instead + of sitting slightly low inside the pill; gateway mode/firmware tags and the + active header link use the same optical alignment. + ### [0.16.0] - 2026-11-10 MQTT and Home Assistant battery dispatch, off by default. It needs both diff --git a/app/static/css/page.css b/app/static/css/page.css index 1af30287..f22f930e 100644 --- a/app/static/css/page.css +++ b/app/static/css/page.css @@ -79,12 +79,18 @@ header { } .header-links a { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 36px; color: var(--text-secondary); text-decoration: none; - padding: 8px 16px; + padding: 0 16px; border-radius: 6px; transition: all 0.2s ease; font-size: 0.9em; + line-height: 1; + box-sizing: border-box; } .header-links a:hover, diff --git a/app/static/index.html b/app/static/index.html index 4f7ef9c5..ca633262 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -1390,6 +1390,16 @@ background: rgba(248, 81, 73, 0.15); color: var(--accent-red); } + .mqtt-status-badge, + .control-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 23px; + padding: 0 10px; + line-height: 1; + box-sizing: border-box; + } .mqtt-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); @@ -1405,14 +1415,16 @@ .gateway-list { display: grid; - grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); gap: 16px; } .gateway-item { - display: flex; - justify-content: space-between; + display: grid; + grid-template-columns: minmax(0, 1fr) auto; align-items: center; + column-gap: 12px; + row-gap: 4px; padding: 16px; background: var(--bg-secondary); border-radius: 8px; @@ -1433,25 +1445,31 @@ } .gateway-info { - display: flex; - flex-direction: column; - gap: 4px; + display: contents; } .gateway-name { + grid-column: 1 / -1; + grid-row: 1; + min-width: 0; font-weight: 600; font-size: 1em; + overflow-wrap: anywhere; } .gateway-host { + grid-column: 1; + grid-row: 2; font-size: 0.85em; color: var(--text-muted); } .gateway-details { display: flex; + flex-wrap: wrap; + grid-column: 1 / -1; + grid-row: 3; gap: 8px; - margin-top: 4px; } .gateway-mode { @@ -1460,6 +1478,7 @@ border-radius: 4px; background: var(--bg-card); color: var(--text-secondary); + white-space: nowrap; } .gateway-firmware { @@ -1468,10 +1487,23 @@ border-radius: 4px; background: rgba(88, 166, 255, 0.15); color: var(--accent-blue); + white-space: nowrap; } - + + .gateway-mode, + .gateway-firmware { + display: inline-flex; + align-items: center; + min-height: 20px; + padding: 1px 8px 3px; + line-height: 1; + box-sizing: border-box; + } + .gateway-status { display: flex; + grid-column: 2; + grid-row: 2; align-items: center; gap: 8px; } @@ -1503,9 +1535,9 @@ } .gateway-error { + grid-column: 1 / -1; font-size: 0.75em; color: var(--accent-red); - margin-top: 4px; } /* Footer */ diff --git a/app/static/powerflow/app.js b/app/static/powerflow/app.js index 75d685fc..2e6ca7d3 100644 --- a/app/static/powerflow/app.js +++ b/app/static/powerflow/app.js @@ -23722,6 +23722,7 @@ ft(this, "_handleIslandingClick", this._handleIslandingClick.bind(this)), (this.state = { activeIcons: [], + viewportWidth: window.innerWidth, colors: { batteryToHome: et.c, batteryToGrid: et.c, @@ -23739,9 +23740,18 @@ componentWillReceiveProps(e) { this.setState({ activeIcons: bt.getActiveIcons(e) }); } + componentDidMount() { + this._handleViewportResize = () => this.setState({ viewportWidth: window.innerWidth }); + window.addEventListener("resize", this._handleViewportResize); + } + componentWillUnmount() { + window.removeEventListener("resize", this._handleViewportResize); + } + _getEffectiveGridWidth() { + return Math.min(this.props.gridWidth, Math.max(0, window.innerWidth - 136)); + } render() { const { - gridWidth: e, gridHeight: t, activeTypes: i, activeGlowRadius: n, @@ -23791,7 +23801,8 @@ gridServicesActive: H, soe: K, } = this.props, - { activeIcons: Y, colors: Q } = this.state, + { activeIcons: Y, colors: Q, viewportWidth: V0 } = this.state, + e = this._getEffectiveGridWidth(), Z = 0 === i.length, J = e / 2, X = t / 2, @@ -24290,8 +24301,9 @@ case et.h.GRID: ((b = this.props.gridPower && f), (I.top = g ? 0.5 * s + 0.5 * o + 2 * _ : 1.5 * s + 0.5 * o + 3 * _ + T + l + d), - (I.left = 0), - (I.right = a + s + 2 * _), + (I.left = `calc(50% - ${a / 2 + s / 2 + _}px)`), + (I.right = "auto"), + (I.transform = "translateX(-50%)"), (I.color = et.d)); break; case et.h.BATTERY: @@ -24305,8 +24317,9 @@ case et.h.USAGE: ((b = this.props.loadPower && v), (I.top = g ? 0.5 * s + 0.5 * o + 2 * _ : 1.5 * s + 0.5 * o + 3 * _ + T + l + d), - (I.left = a + s + 2 * _), - (I.right = 0), + (I.left = `calc(50% + ${a / 2 + s / 2 + _}px)`), + (I.right = "auto"), + (I.transform = "translateX(-50%)"), (I.color = et.g)); } const O = !t.includes(n), @@ -24386,6 +24399,9 @@ s = e ? 267 : 94, _ = e ? 212 : 94, l = e ? 172 : 157; + const labelTop = n + (_ * (100 - a)) / 100 - i / 2, + labelBottomAligned = labelTop > s - i - 1, + gridWidth = this._getEffectiveGridWidth(); ((d = c.createElement( "svg", { className: "powerwall-soe", width: r, height: s, viewBox: `0 0 ${r} ${s}` }, @@ -24410,10 +24426,12 @@ "data-testid": "234320a0-eab5-4528-bc42-aab6c7282f13", className: "label-container soe-label", style: { - top: Math.max(0, n + (_ * (100 - a)) / 100 - i / 2), + top: labelBottomAligned ? "auto" : Math.max(0, labelTop), + bottom: labelBottomAligned ? 0 : "auto", + lineHeight: labelBottomAligned ? "normal" : void 0, left: e - ? 0.5 * this.props.gridWidth - t + 191 - : r + (0.5 * this.props.gridWidth - t) + 12, + ? 0.5 * gridWidth - t + 191 + : r + (0.5 * gridWidth - t) + 12, color: o ? "white" : "black", }, }, From 572db952fbd0f120701a8f5863bac94352da0f8f Mon Sep 17 00:00:00 2001 From: Jason Cox Date: Wed, 11 Nov 2026 23:23:03 -0800 Subject: [PATCH 2/2] fix: wrap long gateway hosts, tidy Power Flow width code - .gateway-host wraps long hostnames and IPv6 addresses inside its column instead of running into the connection status on narrow cards (Copilot) - drop the unused viewportWidth destructure; setState already re-renders - explain the 136px reserved beside the diagram for the grid/home circles Co-Authored-By: Claude Opus 5.5 --- app/static/index.html | 2 ++ app/static/powerflow/app.js | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/static/index.html b/app/static/index.html index ca633262..52c6f642 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -1460,8 +1460,10 @@ .gateway-host { grid-column: 1; grid-row: 2; + min-width: 0; font-size: 0.85em; color: var(--text-muted); + overflow-wrap: anywhere; } .gateway-details { diff --git a/app/static/powerflow/app.js b/app/static/powerflow/app.js index 2e6ca7d3..1d55addb 100644 --- a/app/static/powerflow/app.js +++ b/app/static/powerflow/app.js @@ -23748,6 +23748,8 @@ window.removeEventListener("resize", this._handleViewportResize); } _getEffectiveGridWidth() { + // Keep 68px per side for the grid and home circles, which sit + // outside the diagram's edges, so they fit narrow iframes return Math.min(this.props.gridWidth, Math.max(0, window.innerWidth - 136)); } render() { @@ -23801,7 +23803,7 @@ gridServicesActive: H, soe: K, } = this.props, - { activeIcons: Y, colors: Q, viewportWidth: V0 } = this.state, + { activeIcons: Y, colors: Q } = this.state, e = this._getEffectiveGridWidth(), Z = 0 === i.length, J = e / 2,