Skip to content

Commit 6678143

Browse files
committed
Clean up 2026.9.0 additions: comments, dead refs, storm perf
Strip issue/version numbers from code comments, drop stale dev-console references, and remove leftover phase-plan wording (the multi-tariff cost is handled by the stat_cost path, not later). Rename the corner-chip dimmed flag to hidden to match its visibility:hidden behaviour. Guard the thunderstorm controller so it only writes --wx-flash when the value changes, sparing a per-frame style recalc during the gaps between strikes. Surface the cost detail-panel cumulative total in the changelog.
1 parent 12d0e04 commit 6678143

11 files changed

Lines changed: 65 additions & 60 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ and the project follows a date-based versioning scheme (`YEAR.MONTH.PATCH`).
1111

1212
The weather release, "Your real sky": the scene now reflects the weather over
1313
your home, and the outdoor temperature and humidity join the card. Currently in
14-
alpha (`2026.9.0-a5`).
14+
alpha (`2026.9.0-a6`).
1515

1616
### Added: a cost chip
1717

@@ -34,7 +34,9 @@ own per-tariff cost statistics and sums them. And the chip is **fully in the
3434
editor** now, show or hide it, and pick its colour and icon, like every other
3535
chip. The chip **follows the timeline** as you scrub (the cost at the hovered
3636
moment, like the other chips), and its **day curve around the house** now draws
37-
for multi-tariff grids too, not just a fixed price.
37+
for multi-tariff grids too, not just a fixed price. Open its detail panel for the
38+
cumulative total over the selected period, what you spent, what you earned, and
39+
the net.
3840

3941
### Added: the real weather, painted over the scene
4042

dist/helios.js

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "helios",
3-
"version": "2026.9.0-a5",
3+
"version": "2026.9.0-a6",
44
"description": "HELIOS - real-time solar exposure, cloud cover and PV production card for Home Assistant",
55
"license": "GPL-3.0-or-later",
66
"type": "module",

src/charts/charts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export function gridSourceName(host: ChartHost, index: number, dir: 'import' | '
8686
export function batterySourceName(host: ChartHost, index: number, dir: 'charge' | 'discharge'): string
8787
{
8888
const ed = host._energyDefaults;
89-
//Prefer the Energy dashboard's configured per-source name (#365); else the meter friendly name, then a generic.
89+
//Prefer the Energy dashboard's configured per-source name; else the meter friendly name, then a generic.
9090
const configured = ed.batteryNames[index];
9191
if (configured) { return configured; }
9292
const id = (dir === 'charge' ? ed.batteryStatEnergyTos : ed.batteryStatEnergyFroms)[index];

src/core/config/helios-config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ export function autoHideUi(config: HeliosConfig | undefined): boolean
245245
}
246246

247247

248-
//"Your real sky" weather effects. Default on (explicit false disables). The dev console is opt-in (default off).
248+
//"Your real sky" weather effects. Default on (explicit false disables).
249249
export function weatherEnabled(config: HeliosConfig | undefined): boolean
250250
{
251251
return config?.['weather-enabled'] !== false;

src/css/helios-card-scene-css.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -818,8 +818,8 @@ export const heliosCardStyles = css`
818818
background: radial-gradient(circle, rgba(30, 35, 42, 0.45), rgba(30, 35, 42, 0) 68%);
819819
}
820820
/* Sized in cqw (share of the card's WIDTH, via ha-card's container-type: inline-size) for BOTH axes, so the
821-
shadows stay round at any card shape. Percent height made them tall ovals on a narrow card (#332, tfriberg):
822-
55% of a small width vs 70% of a tall height. left/top keep % for placement; the sweep is element-relative. */
821+
shadows stay round at any card shape. Percent height made them tall ovals on a narrow card: 55% of a small
822+
width vs 70% of a tall height. left/top keep % for placement; the sweep is element-relative. */
823823
.helios-wx-cloud.k1 { width: 55cqw; height: 55cqw; left: -30%; top: -10%; animation: helios-wx-sweep 22s linear infinite; }
824824
.helios-wx-cloud.k2 { width: 42cqw; height: 42cqw; left: -30%; top: 20%; animation: helios-wx-sweep 30s linear infinite; animation-delay: -8s; }
825825
.helios-wx-cloud.k3 { width: 66cqw; height: 66cqw; left: -45%; top: -20%; animation: helios-wx-sweep 40s linear infinite; animation-delay: -16s; }
@@ -833,7 +833,7 @@ export const heliosCardStyles = css`
833833
radial-gradient(35% 25% at 70% 78%, rgba(150, 180, 215, 0.14), transparent 70%);
834834
}
835835
836-
/* Weather chips (temperature, then humidity): a centered row pinned along the BOTTOM of the scene, so they
836+
/* Corner chips (cost, temperature, humidity): a centered row pinned along the BOTTOM of the scene, so they
837837
read as secondary info and group with the home/grid/battery pill family rather than fighting the irradiance
838838
chip up top. Below the timeline's z-index (1000) so the timeline always wins on overlap. Each icon is tinted
839839
by --chip-color. */
@@ -894,11 +894,11 @@ export const heliosCardStyles = css`
894894
display: inline-flex;
895895
align-items: center;
896896
}
897-
/* Clickable + active states, mirroring the scene chips: [role=button] re-enables events (the column is
897+
/* Clickable + active states, mirroring the scene chips: [role=button] re-enables events (the row is
898898
pointer-events:none), .is-chart-active raises the halo, .is-curve-on fills the pill with the metric colour. */
899899
.helios-corner-chip[role="button"] { pointer-events: auto; cursor: pointer; }
900-
/* Day-curve open: the non-active weather chip becomes an inert, invisible placeholder. It still reserves its
901-
slot width so the centered row does not shift the surviving chip when its sibling drops out. */
900+
/* Day-curve open: each non-active corner chip becomes an inert, invisible placeholder. It still reserves its
901+
slot width so the centered row does not shift the surviving chip when its siblings drop out. */
902902
.helios-corner-chip.is-slot-hidden { visibility: hidden; pointer-events: none; }
903903
.helios-corner-chip::after
904904
{

src/data/sources/cost.ts

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
//Live cost rate for the cost chip: the NET money flow right now, in the user's currency per hour.
2-
//
3-
//net rate = import cost - export revenue = importPrice x importPower - exportPrice x exportPower,
4-
//with power taken from the already-computed live grid slots and price per the HYBRID rule agreed for the feature:
5-
// - a single configured price (one entity, or one static number) -> that price, measured, exact, no lag;
6-
// - several prices (a multi-tariff grid, e.g. Tempo) -> resolved later from the recent stat_cost/stat_energy
7-
// delta (the effective active price); until that lands the rate is left null so the chip hides rather than
8-
// guessing the wrong tariff.
9-
//
10-
//Everything is absent unless the user configured cost in the Energy dashboard, matching Helios' rule: measured or
11-
//absent, never invented. Positive rate = you are spending; negative = you are earning (solar surplus sold).
2+
//net rate = import cost - export revenue = importPrice x importPower - exportPrice x exportPower.
3+
//Prices follow a hybrid rule: a single configured price (one entity or one static number) is used directly,
4+
//exact and lag-free; a multi-tariff grid (e.g. Tempo) is read from HA's own cost statistics instead, since
5+
//averaging tariffs would misprice the moment. Nothing is shown unless cost is configured in the Energy dashboard
6+
//(Helios' rule: measured or absent, never invented). Positive rate = spending, negative = earning (surplus sold).
127

138
import { parseNumericState, pvNormalizeToWatts } from '../../core/format/format';
149
import type { EnergyDefaults } from './energy-prefs';
@@ -102,8 +97,8 @@ function latestCostRate(host: CostHost): number | null
10297

10398

10499
//Resolve a single €/kWh price for one direction: exactly one price entity -> its live numeric state; else exactly
105-
//one static number -> that number; otherwise null (no price, or a multi-tariff set handled by the effective-price
106-
//path later). Never averages several tariffs: that would misprice the moment.
100+
//one static number -> that number; otherwise null (no price, or a multi-tariff set the cost-statistic path handles
101+
//instead). Never averages several tariffs: that would misprice the moment.
107102
function singlePrice(hass: CostHost['hass'], entities: string[], numbers: number[]): number | null
108103
{
109104
if (entities.length === 1)
@@ -119,7 +114,7 @@ function singlePrice(hass: CostHost['hass'], entities: string[], numbers: number
119114

120115
//The single static price (currency/kWh) for one direction, or null when there is none or a multi-tariff set.
121116
//Used by the durable cost curve: with a constant price, cost = energy x price is exact across the WHOLE history
122-
//(no HA stat_cost backfill needed). Variable (entity) prices are handled separately, later.
117+
//(no HA stat_cost backfill needed). Variable (entity) prices are handled by the cost-statistic path instead.
123118
export function staticPrice(numbers: number[]): number | null
124119
{
125120
return numbers.length === 1 ? numbers[0] : null;
@@ -164,8 +159,8 @@ export function refreshCostLive(host: CostHost): void
164159
const importPrice = singlePrice(host.hass, d.gridImportPrices, d.gridImportPriceNumbers);
165160
const exportPrice = singlePrice(host.hass, d.gridExportPrices, d.gridExportPriceNumbers);
166161

167-
//No resolvable import price (0 or multi-tariff): leave the rate null for now (Inc. 2 fills multi-tariff from
168-
//the effective price). Export-only pricing with no import price is not a real setup, so gate on import.
162+
//No resolvable single import price (multi-tariff is served by the cost-statistic path above, not here): leave
163+
//the rate null. Export-only pricing with no import price is not a real setup, so gate on import.
169164
if (importPrice === null)
170165
{
171166
if (host._costRate !== null) { host._costRate = null; }

src/data/sources/energy-prefs.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export interface EnergyDefaults
5353
gridName: string;
5454
batteryName: string;
5555
//Per-source battery names (the Energy dashboard `name` field, in source order; '' when unset). Drives the
56-
//battery detail chart's per-bank labels so a multi-bank install reads its own names, not "Battery N" (#365).
56+
//battery detail chart's per-bank labels so a multi-bank install reads its own names, not "Battery N".
5757
batteryNames: string[];
5858
//Individual devices from the dashboard's per-device tracking (`device_consumption`), in dashboard order. NOT part
5959
//of the source flows above (they are sub-measurements of the home load), kept separately for the monitoring
@@ -386,9 +386,9 @@ export function parseEnergyPrefs(prefs: {
386386
//no cost is tracked (the cost chip then stays hidden). Import price/cost live on flow_from + top-level;
387387
//export compensation on flow_to (top-level entity_energy_price is the IMPORT price, never read as export).
388388
//Import flows: price (entity/number) + cost statistic. When no explicit `stat_cost` is set, Home
389-
//Assistant AUTO-GENERATES one named `<stat_energy_from>_cost` as soon as a price is configured (verified
390-
//on real HC/HP + Tempo installs). Deriving it gives an exact, per-tariff cost with zero extra config,
391-
//so a multi-tariff grid (several priced flows) just works. The fetch is graceful if the id doesn't exist.
389+
//Assistant auto-generates one named `<stat_energy_from>_cost` as soon as a price is configured.
390+
//Deriving it gives an exact, per-tariff cost with zero extra config, so a multi-tariff grid (several
391+
//priced flows) just works. The fetch is graceful if the id doesn't exist.
392392
for (const f of [src, ...asRecordArray(src['flow_from'])])
393393
{
394394
const meter = pickFirstString(f['stat_energy_from']);
@@ -434,7 +434,7 @@ export function parseEnergyPrefs(prefs: {
434434
else if (type === 'battery')
435435
{
436436
const battName = pickFirstString(src['name']) ?? '';
437-
//Per-source name, in battery-source order, for the detail chart's per-bank labels (#365).
437+
//Per-source name, in battery-source order, for the detail chart's per-bank labels.
438438
out.batteryNames.push(battName);
439439
if (!out.batteryName)
440440
{

src/helios-card.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,28 +1010,28 @@ export class HeliosCard extends LitElement
10101010
if (!showTemperature(this.config) || !isFinite(this._temperature)) { return nothing; }
10111011
//When a day curve is up, only the active chip is visible; the other stays in the DOM as an invisible
10121012
//placeholder so the visible chip keeps its slot (the centered row must not shift when its sibling drops).
1013-
const dimmed = this._dayCurveOpen && this._chartTarget !== 'temperature';
1014-
return this._cornerChip('temperature', `${this._temperature.toFixed(1)} °C`, dimmed);
1013+
const hidden = this._dayCurveOpen && this._chartTarget !== 'temperature';
1014+
return this._cornerChip('temperature', `${this._temperature.toFixed(1)} °C`, hidden);
10151015
}
10161016
private _renderHumidityChip(): TemplateResult | typeof nothing
10171017
{
10181018
if (!showHumidity(this.config) || !isFinite(this._humidity)) { return nothing; }
1019-
const dimmed = this._dayCurveOpen && this._chartTarget !== 'humidity';
1020-
return this._cornerChip('humidity', `${Math.round(this._humidity)} %`, dimmed);
1019+
const hidden = this._dayCurveOpen && this._chartTarget !== 'humidity';
1020+
return this._cornerChip('humidity', `${Math.round(this._humidity)} %`, hidden);
10211021
}
1022-
private _cornerChip(slot: 'temperature' | 'humidity' | 'cost', text: string, dimmed: boolean): TemplateResult
1022+
private _cornerChip(slot: 'temperature' | 'humidity' | 'cost', text: string, hidden: boolean): TemplateResult
10231023
{
10241024
const color = chipSlotColor(this, this.config, slot);
10251025
const icon = chipSlotIcon(this.config, slot);
10261026
//Same re-targeting gesture as the scene chips: one tap points the chart + around-house curve at this
10271027
//metric, a second tap on the active chip toggles its day curve (onChartTargetClick).
10281028
const active = this._chartTarget === slot;
10291029
const curveOn = active && this._dayCurveOpen;
1030-
//A dimmed chip carries is-slot-hidden (visibility:hidden): it reserves its width but is automatically
1030+
//A hidden chip carries is-slot-hidden (visibility:hidden): it reserves its width but is automatically
10311031
//inert, unfocusable and out of the a11y tree, so role/tabindex/click can stay static.
10321032
return html`
10331033
<div
1034-
class="helios-corner-chip ${active ? 'is-chart-active' : ''} ${curveOn ? 'is-curve-on' : ''} ${dimmed ? 'is-slot-hidden' : ''}"
1034+
class="helios-corner-chip ${active ? 'is-chart-active' : ''} ${curveOn ? 'is-curve-on' : ''} ${hidden ? 'is-slot-hidden' : ''}"
10351035
style=${`--chip-color:${color}`}
10361036
role="button"
10371037
tabindex="0"
@@ -1043,22 +1043,22 @@ export class HeliosCard extends LitElement
10431043
</div>`;
10441044
}
10451045

1046-
//Cost chip (2026.9.0): the live NET money rate in the user's currency per hour. Fixed, user-configurable colour
1046+
//Cost chip: the live NET money rate in the user's currency per hour. Fixed, user-configurable colour
10471047
//+ icon like every other chip (no sign-driven colour); spend vs earn is carried by the value's sign (a negative
10481048
//rate means you are earning). Tap it like any chip to bring up its cost curve. Hidden when turned off or when no
10491049
//cost is configured (no resolvable rate).
10501050
private _renderCostChip(): TemplateResult | typeof nothing
10511051
{
10521052
if (!showCost(this.config) || this._costRate === null) { return nothing; }
1053-
const dimmed = this._dayCurveOpen && this._chartTarget !== 'cost';
1053+
const hidden = this._dayCurveOpen && this._chartTarget !== 'cost';
10541054
const val = this._costRate
10551055
.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
1056-
return this._cornerChip('cost', `${val} ${this._currency}/h`, dimmed);
1056+
return this._cornerChip('cost', `${val} ${this._currency}/h`, hidden);
10571057
}
10581058

10591059
//Push the resolved weather onto the host as --wx-* vars (scene grade + overlay strengths) and drive the
1060-
//rain/snow/storm controllers. Source is the live/scrub weather, or the dev-console override when set. The sun
1061-
//glow is anchored on the real sun position and gated on daylight.
1060+
//rain/snow/storm controllers. Source is the resolved live/scrub weather (any local sensor overrides are
1061+
//already folded in upstream). The sun glow is anchored on the real sun position and gated on daylight.
10621062
private _applyWeather(): void
10631063
{
10641064
this.toggleAttribute('data-wx-on', this._wxOn);
@@ -1094,8 +1094,8 @@ export class HeliosCard extends LitElement
10941094
//"No UI" mode: reflect the faded state onto the host so the CSS fades the timeline + controls.
10951095
this.toggleAttribute('data-ui-hidden', this._uiHidden);
10961096

1097-
//"Your real sky": recompute the weather layers whenever the resolved weather, the master switch, the sun
1098-
//(glow anchor + day/night) or the dev-console override changes.
1097+
//"Your real sky": recompute the weather layers whenever the resolved weather, the master switch or the sun
1098+
//(glow anchor + day/night) changes.
10991099
if (_changedProperties.has('_cloudCover') || _changedProperties.has('_precip')
11001100
|| _changedProperties.has('_snowfall') || _changedProperties.has('_weatherCode')
11011101
|| _changedProperties.has('_wxOn') || _changedProperties.has('_sunScene')

src/hud/scene-hud-controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ export class SceneHudController
213213
//Home anchor: the hollow ring stays while a chip that DOCKS A LEADER on it is visible (production, grid,
214214
//battery, device groups). The sun/irradiance chip sits by the sun disc with no leader to the home, so it
215215
//does NOT hold the ring: a sun-position card (home hidden, only the sun chip) drops the ring and leaves
216-
//just the sun + location, e.g. a shutter/climate page keyed on the sun (#310). Config-driven so it stays
216+
//just the sun + location, e.g. a shutter/climate page keyed on the sun. Config-driven so it stays
217217
//stable across scrubbing.
218218
const anyHomeLeaderChipVisible = showChipProduction || showChipGrid || showChipBattery
219219
|| activeGroups(this.host.config, this.host._energyDefaults).some(g => groupChipVisible(cfg, g) && keeps(groupTarget(g)));

0 commit comments

Comments
 (0)