Repository navigation
UI: legend wrap + nice-tick y-axis + cleaner chart labels - #33
Conversation
- .chart-legend: flex-wrap so items wrap as whole units; .legend-item
keeps white-space nowrap so "PV fc" / "Load fc" / "Sungrow" don't
break mid-label or clip ("Sungrov").
- Live chart y-axis: niceAxis() snaps yMin/yMax to round step sizes
(1/2/5 * 10^k) and makes the gridline count follow the step, so
labels land on "0 W", "1.0 kW" instead of the fractional values the
lerp produced mid-animation ("6 W").
- Drop forecast values from the y-range calculation. The forecast was
pulling yMax/yMin to extreme predicted-load values which cramped the
live segment. The ctx.clip() still keeps forecast lines inside the
plot rect visually.
- Remove the "now" / "predicted ->" in-canvas labels. The amber shaded
band + dashed vertical divider already communicate the boundary,
and the bottom-axis "now" anchor stays.
- Plan chart: SoC "100%" / "0%" labels flush against the plot's right
edge (pad.r - 4) instead of floating 40px off to the right in empty
space.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b63b01f74a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Forecast values are intentionally NOT included in the y-range. | ||
| // Including them made the live segment feel cramped whenever a | ||
| // future slot predicted extreme power. Instead, forecasts are | ||
| // clipped to the actual-data plot rect (see ctx.clip above). | ||
| if (visibleVals.length === 0) { |
There was a problem hiding this comment.
Preserve forecast rendering when live series are hidden
In renderChart(), forecast values are no longer considered before the empty-state check, so if the legend hides all live/battery series, visibleVals becomes empty and the function returns "waiting for data..." even when chartPlan.actions exists and forecast toggles (pv_fc/load_fc) are on. This is a regression from prior behavior and prevents forecast-only viewing, while also showing a misleading no-data state.
Useful? React with 👍 / 👎.
## 1.0.0 (2026-04-15) ### Bug Fixes * **ci:** switch semantic-release to conventionalcommits preset ([15290c7](15290c7)) * Lua driver Command() reading wrong field — Sungrow ignored targets ([9237156](9237156)) ### Drivers * add Eastron SDM630 Lua driver ([srcfl#18](https://github.com/erikarenhill/forty-two-watts/issues/18)) ([d5ad806](d5ad806)) * add Ferroamp Modbus driver (alt transport to ferroamp.lua) ([srcfl#31](https://github.com/erikarenhill/forty-two-watts/issues/31)) ([03b802c](03b802c)) * port Deye SUN-SG hybrid inverter to 42W v2.1 Lua host ([srcfl#29](https://github.com/erikarenhill/forty-two-watts/issues/29)) ([df8fbc0](df8fbc0)) * port Fronius GEN24 (SunSpec) to Lua ([srcfl#19](https://github.com/erikarenhill/forty-two-watts/issues/19)) ([c1fc875](c1fc875)) * port Fronius Smart Meter (SunSpec Modbus, read-only) ([srcfl#24](https://github.com/erikarenhill/forty-two-watts/issues/24)) ([575895c](575895c)) * port GoodWe hybrid inverter (ET-Plus / EH) to Lua v2.1 ([srcfl#28](https://github.com/erikarenhill/forty-two-watts/issues/28)) ([e43d2d9](e43d2d9)) * port Growatt hybrid inverter driver (read-only) ([srcfl#20](https://github.com/erikarenhill/forty-two-watts/issues/20)) ([92524ac](92524ac)) * port Huawei SUN2000 hybrid inverter ([srcfl#15](https://github.com/erikarenhill/forty-two-watts/issues/15)) ([09a8855](09a8855)) * port Kostal Plenticore / Piko IQ (Lua, read-only) ([srcfl#21](https://github.com/erikarenhill/forty-two-watts/issues/21)) ([bdeca96](bdeca96)) * port Pixii PowerShaper battery driver to v2.1 Lua host ([srcfl#22](https://github.com/erikarenhill/forty-two-watts/issues/22)) ([70a96d1](70a96d1)) * port SMA hybrid inverter Lua driver ([srcfl#23](https://github.com/erikarenhill/forty-two-watts/issues/23)) ([dd34555](dd34555)) * port Sofar HYD-ES/HYD-EP from hugin to Lua v2.1 ([srcfl#26](https://github.com/erikarenhill/forty-two-watts/issues/26)) ([14f6131](14f6131)) * port SolarEdge SunSpec inverter + meter to Lua (read-only) ([srcfl#30](https://github.com/erikarenhill/forty-two-watts/issues/30)) ([1007e63](1007e63)) * port Solis hybrid inverter ([srcfl#27](https://github.com/erikarenhill/forty-two-watts/issues/27)) ([98b2a50](98b2a50)) * port Victron Energy GX Modbus driver ([srcfl#25](https://github.com/erikarenhill/forty-two-watts/issues/25)) ([ad71db2](ad71db2)) ### UI * inline target on hover + driver card + collapsible model cards ([de88f43](de88f43)) * legend wrap + nice-tick y-axis + cleaner chart labels ([srcfl#33](https://github.com/erikarenhill/forty-two-watts/issues/33)) ([aeb1d1c](aeb1d1c)) * live version from API + no-cache headers on static assets ([7b8779b](7b8779b)) ### Control loop * fold live DerEV readings into the EV clamp ([srcfl#36](https://github.com/erikarenhill/forty-two-watts/issues/36)) ([5d57d68](5d57d68)) ### MPC planner * fall back to forecast when learned PV twin collapses ([srcfl#39](https://github.com/erikarenhill/forty-two-watts/issues/39)) ([f3062ac](f3062ac)) ### Telemetry * add DerEV type for EV charger readings ([srcfl#34](https://github.com/erikarenhill/forty-two-watts/issues/34)) ([65c9e2c](65c9e2c)) ### TSDB * long-format SQLite (14d) + Parquet rolloff for older ([c53c964](c53c964)) ### Safety * watchdog marks stale drivers offline + reverts to autonomous ([519196c](519196c))
The success step of @semantic-release/github walks every commit
included in the release, parses `(#N)` references out of merge commit
subjects via issue-parser, and then calls GitHub API to annotate each
referenced PR with a "Released in vX.Y.Z" comment, labels, etc. The
v1.0.0 run of this workflow successfully published the tag + GitHub
release, then crashed in that success step with
AggregateError: HttpError: Not Found
- /repos/{owner}/{repo}/pulls/{pull_number}/commits
because most of the referenced PR numbers (srcfl#32, srcfl#33, srcfl#34, srcfl#35, srcfl#36,
srcfl#39) come from UPSTREAM frahlg/forty-two-watts PRs, not from this
fork. In the fork, those PR numbers either don't exist or refer to
unrelated PRs, so the lookup 404s and the whole job fails. The
release itself is fine — the tag, CHANGELOG.md, and GitHub Release
for v1.0.0 all got created — but because the job exit code is
non-zero, the docker build dependency evaluates `needs: release` as
failed and skips.
Disable every feature on @semantic-release/github that walks PR
references. The plugin's only remaining job is to POST to
/repos/{owner}/{repo}/releases once per release, which is what we
actually care about. Explicitly turned off:
successComment — no "released in" comments on PRs/issues
failComment — no failure report issues
failTitle — no fail-report titles
labels — no labeling of PRs on failure
releasedLabels — no "released" labels on PRs
addReleases — no "next release" section on the GitHub Release
These options map 1:1 to the GitHub API calls the plugin makes;
with all six off, the plugin no longer touches /pulls/* endpoints
and the 404 can't happen. Tested locally via config shape only;
correctness will be visible on the next workflow run.
## [1.0.1](v1.0.0...v1.0.1) (2026-04-15) ### Bug Fixes * **ci:** disable @semantic-release/github PR annotation features ([51eb9e2](51eb9e2)), closes [srcfl#32](https://github.com/erikarenhill/forty-two-watts/issues/32) [srcfl#33](https://github.com/erikarenhill/forty-two-watts/issues/33) [srcfl#34](https://github.com/erikarenhill/forty-two-watts/issues/34) [srcfl#35](https://github.com/erikarenhill/forty-two-watts/issues/35) [srcfl#36](https://github.com/erikarenhill/forty-two-watts/issues/36) [srcfl#39](https://github.com/erikarenhill/forty-two-watts/issues/39)
|
🎉 This PR is included in version 2.0.1 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
The success step of @semantic-release/github walks every commit
included in the release, parses `(#N)` references out of merge commit
subjects via issue-parser, and then calls GitHub API to annotate each
referenced PR with a "Released in vX.Y.Z" comment, labels, etc. The
v1.0.0 run of this workflow successfully published the tag + GitHub
release, then crashed in that success step with
AggregateError: HttpError: Not Found
- /repos/{owner}/{repo}/pulls/{pull_number}/commits
because most of the referenced PR numbers (#32, #33, #34, #35, #36,
#39) come from UPSTREAM frahlg/forty-two-watts PRs, not from this
fork. In the fork, those PR numbers either don't exist or refer to
unrelated PRs, so the lookup 404s and the whole job fails. The
release itself is fine — the tag, CHANGELOG.md, and GitHub Release
for v1.0.0 all got created — but because the job exit code is
non-zero, the docker build dependency evaluates `needs: release` as
failed and skips.
Disable every feature on @semantic-release/github that walks PR
references. The plugin's only remaining job is to POST to
/repos/{owner}/{repo}/releases once per release, which is what we
actually care about. Explicitly turned off:
successComment — no "released in" comments on PRs/issues
failComment — no failure report issues
failTitle — no fail-report titles
labels — no labeling of PRs on failure
releasedLabels — no "released" labels on PRs
addReleases — no "next release" section on the GitHub Release
These options map 1:1 to the GitHub API calls the plugin makes;
with all six off, the plugin no longer touches /pulls/* endpoints
and the 404 can't happen. Tested locally via config shape only;
correctness will be visible on the next workflow run.
## [2.2.1](v2.2.0...v2.2.1) (2026-04-16) ### Bug Fixes * **ci:** disable @semantic-release/github PR annotation features ([4020d46](4020d46)), closes [#32](#32) [#33](#33) [#34](#34) [#35](#35) [#36](#36) [#39](#39) * **ci:** switch semantic-release to conventionalcommits preset ([7e0bb89](7e0bb89)) * **ci:** upgrade GitHub Actions to Node.js 24 (drop deprecated Node 20) ([4005bd8](4005bd8)) ### UI * remove manual EV charging slider ([063174c](063174c))
## [0.4.0](v0.3.0...v0.4.0) (2026-04-16) ### Features * config/UI improvements — kWh display, secure EV password, planner tab ([#65](#65)) ([35ab03d](35ab03d)) * Easee Cloud driver + host.http_get/post for Lua drivers ([#56](#56)) ([4cdc942](4cdc942)) * EV charger config + credential masking in API responses ([#58](#58)) ([c22cb80](c22cb80)) ### Bug Fixes * 5 Go-side P1 bugs from Codex review ([#46](#46)) ([0cd2885](0cd2885)) * 5 Go-side P1 bugs from Codex review ([#47](#47)) ([4f2eaf6](4f2eaf6)) * address P2 review comments across control, MPC, drivers, and UI ([#64](#64)) ([fcafa88](fcafa88)) * **ci:** disable @semantic-release/github PR annotation features ([4020d46](4020d46)), closes [#32](#32) [#33](#33) [#34](#34) [#35](#35) [#36](#36) [#39](#39) * **ci:** switch semantic-release to conventionalcommits preset ([7e0bb89](7e0bb89)) * **ci:** upgrade GitHub Actions to Node.js 24 (drop deprecated Node 20) ([4005bd8](4005bd8)) * Lua driver Command() reading wrong field — Sungrow ignored targets ([9237156](9237156)) * populate EV Charger tab from driver config when ev_charger is empty ([5e6b116](5e6b116)) * remove dead evSlider event listeners that crash app.js ([8ae76c7](8ae76c7)) * replace wonky Catmull-Rom spline with simple linear forecast ([abea431](abea431)) * show '...' instead of stale v0.1.0 while JS loads version ([dc65065](dc65065)) * **solaredge_pv:** read SunSpec scale factors every poll, not cached ([#38](#38)) ([26f8793](26f8793)) ### Drivers * add Eastron SDM630 Lua driver ([#18](#18)) ([d5ad806](d5ad806)) * add Ferroamp Modbus driver (alt transport to ferroamp.lua) ([#31](#31)) ([03b802c](03b802c)) * fix 9 P1 bugs flagged by Codex review ([#44](#44)) ([b20e485](b20e485)) * port Deye SUN-SG hybrid inverter to 42W v2.1 Lua host ([#29](#29)) ([df8fbc0](df8fbc0)) * port Fronius GEN24 (SunSpec) to Lua ([#19](#19)) ([c1fc875](c1fc875)) * port Fronius Smart Meter (SunSpec Modbus, read-only) ([#24](#24)) ([575895c](575895c)) * port GoodWe hybrid inverter (ET-Plus / EH) to Lua v2.1 ([#28](#28)) ([e43d2d9](e43d2d9)) * port Growatt hybrid inverter driver (read-only) ([#20](#20)) ([92524ac](92524ac)) * port Huawei SUN2000 hybrid inverter ([#15](#15)) ([09a8855](09a8855)) * port Kostal Plenticore / Piko IQ (Lua, read-only) ([#21](#21)) ([bdeca96](bdeca96)) * port Pixii PowerShaper battery driver to v2.1 Lua host ([#22](#22)) ([70a96d1](70a96d1)) * port SMA hybrid inverter Lua driver ([#23](#23)) ([dd34555](dd34555)) * port Sofar HYD-ES/HYD-EP from hugin to Lua v2.1 ([#26](#26)) ([14f6131](14f6131)) * port SolarEdge SunSpec inverter + meter to Lua (read-only) ([#30](#30)) ([1007e63](1007e63)) * port Solis hybrid inverter ([#27](#27)) ([98b2a50](98b2a50)) * port Victron Energy GX Modbus driver ([#25](#25)) ([ad71db2](ad71db2)) ### UI * add status bar with driver health indicators ([b048d60](b048d60)) * EV status card + Easee control commands ([#59](#59)) ([b03749a](b03749a)) * fix summary cards grid for 7 cards + raise side-by-side breakpoint ([6e19973](6e19973)) * inline target on hover + driver card + collapsible model cards ([de88f43](de88f43)) * legend wrap + nice-tick y-axis + cleaner chart labels ([#33](#33)) ([aeb1d1c](aeb1d1c)) * remove manual EV charging slider ([063174c](063174c)) * show mode band in plan chart + grid target on status card ([877e0bd](877e0bd)) * smooth Catmull-Rom spline for forecast + 15min forecast zone ([dba51a5](dba51a5)) ### Control loop * fold live DerEV readings into the EV clamp ([#36](#36)) ([5d57d68](5d57d68)) * slew-rate anchors on actual battery power, not stale command ([#41](#41)) ([4f73f19](4f73f19)) ### MPC planner * fall back to forecast when learned PV twin collapses ([#39](#39)) ([f3062ac](f3062ac)) * log optimize params + ems_mode per action for plan chart ([9e8c14b](9e8c14b)) * value SoC at import−export spread in self-consumption modes ([#40](#40)) ([a90d525](a90d525)) ### Telemetry * add DerEV type for EV charger readings ([#34](#34)) ([65c9e2c](65c9e2c)) ### TSDB * long-format SQLite (14d) + Parquet rolloff for older ([c53c964](c53c964)) ### Safety * watchdog marks stale drivers offline + reverts to autonomous ([519196c](519196c))
Summary
Tightens up the Live + Plan chart polish — no behavior changes, just layout:
Test plan
🤖 Generated with Claude Code